61
|
1 /*
|
|
2 * Ristipolku Game Engine
|
|
3 * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
|
|
4 */
|
|
5 package game;
|
|
6
|
|
7 import java.util.*;
|
|
8 import java.io.*;
|
|
9 import game.*;
|
|
10 import javax.sound.sampled.*;
|
|
11
|
|
12
|
|
13 public class SoundManager extends ThreadGroup
|
|
14 {
|
|
15 private boolean alive;
|
|
16 private LinkedList queue;
|
|
17 private int id;
|
|
18 private static int poolID;
|
|
19
|
|
20 private AudioFormat playbackFormat;
|
|
21 private ThreadLocal localLine;
|
|
22 private ThreadLocal localBuffer;
|
|
23 private Object pausedLock;
|
|
24 private boolean paused;
|
|
25
|
|
26
|
|
27 public SoundManager(AudioFormat format)
|
|
28 {
|
|
29 this(format, getMaxSimultaneousSounds(format));
|
|
30 }
|
|
31
|
|
32
|
|
33 public SoundManager(AudioFormat format, int maxSounds)
|
|
34 {
|
|
35 super("SoundManagerPool-" + (poolID++));
|
|
36
|
|
37 int numThreads = Math.min(maxSounds, getMaxSimultaneousSounds(playbackFormat));
|
|
38
|
|
39 System.out.print("SMGR.SoundManager() initializing with " + numThreads +" max sounds\n");
|
|
40
|
|
41 setDaemon(true);
|
|
42 alive = true;
|
|
43
|
|
44 queue = new LinkedList();
|
|
45 for (int i = 0; i < numThreads; i++)
|
|
46 new PooledThread().start();
|
|
47
|
|
48 playbackFormat = format;
|
|
49 localLine = new ThreadLocal();
|
|
50 localBuffer = new ThreadLocal();
|
|
51 pausedLock = new Object();
|
|
52
|
|
53 synchronized (this)
|
|
54 {
|
|
55 notifyAll();
|
|
56 }
|
|
57 }
|
|
58
|
|
59
|
|
60 public static int getMaxSimultaneousSounds(AudioFormat playbackFormat)
|
|
61 {
|
|
62 DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat);
|
|
63
|
|
64 Mixer.Info[] info = AudioSystem.getMixerInfo();
|
|
65 System.out.print("getMaxSimultaneousSounds() mixer information:\n");
|
|
66 Mixer.Info select = null;
|
|
67 for (Mixer.Info i : info)
|
|
68 {
|
|
69 System.out.print("#"+i.getName()+"\n");
|
|
70 if (i.getName().equals("Java Sound Audio Engine"))
|
|
71 select = i;
|
|
72 }
|
|
73
|
|
74 Mixer mixer = AudioSystem.getMixer(select);
|
|
75
|
|
76 Mixer.Info i = mixer.getMixerInfo();
|
|
77 System.out.print("selected: "+i.getName()+"\n");
|
|
78
|
|
79 int maxLines = mixer.getMaxLines(lineInfo);
|
|
80 if (maxLines == AudioSystem.NOT_SPECIFIED)
|
|
81 maxLines = 16;
|
|
82
|
|
83 System.out.print("SMGR.getMaxSimultaneousSounds() maxLines="+maxLines+"\n");
|
|
84
|
|
85 return maxLines;
|
|
86 }
|
|
87
|
|
88
|
|
89 protected void cleanUp()
|
|
90 {
|
|
91 System.out.print("SMGR.cleanUp()\n");
|
|
92 // signal to unpause
|
|
93 setPaused(false);
|
|
94
|
|
95 System.out.print("SMGR.cleanUp(): closing mixer\n");
|
|
96
|
|
97 // close the mixer (stops any running sounds)
|
|
98 Mixer mixer = AudioSystem.getMixer(null);
|
|
99 System.out.print("SMGR.cleanUp(): foo\n");
|
|
100 if (mixer.isOpen())
|
|
101 mixer.close();
|
|
102
|
|
103 System.out.print("SMGR.cleanUp(): leaving\n");
|
|
104 }
|
|
105
|
|
106
|
|
107 public void setPaused(boolean paused)
|
|
108 {
|
|
109 if (this.paused != paused)
|
|
110 {
|
|
111 synchronized (pausedLock)
|
|
112 {
|
|
113 this.paused = paused;
|
|
114 if (!paused)
|
|
115 {
|
|
116 // restart sounds
|
|
117 pausedLock.notifyAll();
|
|
118 }
|
|
119 }
|
|
120 }
|
|
121 }
|
|
122
|
|
123
|
|
124 public boolean isPaused()
|
|
125 {
|
|
126 return paused;
|
|
127 }
|
|
128
|
|
129
|
|
130 public Sound getSound(String filename)
|
|
131 {
|
|
132 return getSound(getAudioInputStream(filename));
|
|
133 }
|
|
134
|
|
135
|
|
136 public Sound getSound(InputStream is)
|
|
137 {
|
|
138 return getSound(getAudioInputStream(is));
|
|
139 }
|
|
140
|
|
141
|
|
142 public Sound getSound(AudioInputStream audioStream)
|
|
143 {
|
|
144 if (audioStream == null)
|
|
145 return null;
|
|
146
|
|
147 // get the number of bytes to read
|
|
148 int length = (int)(audioStream.getFrameLength() * audioStream.getFormat().getFrameSize());
|
|
149
|
|
150 // read the entire stream
|
|
151 byte[] samples = new byte[length];
|
|
152 DataInputStream is = new DataInputStream(audioStream);
|
|
153 try {
|
|
154 is.readFully(samples);
|
|
155 is.close();
|
|
156 }
|
|
157 catch (IOException ex)
|
|
158 {
|
|
159 ex.printStackTrace();
|
|
160 }
|
|
161
|
|
162 // return the samples
|
|
163 return new Sound(samples);
|
|
164 }
|
|
165
|
|
166
|
|
167 public AudioInputStream getAudioInputStream(String filename)
|
|
168 {
|
|
169 ResourceLoader res = new ResourceLoader(filename);
|
|
170 if (res == null || res.getStream() == null)
|
|
171 {
|
|
172 System.out.print("Could not load audio resource '"+ filename +"'.\n");
|
|
173 return null;
|
|
174 }
|
|
175 try {
|
|
176 return getAudioInputStream(res.getStream());
|
|
177 }
|
|
178 catch (Exception ex)
|
|
179 {
|
|
180 System.out.print("Could not get AudioInputStream for '"+ filename +"'\n");
|
|
181 return null;
|
|
182 }
|
|
183 }
|
|
184
|
|
185
|
|
186 public AudioInputStream getAudioInputStream(InputStream is)
|
|
187 {
|
|
188 try {
|
|
189 if (!is.markSupported())
|
|
190 is = new BufferedInputStream(is);
|
|
191
|
|
192 // open the source stream
|
|
193 AudioInputStream source = AudioSystem.getAudioInputStream(is);
|
|
194
|
|
195 // convert to playback format
|
|
196 return AudioSystem.getAudioInputStream(playbackFormat, source);
|
|
197 }
|
|
198
|
|
199 catch (UnsupportedAudioFileException ex) {
|
|
200 ex.printStackTrace();
|
|
201 }
|
|
202 catch (IOException ex) {
|
|
203 ex.printStackTrace();
|
|
204 }
|
|
205 catch (IllegalArgumentException ex) {
|
|
206 ex.printStackTrace();
|
|
207 }
|
|
208
|
|
209 return null;
|
|
210 }
|
|
211
|
|
212
|
|
213 public InputStream play(Sound sound)
|
|
214 {
|
|
215 InputStream is;
|
|
216 if (sound != null)
|
|
217 {
|
|
218 is = new ByteArrayInputStream(sound.getSamples());
|
|
219 return play(is);
|
|
220 }
|
|
221 return null;
|
|
222 }
|
|
223
|
|
224
|
|
225 public InputStream play(InputStream is)
|
|
226 {
|
|
227 System.out.print("SMGR.play()\n");
|
|
228 if (is != null)
|
|
229 runTask(new SoundPlayer(is));
|
|
230
|
|
231 return is;
|
|
232 }
|
|
233
|
|
234
|
|
235 protected void threadStarted()
|
|
236 {
|
|
237 synchronized (this)
|
|
238 {
|
|
239 try {
|
|
240 wait();
|
|
241 }
|
|
242 catch (InterruptedException ex) { }
|
|
243 }
|
|
244
|
|
245 System.out.print("SMGR.threadStarted()\n");
|
|
246
|
|
247 // use a short, 100ms (1/10th sec) buffer for filters that
|
|
248 // change in real-time
|
|
249 int bufferSize = playbackFormat.getFrameSize() *
|
|
250 Math.round(playbackFormat.getSampleRate() / 10);
|
|
251
|
|
252 // create, open, and start the line
|
|
253 SourceDataLine line;
|
|
254 DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat);
|
|
255
|
|
256 try {
|
|
257 line = (SourceDataLine) AudioSystem.getLine(lineInfo);
|
|
258 line.open(playbackFormat, bufferSize);
|
|
259 }
|
|
260 catch (LineUnavailableException ex)
|
|
261 {
|
|
262 // the line is unavailable - signal to end this thread
|
|
263 Thread.currentThread().interrupt();
|
|
264 return;
|
|
265 }
|
|
266
|
|
267 /*
|
|
268 Control[] ctrls = line.getControls();
|
|
269 for (Control c : ctrls)
|
|
270 {
|
|
271 System.out.print("#" + c.toString() +"\n");
|
|
272 }
|
|
273 */
|
|
274 Control.Type ct = FloatControl.Type.MASTER_GAIN;
|
|
275 if (line.isControlSupported(ct))
|
|
276 {
|
|
277 FloatControl c = (FloatControl) line.getControl(ct);
|
|
278 c.setValue(-20f);
|
|
279 }
|
|
280
|
|
281 line.start();
|
|
282
|
|
283 // create the buffer
|
|
284 byte[] buffer = new byte[bufferSize];
|
|
285
|
|
286 // set this thread's locals
|
|
287 localLine.set(line);
|
|
288 localBuffer.set(buffer);
|
|
289 }
|
|
290
|
|
291
|
|
292 protected void threadStopped()
|
|
293 {
|
|
294 System.out.print("SMGR.threadStopped()\n");
|
|
295 SourceDataLine line = (SourceDataLine) localLine.get();
|
|
296 if (line != null)
|
|
297 {
|
|
298 line.drain();
|
|
299 line.close();
|
|
300 }
|
|
301 }
|
|
302
|
|
303
|
|
304 protected class SoundPlayer implements Runnable
|
|
305 {
|
|
306 private InputStream source;
|
|
307
|
|
308 public SoundPlayer(InputStream source)
|
|
309 {
|
|
310 this.source = source;
|
|
311 }
|
|
312
|
|
313 public void run()
|
|
314 {
|
|
315 // get line and buffer from ThreadLocals
|
|
316 SourceDataLine line = (SourceDataLine) localLine.get();
|
|
317 byte[] buffer = (byte[])localBuffer.get();
|
|
318
|
|
319 if (line == null || buffer == null)
|
|
320 return;
|
|
321
|
|
322 System.out.print("SMGR.SoundPlayer.run()\n");
|
|
323
|
|
324 // copy data to the line
|
|
325 try {
|
|
326 int numBytesRead = 0;
|
|
327
|
|
328 while (numBytesRead != -1) {
|
|
329 // if paused, wait until unpaused
|
|
330 synchronized (pausedLock)
|
|
331 {
|
|
332 if (paused) {
|
|
333 try {
|
|
334 pausedLock.wait();
|
|
335 }
|
|
336 catch (InterruptedException ex) {
|
|
337 return;
|
|
338 }
|
|
339 }
|
|
340 }
|
|
341
|
|
342 // copy data
|
|
343 numBytesRead = source.read(buffer, 0, buffer.length);
|
|
344 if (numBytesRead != -1)
|
|
345 line.write(buffer, 0, numBytesRead);
|
|
346 }
|
|
347 }
|
|
348
|
|
349 catch (IOException ex) {
|
|
350 ex.printStackTrace();
|
|
351 }
|
|
352
|
|
353 }
|
|
354 }
|
|
355
|
|
356 public synchronized void runTask(Runnable task)
|
|
357 {
|
|
358 if (!alive)
|
|
359 {
|
|
360 throw new IllegalStateException();
|
|
361 }
|
|
362 if (task != null)
|
|
363 {
|
|
364 queue.add(task);
|
|
365 notify();
|
|
366 }
|
|
367
|
|
368 }
|
|
369
|
|
370 protected synchronized Runnable getTask() throws InterruptedException
|
|
371 {
|
|
372 while (queue.size() == 0)
|
|
373 {
|
|
374 if (!alive)
|
|
375 return null;
|
|
376
|
|
377 wait();
|
|
378 }
|
|
379 return (Runnable) queue.removeFirst();
|
|
380 }
|
|
381
|
|
382
|
|
383 public synchronized void close()
|
|
384 {
|
|
385 System.out.print("SMGR.close()\n");
|
|
386
|
|
387 if (alive)
|
|
388 {
|
|
389 System.out.print("SMGR.close(): alive queue clear\n");
|
|
390 // Clear queue
|
|
391 alive = false;
|
|
392 queue.clear();
|
|
393 interrupt();
|
|
394 }
|
|
395
|
|
396 cleanUp();
|
|
397 System.out.print("SMGR.close(): leaving\n");
|
|
398 }
|
|
399
|
|
400
|
|
401 public void join()
|
|
402 {
|
|
403 System.out.print("SMGR.join()\n");
|
|
404 cleanUp();
|
|
405
|
|
406 synchronized (this)
|
|
407 {
|
|
408 alive = false;
|
|
409 notifyAll();
|
|
410 }
|
|
411
|
|
412 Thread[] threads = new Thread[activeCount()];
|
|
413 int count = enumerate(threads);
|
|
414 for (int i = 0; i < count; i++)
|
|
415 {
|
|
416 try {
|
|
417 threads[i].join();
|
|
418 }
|
|
419 catch (InterruptedException ex)
|
|
420 {
|
|
421 }
|
|
422 }
|
|
423 }
|
|
424
|
|
425
|
|
426 private class PooledThread extends Thread
|
|
427 {
|
|
428 public PooledThread()
|
|
429 {
|
|
430 super(SoundManager.this, "SoundManagerPool-" + (id++));
|
|
431 }
|
|
432
|
|
433 public void run()
|
|
434 {
|
|
435 threadStarted();
|
|
436
|
|
437 while (!isInterrupted()) {
|
|
438 Runnable task = null;
|
|
439 try {
|
|
440 task = getTask();
|
|
441 }
|
|
442 catch (InterruptedException ex)
|
|
443 {
|
|
444 }
|
|
445
|
|
446 if (task == null)
|
|
447 break;
|
|
448
|
|
449 try {
|
|
450 task.run();
|
|
451 }
|
|
452 catch (Throwable t) {
|
|
453 uncaughtException(this, t);
|
|
454 }
|
|
455 }
|
|
456 threadStopped();
|
|
457 }
|
|
458 }
|
|
459 }
|