package paulscode.sound.libraries; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.FloatBuffer; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.Set; import javax.sound.sampled.AudioFormat; // From the lwjgl library, http://www.lwjgl.org import org.lwjgl.BufferUtils; import org.lwjgl.LWJGLException; import org.lwjgl.openal.AL; import org.lwjgl.openal.AL10; import paulscode.sound.Channel; import paulscode.sound.FilenameURL; import paulscode.sound.ICodec; import paulscode.sound.Library; import paulscode.sound.ListenerData; import paulscode.sound.SoundBuffer; import paulscode.sound.SoundSystemConfig; import paulscode.sound.SoundSystemException; import paulscode.sound.Source; /** * The LibraryLWJGLOpenAL class interfaces the lwjgl binding of OpenAL. *<b><br><br> * This software is based on or using the LWJGL Lightweight Java Gaming * Library available from * http://www.lwjgl.org/. *</b><br><br> * LWJGL License: *<br><i> * Copyright (c) 2002-2008 Lightweight Java Game Library Project * All rights reserved. *<br> * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * <br> * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. *<br> * * 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. *<br> * * Neither the name of 'Light Weight Java Game Library' nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * <br> * 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. * <br><br><br></i> *<b><i> SoundSystem LibraryLWJGLOpenAL License:</b></i><br><b><br> *<b> * You are free to use this library for any purpose, commercial or otherwise. * You may modify this library or source code, and distribute it any way you * like, provided the following conditions are met: *<br> * 1) You must abide by the conditions of the aforementioned LWJGL License. *<br> * 2) You may not falsely claim to be the author of this library or any * unmodified portion of it. *<br> * 3) You may not copyright this library or a modified version of it and then * sue me for copyright infringement. *<br> * 4) If you modify the source code, you must clearly document the changes * made before redistributing the modified source code, so other users know * it is not the original code. *<br> * 5) You are not required to give me credit for this library in any derived * work, but if you do, you must also mention my website: * http://www.paulscode.com *<br> * 6) I the author will not be responsible for any damages (physical, * financial, or otherwise) caused by the use if this library or any part * of it. *<br> * 7) I the author do not guarantee, warrant, or make any representations, * either expressed or implied, regarding the use of this library or any * part of it. * <br><br> * Author: Paul Lamb * <br> * http://www.paulscode.com * </b> */ public class LibraryLWJGLOpenAL extends Library { /** * Used to return a current value from one of the synchronized * boolean-interface methods. */ private static final boolean GET = false; /** * Used to set the value in one of the synchronized boolean-interface methods. */ private static final boolean SET = true; /** * Used when a parameter for one of the synchronized boolean-interface methods * is not aplicable. */ private static final boolean XXX = false; /** * Position of the listener in 3D space. */ private FloatBuffer listenerPositionAL = null; /** * Information about the listener's orientation. */ private FloatBuffer listenerOrientation = null; /** * Velocity of the listener. */ private FloatBuffer listenerVelocity = null; /** * Map containing OpenAL identifiers for sound buffers. */ private HashMap<String, IntBuffer> ALBufferMap = null; /** * Whether or not the AL_PITCH control is supported. */ private static boolean alPitchSupported = true; /** * Constructor: Instantiates the source map, buffer map and listener * information. Also sets the library type to * SoundSystemConfig.LIBRARY_OPENAL */ public LibraryLWJGLOpenAL() throws SoundSystemException { super(); ALBufferMap = new HashMap<String, IntBuffer>(); reverseByteOrder = true; } /** * Initializes OpenAL, creates the listener, and grabs up audio channels. */ @Override public void init() throws SoundSystemException { boolean errors = false; // set to 'true' if error(s) occur: try { // Try and create the sound system: AL.create(); errors = checkALError(); } catch( LWJGLException e ) { // There was an exception errorMessage( "Unable to initialize OpenAL. Probable cause: " + "OpenAL not supported." ); printStackTrace( e ); throw new LibraryLWJGLOpenAL.Exception( e.getMessage(), LibraryLWJGLOpenAL.Exception.CREATE ); } // Let user know if the library loaded properly if( errors ) importantMessage( "OpenAL did not initialize properly!" ); else message( "OpenAL initialized." ); // Listener is at the origin, facing along the z axis, no velocity: listenerPositionAL = BufferUtils.createFloatBuffer( 3 ).put( new float[] { listener.position.x, listener.position.y, listener.position.z } ); listenerOrientation = BufferUtils.createFloatBuffer( 6 ).put ( new float[] { listener.lookAt.x, listener.lookAt.y, listener.lookAt.z, listener.up.x, listener.up.y, listener.up.z } ); listenerVelocity = BufferUtils.createFloatBuffer( 3 ).put ( new float[] { 0.0f, 0.0f, 0.0f } ); // Flip the buffers, so they can be used: listenerPositionAL.flip(); listenerOrientation.flip(); listenerVelocity.flip(); // Pass the buffers to the sound system, and check for potential errors: AL10.alListener( AL10.AL_POSITION, listenerPositionAL ); errors = checkALError() || errors; AL10.alListener( AL10.AL_ORIENTATION, listenerOrientation ); errors = checkALError() || errors; AL10.alListener( AL10.AL_VELOCITY, listenerVelocity ); errors = checkALError() || errors; AL10.alDopplerFactor( SoundSystemConfig.getDopplerFactor() ); errors = checkALError() || errors; AL10.alDopplerVelocity( SoundSystemConfig.getDopplerVelocity() ); errors = checkALError() || errors; // Let user know what caused the above error messages: if( errors ) { importantMessage( "OpenAL did not initialize properly!" ); throw new LibraryLWJGLOpenAL.Exception( "Problem encountered " + "while loading OpenAL or " + "creating the listener. " + "Probable cause: OpenAL not " + "supported", LibraryLWJGLOpenAL.Exception.CREATE ); } super.init(); // Check if we can use the AL_PITCH control: ChannelLWJGLOpenAL channel = (ChannelLWJGLOpenAL) normalChannels.get( 1 ); try { AL10.alSourcef( channel.ALSource.get( 0 ), AL10.AL_PITCH, 1.0f ); if( checkALError() ) { alPitchSupported( SET, false ); throw new LibraryLWJGLOpenAL.Exception( "OpenAL: AL_PITCH not " + "supported.", LibraryLWJGLOpenAL.Exception.NO_AL_PITCH ); } else { alPitchSupported( SET, true ); } } catch( java.lang.Exception e ) { alPitchSupported( SET, false ); throw new LibraryLWJGLOpenAL.Exception( "OpenAL: AL_PITCH not " + "supported.", LibraryLWJGLOpenAL.Exception.NO_AL_PITCH ); } } /** * Checks if the OpenAL library type is compatible. * @return True or false. */ public static boolean libraryCompatible() { if( AL.isCreated() ) return true; try { AL.create(); } catch( java.lang.Exception e ) { return false; } try { AL.destroy(); } catch( java.lang.Exception e ) {} return true; } /** * Creates a new channel of the specified type (normal or streaming). Possible * values for channel type can be found in the * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} class. * @param type Type of channel. */ @Override protected Channel createChannel( int type ) { ChannelLWJGLOpenAL channel; IntBuffer ALSource; ALSource = BufferUtils.createIntBuffer( 1 ); try { AL10.alGenSources( ALSource ); } catch( java.lang.Exception e ) { AL10.alGetError(); return null; // no more voices left } if( AL10.alGetError() != AL10.AL_NO_ERROR ) return null; channel = new ChannelLWJGLOpenAL( type, ALSource ); return channel; } /** * Stops all sources, shuts down OpenAL, and removes references to all * instantiated objects. */ @Override public void cleanup() { super.cleanup(); Set<String> keys = bufferMap.keySet(); Iterator<String> iter = keys.iterator(); String filename; IntBuffer buffer; // loop through and clear all sound buffers: while( iter.hasNext() ) { filename = iter.next(); buffer = ALBufferMap.get( filename ); if( buffer != null ) { AL10.alDeleteBuffers( buffer ); checkALError(); buffer.clear(); } } bufferMap.clear(); AL.destroy(); bufferMap = null; listenerPositionAL = null; listenerOrientation = null; listenerVelocity = null; } /** * Pre-loads a sound into memory. * @param filenameURL Filename/URL of the sound file to load. * @return True if the sound loaded properly. */ @Override public boolean loadSound( FilenameURL filenameURL ) { // Make sure the buffer map exists: if( bufferMap == null ) { bufferMap = new HashMap<String, SoundBuffer>(); importantMessage( "Buffer Map was null in method 'loadSound'" ); } // Make sure the OpenAL buffer map exists: if( ALBufferMap == null ) { ALBufferMap = new HashMap<String, IntBuffer>(); importantMessage( "Open AL Buffer Map was null in method" + "'loadSound'" ); } // make sure they gave us a filename: if( errorCheck( filenameURL == null, "Filename/URL not specified in method 'loadSound'" ) ) return false; // check if it is already loaded: if( bufferMap.get( filenameURL.getFilename() ) != null ) return true; ICodec codec = SoundSystemConfig.getCodec( filenameURL.getFilename() ); if( errorCheck( codec == null, "No codec found for file '" + filenameURL.getFilename() + "' in method 'loadSound'" ) ) return false; codec.reverseByteOrder( true ); URL url = filenameURL.getURL(); if( errorCheck( url == null, "Unable to open file '" + filenameURL.getFilename() + "' in method 'loadSound'" ) ) return false; codec.initialize( url ); SoundBuffer buffer = codec.readAll(); codec.cleanup(); codec = null; if( errorCheck( buffer == null, "Sound buffer null in method 'loadSound'" ) ) return false; bufferMap.put( filenameURL.getFilename(), buffer ); AudioFormat audioFormat = buffer.audioFormat; int soundFormat = 0; if( audioFormat.getChannels() == 1 ) { if( audioFormat.getSampleSizeInBits() == 8 ) { soundFormat = AL10.AL_FORMAT_MONO8; } else if( audioFormat.getSampleSizeInBits() == 16 ) { soundFormat = AL10.AL_FORMAT_MONO16; } else { errorMessage( "Illegal sample size in method 'loadSound'" ); return false; } } else if( audioFormat.getChannels() == 2 ) { if( audioFormat.getSampleSizeInBits() == 8 ) { soundFormat = AL10.AL_FORMAT_STEREO8; } else if( audioFormat.getSampleSizeInBits() == 16 ) { soundFormat = AL10.AL_FORMAT_STEREO16; } else { errorMessage( "Illegal sample size in method 'loadSound'" ); return false; } } else { errorMessage( "File neither mono nor stereo in method " + "'loadSound'" ); return false; } IntBuffer intBuffer = BufferUtils.createIntBuffer( 1 ); AL10.alGenBuffers( intBuffer ); if( errorCheck( AL10.alGetError() != AL10.AL_NO_ERROR, "alGenBuffers error when loading " + filenameURL.getFilename() ) ) return false; // AL10.alBufferData( intBuffer.get( 0 ), soundFormat, // ByteBuffer.wrap( buffer.audioData ), // (int) audioFormat.getSampleRate() ); AL10.alBufferData( intBuffer.get( 0 ), soundFormat, (ByteBuffer) BufferUtils.createByteBuffer( buffer.audioData.length ).put( buffer.audioData ).flip(), (int) audioFormat.getSampleRate() ); if( errorCheck( AL10.alGetError() != AL10.AL_NO_ERROR, "alBufferData error when loading " + filenameURL.getFilename() ) ) if( errorCheck( intBuffer == null, "Sound buffer was not created for " + filenameURL.getFilename() ) ) return false; ALBufferMap.put( filenameURL.getFilename(), intBuffer ); return true; } /** * Saves the specified sample data, under the specified identifier. This * identifier can be later used in place of 'filename' parameters to reference * the sample data. * @param buffer the sample data and audio format to save. * @param identifier What to call the sample. * @return True if there weren't any problems. */ @Override public boolean loadSound( SoundBuffer buffer, String identifier ) { // Make sure the buffer map exists: if( bufferMap == null ) { bufferMap = new HashMap<String, SoundBuffer>(); importantMessage( "Buffer Map was null in method 'loadSound'" ); } // Make sure the OpenAL buffer map exists: if( ALBufferMap == null ) { ALBufferMap = new HashMap<String, IntBuffer>(); importantMessage( "Open AL Buffer Map was null in method" + "'loadSound'" ); } // make sure they gave us an identifier: if( errorCheck( identifier == null, "Identifier not specified in method 'loadSound'" ) ) return false; // check if it is already loaded: if( bufferMap.get( identifier ) != null ) return true; if( errorCheck( buffer == null, "Sound buffer null in method 'loadSound'" ) ) return false; bufferMap.put( identifier, buffer ); AudioFormat audioFormat = buffer.audioFormat; int soundFormat = 0; if( audioFormat.getChannels() == 1 ) { if( audioFormat.getSampleSizeInBits() == 8 ) { soundFormat = AL10.AL_FORMAT_MONO8; } else if( audioFormat.getSampleSizeInBits() == 16 ) { soundFormat = AL10.AL_FORMAT_MONO16; } else { errorMessage( "Illegal sample size in method 'loadSound'" ); return false; } } else if( audioFormat.getChannels() == 2 ) { if( audioFormat.getSampleSizeInBits() == 8 ) { soundFormat = AL10.AL_FORMAT_STEREO8; } else if( audioFormat.getSampleSizeInBits() == 16 ) { soundFormat = AL10.AL_FORMAT_STEREO16; } else { errorMessage( "Illegal sample size in method 'loadSound'" ); return false; } } else { errorMessage( "File neither mono nor stereo in method " + "'loadSound'" ); return false; } IntBuffer intBuffer = BufferUtils.createIntBuffer( 1 ); AL10.alGenBuffers( intBuffer ); if( errorCheck( AL10.alGetError() != AL10.AL_NO_ERROR, "alGenBuffers error when saving " + identifier ) ) return false; // AL10.alBufferData( intBuffer.get( 0 ), soundFormat, // ByteBuffer.wrap( buffer.audioData ), // (int) audioFormat.getSampleRate() ); AL10.alBufferData( intBuffer.get( 0 ), soundFormat, (ByteBuffer) BufferUtils.createByteBuffer( buffer.audioData.length ).put( buffer.audioData ).flip(), (int) audioFormat.getSampleRate() ); if( errorCheck( AL10.alGetError() != AL10.AL_NO_ERROR, "alBufferData error when saving " + identifier ) ) if( errorCheck( intBuffer == null, "Sound buffer was not created for " + identifier ) ) return false; ALBufferMap.put( identifier, intBuffer ); return true; } /** * Removes a pre-loaded sound from memory. This is a good method to use for * freeing up memory after a large sound file is no longer needed. NOTE: the * source will remain in memory after this method has been called, for as long * as the sound is attached to an existing source. * @param filename Filename/identifier of the sound file to unload. */ @Override public void unloadSound( String filename ) { ALBufferMap.remove( filename ); super.unloadSound( filename ); } /** * Sets the overall volume to the specified value, affecting all sources. * @param value New volume, float value ( 0.0f - 1.0f ). */ @Override public void setMasterVolume( float value ) { super.setMasterVolume( value ); AL10.alListenerf( AL10.AL_GAIN, value ); checkALError(); } /** * Creates a new source and places it into the source map. * @param priority Setting this to true will prevent other sounds from overriding this one. * @param toStream Setting this to true will load the sound in pieces rather than all at once. * @param toLoop Should this source loop, or play only once. * @param sourcename A unique identifier for this source. Two sources may not use the same sourcename. * @param filenameURL Filename/URL of the sound file to play at this source. * @param x X position for this source. * @param y Y position for this source. * @param z Z position for this source. * @param attModel Attenuation model to use. * @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel". */ @Override public void newSource( boolean priority, boolean toStream, boolean toLoop, String sourcename, FilenameURL filenameURL, float x, float y, float z, int attModel, float distOrRoll ) { IntBuffer myBuffer = null; if( !toStream ) { // Grab the sound buffer for this file: myBuffer = ALBufferMap.get( filenameURL.getFilename() ); // if not found, try loading it: if( myBuffer == null ) { if( !loadSound( filenameURL ) ) { errorMessage( "Source '" + sourcename + "' was not created " + "because an error occurred while loading " + filenameURL.getFilename() ); return; } } // try and grab the sound buffer again: myBuffer = ALBufferMap.get( filenameURL.getFilename() ); // see if it was there this time: if( myBuffer == null ) { errorMessage( "Source '" + sourcename + "' was not created " + "because a sound buffer was not found for " + filenameURL.getFilename() ); return; } } SoundBuffer buffer = null; if( !toStream ) { // Grab the audio data for this file: buffer = bufferMap.get( filenameURL.getFilename() ); // if not found, try loading it: if( buffer == null ) { if( !loadSound( filenameURL ) ) { errorMessage( "Source '" + sourcename + "' was not created " + "because an error occurred while loading " + filenameURL.getFilename() ); return; } } // try and grab the sound buffer again: buffer = bufferMap.get( filenameURL.getFilename() ); // see if it was there this time: if( buffer == null ) { errorMessage( "Source '" + sourcename + "' was not created " + "because audio data was not found for " + filenameURL.getFilename() ); return; } } sourceMap.put( sourcename, new SourceLWJGLOpenAL( listenerPositionAL, myBuffer, priority, toStream, toLoop, sourcename, filenameURL, buffer, x, y, z, attModel, distOrRoll, false ) ); } /** * Opens a direct line for streaming audio data. * @param audioFormat Format that the data will be in. * @param priority Setting this to true will prevent other sounds from overriding this one. * @param sourcename A unique identifier for this source. Two sources may not use the same sourcename. * @param x X position for this source. * @param y Y position for this source. * @param z Z position for this source. * @param attModel Attenuation model to use. * @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel". */ @Override public void rawDataStream( AudioFormat audioFormat, boolean priority, String sourcename, float x, float y, float z, int attModel, float distOrRoll ) { sourceMap.put( sourcename, new SourceLWJGLOpenAL( listenerPositionAL, audioFormat, priority, sourcename, x, y, z, attModel, distOrRoll ) ); } /** * Creates and immediately plays a new source. * @param priority Setting this to true will prevent other sounds from overriding this one. * @param toStream Setting this to true will load the sound in pieces rather than all at once. * @param toLoop Should this source loop, or play only once. * @param sourcename A unique identifier for this source. Two sources may not use the same sourcename. * @param filenameURL Filename/URL of the sound file to play at this source. * @param x X position for this source. * @param y Y position for this source. * @param z Z position for this source. * @param attModel Attenuation model to use. * @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel". * @param temporary Whether or not this source should be removed after it finishes playing. */ @Override public void quickPlay( boolean priority, boolean toStream, boolean toLoop, String sourcename, FilenameURL filenameURL, float x, float y, float z, int attModel, float distOrRoll, boolean temporary ) { IntBuffer myBuffer = null; if( !toStream ) { // Grab the sound buffer for this file: myBuffer = ALBufferMap.get( filenameURL.getFilename() ); // if not found, try loading it: if( myBuffer == null ) loadSound( filenameURL ); // try and grab the sound buffer again: myBuffer = ALBufferMap.get( filenameURL.getFilename() ); // see if it was there this time: if( myBuffer == null ) { errorMessage( "Sound buffer was not created for " + filenameURL.getFilename() ); return; } } SoundBuffer buffer = null; if( !toStream ) { // Grab the sound buffer for this file: buffer = bufferMap.get( filenameURL.getFilename() ); // if not found, try loading it: if( buffer == null ) { if( !loadSound( filenameURL ) ) { errorMessage( "Source '" + sourcename + "' was not created " + "because an error occurred while loading " + filenameURL.getFilename() ); return; } } // try and grab the sound buffer again: buffer = bufferMap.get( filenameURL.getFilename() ); // see if it was there this time: if( buffer == null ) { errorMessage( "Source '" + sourcename + "' was not created " + "because audio data was not found for " + filenameURL.getFilename() ); return; } } SourceLWJGLOpenAL s = new SourceLWJGLOpenAL( listenerPositionAL, myBuffer, priority, toStream, toLoop, sourcename, filenameURL, buffer, x, y, z, attModel, distOrRoll, false ); sourceMap.put( sourcename, s ); play( s ); if( temporary ) s.setTemporary( true ); } /** * Creates sources based on the source map provided. * @param srcMap Sources to copy. */ @Override public void copySources( HashMap<String, Source> srcMap ) { if( srcMap == null ) return; Set<String> keys = srcMap.keySet(); Iterator<String> iter = keys.iterator(); String sourcename; Source source; // Make sure the buffer map exists: if( bufferMap == null ) { bufferMap = new HashMap<String, SoundBuffer>(); importantMessage( "Buffer Map was null in method 'copySources'" ); } // Make sure the OpenAL buffer map exists: if( ALBufferMap == null ) { ALBufferMap = new HashMap<String, IntBuffer>(); importantMessage( "Open AL Buffer Map was null in method" + "'copySources'" ); } // remove any existing sources before starting: sourceMap.clear(); SoundBuffer buffer; // loop through and copy all the sources: while( iter.hasNext() ) { sourcename = iter.next(); source = srcMap.get( sourcename ); if( source != null ) { buffer = null; if( !source.toStream ) { loadSound( source.filenameURL ); buffer = bufferMap.get( source.filenameURL.getFilename() ); } if( source.toStream || buffer != null ) sourceMap.put( sourcename, new SourceLWJGLOpenAL( listenerPositionAL, ALBufferMap.get( source.filenameURL.getFilename() ), source, buffer ) ); } } } /** * Changes the listener's position. * @param x Destination X coordinate. * @param y Destination Y coordinate. * @param z Destination Z coordinate. */ @Override public void setListenerPosition( float x, float y, float z ) { super.setListenerPosition( x, y, z ); listenerPositionAL.put( 0, x ); listenerPositionAL.put( 1, y ); listenerPositionAL.put( 2, z ); // Update OpenAL listener position: AL10.alListener( AL10.AL_POSITION, listenerPositionAL ); // Check for errors: checkALError(); } /** * Changes the listeners orientation to the specified 'angle' radians * counterclockwise around the y-Axis. * @param angle Radians. */ @Override public void setListenerAngle( float angle ) { super.setListenerAngle( angle ); listenerOrientation.put( 0, listener.lookAt.x ); listenerOrientation.put( 2, listener.lookAt.z ); // Update OpenAL listener orientation: AL10.alListener( AL10.AL_ORIENTATION, listenerOrientation ); // Check for errors: checkALError(); } /** * Changes the listeners orientation using the specified coordinates. * @param lookX X element of the look-at direction. * @param lookY Y element of the look-at direction. * @param lookZ Z element of the look-at direction. * @param upX X element of the up direction. * @param upY Y element of the up direction. * @param upZ Z element of the up direction. */ @Override public void setListenerOrientation( float lookX, float lookY, float lookZ, float upX, float upY, float upZ ) { super.setListenerOrientation( lookX, lookY, lookZ, upX, upY, upZ ); listenerOrientation.put( 0, lookX ); listenerOrientation.put( 1, lookY ); listenerOrientation.put( 2, lookZ ); listenerOrientation.put( 3, upX ); listenerOrientation.put( 4, upY ); listenerOrientation.put( 5, upZ ); AL10.alListener( AL10.AL_ORIENTATION, listenerOrientation ); checkALError(); } /** * Changes the listeners position and orientation using the specified listener * data. * @param l Listener data to use. */ @Override public void setListenerData( ListenerData l ) { super.setListenerData( l ); listenerPositionAL.put( 0, l.position.x ); listenerPositionAL.put( 1, l.position.y ); listenerPositionAL.put( 2, l.position.z ); AL10.alListener( AL10.AL_POSITION, listenerPositionAL ); checkALError(); listenerOrientation.put( 0, l.lookAt.x ); listenerOrientation.put( 1, l.lookAt.y ); listenerOrientation.put( 2, l.lookAt.z ); listenerOrientation.put( 3, l.up.x ); listenerOrientation.put( 4, l.up.y ); listenerOrientation.put( 5, l.up.z ); AL10.alListener( AL10.AL_ORIENTATION, listenerOrientation ); checkALError(); listenerVelocity.put( 0, l.velocity.x ); listenerVelocity.put( 1, l.velocity.y ); listenerVelocity.put( 2, l.velocity.z ); AL10.alListener( AL10.AL_VELOCITY, listenerVelocity ); checkALError(); } /** * Sets the listener's velocity, for use in Doppler effect. * @param x Velocity along world x-axis. * @param y Velocity along world y-axis. * @param z Velocity along world z-axis. */ @Override public void setListenerVelocity( float x, float y, float z ) { super.setListenerVelocity( x, y, z ); listenerVelocity.put( 0, listener.velocity.x ); listenerVelocity.put( 1, listener.velocity.y ); listenerVelocity.put( 2, listener.velocity.z ); AL10.alListener( AL10.AL_VELOCITY, listenerVelocity ); } /** * The Doppler parameters have changed. */ @Override public void dopplerChanged() { super.dopplerChanged(); AL10.alDopplerFactor( SoundSystemConfig.getDopplerFactor() ); checkALError(); AL10.alDopplerVelocity( SoundSystemConfig.getDopplerVelocity() ); checkALError(); } /** * Checks for OpenAL errors, and prints a message if there is an error. * @return True if there was an error, False if not. */ private boolean checkALError() { switch( AL10.alGetError() ) { case AL10.AL_NO_ERROR: return false; case AL10.AL_INVALID_NAME: errorMessage( "Invalid name parameter." ); return true; case AL10.AL_INVALID_ENUM: errorMessage( "Invalid parameter." ); return true; case AL10.AL_INVALID_VALUE: errorMessage( "Invalid enumerated parameter value." ); return true; case AL10.AL_INVALID_OPERATION: errorMessage( "Illegal call." ); return true; case AL10.AL_OUT_OF_MEMORY: errorMessage( "Unable to allocate memory." ); return true; default: errorMessage( "An unrecognized error occurred." ); return true; } } /** * Whether or not the AL_PITCH control is supported. * @return True if AL_PITCH is supported. */ public static boolean alPitchSupported() { return alPitchSupported( GET, XXX ); } /** * Sets or returns the value of boolean 'alPitchSupported'. * @param action Action to perform (GET or SET). * @param value New value if action is SET, otherwise XXX. * @return value of boolean 'alPitchSupported'. */ private static synchronized boolean alPitchSupported( boolean action, boolean value ) { if( action == SET ) alPitchSupported = value; return alPitchSupported; } /** * Returns the short title of this library type. * @return A short title. */ public static String getTitle() { return "LWJGL OpenAL"; } /** * Returns a longer description of this library type. * @return A longer description. */ public static String getDescription() { return "The LWJGL binding of OpenAL. For more information, see " + "http://www.lwjgl.org"; } /** * Returns the name of the class. * @return "Library" + library title. */ @Override public String getClassName() { return "LibraryLWJGLOpenAL"; } /** * The LibraryLWJGLOpenAL.Exception class provides library-specific error * information. */ public static class Exception extends SoundSystemException { /** * Global identifier for an exception during AL.create(). Probably * means that OpenAL is not supported. */ public static final int CREATE = 101; /** * Global identifier for an invalid name parameter in OpenAL. */ public static final int INVALID_NAME = 102; /** * Global identifier for an invalid parameter in OpenAL. */ public static final int INVALID_ENUM = 103; /** * Global identifier for an invalid enumerated parameter value in OpenAL. */ public static final int INVALID_VALUE = 104; /** * Global identifier for an illegal call in OpenAL. */ public static final int INVALID_OPERATION = 105; /** * Global identifier for OpenAL out of memory. */ public static final int OUT_OF_MEMORY = 106; /** * Global identifier for an exception while creating the OpenAL Listener. */ public static final int LISTENER = 107; /** * Global identifier for OpenAL AL_PITCH not supported. */ public static final int NO_AL_PITCH = 108; /** * Constructor: Generates a standard "unknown error" exception with the * specified message. * @param message A brief description of the problem that occurred. */ public Exception( String message ) { super( message ); } /** * Constructor: Generates an exception of the specified type, with the * specified message. * @param message A brief description of the problem that occurred. * @param type Identifier indicating they type of error. */ public Exception( String message, int type ) { super( message, type ); } } }