Mercurial > hg > ristipolku
annotate game/SoundManager.java @ 75:b586ce4f6d97
Large GUI code cleanup.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 03 Mar 2011 04:04:57 +0200 |
parents | 1c7a97d80120 |
children | e1d657e6c25b |
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 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(); | |
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 { | |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
226 System.out.print("SMGR.play("+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 System.out.print("SMGR.threadStarted()\n"); | |
246 | |
247 // use a short, 100ms (1/10th sec) buffer for filters that | |
248 // change in real-time | |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
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); | |
254 | |
255 try { | |
256 line = (SourceDataLine) AudioSystem.getLine(lineInfo); | |
257 line.open(playbackFormat, bufferSize); | |
258 } | |
259 catch (LineUnavailableException ex) | |
260 { | |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
261 System.out.print("SMGR.threadStarted() line unavailable!\n"); |
61 | 262 // the line is unavailable - signal to end this thread |
263 Thread.currentThread().interrupt(); | |
264 return; | |
265 } | |
266 | |
63
1c7a97d80120
Some work on the sound code ...
Matti Hamalainen <ccr@tnsp.org>
parents:
61
diff
changeset
|
267 // Change volume |
61 | 268 Control.Type ct = FloatControl.Type.MASTER_GAIN; |
269 if (line.isControlSupported(ct)) | |
270 { | |
271 FloatControl c = (FloatControl) line.getControl(ct); | |
272 c.setValue(-20f); | |
273 } | |
274 | |
275 line.start(); | |
276 | |
277 // create the buffer | |
278 byte[] buffer = new byte[bufferSize]; | |
279 | |
280 // set this thread's locals | |
281 localLine.set(line); | |
282 localBuffer.set(buffer); | |
283 } | |
284 | |
285 | |
286 protected void threadStopped() | |
287 { | |
288 System.out.print("SMGR.threadStopped()\n"); | |
289 SourceDataLine line = (SourceDataLine) localLine.get(); | |
290 if (line != null) | |
291 { | |
292 line.drain(); | |
293 line.close(); | |
294 } | |
295 } | |
296 | |
297 | |
298 protected class SoundPlayer implements Runnable | |
299 { | |
300 private InputStream source; | |
301 | |
302 public SoundPlayer(InputStream source) | |
303 { | |
304 this.source = source; | |
305 } | |
306 | |
307 public void run() | |
308 { | |
309 // get line and buffer from ThreadLocals | |
310 SourceDataLine line = (SourceDataLine) localLine.get(); | |
311 byte[] buffer = (byte[])localBuffer.get(); | |
312 | |
313 if (line == null || buffer == null) | |
314 return; | |
315 | |
316 System.out.print("SMGR.SoundPlayer.run()\n"); | |
317 | |
318 // copy data to the line | |
319 try { | |
320 int numBytesRead = 0; | |
321 | |
322 while (numBytesRead != -1) { | |
323 // if paused, wait until unpaused | |
324 synchronized (pausedLock) | |
325 { | |
326 if (paused) { | |
327 try { | |
328 pausedLock.wait(); | |
329 } | |
330 catch (InterruptedException ex) { | |
331 return; | |
332 } | |
333 } | |
334 } | |
335 | |
336 // copy data | |
337 numBytesRead = source.read(buffer, 0, buffer.length); | |
338 if (numBytesRead != -1) | |
339 line.write(buffer, 0, numBytesRead); | |
340 } | |
341 } | |
342 | |
343 catch (IOException ex) { | |
344 ex.printStackTrace(); | |
345 } | |
346 | |
347 } | |
348 } | |
349 | |
350 public synchronized void runTask(Runnable task) | |
351 { | |
352 if (!alive) | |
353 { | |
354 throw new IllegalStateException(); | |
355 } | |
356 if (task != null) | |
357 { | |
358 queue.add(task); | |
359 notify(); | |
360 } | |
361 | |
362 } | |
363 | |
364 protected synchronized Runnable getTask() throws InterruptedException | |
365 { | |
366 while (queue.size() == 0) | |
367 { | |
368 if (!alive) | |
369 return null; | |
370 | |
371 wait(); | |
372 } | |
373 return (Runnable) queue.removeFirst(); | |
374 } | |
375 | |
376 | |
377 public synchronized void close() | |
378 { | |
379 System.out.print("SMGR.close()\n"); | |
380 | |
381 if (alive) | |
382 { | |
383 System.out.print("SMGR.close(): alive queue clear\n"); | |
384 // Clear queue | |
385 alive = false; | |
386 queue.clear(); | |
387 interrupt(); | |
388 } | |
389 | |
390 cleanUp(); | |
391 System.out.print("SMGR.close(): leaving\n"); | |
392 } | |
393 | |
394 | |
395 public void join() | |
396 { | |
397 System.out.print("SMGR.join()\n"); | |
398 cleanUp(); | |
399 | |
400 synchronized (this) | |
401 { | |
402 alive = false; | |
403 notifyAll(); | |
404 } | |
405 | |
406 Thread[] threads = new Thread[activeCount()]; | |
407 int count = enumerate(threads); | |
408 for (int i = 0; i < count; i++) | |
409 { | |
410 try { | |
411 threads[i].join(); | |
412 } | |
413 catch (InterruptedException ex) | |
414 { | |
415 } | |
416 } | |
417 } | |
418 | |
419 | |
420 private class PooledThread extends Thread | |
421 { | |
422 public PooledThread() | |
423 { | |
424 super(SoundManager.this, "SoundManagerPool-" + (id++)); | |
425 } | |
426 | |
427 public void run() | |
428 { | |
429 threadStarted(); | |
430 | |
431 while (!isInterrupted()) { | |
432 Runnable task = null; | |
433 try { | |
434 task = getTask(); | |
435 } | |
436 catch (InterruptedException ex) | |
437 { | |
438 } | |
439 | |
440 if (task == null) | |
441 break; | |
442 | |
443 try { | |
444 task.run(); | |
445 } | |
446 catch (Throwable t) { | |
447 uncaughtException(this, t); | |
448 } | |
449 } | |
450 threadStopped(); | |
451 } | |
452 } | |
453 } |