/** * Copyright 2012 Jason Sorensen (sorensenj@smert.net) * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package net.smert.frameworkgl.openal; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import net.smert.frameworkgl.openal.codecs.Codec; import org.lwjgl.openal.AL10; import org.lwjgl.openal.ALContext; import org.lwjgl.openal.ALDevice; import org.lwjgl.openal.Util; /** * * @author Jason Sorensen <sorensenj@smert.net> */ public class OpenAL { private boolean contextSynchronized; private float defaultSourceMaxDistance; private float defaultSourceMusicVolume; private float defaultSourceReferenceDistance; private float defaultSourceRolloff; private float defaultSourceSoundVolume; private float masterVolume; private int contextFrequency; private int contextRefresh; private int maxChannels; private int numberOfMusicChannels; private int numberOfSoundChannels; private final Config config; private final List<Integer> musicSourcesPlaying; private final List<Integer> soundSourcesPlaying; private final List<OpenALSource> tempSources; private final Map<String, OpenALBuffer> nameToOpenALBuffer; private final Map<String, OpenALBuffer> nameToTempOpenALBuffer; private final Map<String, Codec> extensionToCodec; private ALContext alContext; private OpenALListener listener; private String device; public OpenAL() { config = new Config(); musicSourcesPlaying = new ArrayList<>(); soundSourcesPlaying = new ArrayList<>(); tempSources = new ArrayList<>(); nameToOpenALBuffer = new HashMap<>(); nameToTempOpenALBuffer = new HashMap<>(); extensionToCodec = new HashMap<>(); reset(); } protected void checkForError(String message) { int error = AL10.alGetError(); if (error != AL10.AL_NO_ERROR) { String errorMessage = getErrorMessage(error); throw new RuntimeException("Error " + message + ": Error Number: " + error + " Message: " + errorMessage); } } protected OpenALBuffer createALBuffer() { OpenALBuffer buffer = AL.alFactory.createOpenALBuffer(); buffer.create(); return buffer; } protected OpenALSource createALSource() { OpenALSource source = AL.alFactory.createOpenALSource(); source.create(); return source; } protected OpenALBuffer createTempALBuffer(String audioFile) { OpenALBuffer buffer = createALBuffer(); nameToTempOpenALBuffer.put(audioFile, buffer); return buffer; } protected OpenALSource createTempALSource() { OpenALSource source = createALSource(); tempSources.add(source); return source; } protected Codec.Data getCodecData(String filename) throws IOException { // Get the extension from the filename int posOfLastPeriod = filename.lastIndexOf("."); if (posOfLastPeriod == -1) { throw new IllegalArgumentException("The filename must have an extension: " + filename); } String extension = filename.substring(posOfLastPeriod + 1).toLowerCase(); // Does the codec for this extension exist? if (!extensionToCodec.containsKey(extension)) { throw new IllegalArgumentException("The extension has not been registered: " + extension); } // Load the filename using the codec Codec codec = extensionToCodec.get(extension); return codec.load(filename); } public void destroy() { org.lwjgl.openal.AL.destroy(alContext); } public float getDopplerFactor() { return AL10.alGetFloat(AL10.AL_DOPPLER_FACTOR); } public void setDopplerFactor(float factor) { AL10.alDopplerFactor(factor); } public void setDopplerVelocity(float velocity) { AL10.alDopplerVelocity(velocity); } public float getMasterVolume() { return masterVolume; } public void setMasterVolume(float masterVolume) { this.masterVolume = masterVolume; } public float getVolume(int soundID) { if (soundID == 0) { return 0; } float volume = AL.sourceHelper.getGain(soundID); checkForError("getting sound volume"); return volume; } public void setVolume(int soundID, float volume) { if (soundID == 0) { return; } AL.sourceHelper.setGain(soundID, volume); checkForError("setting sound volume"); } public int getDistanceModel() { return AL10.alGetInteger(AL10.AL_DISTANCE_MODEL); } public void setDistanceModelInverseDistance() { AL10.alDistanceModel(AL10.AL_INVERSE_DISTANCE); } public void setDistanceModelInverseDistanceClamped() { AL10.alDistanceModel(AL10.AL_INVERSE_DISTANCE_CLAMPED); } public void setDistanceModelNone() { AL10.alDistanceModel(AL10.AL_NONE); } public Config getConfig() { return config; } public OpenALListener getListener() { return listener; } public void setListener(OpenALListener listener) { this.listener = listener; } public String getErrorMessage(int error) { String message; switch (error) { case AL10.AL_NO_ERROR: message = ""; break; case AL10.AL_INVALID_ENUM: message = "Invalid enum"; break; case AL10.AL_INVALID_NAME: message = "Invalid name"; break; case AL10.AL_INVALID_OPERATION: message = "Invalid operation"; break; case AL10.AL_INVALID_VALUE: message = "Invalid value"; break; case AL10.AL_OUT_OF_MEMORY: message = "Out of memory"; break; default: message = "Unknown error"; break; } return message; } public String getExtensions() { return AL10.alGetString(AL10.AL_EXTENSIONS); } public String getRenderer() { return AL10.alGetString(AL10.AL_RENDERER); } public String getVendor() { return AL10.alGetString(AL10.AL_VENDOR); } public String getVersion() { return AL10.alGetString(AL10.AL_VERSION); } public void init() { if (alContext != null) { return; } // Initialize OpenAL ALDevice alDevice = ALDevice.create(device); alContext = ALContext.create(alDevice, contextFrequency, contextRefresh, contextSynchronized); // Check for errors checkForError("initializing OpenAL"); // Set a default distance model setDistanceModelInverseDistanceClamped(); update(); } public boolean isPlaying(int soundID) { if (soundID == 0) { return false; } boolean isPlaying = (AL.sourceHelper.getSourceState(soundID) == AL10.AL_PLAYING); checkForError("checking if sound was playing"); return isPlaying; } public void pause(int soundID) { if (soundID == 0) { return; } AL.sourceHelper.pause(soundID); checkForError("pausing sound"); } public int playMusic(String audioFile, boolean loop) throws IOException { return playMusic(audioFile, loop, true); } public int playMusic(String audioFile, boolean loop, boolean priority) throws IOException { // Get codec and load the audio file Codec.Data codecData = getCodecData(audioFile); // Create a new source OpenALSource source = createTempALSource(); int sourceID = source.getSourceID(); // Create a new buffer OpenALBuffer buffer = createTempALBuffer(audioFile); int bufferID = buffer.getBufferID(); // Send buffer data AL.bufferHelper.setData(bufferID, codecData.format, codecData.buffer, codecData.sampleRate); checkForError("setting buffer data"); // Set source parameters AL.sourceHelper.setBuffer(sourceID, bufferID); AL.sourceHelper.setGain(sourceID, defaultSourceMusicVolume); AL.sourceHelper.setLooping(sourceID, loop ? AL.sourceHelper.getConstTrue() : AL.sourceHelper.getConstFalse()); AL.sourceHelper.setPosition(sourceID, 0, 0, 0); AL.sourceHelper.setVelocity(sourceID, 0, 0, 0); checkForError("setting source parameters"); // Play music AL.sourceHelper.play(sourceID); checkForError("playing music"); return sourceID; } public int playSound(String audioFile, boolean loop, boolean priority) throws IOException { return playSound(audioFile, loop, priority, 0, 0, 0, defaultSourceMaxDistance, defaultSourceReferenceDistance, defaultSourceRolloff); } public int playSound(String audioFile, boolean loop, boolean priority, float x, float y, float z) throws IOException { return playSound(audioFile, loop, priority, x, y, z, defaultSourceMaxDistance, defaultSourceReferenceDistance, defaultSourceRolloff); } public int playSound(String audioFile, boolean loop, boolean priority, float x, float y, float z, float maxDistance) throws IOException { return playSound(audioFile, loop, priority, x, y, z, maxDistance, defaultSourceReferenceDistance, defaultSourceRolloff); } public int playSound(String audioFile, boolean loop, boolean priority, float x, float y, float z, float maxDistance, float referenceDistance) throws IOException { return playSound(audioFile, loop, priority, x, y, z, maxDistance, referenceDistance, defaultSourceRolloff); } public int playSound(String audioFile, boolean loop, boolean priority, float x, float y, float z, float maxDistance, float referenceDistance, float rolloff) throws IOException { // Get codec and load the audio file Codec.Data codecData = getCodecData(audioFile); // Create a new source OpenALSource source = createTempALSource(); int sourceID = source.getSourceID(); // Create a new buffer OpenALBuffer buffer = createTempALBuffer(audioFile); int bufferID = buffer.getBufferID(); // Send buffer data AL.bufferHelper.setData(bufferID, codecData.format, codecData.buffer, codecData.sampleRate); checkForError("setting buffer data"); // Set source parameters AL.sourceHelper.setBuffer(sourceID, bufferID); AL.sourceHelper.setGain(sourceID, defaultSourceSoundVolume); AL.sourceHelper.setLooping(sourceID, loop ? AL.sourceHelper.getConstTrue() : AL.sourceHelper.getConstFalse()); AL.sourceHelper.setMaxDistance(sourceID, maxDistance); AL.sourceHelper.setPosition(sourceID, x, y, z); AL.sourceHelper.setReferenceDistance(sourceID, referenceDistance); AL.sourceHelper.setRolloffFactor(sourceID, rolloff); AL.sourceHelper.setVelocity(sourceID, 0, 0, 0); checkForError("setting source parameters"); // Play sound AL.sourceHelper.play(sourceID); checkForError("playing sound"); return sourceID; } public void registerCodec(String extension, Codec codec) { extension = extension.toLowerCase(); if (extensionToCodec.containsKey(extension)) { throw new IllegalArgumentException("The extension has already been registered: " + extension); } extensionToCodec.put(extension, codec); } public final void reset() { contextSynchronized = false; defaultSourceMaxDistance = Float.MAX_VALUE; defaultSourceMusicVolume = 1f; defaultSourceReferenceDistance = 1f; defaultSourceRolloff = 1f; defaultSourceSoundVolume = 1f; masterVolume = .8f; contextFrequency = 44100; contextRefresh = 60; maxChannels = 32; numberOfMusicChannels = 4; numberOfSoundChannels = 28; musicSourcesPlaying.clear(); soundSourcesPlaying.clear(); nameToTempOpenALBuffer.clear(); tempSources.clear(); nameToOpenALBuffer.clear(); listener = null; device = null; assert ((numberOfMusicChannels + numberOfSoundChannels) == maxChannels); } public void resume(int soundID) { if (soundID == 0) { return; } AL.sourceHelper.play(soundID); checkForError("resuming sound"); } public void rewind(int soundID) { if (soundID == 0) { return; } AL.sourceHelper.rewind(soundID); checkForError("rewinding sound"); } public void stop(int soundID) { if (soundID == 0) { return; } AL.sourceHelper.stop(soundID); checkForError("stopping sound"); } public void unregisterCodec(String extension) { extension = extension.toLowerCase(); if (!extensionToCodec.containsKey(extension)) { throw new IllegalArgumentException("The extension has not been registered: " + extension); } extensionToCodec.remove(extension); } public void update() { // Update the listener listener.setGain(masterVolume); listener.update(); checkForError("updating listener"); Util.checkALError(); } public class Config { public float getDefaultSourceMaxDistance() { return defaultSourceMaxDistance; } public void setDefaultSourceMaxDistance(float defaultSourceMaxDistance) { OpenAL.this.defaultSourceMaxDistance = defaultSourceMaxDistance; } public float getDefaultSourceMusicVolume() { return defaultSourceMusicVolume; } public void setDefaultSourceMusicVolume(float defaultSourceMusicVolume) { OpenAL.this.defaultSourceMusicVolume = defaultSourceMusicVolume; } public float getDefaultSourceReferenceDistance() { return defaultSourceReferenceDistance; } public void setDefaultSourceReferenceDistance(float defaultSourceReferenceDistance) { OpenAL.this.defaultSourceReferenceDistance = defaultSourceReferenceDistance; } public float getDefaultSourceRolloff() { return defaultSourceRolloff; } public void setDefaultSourceRolloff(float defaultSourceRolloff) { OpenAL.this.defaultSourceRolloff = defaultSourceRolloff; } public float getDefaultSourceSoundVolume() { return defaultSourceSoundVolume; } public void setDefaultSourceSoundVolume(float defaultSourceSoundVolume) { OpenAL.this.defaultSourceSoundVolume = defaultSourceSoundVolume; } public int getContextFrequency() { return contextFrequency; } public void setContextFrequency(int contextFrequency) { OpenAL.this.contextFrequency = contextFrequency; } public int getContextRefresh() { return contextRefresh; } public void setContextRefresh(int contextRefresh) { OpenAL.this.contextRefresh = contextRefresh; } public int getMaxChannels() { return maxChannels; } public void setMaxChannels(int maxChannels) { OpenAL.this.maxChannels = maxChannels; } public int getNumberOfMusicChannels() { return numberOfMusicChannels; } public void setNumberOfMusicChannels(int numberOfMusicChannels) { OpenAL.this.numberOfMusicChannels = numberOfMusicChannels; } public int getNumberOfSoundChannels() { return numberOfSoundChannels; } public void setNumberOfSoundChannels(int numberOfSoundChannels) { OpenAL.this.numberOfSoundChannels = numberOfSoundChannels; } public String getDevice() { return device; } public void setDevice(String device) { OpenAL.this.device = device; } public boolean isContextSynchronized() { return contextSynchronized; } public void setContextSynchronized(boolean contextSynchronized) { OpenAL.this.contextSynchronized = contextSynchronized; } } }