Mercurial > hg > ristipolku
annotate game/SoundManager.java @ 201:bd3cde4bc15c
Add a comment.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 25 Apr 2019 12:58:17 +0300 |
parents | 8dbaa093c562 |
children |
rev | line source |
---|---|
61 | 1 /* |
2 * Ristipolku Game Engine | |
151 | 3 * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org> |
61 | 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)); | |
134
4c0dec72e2f0
Whitespace cosmetic cleanup.
Matti Hamalainen <ccr@tnsp.org>
parents:
110
diff
changeset
|
38 |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
39 G.debug("SMGR.SoundManager() initializing with " + numThreads +" max sounds\n"); |
134
4c0dec72e2f0
Whitespace cosmetic cleanup.
Matti Hamalainen <ccr@tnsp.org>
parents:
110
diff
changeset
|
40 |
61 | 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(); | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
65 G.debug("SMGR.getMaxSimultaneousSounds() mixer information:\n"); |
61 | 66 Mixer.Info select = null; |
67 for (Mixer.Info i : info) | |
68 { | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
69 G.debug(" - '"+i.getName()+"'\n"); |
61 | 70 if (i.getName().equals("Java Sound Audio Engine")) |
71 select = i; | |
72 } | |
73 | |
201 | 74 // if 'select' == null, uses default |
61 | 75 Mixer mixer = AudioSystem.getMixer(select); |
76 | |
77 Mixer.Info i = mixer.getMixerInfo(); | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
78 G.debug(" * selected='"+i.getName()+"'\n"); |
61 | 79 |
80 int maxLines = mixer.getMaxLines(lineInfo); | |
81 if (maxLines == AudioSystem.NOT_SPECIFIED) | |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
82 maxLines = 8; |
61 | 83 |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
84 G.debug(" * maxLines="+maxLines+"\n"); |
61 | 85 |
86 return maxLines; | |
87 } | |
88 | |
89 | |
90 protected void cleanUp() | |
91 { | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
92 G.debug("SMGR.cleanUp()\n"); |
61 | 93 // signal to unpause |
94 setPaused(false); | |
95 | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
96 G.debug("SMGR.cleanUp(): closing mixer\n"); |
61 | 97 |
98 // close the mixer (stops any running sounds) | |
99 Mixer mixer = AudioSystem.getMixer(null); | |
100 if (mixer.isOpen()) | |
101 mixer.close(); | |
102 | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
103 G.debug("SMGR.cleanUp(): leaving\n"); |
61 | 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 { | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
172 G.debug("Could not load audio resource '"+ filename +"'.\n"); |
61 | 173 return null; |
174 } | |
175 try { | |
176 return getAudioInputStream(res.getStream()); | |
177 } | |
178 catch (Exception ex) | |
179 { | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
180 G.debug("Could not get AudioInputStream for '"+ filename +"'\n"); |
61 | 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 { | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
227 G.debug("SMGR.play(is="+is+")\n"); |
61 | 228 if (is != null) |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
229 { |
61 | 230 runTask(new SoundPlayer(is)); |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
231 } |
61 | 232 return is; |
233 } | |
234 | |
235 | |
236 protected void threadStarted() | |
237 { | |
238 synchronized (this) | |
239 { | |
240 try { | |
241 wait(); | |
242 } | |
243 catch (InterruptedException ex) { } | |
244 } | |
245 | |
246 | |
247 // use a short, 100ms (1/10th sec) buffer for filters that | |
248 // change in real-time | |
110
3551b61b3c0b
Increase buffer size a bit.
Matti Hamalainen <ccr@tnsp.org>
parents:
107
diff
changeset
|
249 int bufferSize = playbackFormat.getFrameSize() * Math.round(playbackFormat.getSampleRate() / 10); |
61 | 250 |
251 // create, open, and start the line | |
252 SourceDataLine line; | |
253 DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat); | |
134
4c0dec72e2f0
Whitespace cosmetic cleanup.
Matti Hamalainen <ccr@tnsp.org>
parents:
110
diff
changeset
|
254 |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
255 G.debug("SMGR.threadStarted(): "+lineInfo.toString()+"\n"); |
61 | 256 |
257 try { | |
258 line = (SourceDataLine) AudioSystem.getLine(lineInfo); | |
259 line.open(playbackFormat, bufferSize); | |
260 } | |
261 catch (LineUnavailableException ex) | |
262 { | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
263 G.debug("SMGR.threadStarted() line unavailable!\n"); |
61 | 264 // the line is unavailable - signal to end this thread |
265 Thread.currentThread().interrupt(); | |
266 return; | |
267 } | |
134
4c0dec72e2f0
Whitespace cosmetic cleanup.
Matti Hamalainen <ccr@tnsp.org>
parents:
110
diff
changeset
|
268 |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
269 // Change volume |
61 | 270 Control.Type ct = FloatControl.Type.MASTER_GAIN; |
271 if (line.isControlSupported(ct)) | |
272 { | |
273 FloatControl c = (FloatControl) line.getControl(ct); | |
274 c.setValue(-20f); | |
275 } | |
276 | |
277 line.start(); | |
278 | |
279 // create the buffer | |
280 byte[] buffer = new byte[bufferSize]; | |
281 | |
282 // set this thread's locals | |
283 localLine.set(line); | |
284 localBuffer.set(buffer); | |
285 } | |
286 | |
287 | |
288 protected void threadStopped() | |
289 { | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
290 G.debug("SMGR.threadStopped()\n"); |
61 | 291 SourceDataLine line = (SourceDataLine) localLine.get(); |
292 if (line != null) | |
293 { | |
294 line.drain(); | |
295 line.close(); | |
296 } | |
297 } | |
298 | |
299 | |
300 protected class SoundPlayer implements Runnable | |
301 { | |
302 private InputStream source; | |
303 | |
304 public SoundPlayer(InputStream source) | |
305 { | |
306 this.source = source; | |
307 } | |
308 | |
309 public void run() | |
310 { | |
311 // get line and buffer from ThreadLocals | |
312 SourceDataLine line = (SourceDataLine) localLine.get(); | |
313 byte[] buffer = (byte[])localBuffer.get(); | |
134
4c0dec72e2f0
Whitespace cosmetic cleanup.
Matti Hamalainen <ccr@tnsp.org>
parents:
110
diff
changeset
|
314 |
61 | 315 if (line == null || buffer == null) |
316 return; | |
317 | |
318 // copy data to the line | |
319 try { | |
107
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
320 boolean playing = true; |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
321 while (playing) { |
61 | 322 // if paused, wait until unpaused |
323 synchronized (pausedLock) | |
324 { | |
325 if (paused) { | |
326 try { | |
327 pausedLock.wait(); | |
328 } | |
329 catch (InterruptedException ex) { | |
330 return; | |
331 } | |
332 } | |
333 } | |
134
4c0dec72e2f0
Whitespace cosmetic cleanup.
Matti Hamalainen <ccr@tnsp.org>
parents:
110
diff
changeset
|
334 |
61 | 335 // copy data |
107
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
336 int bufPos = 0; |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
337 while (bufPos < buffer.length && playing) |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
338 { |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
339 int res = source.read(buffer, bufPos, buffer.length - bufPos); |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
340 if (res != -1) |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
341 bufPos += res; |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
342 else |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
343 playing = false; |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
344 } |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
345 if (playing) |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
346 line.write(buffer, 0, bufPos); |
61 | 347 } |
348 } | |
349 | |
350 catch (IOException ex) { | |
351 ex.printStackTrace(); | |
352 } | |
353 | |
354 } | |
355 } | |
356 | |
357 public synchronized void runTask(Runnable task) | |
358 { | |
359 if (!alive) | |
360 { | |
361 throw new IllegalStateException(); | |
362 } | |
363 if (task != null) | |
364 { | |
365 queue.add(task); | |
366 notify(); | |
367 } | |
368 | |
369 } | |
370 | |
371 protected synchronized Runnable getTask() throws InterruptedException | |
372 { | |
373 while (queue.size() == 0) | |
374 { | |
375 if (!alive) | |
376 return null; | |
377 | |
378 wait(); | |
379 } | |
380 return (Runnable) queue.removeFirst(); | |
381 } | |
382 | |
383 | |
384 public synchronized void close() | |
385 { | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
386 G.debug("SMGR.close()\n"); |
61 | 387 |
388 if (alive) | |
389 { | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
390 G.debug("SMGR.close(): alive queue clear\n"); |
61 | 391 // Clear queue |
392 alive = false; | |
393 queue.clear(); | |
394 interrupt(); | |
395 } | |
396 | |
397 cleanUp(); | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
398 G.debug("SMGR.close(): leaving\n"); |
61 | 399 } |
400 | |
401 | |
402 public void join() | |
403 { | |
192
8dbaa093c562
Improve debug message handling.
Matti Hamalainen <ccr@tnsp.org>
parents:
162
diff
changeset
|
404 G.debug("SMGR.join()\n"); |
61 | 405 cleanUp(); |
406 | |
407 synchronized (this) | |
408 { | |
409 alive = false; | |
410 notifyAll(); | |
411 } | |
412 | |
413 Thread[] threads = new Thread[activeCount()]; | |
414 int count = enumerate(threads); | |
415 for (int i = 0; i < count; i++) | |
416 { | |
417 try { | |
418 threads[i].join(); | |
419 } | |
420 catch (InterruptedException ex) | |
421 { | |
422 } | |
423 } | |
424 } | |
425 | |
426 | |
427 private class PooledThread extends Thread | |
428 { | |
429 public PooledThread() | |
430 { | |
431 super(SoundManager.this, "SoundManagerPool-" + (id++)); | |
432 } | |
433 | |
434 public void run() | |
435 { | |
436 threadStarted(); | |
437 | |
438 while (!isInterrupted()) { | |
439 Runnable task = null; | |
440 try { | |
441 task = getTask(); | |
442 } | |
443 catch (InterruptedException ex) | |
444 { | |
445 } | |
446 | |
447 if (task == null) | |
448 break; | |
449 | |
450 try { | |
451 task.run(); | |
452 } | |
453 catch (Throwable t) { | |
454 uncaughtException(this, t); | |
455 } | |
456 } | |
457 threadStopped(); | |
458 } | |
459 } | |
460 } |