Mercurial > hg > ristipolku
annotate game/SoundManager.java @ 143:5eea4719b429
Remove useless state saving.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 25 Nov 2011 13:47:50 +0200 |
parents | 4c0dec72e2f0 |
children | d6d92845d6a2 |
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)); | |
134
4c0dec72e2f0
Whitespace cosmetic cleanup.
Matti Hamalainen <ccr@tnsp.org>
parents:
110
diff
changeset
|
38 |
61 | 39 System.out.print("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(); | |
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 | |
110
3551b61b3c0b
Increase buffer size a bit.
Matti Hamalainen <ccr@tnsp.org>
parents:
107
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); | |
134
4c0dec72e2f0
Whitespace cosmetic cleanup.
Matti Hamalainen <ccr@tnsp.org>
parents:
110
diff
changeset
|
253 |
93 | 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 } | |
134
4c0dec72e2f0
Whitespace cosmetic cleanup.
Matti Hamalainen <ccr@tnsp.org>
parents:
110
diff
changeset
|
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(); | |
134
4c0dec72e2f0
Whitespace cosmetic cleanup.
Matti Hamalainen <ccr@tnsp.org>
parents:
110
diff
changeset
|
313 |
61 | 314 if (line == null || buffer == null) |
315 return; | |
316 | |
317 // copy data to the line | |
318 try { | |
107
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
319 boolean playing = true; |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
320 while (playing) { |
61 | 321 // if paused, wait until unpaused |
322 synchronized (pausedLock) | |
323 { | |
324 if (paused) { | |
325 try { | |
326 pausedLock.wait(); | |
327 } | |
328 catch (InterruptedException ex) { | |
329 return; | |
330 } | |
331 } | |
332 } | |
134
4c0dec72e2f0
Whitespace cosmetic cleanup.
Matti Hamalainen <ccr@tnsp.org>
parents:
110
diff
changeset
|
333 |
61 | 334 // copy data |
107
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
335 int bufPos = 0; |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
336 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
|
337 { |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
338 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
|
339 if (res != -1) |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
340 bufPos += res; |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
341 else |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
342 playing = false; |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
343 } |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
344 if (playing) |
dd896bc7352b
Fix sound streaming code to cope with partial buffer reads.
Matti Hamalainen <ccr@tnsp.org>
parents:
93
diff
changeset
|
345 line.write(buffer, 0, bufPos); |
61 | 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 } |