LwjglAudioRenderer.java revision 59b2e6871c65f58fdad78cd7229c292f6a177578
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
33package com.jme3.audio.lwjgl;
34
35import com.jme3.audio.AudioNode.Status;
36import com.jme3.audio.*;
37import com.jme3.math.Vector3f;
38import com.jme3.util.BufferUtils;
39import com.jme3.util.NativeObjectManager;
40import java.nio.ByteBuffer;
41import java.nio.FloatBuffer;
42import java.nio.IntBuffer;
43import java.util.ArrayList;
44import java.util.concurrent.atomic.AtomicBoolean;
45import java.util.logging.Level;
46import java.util.logging.Logger;
47import org.lwjgl.LWJGLException;
48import static org.lwjgl.openal.AL10.*;
49import org.lwjgl.openal.*;
50
51public 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
87    public LwjglAudioRenderer(){
88    }
89
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
100    private void checkDead(){
101        if (audioThread.getState() == Thread.State.TERMINATED)
102            throw new IllegalStateException("Audio thread is terminated");
103    }
104
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
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
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
260    public void cleanup(){
261        // kill audio thread
262        if (audioThread.isAlive()){
263            audioThread.interrupt();
264        }
265    }
266
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
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
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
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
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
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
573    private void freeChannel(int index){
574        if (index == nextChan-1){
575            nextChan--;
576        } else{
577            freeChans.add(index);
578        }
579    }
580
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
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
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
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
676    private boolean attachBufferToSource(int sourceId, AudioBuffer buffer){
677        alSourcei(sourceId, AL_BUFFER, buffer.getId());
678        return true;
679    }
680
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
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
722    public void update(float tpf){
723        // does nothing
724    }
725
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
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
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
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
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
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
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
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
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
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
1016    public void deleteFilter(Filter filter) {
1017        int id = filter.getId();
1018        if (id != -1){
1019            EFX10.alDeleteFilters(id);
1020        }
1021    }
1022
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