comparison game/SoundManager.java @ 162:e8eeac403e5f

Backed out changeset fb33d3796942
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 01 Dec 2016 14:33:25 +0200
parents src/SoundManager.java@fb33d3796942
children 8dbaa093c562
comparison
equal deleted inserted replaced
161:fb33d3796942 162:e8eeac403e5f
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
49 queue = new LinkedList();
50 for (int i = 0; i < numThreads; i++)
51 new PooledThread().start();
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("SMGR.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 = 8;
82
83 System.out.print(" * 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 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 {
226 System.out.print("SMGR.play(is="+is+")\n");
227 if (is != null)
228 {
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
246 // use a short, 100ms (1/10th sec) buffer for filters that
247 // change in real-time
248 int bufferSize = playbackFormat.getFrameSize() * Math.round(playbackFormat.getSampleRate() / 10);
249
250 // create, open, and start the line
251 SourceDataLine line;
252 DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat);
253
254 System.out.print("SMGR.threadStarted(): "+lineInfo.toString()+"\n");
255
256 try {
257 line = (SourceDataLine) AudioSystem.getLine(lineInfo);
258 line.open(playbackFormat, bufferSize);
259 }
260 catch (LineUnavailableException ex)
261 {
262 System.out.print("SMGR.threadStarted() line unavailable!\n");
263 // the line is unavailable - signal to end this thread
264 Thread.currentThread().interrupt();
265 return;
266 }
267
268 // Change volume
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 // copy data to the line
318 try {
319 boolean playing = true;
320 while (playing) {
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 }
333
334 // copy data
335 int bufPos = 0;
336 while (bufPos < buffer.length && playing)
337 {
338 int res = source.read(buffer, bufPos, buffer.length - bufPos);
339 if (res != -1)
340 bufPos += res;
341 else
342 playing = false;
343 }
344 if (playing)
345 line.write(buffer, 0, bufPos);
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 }