package electrosphere.audio; import electrosphere.engine.Globals; import electrosphere.entity.types.camera.CameraEntityUtils; import electrosphere.logger.LoggerInterface; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioSystem; import org.joml.Vector3f; import org.lwjgl.BufferUtils; import org.lwjgl.openal.AL; import org.lwjgl.openal.ALC; import org.lwjgl.openal.ALC10; import org.lwjgl.openal.ALC11; import org.lwjgl.openal.ALCCapabilities; import org.lwjgl.openal.SOFTHRTF; import org.lwjgl.system.MemoryUtil; import static org.lwjgl.openal.ALC10.alcDestroyContext; import static org.lwjgl.openal.ALC10.alcCloseDevice; import static org.lwjgl.system.MemoryUtil.NULL; /** * Main class that handles audio processing */ public class AudioEngine { //Controls whether the engine initialized or not boolean initialized = false; //openal device private long device; //openal context private long context; //the listener data for the audio landscape private AudioListener listener; //the current gain level of the engine private float engineGain = 1.0f; //The current device String currentDevice = ""; //the default device String defaultDevice = ""; //if true, hrtf present and active boolean hasHRTF = false; //if true, efx present and active boolean hasEFX = false; //The list of sources being tracked private List openALSources = new CopyOnWriteArrayList(); /** * Creates an audio engine */ public AudioEngine() { } /** * Initializes the audio engine */ public void init() { try { initDevice(); echoJavaAudioSupport(); } catch (Exception ex) { LoggerInterface.loggerEngine.ERROR("Error initializing audio device", ex); } if(initialized){ //recursively load all audio files listener = new AudioListener(); } } /** * Lists all available devices */ public void listAllDevices(){ currentDevice = ALC11.alcGetString(NULL,ALC11.ALC_ALL_DEVICES_SPECIFIER); LoggerInterface.loggerAudio.INFO("AL device: " + currentDevice); defaultDevice = ALC11.alcGetString(NULL,ALC11.ALC_DEFAULT_ALL_DEVICES_SPECIFIER); LoggerInterface.loggerAudio.INFO("AL default device: " + defaultDevice); } /** * Initializes audio devices * @throws Exception Thrown if there are no audio devices or fails to create openal context */ void initDevice() throws Exception { //create device LoggerInterface.loggerAudio.DEBUG("Open ALC device"); this.device = ALC10.alcOpenDevice((ByteBuffer) null); if (device == NULL) { throw new IllegalStateException("Failed to open the default OpenAL device."); } //create capabilities LoggerInterface.loggerAudio.DEBUG("Create device capabilities"); ALCCapabilities deviceCaps = ALC.createCapabilities(device); //create context LoggerInterface.loggerAudio.DEBUG("Create context"); IntBuffer attrBuffer = getContextAttrs(deviceCaps); this.context = ALC10.alcCreateContext(device, attrBuffer); MemoryUtil.memFree(attrBuffer); if (context == NULL) { throw new IllegalStateException("Failed to create OpenAL context."); } LoggerInterface.loggerAudio.DEBUG("Make Context Current"); ALC10.alcMakeContextCurrent(context); AL.createCapabilities(deviceCaps); this.initialized = true; } /** * Gets the attrs buffer for creating a context * @param deviceCaps The device capabilities * @return The buffer (may be null if no desired extensions present) */ IntBuffer getContextAttrs(ALCCapabilities deviceCaps){ int bufferSize = 0; //check for available extensions if(deviceCaps.ALC_EXT_EFX){ LoggerInterface.loggerAudio.INFO("EFX PRESENT"); hasEFX = true; } else { LoggerInterface.loggerAudio.INFO("EFX NOT PRESENT"); } if(deviceCaps.ALC_SOFT_HRTF){ LoggerInterface.loggerAudio.INFO("SOFT HRTF PRESENT"); // hasHRTF = true; // bufferSize++; } else { LoggerInterface.loggerAudio.INFO("SOFT HRTF NOT PRESENT"); } IntBuffer rVal = null; //construct buffer if any were found if(bufferSize > 0 ){ rVal = BufferUtils.createIntBuffer(bufferSize * 2 + 1); if(deviceCaps.ALC_SOFT_HRTF){ rVal.put(SOFTHRTF.ALC_HRTF_SOFT); rVal.put(ALC11.ALC_TRUE); } rVal.put(0); rVal.flip(); } LoggerInterface.loggerAudio.INFO("Create attributes with size: " + bufferSize); return rVal; } /** * Echos the available support for different audio types from JRE itself */ private void echoJavaAudioSupport(){ LoggerInterface.loggerAudio.INFO("Check JRE-supported audio file types"); for(AudioFileFormat.Type audioType : AudioSystem.getAudioFileTypes()){ LoggerInterface.loggerAudio.INFO(audioType.getExtension() + " support: " + AudioSystem.isFileTypeSupported(audioType)); } } /** * Updates the orientation of the listener based on the global player camera */ private void updateListener(){ if(Globals.playerCamera != null){ Vector3f cameraPos = CameraEntityUtils.getCameraCenter(Globals.playerCamera); Vector3f cameraEye = new Vector3f(CameraEntityUtils.getCameraEye(Globals.playerCamera)).mul(-1); Vector3f cameraUp = new Vector3f(0,1,0); listener.setPosition(cameraPos); listener.setOrientation(cameraEye, cameraUp); } } /** * Updates the audio engine */ public void update(){ updateListener(); updateOpenALSources(); } /** * Shuts down the engine */ public void shutdown(){ alcDestroyContext(context); alcCloseDevice(device); } /** * Registers an openal audio source with the engine * @param source The audio source */ protected void registerSource(AudioSource source){ this.openALSources.add(source); } /** * Updates the status of all tracked sources */ private void updateOpenALSources(){ List toRemove = new LinkedList(); for(AudioSource source : this.openALSources){ if(!source.isPlaying()){ toRemove.add(source); } } for(AudioSource source : toRemove){ this.openALSources.remove(source); source.cleanup(); } } /** * Gets the list of openal sources * @return The list of sources */ public List getOpenALSources(){ return Collections.unmodifiableList(this.openALSources); } /** * Sets the gain of the engine * @param gain The gain value */ public void setGain(float gain){ engineGain = gain; } /** * Gets the gain of the engine * @return The gain value */ public float getGain(){ return engineGain; } /** * Gets the current openal device * @return The current openal device */ public String getDevice(){ return currentDevice; } /** * Gets the default openal device * @return the default openal device */ public String getDefaultDevice(){ return defaultDevice; } /** * Gets the HRTF status * @return The HRTF status */ public boolean getHRTFStatus(){ return hasHRTF; } /** * Gets the listener for the audio engine * @return the listener */ public AudioListener getListener(){ return listener; } /** * Checks if the engine has initialized or not * @return true if initialized, false otherwise */ public boolean initialized(){ return this.initialized; } }