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