Home | History | Annotate | Download | only in lwjgl
      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 
     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