/* * Copyright (c) 2009-2012 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.audio.android; import com.jme3.audio.*; import com.jme3.audio.AudioSource.Status; import com.jme3.math.Vector3f; import com.jme3.util.BufferUtils; import com.jme3.util.NativeObjectManager; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; public class AndroidOpenALSoftAudioRenderer implements AndroidAudioRenderer, Runnable { private static final Logger logger = Logger.getLogger(AndroidOpenALSoftAudioRenderer.class.getName()); private final NativeObjectManager objManager = new NativeObjectManager(); // When multiplied by STREAMING_BUFFER_COUNT, will equal 44100 * 2 * 2 // which is exactly 1 second of audio. private static final int BUFFER_SIZE = 35280; private static final int STREAMING_BUFFER_COUNT = 5; private final static int MAX_NUM_CHANNELS = 64; private IntBuffer ib = BufferUtils.createIntBuffer(1); private final FloatBuffer fb = BufferUtils.createVector3Buffer(2); private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE); private final byte[] arrayBuf = new byte[BUFFER_SIZE]; private int[] channels; private AudioSource[] chanSrcs; private int nextChan = 0; private ArrayList freeChans = new ArrayList(); private Listener listener; private boolean audioDisabled = false; private boolean supportEfx = false; private int auxSends = 0; private int reverbFx = -1; private int reverbFxSlot = -1; // Update audio 20 times per second private static final float UPDATE_RATE = 0.05f; private final Thread audioThread = new Thread(this, "jME3 Audio Thread"); private final AtomicBoolean threadLock = new AtomicBoolean(false); private boolean initialized = false; public AndroidOpenALSoftAudioRenderer() { } public void initialize() { if (!audioThread.isAlive()) { audioThread.setDaemon(true); audioThread.setPriority(Thread.NORM_PRIORITY + 1); audioThread.start(); } else { throw new IllegalStateException("Initialize already called"); } } private void checkDead() { if (audioThread.getState() == Thread.State.TERMINATED) { throw new IllegalStateException("Audio thread is terminated"); } } public void run() { initInThread(); synchronized (threadLock) { threadLock.set(true); threadLock.notifyAll(); } initialized = true; long updateRateNanos = (long) (UPDATE_RATE * 1000000000); mainloop: while (true) { long startTime = System.nanoTime(); if (Thread.interrupted()) { break; } synchronized (threadLock) { updateInThread(UPDATE_RATE); } long endTime = System.nanoTime(); long diffTime = endTime - startTime; if (diffTime < updateRateNanos) { long desiredEndTime = startTime + updateRateNanos; while (System.nanoTime() < desiredEndTime) { try { Thread.sleep(1); } catch (InterruptedException ex) { break mainloop; } } } } initialized = false; synchronized (threadLock) { cleanupInThread(); } } public void initInThread() { try { if (!alIsCreated()) { //AL.create(); alCreate(); checkError(false); } // } catch (OpenALException ex) { // logger.log(Level.SEVERE, "Failed to load audio library", ex); // audioDisabled = true; // return; // } catch (LWJGLException ex) { // logger.log(Level.SEVERE, "Failed to load audio library", ex); // audioDisabled = true; // return; } catch (UnsatisfiedLinkError ex) { logger.log(Level.SEVERE, "Failed to load audio library", ex); audioDisabled = true; return; } //ALCdevice device = AL.getDevice(); /* device maintained in jni */ //String deviceName = ALC10.alcGetString(device, ALC10.ALC_DEVICE_SPECIFIER); String deviceName = alcGetString(AL.ALC_DEVICE_SPECIFIER); logger.log(Level.INFO, "Audio Device: {0}", deviceName); //logger.log(Level.INFO, "Audio Vendor: {0}", alGetString(AL_VENDOR)); //logger.log(Level.INFO, "Audio Renderer: {0}", alGetString(AL_RENDERER)); //logger.log(Level.INFO, "Audio Version: {0}", alGetString(AL_VERSION)); logger.log(Level.INFO, "Audio Vendor: {0}", alGetString(AL.AL_VENDOR)); logger.log(Level.INFO, "Audio Renderer: {0}", alGetString(AL.AL_RENDERER)); logger.log(Level.INFO, "Audio Version: {0}", alGetString(AL.AL_VERSION)); // Find maximum # of sources supported by this implementation ArrayList channelList = new ArrayList(); for (int i = 0; i < MAX_NUM_CHANNELS; i++) { int chan = alGenSources(); //if (alGetError() != 0) { if (checkError(false) != 0) { break; } else { channelList.add(chan); } } channels = new int[channelList.size()]; for (int i = 0; i < channels.length; i++) { channels[i] = channelList.get(i); } ib = BufferUtils.createIntBuffer(channels.length); chanSrcs = new AudioSource[channels.length]; logger.log(Level.INFO, "AudioRenderer supports {0} channels", channels.length); //supportEfx = alcIsExtensionPresent(device, "ALC_EXT_EFX"); supportEfx = alcIsExtensionPresent(AL.ALC_EXT_EFX_NAME); if (supportEfx) { ib.position(0).limit(1); //ALC10.alcGetInteger(device, EFX10.ALC_EFX_MAJOR_VERSION, ib); alcGetInteger(AL.ALC_EFX_MAJOR_VERSION, ib, 1); int major = ib.get(0); ib.position(0).limit(1); //ALC10.alcGetInteger(device, EFX10.ALC_EFX_MINOR_VERSION, ib); alcGetInteger(AL.ALC_EFX_MINOR_VERSION, ib, 1); int minor = ib.get(0); logger.log(Level.INFO, "Audio effect extension version: {0}.{1}", new Object[]{major, minor}); //ALC10.alcGetInteger(device, EFX10.ALC_MAX_AUXILIARY_SENDS, ib); alcGetInteger(AL.ALC_MAX_AUXILIARY_SENDS, ib, 1); auxSends = ib.get(0); logger.log(Level.INFO, "Audio max auxilary sends: {0}", auxSends); // create slot ib.position(0).limit(1); //EFX10.alGenAuxiliaryEffectSlots(ib); alGenAuxiliaryEffectSlots(1, ib); reverbFxSlot = ib.get(0); // create effect ib.position(0).limit(1); //EFX10.alGenEffects(ib); alGenEffects(1, ib); reverbFx = ib.get(0); //EFX10.alEffecti(reverbFx, EFX10.AL_EFFECT_TYPE, EFX10.AL_EFFECT_REVERB); alEffecti(reverbFx, AL.AL_EFFECT_TYPE, AL.AL_EFFECT_REVERB); // attach reverb effect to effect slot //EFX10.alAuxiliaryEffectSloti(reverbFxSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbFx); alAuxiliaryEffectSloti(reverbFxSlot, AL.AL_EFFECTSLOT_EFFECT, reverbFx); } else { logger.log(Level.WARNING, "OpenAL EFX not available! Audio effects won't work."); } } public void cleanupInThread() { if (audioDisabled) { //AL.destroy(); alDestroy(); checkError(true); return; } // stop any playing channels for (int i = 0; i < chanSrcs.length; i++) { if (chanSrcs[i] != null) { clearChannel(i); } } // delete channel-based sources ib.clear(); ib.put(channels); ib.flip(); //alDeleteSources(ib); alDeleteSources(channels.length, ib); checkError(true); // delete audio buffers and filters objManager.deleteAllObjects(this); if (supportEfx) { ib.position(0).limit(1); ib.put(0, reverbFx); //EFX10.alDeleteEffects(ib); alDeleteEffects(1, ib); // If this is not allocated, why is it deleted? // Commented out to fix native crash in OpenAL. ib.position(0).limit(1); ib.put(0, reverbFxSlot); //EFX10.alDeleteAuxiliaryEffectSlots(ib); alDeleteAuxiliaryEffectSlots(1, ib); } //AL.destroy(); logger.log(Level.INFO, "Destroying OpenAL Soft Renderer"); alDestroy(); } public void cleanup() { // kill audio thread if (audioThread.isAlive()) { audioThread.interrupt(); } } private void updateFilter(Filter f) { int id = f.getId(); if (id == -1) { ib.position(0).limit(1); //EFX10.alGenFilters(ib); alGenFilters(1, ib); id = ib.get(0); f.setId(id); objManager.registerObject(f); } if (f instanceof LowPassFilter) { LowPassFilter lpf = (LowPassFilter) f; //EFX10.alFilteri(id, EFX10.AL_FILTER_TYPE, EFX10.AL_FILTER_LOWPASS); alFilteri(id, AL.AL_FILTER_TYPE, AL.AL_FILTER_LOWPASS); //EFX10.alFilterf(id, EFX10.AL_LOWPASS_GAIN, lpf.getVolume()); alFilterf(id, AL.AL_LOWPASS_GAIN, lpf.getVolume()); //EFX10.alFilterf(id, EFX10.AL_LOWPASS_GAINHF, lpf.getHighFreqVolume()); alFilterf(id, AL.AL_LOWPASS_GAINHF, lpf.getHighFreqVolume()); } else { throw new UnsupportedOperationException("Filter type unsupported: " + f.getClass().getName()); } f.clearUpdateNeeded(); } public void updateSourceParam(AudioSource src, AudioParam param) { checkDead(); synchronized (threadLock) { while (!threadLock.get()) { try { threadLock.wait(); } catch (InterruptedException ex) { } } if (audioDisabled) { return; } // There is a race condition in AudioSource that can // cause this to be called for a node that has been // detached from its channel. For example, setVolume() // called from the render thread may see that that AudioSource // still has a channel value but the audio thread may // clear that channel before setVolume() gets to call // updateSourceParam() (because the audio stopped playing // on its own right as the volume was set). In this case, // it should be safe to just ignore the update if (src.getChannel() < 0) { return; } assert src.getChannel() >= 0; int id = channels[src.getChannel()]; switch (param) { case Position: if (!src.isPositional()) { return; } Vector3f pos = src.getPosition(); //alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z); alSource3f(id, AL.AL_POSITION, pos.x, pos.y, pos.z); checkError(true); break; case Velocity: if (!src.isPositional()) { return; } Vector3f vel = src.getVelocity(); //alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z); alSource3f(id, AL.AL_VELOCITY, vel.x, vel.y, vel.z); checkError(true); break; case MaxDistance: if (!src.isPositional()) { return; } //alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance()); alSourcef(id, AL.AL_MAX_DISTANCE, src.getMaxDistance()); checkError(true); break; case RefDistance: if (!src.isPositional()) { return; } //alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance()); alSourcef(id, AL.AL_REFERENCE_DISTANCE, src.getRefDistance()); checkError(true); break; case ReverbFilter: if (!supportEfx || !src.isPositional() || !src.isReverbEnabled()) { return; } int filter = AL.AL_FILTER_NULL; if (src.getReverbFilter() != null) { Filter f = src.getReverbFilter(); if (f.isUpdateNeeded()) { updateFilter(f); } filter = f.getId(); } //AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); alSource3i(id, AL.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); break; case ReverbEnabled: if (!supportEfx || !src.isPositional()) { return; } if (src.isReverbEnabled()) { updateSourceParam(src, AudioParam.ReverbFilter); } else { //AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX10.AL_FILTER_NULL); alSource3i(id, AL.AL_AUXILIARY_SEND_FILTER, 0, 0, AL.AL_FILTER_NULL); } break; case IsPositional: if (!src.isPositional()) { // Play in headspace //alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE); alSourcei(id, AL.AL_SOURCE_RELATIVE, AL.AL_TRUE); checkError(true); //alSource3f(id, AL_POSITION, 0, 0, 0); alSource3f(id, AL.AL_POSITION, 0, 0, 0); checkError(true); //alSource3f(id, AL_VELOCITY, 0, 0, 0); alSource3f(id, AL.AL_VELOCITY, 0, 0, 0); checkError(true); // Disable reverb //AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX10.AL_FILTER_NULL); alSource3i(id, AL.AL_AUXILIARY_SEND_FILTER, 0, 0, AL.AL_FILTER_NULL); } else { //alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE); alSourcei(id, AL.AL_SOURCE_RELATIVE, AL.AL_FALSE); checkError(true); updateSourceParam(src, AudioParam.Position); updateSourceParam(src, AudioParam.Velocity); updateSourceParam(src, AudioParam.MaxDistance); updateSourceParam(src, AudioParam.RefDistance); updateSourceParam(src, AudioParam.ReverbEnabled); } break; case Direction: if (!src.isDirectional()) { return; } Vector3f dir = src.getDirection(); //alSource3f(id, AL_DIRECTION, dir.x, dir.y, dir.z); alSource3f(id, AL.AL_DIRECTION, dir.x, dir.y, dir.z); checkError(true); break; case InnerAngle: if (!src.isDirectional()) { return; } //alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle()); alSourcef(id, AL.AL_CONE_INNER_ANGLE, src.getInnerAngle()); checkError(true); break; case OuterAngle: if (!src.isDirectional()) { return; } //alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); alSourcef(id, AL.AL_CONE_OUTER_ANGLE, src.getOuterAngle()); checkError(true); break; case IsDirectional: if (src.isDirectional()) { updateSourceParam(src, AudioParam.Direction); updateSourceParam(src, AudioParam.InnerAngle); updateSourceParam(src, AudioParam.OuterAngle); //alSourcef(id, AL_CONE_OUTER_GAIN, 0); alSourcef(id, AL.AL_CONE_OUTER_GAIN, 0); checkError(true); } else { //alSourcef(id, AL_CONE_INNER_ANGLE, 360); alSourcef(id, AL.AL_CONE_INNER_ANGLE, 360); checkError(true); //alSourcef(id, AL_CONE_OUTER_ANGLE, 360); alSourcef(id, AL.AL_CONE_OUTER_ANGLE, 360); checkError(true); //alSourcef(id, AL_CONE_OUTER_GAIN, 1f); alSourcef(id, AL.AL_CONE_OUTER_GAIN, 1f); checkError(true); } break; case DryFilter: if (!supportEfx) { return; } if (src.getDryFilter() != null) { Filter f = src.getDryFilter(); if (f.isUpdateNeeded()) { updateFilter(f); // NOTE: must re-attach filter for changes to apply. //alSourcei(id, EFX10.AL_DIRECT_FILTER, f.getId()); alSourcei(id, AL.AL_DIRECT_FILTER, f.getId()); } } else { //alSourcei(id, EFX10.AL_DIRECT_FILTER, EFX10.AL_FILTER_NULL); alSourcei(id, AL.AL_DIRECT_FILTER, AL.AL_FILTER_NULL); } break; case Looping: if (src.isLooping()) { if (!(src.getAudioData() instanceof AudioStream)) { //alSourcei(id, AL_LOOPING, AL_TRUE); alSourcei(id, AL.AL_LOOPING, AL.AL_TRUE); checkError(true); } } else { //alSourcei(id, AL_LOOPING, AL_FALSE); alSourcei(id, AL.AL_LOOPING, AL.AL_FALSE); checkError(true); } break; case Volume: //alSourcef(id, AL_GAIN, src.getVolume()); alSourcef(id, AL.AL_GAIN, src.getVolume()); checkError(true); break; case Pitch: //alSourcef(id, AL_PITCH, src.getPitch()); alSourcef(id, AL.AL_PITCH, src.getPitch()); checkError(true); break; } } } private void setSourceParams(int id, AudioSource src, boolean forceNonLoop) { if (src.isPositional()) { Vector3f pos = src.getPosition(); Vector3f vel = src.getVelocity(); //alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z); alSource3f(id, AL.AL_POSITION, pos.x, pos.y, pos.z); checkError(true); //alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z); alSource3f(id, AL.AL_VELOCITY, vel.x, vel.y, vel.z); checkError(true); //alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance()); alSourcef(id, AL.AL_MAX_DISTANCE, src.getMaxDistance()); checkError(true); //alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance()); alSourcef(id, AL.AL_REFERENCE_DISTANCE, src.getRefDistance()); checkError(true); //alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE); alSourcei(id, AL.AL_SOURCE_RELATIVE, AL.AL_FALSE); checkError(true); if (src.isReverbEnabled() && supportEfx) { //int filter = EFX10.AL_FILTER_NULL; int filter = AL.AL_FILTER_NULL; if (src.getReverbFilter() != null) { Filter f = src.getReverbFilter(); if (f.isUpdateNeeded()) { updateFilter(f); } filter = f.getId(); } //AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); alSource3i(id, AL.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); } } else { // play in headspace //alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE); alSourcei(id, AL.AL_SOURCE_RELATIVE, AL.AL_TRUE); checkError(true); //alSource3f(id, AL_POSITION, 0, 0, 0); alSource3f(id, AL.AL_POSITION, 0, 0, 0); checkError(true); //alSource3f(id, AL_VELOCITY, 0, 0, 0); alSource3f(id, AL.AL_VELOCITY, 0, 0, 0); checkError(true); } if (src.getDryFilter() != null && supportEfx) { Filter f = src.getDryFilter(); if (f.isUpdateNeeded()) { updateFilter(f); // NOTE: must re-attach filter for changes to apply. //alSourcei(id, EFX10.AL_DIRECT_FILTER, f.getId()); alSourcei(id, AL.AL_DIRECT_FILTER, f.getId()); } } if (forceNonLoop) { //alSourcei(id, AL_LOOPING, AL_FALSE); alSourcei(id, AL.AL_LOOPING, AL.AL_FALSE); checkError(true); } else { //alSourcei(id, AL_LOOPING, src.isLooping() ? AL_TRUE : AL_FALSE); alSourcei(id, AL.AL_LOOPING, src.isLooping() ? AL.AL_TRUE : AL.AL_FALSE); checkError(true); } //alSourcef(id, AL_GAIN, src.getVolume()); alSourcef(id, AL.AL_GAIN, src.getVolume()); checkError(true); //alSourcef(id, AL_PITCH, src.getPitch()); alSourcef(id, AL.AL_PITCH, src.getPitch()); checkError(true); //alSourcef(id, AL11.AL_SEC_OFFSET, src.getTimeOffset()); alSourcef(id, AL.AL_SEC_OFFSET, src.getTimeOffset()); checkError(true); if (src.isDirectional()) { Vector3f dir = src.getDirection(); //alSource3f(id, AL_DIRECTION, dir.x, dir.y, dir.z); alSource3f(id, AL.AL_DIRECTION, dir.x, dir.y, dir.z); checkError(true); //alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle()); alSourcef(id, AL.AL_CONE_INNER_ANGLE, src.getInnerAngle()); checkError(true); //alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); alSourcef(id, AL.AL_CONE_OUTER_ANGLE, src.getOuterAngle()); checkError(true); //alSourcef(id, AL_CONE_OUTER_GAIN, 0); alSourcef(id, AL.AL_CONE_OUTER_GAIN, 0); checkError(true); } else { //alSourcef(id, AL_CONE_INNER_ANGLE, 360); alSourcef(id, AL.AL_CONE_INNER_ANGLE, 360); checkError(true); //alSourcef(id, AL_CONE_OUTER_ANGLE, 360); alSourcef(id, AL.AL_CONE_OUTER_ANGLE, 360); checkError(true); //alSourcef(id, AL_CONE_OUTER_GAIN, 1f); alSourcef(id, AL.AL_CONE_OUTER_GAIN, 1f); checkError(true); } } public void updateListenerParam(Listener listener, ListenerParam param) { checkDead(); synchronized (threadLock) { while (!threadLock.get()) { try { threadLock.wait(); } catch (InterruptedException ex) { } } if (audioDisabled) { return; } switch (param) { case Position: Vector3f pos = listener.getLocation(); //alListener3f(AL_POSITION, pos.x, pos.y, pos.z); alListener3f(AL.AL_POSITION, pos.x, pos.y, pos.z); checkError(true); break; case Rotation: Vector3f dir = listener.getDirection(); Vector3f up = listener.getUp(); fb.rewind(); fb.put(dir.x).put(dir.y).put(dir.z); fb.put(up.x).put(up.y).put(up.z); fb.flip(); //alListener(AL_ORIENTATION, fb); alListener(AL.AL_ORIENTATION, fb); checkError(true); break; case Velocity: Vector3f vel = listener.getVelocity(); //alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); alListener3f(AL.AL_VELOCITY, vel.x, vel.y, vel.z); checkError(true); break; case Volume: //alListenerf(AL_GAIN, listener.getVolume()); alListenerf(AL.AL_GAIN, listener.getVolume()); checkError(true); break; } } } private void setListenerParams(Listener listener) { Vector3f pos = listener.getLocation(); Vector3f vel = listener.getVelocity(); Vector3f dir = listener.getDirection(); Vector3f up = listener.getUp(); //alListener3f(AL_POSITION, pos.x, pos.y, pos.z); alListener3f(AL.AL_POSITION, pos.x, pos.y, pos.z); checkError(true); //alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); alListener3f(AL.AL_VELOCITY, vel.x, vel.y, vel.z); checkError(true); fb.rewind(); fb.put(dir.x).put(dir.y).put(dir.z); fb.put(up.x).put(up.y).put(up.z); fb.flip(); //alListener(AL_ORIENTATION, fb); alListener(AL.AL_ORIENTATION, fb); checkError(true); //alListenerf(AL_GAIN, listener.getVolume()); alListenerf(AL.AL_GAIN, listener.getVolume()); checkError(true); } private int newChannel() { if (freeChans.size() > 0) { return freeChans.remove(0); } else if (nextChan < channels.length) { return nextChan++; } else { return -1; } } private void freeChannel(int index) { if (index == nextChan - 1) { nextChan--; } else { freeChans.add(index); } } public void setEnvironment(Environment env) { checkDead(); synchronized (threadLock) { while (!threadLock.get()) { try { threadLock.wait(); } catch (InterruptedException ex) { } } if (audioDisabled || !supportEfx) { return; } //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DENSITY, env.getDensity()); alEffectf(reverbFx, AL.AL_REVERB_DENSITY, env.getDensity()); //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DIFFUSION, env.getDiffusion()); alEffectf(reverbFx, AL.AL_REVERB_DIFFUSION, env.getDiffusion()); //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_GAIN, env.getGain()); alEffectf(reverbFx, AL.AL_REVERB_GAIN, env.getGain()); //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_GAINHF, env.getGainHf()); alEffectf(reverbFx, AL.AL_REVERB_GAINHF, env.getGainHf()); //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DECAY_TIME, env.getDecayTime()); alEffectf(reverbFx, AL.AL_REVERB_DECAY_TIME, env.getDecayTime()); //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DECAY_HFRATIO, env.getDecayHFRatio()); alEffectf(reverbFx, AL.AL_REVERB_DECAY_HFRATIO, env.getDecayHFRatio()); //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_REFLECTIONS_GAIN, env.getReflectGain()); alEffectf(reverbFx, AL.AL_REVERB_REFLECTIONS_GAIN, env.getReflectGain()); //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_REFLECTIONS_DELAY, env.getReflectDelay()); alEffectf(reverbFx, AL.AL_REVERB_REFLECTIONS_DELAY, env.getReflectDelay()); //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_LATE_REVERB_GAIN, env.getLateReverbGain()); alEffectf(reverbFx, AL.AL_REVERB_LATE_REVERB_GAIN, env.getLateReverbGain()); //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_LATE_REVERB_DELAY, env.getLateReverbDelay()); alEffectf(reverbFx, AL.AL_REVERB_LATE_REVERB_DELAY, env.getLateReverbDelay()); //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_AIR_ABSORPTION_GAINHF, env.getAirAbsorbGainHf()); alEffectf(reverbFx, AL.AL_REVERB_AIR_ABSORPTION_GAINHF, env.getAirAbsorbGainHf()); //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_ROOM_ROLLOFF_FACTOR, env.getRoomRolloffFactor()); alEffectf(reverbFx, AL.AL_REVERB_ROOM_ROLLOFF_FACTOR, env.getRoomRolloffFactor()); // attach effect to slot //EFX10.alAuxiliaryEffectSloti(reverbFxSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbFx); alAuxiliaryEffectSloti(reverbFxSlot, AL.AL_EFFECTSLOT_EFFECT, reverbFx); } } private boolean fillBuffer(AudioStream stream, int id) { int size = 0; int result; while (size < arrayBuf.length) { result = stream.readSamples(arrayBuf, size, arrayBuf.length - size); if (result > 0) { size += result; } else { break; } } if (size == 0) { return false; } nativeBuf.clear(); nativeBuf.put(arrayBuf, 0, size); nativeBuf.flip(); //alBufferData(id, convertFormat(stream), nativeBuf, stream.getSampleRate()); alBufferData(id, convertFormat(stream), nativeBuf, size, stream.getSampleRate()); checkError(true); return true; } private boolean fillStreamingSource(int sourceId, AudioStream stream) { if (!stream.isOpen()) { return false; } boolean active = true; //int processed = alGetSourcei(sourceId, AL_BUFFERS_PROCESSED); int processed = alGetSourcei(sourceId, AL.AL_BUFFERS_PROCESSED); checkError(true); //while((processed--) != 0){ if (processed > 0) { int buffer; ib.position(0).limit(1); //alSourceUnqueueBuffers(sourceId, ib); alSourceUnqueueBuffers(sourceId, 1, ib); checkError(true); buffer = ib.get(0); active = fillBuffer(stream, buffer); ib.position(0).limit(1); ib.put(0, buffer); //alSourceQueueBuffers(sourceId, ib); alSourceQueueBuffers(sourceId, 1, ib); checkError(true); } if (!active && stream.isOpen()) { stream.close(); } return active; } private boolean attachStreamToSource(int sourceId, AudioStream stream) { boolean active = true; int activeBufferCount = 0; for (int id : stream.getIds()) { active = fillBuffer(stream, id); ib.position(0).limit(1); ib.put(id).flip(); //alSourceQueueBuffers(sourceId, ib); // OpenAL Soft does not like 0 size buffer data in alSourceQueueBuffers // Produces error code 40964 (0xA004) = AL_INVALID_OPERATION and // does not return (crashes) so that the error code can be checked. // active is FALSE when the data size is 0 if (active) { alSourceQueueBuffers(sourceId, 1, ib); checkError(true); activeBufferCount++; } } // adjust the steam id array if the audio data is smaller than STREAMING_BUFFER_COUNT // this is to avoid an error with OpenAL Soft when alSourceUnenqueueBuffers // is called with more buffers than were originally used with alSourceQueueBuffers if (activeBufferCount < STREAMING_BUFFER_COUNT) { int[] newIds = new int[activeBufferCount]; for (int i=0; i