Mercurial > hg > ristipolku
annotate game/SoundManager.java @ 93:e1d657e6c25b
Work on audio code.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 03 Mar 2011 18:21:45 +0200 |
parents | 1c7a97d80120 |
children | dd896bc7352b |
rev | line source |
---|---|
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 playbackFormat = format; | |
45 localLine = new ThreadLocal(); | |
46 localBuffer = new ThreadLocal(); | |
47 pausedLock = new Object(); | |
48 | |
93 | 49 queue = new LinkedList(); |
50 for (int i = 0; i < numThreads; i++) | |
51 new PooledThread().start(); | |
52 | |
61 | 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(); | |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
65 System.out.print("SMGR.getMaxSimultaneousSounds() mixer information:\n"); |
61 | 66 Mixer.Info select = null; |
67 for (Mixer.Info i : info) | |
68 { | |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
69 System.out.print(" - '"+i.getName()+"'\n"); |
61 | 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(); | |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
77 System.out.print(" * selected='"+i.getName()+"'\n"); |
61 | 78 |
79 int maxLines = mixer.getMaxLines(lineInfo); | |
80 if (maxLines == AudioSystem.NOT_SPECIFIED) | |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
81 maxLines = 8; |
61 | 82 |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
83 System.out.print(" * maxLines="+maxLines+"\n"); |
61 | 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 if (mixer.isOpen()) | |
100 mixer.close(); | |
101 | |
102 System.out.print("SMGR.cleanUp(): leaving\n"); | |
103 } | |
104 | |
105 | |
106 public void setPaused(boolean paused) | |
107 { | |
108 if (this.paused != paused) | |
109 { | |
110 synchronized (pausedLock) | |
111 { | |
112 this.paused = paused; | |
113 if (!paused) | |
114 { | |
115 // restart sounds | |
116 pausedLock.notifyAll(); | |
117 } | |
118 } | |
119 } | |
120 } | |
121 | |
122 | |
123 public boolean isPaused() | |
124 { | |
125 return paused; | |
126 } | |
127 | |
128 | |
129 public Sound getSound(String filename) | |
130 { | |
131 return getSound(getAudioInputStream(filename)); | |
132 } | |
133 | |
134 | |
135 public Sound getSound(InputStream is) | |
136 { | |
137 return getSound(getAudioInputStream(is)); | |
138 } | |
139 | |
140 | |
141 public Sound getSound(AudioInputStream audioStream) | |
142 { | |
143 if (audioStream == null) | |
144 return null; | |
145 | |
146 // get the number of bytes to read | |
147 int length = (int)(audioStream.getFrameLength() * audioStream.getFormat().getFrameSize()); | |
148 | |
149 // read the entire stream | |
150 byte[] samples = new byte[length]; | |
151 DataInputStream is = new DataInputStream(audioStream); | |
152 try { | |
153 is.readFully(samples); | |
154 is.close(); | |
155 } | |
156 catch (IOException ex) | |
157 { | |
158 ex.printStackTrace(); | |
159 } | |
160 | |
161 // return the samples | |
162 return new Sound(samples); | |
163 } | |
164 | |
165 | |
166 public AudioInputStream getAudioInputStream(String filename) | |
167 { | |
168 ResourceLoader res = new ResourceLoader(filename); | |
169 if (res == null || res.getStream() == null) | |
170 { | |
171 System.out.print("Could not load audio resource '"+ filename +"'.\n"); | |
172 return null; | |
173 } | |
174 try { | |
175 return getAudioInputStream(res.getStream()); | |
176 } | |
177 catch (Exception ex) | |
178 { | |
179 System.out.print("Could not get AudioInputStream for '"+ filename +"'\n"); | |
180 return null; | |
181 } | |
182 } | |
183 | |
184 | |
185 public AudioInputStream getAudioInputStream(InputStream is) | |
186 { | |
187 try { | |
188 if (!is.markSupported()) | |
189 is = new BufferedInputStream(is); | |
190 | |
191 // open the source stream | |
192 AudioInputStream source = AudioSystem.getAudioInputStream(is); | |
193 | |
194 // convert to playback format | |
195 return AudioSystem.getAudioInputStream(playbackFormat, source); | |
196 } | |
197 | |
198 catch (UnsupportedAudioFileException ex) { | |
199 ex.printStackTrace(); | |
200 } | |
201 catch (IOException ex) { | |
202 ex.printStackTrace(); | |
203 } | |
204 catch (IllegalArgumentException ex) { | |
205 ex.printStackTrace(); | |
206 } | |
207 | |
208 return null; | |
209 } | |
210 | |
211 | |
212 public InputStream play(Sound sound) | |
213 { | |
214 InputStream is; | |
215 if (sound != null) | |
216 { | |
217 is = new ByteArrayInputStream(sound.getSamples()); | |
218 return play(is); | |
219 } | |
220 return null; | |
221 } | |
222 | |
223 | |
224 public InputStream play(InputStream is) | |
225 { | |
93 | 226 System.out.print("SMGR.play(is="+is+")\n"); |
61 | 227 if (is != null) |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
228 { |
61 | 229 runTask(new SoundPlayer(is)); |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
230 } |
61 | 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 | |
246 // use a short, 100ms (1/10th sec) buffer for filters that | |
247 // change in real-time | |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
248 int bufferSize = playbackFormat.getFrameSize() * Math.round(playbackFormat.getSampleRate() / 10); |
61 | 249 |
250 // create, open, and start the line | |
251 SourceDataLine line; | |
252 DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat); | |
93 | 253 |
254 System.out.print("SMGR.threadStarted(): "+lineInfo.toString()+"\n"); | |
61 | 255 |
256 try { | |
257 line = (SourceDataLine) AudioSystem.getLine(lineInfo); | |
258 line.open(playbackFormat, bufferSize); | |
259 } | |
260 catch (LineUnavailableException ex) | |
261 { | |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
262 System.out.print("SMGR.threadStarted() line unavailable!\n"); |
61 | 263 // the line is unavailable - signal to end this thread |
264 Thread.currentThread().interrupt(); | |
265 return; | |
266 } | |
267 | |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
268 // Change volume |
61 | 269 Control.Type ct = FloatControl.Type.MASTER_GAIN; |
270 if (line.isControlSupported(ct)) | |
271 { | |
272 FloatControl c = (FloatControl) line.getControl(ct); | |
273 c.setValue(-20f); | |
274 } | |
275 | |
276 line.start(); | |
277 | |
278 // create the buffer | |
279 byte[] buffer = new byte[bufferSize]; | |
280 | |
281 // set this thread's locals | |
282 localLine.set(line); | |
283 localBuffer.set(buffer); | |
284 } | |
285 | |
286 | |
287 protected void threadStopped() | |
288 { | |
289 System.out.print("SMGR.threadStopped()\n"); | |
290 SourceDataLine line = (SourceDataLine) localLine.get(); | |
291 if (line != null) | |
292 { | |
293 line.drain(); | |
294 line.close(); | |
295 } | |
296 } | |
297 | |
298 | |
299 protected class SoundPlayer implements Runnable | |
300 { | |
301 private InputStream source; | |
302 | |
303 public SoundPlayer(InputStream source) | |
304 { | |
305 this.source = source; | |
306 } | |
307 | |
308 public void run() | |
309 { | |
310 // get line and buffer from ThreadLocals | |
311 SourceDataLine line = (SourceDataLine) localLine.get(); | |
312 byte[] buffer = (byte[])localBuffer.get(); | |
313 | |
314 if (line == null || buffer == null) | |
315 return; | |
316 | |
317 System.out.print("SMGR.SoundPlayer.run()\n"); | |
318 | |
319 // copy data to the line | |
320 try { | |
321 int numBytesRead = 0; | |
322 | |
323 while (numBytesRead != -1) { | |
324 // if paused, wait until unpaused | |
325 synchronized (pausedLock) | |
326 { | |
327 if (paused) { | |
328 try { | |
329 pausedLock.wait(); | |
330 } | |
331 catch (InterruptedException ex) { | |
332 return; | |
333 } | |
334 } | |
335 } | |
336 | |
337 // copy data | |
338 numBytesRead = source.read(buffer, 0, buffer.length); | |
339 if (numBytesRead != -1) | |
340 line.write(buffer, 0, numBytesRead); | |
341 } | |
342 } | |
343 | |
344 catch (IOException ex) { | |
345 ex.printStackTrace(); | |
346 } | |
347 | |
348 } | |
349 } | |
350 | |
351 public synchronized void runTask(Runnable task) | |
352 { | |
353 if (!alive) | |
354 { | |
355 throw new IllegalStateException(); | |
356 } | |
357 if (task != null) | |
358 { | |
359 queue.add(task); | |
360 notify(); | |
361 } | |
362 | |
363 } | |
364 | |
365 protected synchronized Runnable getTask() throws InterruptedException | |
366 { | |
367 while (queue.size() == 0) | |
368 { | |
369 if (!alive) | |
370 return null; | |
371 | |
372 wait(); | |
373 } | |
374 return (Runnable) queue.removeFirst(); | |
375 } | |
376 | |
377 | |
378 public synchronized void close() | |
379 { | |
380 System.out.print("SMGR.close()\n"); | |
381 | |
382 if (alive) | |
383 { | |
384 System.out.print("SMGR.close(): alive queue clear\n"); | |
385 // Clear queue | |
386 alive = false; | |
387 queue.clear(); | |
388 interrupt(); | |
389 } | |
390 | |
391 cleanUp(); | |
392 System.out.print("SMGR.close(): leaving\n"); | |
393 } | |
394 | |
395 | |
396 public void join() | |
397 { | |
398 System.out.print("SMGR.join()\n"); | |
399 cleanUp(); | |
400 | |
401 synchronized (this) | |
402 { | |
403 alive = false; | |
404 notifyAll(); | |
405 } | |
406 | |
407 Thread[] threads = new Thread[activeCount()]; | |
408 int count = enumerate(threads); | |
409 for (int i = 0; i < count; i++) | |
410 { | |
411 try { | |
412 threads[i].join(); | |
413 } | |
414 catch (InterruptedException ex) | |
415 { | |
416 } | |
417 } | |
418 } | |
419 | |
420 | |
421 private class PooledThread extends Thread | |
422 { | |
423 public PooledThread() | |
424 { | |
425 super(SoundManager.this, "SoundManagerPool-" + (id++)); | |
426 } | |
427 | |
428 public void run() | |
429 { | |
430 threadStarted(); | |
431 | |
432 while (!isInterrupted()) { | |
433 Runnable task = null; | |
434 try { | |
435 task = getTask(); | |
436 } | |
437 catch (InterruptedException ex) | |
438 { | |
439 } | |
440 | |
441 if (task == null) | |
442 break; | |
443 | |
444 try { | |
445 task.run(); | |
446 } | |
447 catch (Throwable t) { | |
448 uncaughtException(this, t); | |
449 } | |
450 } | |
451 threadStopped(); | |
452 } | |
453 } | |
454 } |