292 lines
8.2 KiB
Java
292 lines
8.2 KiB
Java
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<AudioSource> openALSources = new CopyOnWriteArrayList<AudioSource>();
|
|
|
|
|
|
/**
|
|
* 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<AudioSource> toRemove = new LinkedList<AudioSource>();
|
|
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<AudioSource> 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;
|
|
}
|
|
|
|
|
|
|
|
}
|