Mercurial > audio-send
comparison RecordAudioRenderer.java @ 0:f9476ff7637e
initial forking of open-al to create multiple listeners
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Tue, 25 Oct 2011 13:02:31 -0700 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:f9476ff7637e |
---|---|
1 package com.jme3.capture; | |
2 | |
3 import static org.lwjgl.openal.AL10.AL_BUFFER; | |
4 import static org.lwjgl.openal.AL10.AL_BUFFERS_PROCESSED; | |
5 import static org.lwjgl.openal.AL10.AL_CONE_INNER_ANGLE; | |
6 import static org.lwjgl.openal.AL10.AL_CONE_OUTER_ANGLE; | |
7 import static org.lwjgl.openal.AL10.AL_CONE_OUTER_GAIN; | |
8 import static org.lwjgl.openal.AL10.AL_DIRECTION; | |
9 import static org.lwjgl.openal.AL10.AL_FALSE; | |
10 import static org.lwjgl.openal.AL10.AL_FORMAT_MONO16; | |
11 import static org.lwjgl.openal.AL10.AL_FORMAT_MONO8; | |
12 import static org.lwjgl.openal.AL10.AL_FORMAT_STEREO16; | |
13 import static org.lwjgl.openal.AL10.AL_FORMAT_STEREO8; | |
14 import static org.lwjgl.openal.AL10.AL_GAIN; | |
15 import static org.lwjgl.openal.AL10.AL_LOOPING; | |
16 import static org.lwjgl.openal.AL10.AL_MAX_DISTANCE; | |
17 import static org.lwjgl.openal.AL10.AL_ORIENTATION; | |
18 import static org.lwjgl.openal.AL10.AL_PAUSED; | |
19 import static org.lwjgl.openal.AL10.AL_PITCH; | |
20 import static org.lwjgl.openal.AL10.AL_POSITION; | |
21 import static org.lwjgl.openal.AL10.AL_REFERENCE_DISTANCE; | |
22 import static org.lwjgl.openal.AL10.AL_RENDERER; | |
23 import static org.lwjgl.openal.AL10.AL_SOURCE_RELATIVE; | |
24 import static org.lwjgl.openal.AL10.AL_SOURCE_STATE; | |
25 import static org.lwjgl.openal.AL10.AL_STOPPED; | |
26 import static org.lwjgl.openal.AL10.AL_TRUE; | |
27 import static org.lwjgl.openal.AL10.AL_VELOCITY; | |
28 import static org.lwjgl.openal.AL10.AL_VENDOR; | |
29 import static org.lwjgl.openal.AL10.AL_VERSION; | |
30 import static org.lwjgl.openal.AL10.alBufferData; | |
31 import static org.lwjgl.openal.AL10.alDeleteBuffers; | |
32 import static org.lwjgl.openal.AL10.alDeleteSources; | |
33 import static org.lwjgl.openal.AL10.alGenBuffers; | |
34 import static org.lwjgl.openal.AL10.alGenSources; | |
35 import static org.lwjgl.openal.AL10.alGetError; | |
36 import static org.lwjgl.openal.AL10.alGetSourcei; | |
37 import static org.lwjgl.openal.AL10.alGetString; | |
38 import static org.lwjgl.openal.AL10.alListener; | |
39 import static org.lwjgl.openal.AL10.alListener3f; | |
40 import static org.lwjgl.openal.AL10.alListenerf; | |
41 import static org.lwjgl.openal.AL10.alSource3f; | |
42 import static org.lwjgl.openal.AL10.alSourcePause; | |
43 import static org.lwjgl.openal.AL10.alSourcePlay; | |
44 import static org.lwjgl.openal.AL10.alSourceQueueBuffers; | |
45 import static org.lwjgl.openal.AL10.alSourceStop; | |
46 import static org.lwjgl.openal.AL10.alSourceUnqueueBuffers; | |
47 import static org.lwjgl.openal.AL10.alSourcef; | |
48 import static org.lwjgl.openal.AL10.alSourcei; | |
49 | |
50 import java.lang.reflect.Field; | |
51 import java.nio.ByteBuffer; | |
52 import java.nio.FloatBuffer; | |
53 import java.nio.IntBuffer; | |
54 import java.util.ArrayList; | |
55 import java.util.Vector; | |
56 import java.util.concurrent.atomic.AtomicBoolean; | |
57 import java.util.logging.Level; | |
58 import java.util.logging.Logger; | |
59 | |
60 import org.lwjgl.LWJGLException; | |
61 import org.lwjgl.openal.AL; | |
62 import org.lwjgl.openal.AL11; | |
63 import org.lwjgl.openal.ALC10; | |
64 import org.lwjgl.openal.ALCdevice; | |
65 import org.lwjgl.openal.EFX10; | |
66 import org.lwjgl.openal.OpenALException; | |
67 | |
68 import com.jme3.audio.AudioBuffer; | |
69 import com.jme3.audio.AudioData; | |
70 import com.jme3.audio.AudioNode; | |
71 import com.jme3.audio.AudioNode.Status; | |
72 import com.jme3.audio.AudioParam; | |
73 import com.jme3.audio.AudioRenderer; | |
74 import com.jme3.audio.AudioStream; | |
75 import com.jme3.audio.Environment; | |
76 import com.jme3.audio.Filter; | |
77 import com.jme3.audio.Listener; | |
78 import com.jme3.audio.ListenerParam; | |
79 import com.jme3.audio.LowPassFilter; | |
80 import com.jme3.math.Vector3f; | |
81 import com.jme3.util.BufferUtils; | |
82 | |
83 | |
84 | |
85 public class RecordAudioRenderer implements AudioRenderer, Runnable { | |
86 | |
87 | |
88 | |
89 public static void getMainSamples(){ | |
90 | |
91 } | |
92 | |
93 | |
94 private static final Logger logger = Logger.getLogger(RecordAudioRenderer.class.getName()); | |
95 | |
96 // When multiplied by STREAMING_BUFFER_COUNT, will equal 44100 * 2 * 2 | |
97 // which is exactly 1 second of audio. | |
98 private static final int BUFFER_SIZE = 35280; | |
99 private static final int STREAMING_BUFFER_COUNT = 5; | |
100 | |
101 private final static int MAX_NUM_CHANNELS = 2; | |
102 private IntBuffer ib = BufferUtils.createIntBuffer(1); | |
103 private final FloatBuffer fb = BufferUtils.createVector3Buffer(2); | |
104 private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE); | |
105 private final byte[] arrayBuf = new byte[BUFFER_SIZE]; | |
106 | |
107 private int[] channels; | |
108 private AudioNode[] chanSrcs; | |
109 private int nextChan = 0; | |
110 private ArrayList<Integer> freeChans = new ArrayList<Integer>(); | |
111 | |
112 private Listener listener; | |
113 private boolean audioDisabled = false; | |
114 | |
115 private boolean supportEfx = false; | |
116 private int auxSends = 0; | |
117 private int reverbFx = -1; | |
118 private int reverbFxSlot = -1; | |
119 | |
120 // RLM: this is to call the native methods which require the OpenAL device ID. | |
121 // currently it is obtained through reflection. | |
122 private long deviceID; | |
123 | |
124 // Update audio 20 times per second | |
125 private static final float UPDATE_RATE = 0.05f; | |
126 | |
127 private final Thread audioThread = new Thread(this, "jME3 Audio Thread"); | |
128 private final AtomicBoolean threadLock = new AtomicBoolean(false); | |
129 | |
130 public RecordAudioRenderer(){ | |
131 } | |
132 | |
133 public static native void helloEveryone(); | |
134 | |
135 | |
136 public static native void nstep(long device); | |
137 public void step(){ | |
138 nstep(this.deviceID); | |
139 } | |
140 | |
141 | |
142 | |
143 public void getMainSamples(ByteBuffer buffer){ | |
144 ngetMainSamples(this.deviceID, buffer, buffer.position()); | |
145 } | |
146 public static native void ngetMainSamples(long device, ByteBuffer buffer, int position); | |
147 | |
148 | |
149 public void getAuxSamples(ByteBuffer buffer){ | |
150 ngetAuxSamples(this.deviceID, buffer, buffer.position()); | |
151 } | |
152 public static native void ngetAuxSamples(long device, ByteBuffer buffer, int position); | |
153 | |
154 | |
155 | |
156 public void initialize(){ | |
157 if (!audioThread.isAlive()){ | |
158 audioThread.setDaemon(true); | |
159 audioThread.setPriority(Thread.NORM_PRIORITY+1); | |
160 audioThread.start(); | |
161 }else{ | |
162 throw new IllegalStateException("Initialize already called"); | |
163 } | |
164 } | |
165 | |
166 private void checkDead(){ | |
167 if (audioThread.getState() == Thread.State.TERMINATED) | |
168 throw new IllegalStateException("Audio thread is terminated"); | |
169 } | |
170 | |
171 public void run(){ | |
172 initInThread(); | |
173 synchronized (threadLock){ | |
174 threadLock.set(true); | |
175 threadLock.notifyAll(); | |
176 } | |
177 | |
178 | |
179 helloEveryone(); | |
180 System.out.println("AudioRecorder: Trying to call native methods."); | |
181 System.out.println("our device ID is : " + this.deviceID); | |
182 | |
183 | |
184 | |
185 | |
186 long updateRateNanos = (long) (UPDATE_RATE * 1000000000); | |
187 mainloop: while (true){ | |
188 long startTime = System.nanoTime(); | |
189 | |
190 if (Thread.interrupted()) | |
191 break; | |
192 | |
193 synchronized (threadLock){ | |
194 updateInThread(UPDATE_RATE); | |
195 } | |
196 | |
197 long endTime = System.nanoTime(); | |
198 long diffTime = endTime - startTime; | |
199 | |
200 if (diffTime < updateRateNanos){ | |
201 long desiredEndTime = startTime + updateRateNanos; | |
202 while (System.nanoTime() < desiredEndTime){ | |
203 try{ | |
204 Thread.sleep(1); | |
205 }catch (InterruptedException ex){ | |
206 break mainloop; | |
207 } | |
208 } | |
209 } | |
210 } | |
211 | |
212 synchronized (threadLock){ | |
213 cleanupInThread(); | |
214 } | |
215 } | |
216 | |
217 public void initInThread(){ | |
218 try{ | |
219 if (!AL.isCreated()){ | |
220 AL.create("Aurellem", 44100, 15, false); | |
221 } | |
222 }catch (OpenALException ex){ | |
223 logger.log(Level.SEVERE, "Failed to load audio library", ex); | |
224 audioDisabled = true; | |
225 return; | |
226 }catch (LWJGLException ex){ | |
227 logger.log(Level.SEVERE, "Failed to load audio library", ex); | |
228 audioDisabled = true; | |
229 return; | |
230 } | |
231 | |
232 ALCdevice device = AL.getDevice(); | |
233 | |
234 // RLM: use reflection to grab the ID of our device for use later. | |
235 try { | |
236 Field deviceIDField; | |
237 deviceIDField = ALCdevice.class.getDeclaredField("device"); | |
238 deviceIDField.setAccessible(true); | |
239 try {deviceID = (Long)deviceIDField.get(device);} | |
240 catch (IllegalArgumentException e) {e.printStackTrace();} | |
241 catch (IllegalAccessException e) {e.printStackTrace();} | |
242 deviceIDField.setAccessible(false);} | |
243 catch (SecurityException e) {e.printStackTrace();} | |
244 catch (NoSuchFieldException e) {e.printStackTrace();} | |
245 | |
246 | |
247 | |
248 String deviceName = ALC10.alcGetString(device, ALC10.ALC_DEVICE_SPECIFIER); | |
249 | |
250 logger.log(Level.FINER, "Audio Device: {0}", deviceName); | |
251 logger.log(Level.FINER, "Audio Vendor: {0}", alGetString(AL_VENDOR)); | |
252 logger.log(Level.FINER, "Audio Renderer: {0}", alGetString(AL_RENDERER)); | |
253 logger.log(Level.FINER, "Audio Version: {0}", alGetString(AL_VERSION)); | |
254 | |
255 // Find maximum # of sources supported by this implementation | |
256 // RLM: this may not be wise -- exceeding the number of available channels | |
257 // can crash some versions of OpenAL | |
258 ArrayList<Integer> channelList = new ArrayList<Integer>(); | |
259 for (int i = 0; i < MAX_NUM_CHANNELS; i++){ | |
260 int chan = alGenSources(); | |
261 if (alGetError() != 0){ | |
262 break; | |
263 }else{ | |
264 channelList.add(chan); | |
265 } | |
266 } | |
267 | |
268 channels = new int[channelList.size()]; | |
269 for (int i = 0; i < channels.length; i++){ | |
270 channels[i] = channelList.get(i); | |
271 } | |
272 | |
273 ib = BufferUtils.createIntBuffer(channels.length); | |
274 chanSrcs = new AudioNode[channels.length]; | |
275 | |
276 logger.log(Level.INFO, "AudioRenderer supports {0} channels", channels.length); | |
277 | |
278 supportEfx = ALC10.alcIsExtensionPresent(device, "ALC_EXT_EFX"); | |
279 // RLM: disable this for now. | |
280 supportEfx = false; | |
281 logger.log(Level.FINER, "Audio EFX support: {0}", supportEfx); | |
282 | |
283 if (supportEfx){ | |
284 ib.position(0).limit(1); | |
285 ALC10.alcGetInteger(device, EFX10.ALC_EFX_MAJOR_VERSION, ib); | |
286 int major = ib.get(0); | |
287 ib.position(0).limit(1); | |
288 ALC10.alcGetInteger(device, EFX10.ALC_EFX_MINOR_VERSION, ib); | |
289 int minor = ib.get(0); | |
290 logger.log(Level.INFO, "Audio effect extension version: {0}.{1}", new Object[]{major, minor}); | |
291 | |
292 ALC10.alcGetInteger(device, EFX10.ALC_MAX_AUXILIARY_SENDS, ib); | |
293 auxSends = ib.get(0); | |
294 logger.log(Level.INFO, "Audio max auxilary sends: {0}", auxSends); | |
295 | |
296 // create slot | |
297 ib.position(0).limit(1); | |
298 EFX10.alGenAuxiliaryEffectSlots(ib); | |
299 reverbFxSlot = ib.get(0); | |
300 | |
301 // create effect | |
302 ib.position(0).limit(1); | |
303 EFX10.alGenEffects(ib); | |
304 reverbFx = ib.get(0); | |
305 EFX10.alEffecti(reverbFx, EFX10.AL_EFFECT_TYPE, EFX10.AL_EFFECT_REVERB); | |
306 | |
307 // attach reverb effect to effect slot | |
308 // EFX10.alAuxiliaryEffectSloti(reverbFxSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbFx); | |
309 } | |
310 } | |
311 | |
312 public void cleanupInThread(){ | |
313 | |
314 | |
315 if (audioDisabled){ | |
316 AL.destroy(); | |
317 return; | |
318 } | |
319 | |
320 // delete channel-based sources | |
321 ib.clear(); | |
322 ib.put(channels); | |
323 ib.flip(); | |
324 alDeleteSources(ib); | |
325 | |
326 if (supportEfx){ | |
327 ib.position(0).limit(1); | |
328 ib.put(0, reverbFx); | |
329 EFX10.alDeleteEffects(ib); | |
330 | |
331 ib.position(0).limit(1); | |
332 ib.put(0, reverbFxSlot); | |
333 EFX10.alDeleteAuxiliaryEffectSlots(ib); | |
334 } | |
335 | |
336 // XXX: Delete other buffers/sources | |
337 AL.destroy(); | |
338 } | |
339 | |
340 public void cleanup(){ | |
341 // kill audio thread | |
342 | |
343 if (audioThread.isAlive()){ | |
344 audioThread.interrupt(); | |
345 } | |
346 | |
347 Byte[] data1 = new Byte[this.fullWaveData1.size()]; | |
348 data1 = this.fullWaveData1.toArray(data1); | |
349 | |
350 Byte[] data2 = new Byte[this.fullWaveData2.size()]; | |
351 data2 = this.fullWaveData2.toArray(data2); | |
352 System.out.println(this.fullWaveData1.size()); | |
353 System.out.println("Saving WAVE data!"); | |
354 /*for (int i = 0; i < data1.length;i++){ | |
355 System.out.print(data1[i]+","); | |
356 if (i%32 ==0){System.out.println();} | |
357 } | |
358 */ | |
359 | |
360 | |
361 StdAudio.save("/home/r/wave-output/data2.wav", data2); | |
362 StdAudio.save("/home/r/wave-output/data1.wav", data1); | |
363 } | |
364 | |
365 private void updateFilter(Filter f){ | |
366 int id = f.getId(); | |
367 if (id == -1){ | |
368 ib.position(0).limit(1); | |
369 EFX10.alGenFilters(ib); | |
370 id = ib.get(0); | |
371 f.setId(id); | |
372 } | |
373 | |
374 if (f instanceof LowPassFilter){ | |
375 LowPassFilter lpf = (LowPassFilter) f; | |
376 EFX10.alFilteri(id, EFX10.AL_FILTER_TYPE, EFX10.AL_FILTER_LOWPASS); | |
377 EFX10.alFilterf(id, EFX10.AL_LOWPASS_GAIN, lpf.getVolume()); | |
378 EFX10.alFilterf(id, EFX10.AL_LOWPASS_GAINHF, lpf.getHighFreqVolume()); | |
379 }else{ | |
380 throw new UnsupportedOperationException("Filter type unsupported: "+ | |
381 f.getClass().getName()); | |
382 } | |
383 | |
384 f.clearUpdateNeeded(); | |
385 } | |
386 | |
387 public void updateSourceParam(AudioNode src, AudioParam param){ | |
388 checkDead(); | |
389 synchronized (threadLock){ | |
390 while (!threadLock.get()){ | |
391 try { | |
392 threadLock.wait(); | |
393 } catch (InterruptedException ex) { | |
394 } | |
395 } | |
396 if (audioDisabled) | |
397 return; | |
398 | |
399 // There is a race condition in AudioNode that can | |
400 // cause this to be called for a node that has been | |
401 // detached from its channel. For example, setVolume() | |
402 // called from the render thread may see that that AudioNode | |
403 // still has a channel value but the audio thread may | |
404 // clear that channel before setVolume() gets to call | |
405 // updateSourceParam() (because the audio stopped playing | |
406 // on its own right as the volume was set). In this case, | |
407 // it should be safe to just ignore the update | |
408 if (src.getChannel() < 0) | |
409 return; | |
410 | |
411 assert src.getChannel() >= 0; | |
412 | |
413 int id = channels[src.getChannel()]; | |
414 switch (param){ | |
415 case Position: | |
416 if (!src.isPositional()) | |
417 return; | |
418 | |
419 Vector3f pos = src.getWorldTranslation(); | |
420 alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z); | |
421 break; | |
422 case Velocity: | |
423 if (!src.isPositional()) | |
424 return; | |
425 | |
426 Vector3f vel = src.getVelocity(); | |
427 alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z); | |
428 break; | |
429 case MaxDistance: | |
430 if (!src.isPositional()) | |
431 return; | |
432 | |
433 alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance()); | |
434 break; | |
435 case RefDistance: | |
436 if (!src.isPositional()) | |
437 return; | |
438 | |
439 alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance()); | |
440 break; | |
441 case ReverbFilter: | |
442 if (!src.isPositional() || !src.isReverbEnabled()) | |
443 return; | |
444 | |
445 int filter = EFX10.AL_FILTER_NULL; | |
446 if (src.getReverbFilter() != null){ | |
447 Filter f = src.getReverbFilter(); | |
448 if (f.isUpdateNeeded()){ | |
449 updateFilter(f); | |
450 } | |
451 filter = f.getId(); | |
452 } | |
453 AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); | |
454 break; | |
455 case ReverbEnabled: | |
456 if (!src.isPositional()) | |
457 return; | |
458 | |
459 if (src.isReverbEnabled()){ | |
460 updateSourceParam(src, AudioParam.ReverbFilter); | |
461 }else{ | |
462 AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX10.AL_FILTER_NULL); | |
463 } | |
464 break; | |
465 case IsPositional: | |
466 if (!src.isPositional()){ | |
467 // play in headspace | |
468 alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE); | |
469 alSource3f(id, AL_POSITION, 0,0,0); | |
470 alSource3f(id, AL_VELOCITY, 0,0,0); | |
471 }else{ | |
472 alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE); | |
473 updateSourceParam(src, AudioParam.Position); | |
474 updateSourceParam(src, AudioParam.Velocity); | |
475 updateSourceParam(src, AudioParam.MaxDistance); | |
476 updateSourceParam(src, AudioParam.RefDistance); | |
477 updateSourceParam(src, AudioParam.ReverbEnabled); | |
478 } | |
479 break; | |
480 case Direction: | |
481 if (!src.isDirectional()) | |
482 return; | |
483 | |
484 Vector3f dir = src.getDirection(); | |
485 alSource3f(id, AL_DIRECTION, dir.x, dir.y, dir.z); | |
486 break; | |
487 case InnerAngle: | |
488 if (!src.isDirectional()) | |
489 return; | |
490 | |
491 alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle()); | |
492 break; | |
493 case OuterAngle: | |
494 if (!src.isDirectional()) | |
495 return; | |
496 | |
497 alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); | |
498 break; | |
499 case IsDirectional: | |
500 if (src.isDirectional()){ | |
501 updateSourceParam(src, AudioParam.Direction); | |
502 updateSourceParam(src, AudioParam.InnerAngle); | |
503 updateSourceParam(src, AudioParam.OuterAngle); | |
504 alSourcef(id, AL_CONE_OUTER_GAIN, 0); | |
505 }else{ | |
506 alSourcef(id, AL_CONE_INNER_ANGLE, 360); | |
507 alSourcef(id, AL_CONE_OUTER_ANGLE, 360); | |
508 alSourcef(id, AL_CONE_OUTER_GAIN, 1f); | |
509 } | |
510 break; | |
511 case DryFilter: | |
512 if (src.getDryFilter() != null){ | |
513 Filter f = src.getDryFilter(); | |
514 if (f.isUpdateNeeded()){ | |
515 updateFilter(f); | |
516 | |
517 // NOTE: must re-attach filter for changes to apply. | |
518 alSourcei(id, EFX10.AL_DIRECT_FILTER, f.getId()); | |
519 } | |
520 }else{ | |
521 alSourcei(id, EFX10.AL_DIRECT_FILTER, EFX10.AL_FILTER_NULL); | |
522 } | |
523 break; | |
524 case Looping: | |
525 if (src.isLooping()){ | |
526 if (!(src.getAudioData() instanceof AudioStream)){ | |
527 alSourcei(id, AL_LOOPING, AL_TRUE); | |
528 } | |
529 }else{ | |
530 alSourcei(id, AL_LOOPING, AL_FALSE); | |
531 } | |
532 break; | |
533 case Volume: | |
534 alSourcef(id, AL_GAIN, src.getVolume()); | |
535 break; | |
536 case Pitch: | |
537 alSourcef(id, AL_PITCH, src.getPitch()); | |
538 break; | |
539 } | |
540 } | |
541 } | |
542 | |
543 private void setSourceParams(int id, AudioNode src, boolean forceNonLoop){ | |
544 if (src.isPositional()){ | |
545 Vector3f pos = src.getWorldTranslation(); | |
546 Vector3f vel = src.getVelocity(); | |
547 alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z); | |
548 alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z); | |
549 alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance()); | |
550 alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance()); | |
551 alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE); | |
552 | |
553 if (src.isReverbEnabled()){ | |
554 int filter = EFX10.AL_FILTER_NULL; | |
555 if (src.getReverbFilter() != null){ | |
556 Filter f = src.getReverbFilter(); | |
557 if (f.isUpdateNeeded()){ | |
558 updateFilter(f); | |
559 } | |
560 filter = f.getId(); | |
561 } | |
562 AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); | |
563 } | |
564 }else{ | |
565 // play in headspace | |
566 alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE); | |
567 alSource3f(id, AL_POSITION, 0,0,0); | |
568 alSource3f(id, AL_VELOCITY, 0,0,0); | |
569 } | |
570 | |
571 if (src.getDryFilter() != null){ | |
572 Filter f = src.getDryFilter(); | |
573 if (f.isUpdateNeeded()){ | |
574 updateFilter(f); | |
575 | |
576 // NOTE: must re-attach filter for changes to apply. | |
577 alSourcei(id, EFX10.AL_DIRECT_FILTER, f.getId()); | |
578 } | |
579 } | |
580 | |
581 if (forceNonLoop){ | |
582 alSourcei(id, AL_LOOPING, AL_FALSE); | |
583 }else{ | |
584 alSourcei(id, AL_LOOPING, src.isLooping() ? AL_TRUE : AL_FALSE); | |
585 } | |
586 alSourcef(id, AL_GAIN, src.getVolume()); | |
587 alSourcef(id, AL_PITCH, src.getPitch()); | |
588 alSourcef(id, AL11.AL_SEC_OFFSET, src.getTimeOffset()); | |
589 | |
590 if (src.isDirectional()){ | |
591 Vector3f dir = src.getDirection(); | |
592 alSource3f(id, AL_DIRECTION, dir.x, dir.y, dir.z); | |
593 alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle()); | |
594 alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); | |
595 alSourcef(id, AL_CONE_OUTER_GAIN, 0); | |
596 }else{ | |
597 alSourcef(id, AL_CONE_INNER_ANGLE, 360); | |
598 alSourcef(id, AL_CONE_OUTER_ANGLE, 360); | |
599 alSourcef(id, AL_CONE_OUTER_GAIN, 1f); | |
600 } | |
601 } | |
602 | |
603 public void updateListenerParam(Listener listener, ListenerParam param){ | |
604 checkDead(); | |
605 synchronized (threadLock){ | |
606 while (!threadLock.get()){ | |
607 try { | |
608 threadLock.wait(); | |
609 } catch (InterruptedException ex) { | |
610 } | |
611 } | |
612 if (audioDisabled) | |
613 return; | |
614 | |
615 switch (param){ | |
616 case Position: | |
617 Vector3f pos = listener.getLocation(); | |
618 alListener3f(AL_POSITION, pos.x, pos.y, pos.z); | |
619 break; | |
620 case Rotation: | |
621 Vector3f dir = listener.getDirection(); | |
622 Vector3f up = listener.getUp(); | |
623 fb.rewind(); | |
624 fb.put(dir.x).put(dir.y).put(dir.z); | |
625 fb.put(up.x).put(up.y).put(up.z); | |
626 fb.flip(); | |
627 alListener(AL_ORIENTATION, fb); | |
628 break; | |
629 case Velocity: | |
630 Vector3f vel = listener.getVelocity(); | |
631 alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); | |
632 break; | |
633 case Volume: | |
634 alListenerf(AL_GAIN, listener.getVolume()); | |
635 break; | |
636 } | |
637 } | |
638 } | |
639 | |
640 private void setListenerParams(Listener listener){ | |
641 Vector3f pos = listener.getLocation(); | |
642 Vector3f vel = listener.getVelocity(); | |
643 Vector3f dir = listener.getDirection(); | |
644 Vector3f up = listener.getUp(); | |
645 | |
646 alListener3f(AL_POSITION, pos.x, pos.y, pos.z); | |
647 alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); | |
648 fb.rewind(); | |
649 fb.put(dir.x).put(dir.y).put(dir.z); | |
650 fb.put(up.x).put(up.y).put(up.z); | |
651 fb.flip(); | |
652 alListener(AL_ORIENTATION, fb); | |
653 alListenerf(AL_GAIN, listener.getVolume()); | |
654 } | |
655 | |
656 private int newChannel(){ | |
657 if (freeChans.size() > 0) | |
658 return freeChans.remove(0); | |
659 else if (nextChan < channels.length){ | |
660 return nextChan++; | |
661 }else{ | |
662 return -1; | |
663 } | |
664 } | |
665 | |
666 private void freeChannel(int index){ | |
667 if (index == nextChan-1){ | |
668 nextChan--; | |
669 } else{ | |
670 freeChans.add(index); | |
671 } | |
672 } | |
673 | |
674 public void setEnvironment(Environment env){ | |
675 checkDead(); | |
676 synchronized (threadLock){ | |
677 while (!threadLock.get()){ | |
678 try { | |
679 threadLock.wait(); | |
680 } catch (InterruptedException ex) { | |
681 } | |
682 } | |
683 if (audioDisabled) | |
684 return; | |
685 | |
686 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DENSITY, env.getDensity()); | |
687 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DIFFUSION, env.getDiffusion()); | |
688 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_GAIN, env.getGain()); | |
689 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_GAINHF, env.getGainHf()); | |
690 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DECAY_TIME, env.getDecayTime()); | |
691 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DECAY_HFRATIO, env.getDecayHFRatio()); | |
692 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_REFLECTIONS_GAIN, env.getReflectGain()); | |
693 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_REFLECTIONS_DELAY, env.getReflectDelay()); | |
694 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_LATE_REVERB_GAIN, env.getLateReverbGain()); | |
695 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_LATE_REVERB_DELAY, env.getLateReverbDelay()); | |
696 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_AIR_ABSORPTION_GAINHF, env.getAirAbsorbGainHf()); | |
697 EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_ROOM_ROLLOFF_FACTOR, env.getRoomRolloffFactor()); | |
698 | |
699 // attach effect to slot | |
700 EFX10.alAuxiliaryEffectSloti(reverbFxSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbFx); | |
701 } | |
702 } | |
703 | |
704 private boolean fillBuffer(AudioStream stream, int id){ | |
705 int size = 0; | |
706 int result; | |
707 | |
708 while (size < arrayBuf.length){ | |
709 result = stream.readSamples(arrayBuf, size, arrayBuf.length - size); | |
710 | |
711 if(result > 0){ | |
712 size += result; | |
713 }else{ | |
714 break; | |
715 } | |
716 } | |
717 | |
718 if(size == 0) | |
719 return false; | |
720 | |
721 nativeBuf.clear(); | |
722 nativeBuf.put(arrayBuf, 0, size); | |
723 nativeBuf.flip(); | |
724 | |
725 alBufferData(id, convertFormat(stream), nativeBuf, stream.getSampleRate()); | |
726 | |
727 return true; | |
728 } | |
729 | |
730 private boolean fillStreamingSource(int sourceId, AudioStream stream){ | |
731 if (!stream.isOpen()) | |
732 return false; | |
733 | |
734 boolean active = true; | |
735 int processed = alGetSourcei(sourceId, AL_BUFFERS_PROCESSED); | |
736 | |
737 // while((processed--) != 0){ | |
738 if (processed > 0){ | |
739 int buffer; | |
740 | |
741 ib.position(0).limit(1); | |
742 alSourceUnqueueBuffers(sourceId, ib); | |
743 buffer = ib.get(0); | |
744 | |
745 active = fillBuffer(stream, buffer); | |
746 | |
747 ib.position(0).limit(1); | |
748 ib.put(0, buffer); | |
749 alSourceQueueBuffers(sourceId, ib); | |
750 } | |
751 | |
752 if (!active && stream.isOpen()) | |
753 stream.close(); | |
754 | |
755 return active; | |
756 } | |
757 | |
758 private boolean attachStreamToSource(int sourceId, AudioStream stream){ | |
759 boolean active = true; | |
760 for (int id : stream.getIds()){ | |
761 active = fillBuffer(stream, id); | |
762 ib.position(0).limit(1); | |
763 ib.put(id).flip(); | |
764 alSourceQueueBuffers(sourceId, ib); | |
765 } | |
766 return active; | |
767 } | |
768 | |
769 private boolean attachBufferToSource(int sourceId, AudioBuffer buffer){ | |
770 alSourcei(sourceId, AL_BUFFER, buffer.getId()); | |
771 return true; | |
772 } | |
773 | |
774 private boolean attachAudioToSource(int sourceId, AudioData data){ | |
775 if (data instanceof AudioBuffer){ | |
776 return attachBufferToSource(sourceId, (AudioBuffer) data); | |
777 }else if (data instanceof AudioStream){ | |
778 return attachStreamToSource(sourceId, (AudioStream) data); | |
779 } | |
780 throw new UnsupportedOperationException(); | |
781 } | |
782 | |
783 private void clearChannel(int index){ | |
784 // make room at this channel | |
785 if (chanSrcs[index] != null){ | |
786 AudioNode src = chanSrcs[index]; | |
787 | |
788 int sourceId = channels[index]; | |
789 alSourceStop(sourceId); | |
790 | |
791 if (src.getAudioData() instanceof AudioStream){ | |
792 AudioStream str = (AudioStream) src.getAudioData(); | |
793 ib.position(0).limit(STREAMING_BUFFER_COUNT); | |
794 ib.put(str.getIds()).flip(); | |
795 alSourceUnqueueBuffers(sourceId, ib); | |
796 }else if (src.getAudioData() instanceof AudioBuffer){ | |
797 alSourcei(sourceId, AL_BUFFER, 0); | |
798 } | |
799 | |
800 if (src.getDryFilter() != null){ | |
801 // detach filter | |
802 alSourcei(sourceId, EFX10.AL_DIRECT_FILTER, EFX10.AL_FILTER_NULL); | |
803 } | |
804 if (src.isPositional()){ | |
805 AudioNode pas = (AudioNode) src; | |
806 if (pas.isReverbEnabled()) { | |
807 AL11.alSource3i(sourceId, EFX10.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX10.AL_FILTER_NULL); | |
808 } | |
809 } | |
810 | |
811 chanSrcs[index] = null; | |
812 } | |
813 } | |
814 | |
815 public void update(float tpf){ | |
816 //ByteBuffer test = BufferUtils.createByteBuffer(1); | |
817 //AurellemTransport.getAuxSamples(AL.getDevice(), test); | |
818 } | |
819 | |
820 Vector<Byte> fullWaveData1 = new Vector<Byte>(); | |
821 Vector<Byte> fullWaveData2 = new Vector<Byte>(); | |
822 | |
823 public void updateInThread(float tpf){ | |
824 if (audioDisabled) | |
825 return; | |
826 | |
827 step(); | |
828 ByteBuffer test = BufferUtils.createByteBuffer(4096); | |
829 test.clear(); | |
830 this.getMainSamples(test); | |
831 byte[] waveData = new byte[4096]; | |
832 test.get(waveData, 0, 4096); | |
833 //System.out.println("J DATA:"); | |
834 /*for (int j = 0; j < 1; j++){ | |
835 for(int i = 64 * j; i < (64*j) + 64; i++){ | |
836 System.out.print(waveData[i]); | |
837 } | |
838 System.out.println(); | |
839 }*/ | |
840 | |
841 ByteBuffer test2 = BufferUtils.createByteBuffer(4096); | |
842 test2.clear(); | |
843 this.getAuxSamples(test2); | |
844 byte[] waveData2 = new byte[4096]; | |
845 test2.get(waveData2, 0, 4096); | |
846 //System.out.print("wave1:"); | |
847 //for (int j = 0; j< 32; j++){ | |
848 // System.out.print(waveData[j]+","); | |
849 // } | |
850 //System.out.println(); | |
851 //System.out.print("wave2:"); | |
852 // for (int j = 0; j< 4096; j++){ | |
853 // if (waveData2[j] != 0){ | |
854 // System.out.println("fucked at : " + j); | |
855 // } | |
856 | |
857 /* System.out.print(waveData2[j]+","); | |
858 if (0 == (j % 64)){System.out.println();}*/ | |
859 //} | |
860 //System.out.println(); | |
861 | |
862 for (byte b : waveData){ | |
863 this.fullWaveData1.add(b); | |
864 } | |
865 | |
866 for (byte b : waveData2){ | |
867 this.fullWaveData2.add(b); | |
868 } | |
869 | |
870 | |
871 for (int i = 0; i < channels.length; i++){ | |
872 AudioNode src = chanSrcs[i]; | |
873 if (src == null) | |
874 continue; | |
875 | |
876 int sourceId = channels[i]; | |
877 | |
878 // is the source bound to this channel | |
879 // if false, it's an instanced playback | |
880 boolean boundSource = i == src.getChannel(); | |
881 | |
882 // source's data is streaming | |
883 boolean streaming = src.getAudioData() instanceof AudioStream; | |
884 | |
885 // only buffered sources can be bound | |
886 assert (boundSource && streaming) || (!streaming); | |
887 | |
888 int state = alGetSourcei(sourceId, AL_SOURCE_STATE); | |
889 boolean wantPlaying = src.getStatus() == Status.Playing; | |
890 boolean stopped = state == AL_STOPPED; | |
891 | |
892 if (streaming && wantPlaying){ | |
893 AudioStream stream = (AudioStream) src.getAudioData(); | |
894 if (stream.isOpen()){ | |
895 fillStreamingSource(sourceId, stream); | |
896 if (stopped) | |
897 alSourcePlay(sourceId); | |
898 | |
899 }else{ | |
900 if (stopped){ | |
901 // became inactive | |
902 src.setStatus(Status.Stopped); | |
903 src.setChannel(-1); | |
904 clearChannel(i); | |
905 freeChannel(i); | |
906 | |
907 // And free the audio since it cannot be | |
908 // played again anyway. | |
909 deleteAudioData(stream); | |
910 } | |
911 } | |
912 }else if (!streaming){ | |
913 boolean paused = state == AL_PAUSED; | |
914 | |
915 // make sure OAL pause state & source state coincide | |
916 assert (src.getStatus() == Status.Paused && paused) || (!paused); | |
917 | |
918 if (stopped){ | |
919 if (boundSource){ | |
920 src.setStatus(Status.Stopped); | |
921 src.setChannel(-1); | |
922 } | |
923 clearChannel(i); | |
924 freeChannel(i); | |
925 } | |
926 } | |
927 } | |
928 } | |
929 | |
930 public void setListener(Listener listener) { | |
931 checkDead(); | |
932 synchronized (threadLock){ | |
933 while (!threadLock.get()){ | |
934 try { | |
935 threadLock.wait(); | |
936 } catch (InterruptedException ex) { | |
937 } | |
938 } | |
939 if (audioDisabled) | |
940 return; | |
941 | |
942 if (this.listener != null){ | |
943 // previous listener no longer associated with current | |
944 // renderer | |
945 this.listener.setRenderer(null); | |
946 } | |
947 | |
948 this.listener = listener; | |
949 this.listener.setRenderer(this); | |
950 setListenerParams(listener); | |
951 } | |
952 } | |
953 | |
954 public void playSourceInstance(AudioNode src){ | |
955 checkDead(); | |
956 synchronized (threadLock){ | |
957 while (!threadLock.get()){ | |
958 try { | |
959 threadLock.wait(); | |
960 } catch (InterruptedException ex) { | |
961 } | |
962 } | |
963 if (audioDisabled) | |
964 return; | |
965 | |
966 if (src.getAudioData() instanceof AudioStream) | |
967 throw new UnsupportedOperationException( | |
968 "Cannot play instances " + | |
969 "of audio streams. Use playSource() instead."); | |
970 | |
971 if (src.getAudioData().isUpdateNeeded()){ | |
972 updateAudioData(src.getAudioData()); | |
973 } | |
974 | |
975 // create a new index for an audio-channel | |
976 int index = newChannel(); | |
977 if (index == -1) | |
978 return; | |
979 | |
980 int sourceId = channels[index]; | |
981 | |
982 clearChannel(index); | |
983 | |
984 // set parameters, like position and max distance | |
985 setSourceParams(sourceId, src, true); | |
986 attachAudioToSource(sourceId, src.getAudioData()); | |
987 chanSrcs[index] = src; | |
988 | |
989 // play the channel | |
990 alSourcePlay(sourceId); | |
991 } | |
992 } | |
993 | |
994 | |
995 public void playSource(AudioNode src) { | |
996 checkDead(); | |
997 synchronized (threadLock){ | |
998 while (!threadLock.get()){ | |
999 try { | |
1000 threadLock.wait(); | |
1001 } catch (InterruptedException ex) { | |
1002 } | |
1003 } | |
1004 if (audioDisabled) | |
1005 return; | |
1006 | |
1007 //assert src.getStatus() == Status.Stopped || src.getChannel() == -1; | |
1008 | |
1009 if (src.getStatus() == Status.Playing){ | |
1010 return; | |
1011 }else if (src.getStatus() == Status.Stopped){ | |
1012 | |
1013 // allocate channel to this source | |
1014 int index = newChannel(); | |
1015 if (index == -1) { | |
1016 logger.log(Level.WARNING, "No channel available to play {0}", src); | |
1017 return; | |
1018 } | |
1019 clearChannel(index); | |
1020 src.setChannel(index); | |
1021 | |
1022 AudioData data = src.getAudioData(); | |
1023 if (data.isUpdateNeeded()) | |
1024 updateAudioData(data); | |
1025 | |
1026 chanSrcs[index] = src; | |
1027 setSourceParams(channels[index], src, false); | |
1028 attachAudioToSource(channels[index], data); | |
1029 } | |
1030 | |
1031 alSourcePlay(channels[src.getChannel()]); | |
1032 src.setStatus(Status.Playing); | |
1033 } | |
1034 } | |
1035 | |
1036 | |
1037 public void pauseSource(AudioNode src) { | |
1038 checkDead(); | |
1039 synchronized (threadLock){ | |
1040 while (!threadLock.get()){ | |
1041 try { | |
1042 threadLock.wait(); | |
1043 } catch (InterruptedException ex) { | |
1044 } | |
1045 } | |
1046 if (audioDisabled) | |
1047 return; | |
1048 | |
1049 if (src.getStatus() == Status.Playing){ | |
1050 assert src.getChannel() != -1; | |
1051 | |
1052 alSourcePause(channels[src.getChannel()]); | |
1053 src.setStatus(Status.Paused); | |
1054 } | |
1055 } | |
1056 } | |
1057 | |
1058 | |
1059 public void stopSource(AudioNode src) { | |
1060 synchronized (threadLock){ | |
1061 while (!threadLock.get()){ | |
1062 try { | |
1063 threadLock.wait(); | |
1064 } catch (InterruptedException ex) { | |
1065 } | |
1066 } | |
1067 if (audioDisabled) | |
1068 return; | |
1069 | |
1070 if (src.getStatus() != Status.Stopped){ | |
1071 int chan = src.getChannel(); | |
1072 assert chan != -1; // if it's not stopped, must have id | |
1073 | |
1074 src.setStatus(Status.Stopped); | |
1075 src.setChannel(-1); | |
1076 clearChannel(chan); | |
1077 freeChannel(chan); | |
1078 | |
1079 if (src.getAudioData() instanceof AudioStream) { | |
1080 | |
1081 AudioStream stream = (AudioStream)src.getAudioData(); | |
1082 if (stream.isOpen()) { | |
1083 stream.close(); | |
1084 } | |
1085 | |
1086 // And free the audio since it cannot be | |
1087 // played again anyway. | |
1088 deleteAudioData(src.getAudioData()); | |
1089 } | |
1090 } | |
1091 } | |
1092 } | |
1093 | |
1094 private int convertFormat(AudioData ad){ | |
1095 switch (ad.getBitsPerSample()){ | |
1096 case 8: | |
1097 if (ad.getChannels() == 1) | |
1098 return AL_FORMAT_MONO8; | |
1099 else if (ad.getChannels() == 2) | |
1100 return AL_FORMAT_STEREO8; | |
1101 | |
1102 break; | |
1103 case 16: | |
1104 if (ad.getChannels() == 1) | |
1105 return AL_FORMAT_MONO16; | |
1106 else | |
1107 return AL_FORMAT_STEREO16; | |
1108 } | |
1109 throw new UnsupportedOperationException("Unsupported channels/bits combination: "+ | |
1110 "bits="+ad.getBitsPerSample()+", channels="+ad.getChannels()); | |
1111 } | |
1112 | |
1113 private void updateAudioBuffer(AudioBuffer ab){ | |
1114 int id = ab.getId(); | |
1115 if (ab.getId() == -1){ | |
1116 ib.position(0).limit(1); | |
1117 alGenBuffers(ib); | |
1118 id = ib.get(0); | |
1119 ab.setId(id); | |
1120 } | |
1121 | |
1122 ab.getData().clear(); | |
1123 alBufferData(id, convertFormat(ab), ab.getData(), ab.getSampleRate()); | |
1124 ab.clearUpdateNeeded(); | |
1125 } | |
1126 | |
1127 private void updateAudioStream(AudioStream as){ | |
1128 if (as.getIds() != null){ | |
1129 deleteAudioData(as); | |
1130 } | |
1131 | |
1132 int[] ids = new int[STREAMING_BUFFER_COUNT]; | |
1133 ib.position(0).limit(STREAMING_BUFFER_COUNT); | |
1134 alGenBuffers(ib); | |
1135 ib.position(0).limit(STREAMING_BUFFER_COUNT); | |
1136 ib.get(ids); | |
1137 | |
1138 as.setIds(ids); | |
1139 as.clearUpdateNeeded(); | |
1140 } | |
1141 | |
1142 private void updateAudioData(AudioData ad){ | |
1143 if (ad instanceof AudioBuffer){ | |
1144 updateAudioBuffer((AudioBuffer) ad); | |
1145 }else if (ad instanceof AudioStream){ | |
1146 updateAudioStream((AudioStream) ad); | |
1147 } | |
1148 } | |
1149 | |
1150 public void deleteAudioData(AudioData ad){ | |
1151 synchronized (threadLock){ | |
1152 while (!threadLock.get()){ | |
1153 try { | |
1154 threadLock.wait(); | |
1155 } catch (InterruptedException ex) { | |
1156 } | |
1157 } | |
1158 if (audioDisabled) | |
1159 return; | |
1160 | |
1161 if (ad instanceof AudioBuffer){ | |
1162 AudioBuffer ab = (AudioBuffer) ad; | |
1163 int id = ab.getId(); | |
1164 if (id != -1){ | |
1165 ib.put(0,id); | |
1166 ib.position(0).limit(1); | |
1167 alDeleteBuffers(ib); | |
1168 ab.resetObject(); | |
1169 } | |
1170 }else if (ad instanceof AudioStream){ | |
1171 AudioStream as = (AudioStream) ad; | |
1172 int[] ids = as.getIds(); | |
1173 if (ids != null){ | |
1174 ib.clear(); | |
1175 ib.put(ids).flip(); | |
1176 alDeleteBuffers(ib); | |
1177 as.resetObject(); | |
1178 } | |
1179 } | |
1180 } | |
1181 } | |
1182 | |
1183 } |