Path: blob/main/src/lwjgl/java/paulscode/sound/libraries/SourceLWJGLOpenAL.java
8650 views
package paulscode.sound.libraries;12import java.nio.IntBuffer;3import java.nio.FloatBuffer;4import java.util.LinkedList;5import javax.sound.sampled.AudioFormat;67// From the lwjgl library, http://www.lwjgl.org8import org.lwjgl.BufferUtils;9import org.lwjgl.openal.AL10;1011import paulscode.sound.Channel;12import paulscode.sound.FilenameURL;13import paulscode.sound.Source;14import paulscode.sound.SoundBuffer;15import paulscode.sound.SoundSystemConfig;1617/**18* The SourceLWJGLOpenAL class provides an interface to the lwjgl binding of OpenAL.19*<b><br><br>20* This software is based on or using the LWJGL Lightweight Java Gaming21* Library available from22* http://www.lwjgl.org/.23*</b><br><br>24* LWJGL License:25*<br><i>26* Copyright (c) 2002-2008 Lightweight Java Game Library Project27* All rights reserved.28*<br>29* Redistribution and use in source and binary forms, with or without30* modification, are permitted provided that the following conditions are31* met:32* <br>33* * Redistributions of source code must retain the above copyright34* notice, this list of conditions and the following disclaimer.35*<br>36* * Redistributions in binary form must reproduce the above copyright37* notice, this list of conditions and the following disclaimer in the38* documentation and/or other materials provided with the distribution.39*<br>40* * Neither the name of 'Light Weight Java Game Library' nor the names of41* its contributors may be used to endorse or promote products derived42* from this software without specific prior written permission.43* <br>44* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS45* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED46* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR47* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR48* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,49* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,50* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR51* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF52* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING53* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS54* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.55* <br><br><br></i>56*<b><i> SoundSystem LibraryLWJGLOpenAL License:</b></i><br><b><br>57*<b>58* You are free to use this library for any purpose, commercial or otherwise.59* You may modify this library or source code, and distribute it any way you60* like, provided the following conditions are met:61*<br>62* 1) You must abide by the conditions of the aforementioned LWJGL License.63*<br>64* 2) You may not falsely claim to be the author of this library or any65* unmodified portion of it.66*<br>67* 3) You may not copyright this library or a modified version of it and then68* sue me for copyright infringement.69*<br>70* 4) If you modify the source code, you must clearly document the changes71* made before redistributing the modified source code, so other users know72* it is not the original code.73*<br>74* 5) You are not required to give me credit for this library in any derived75* work, but if you do, you must also mention my website:76* http://www.paulscode.com77*<br>78* 6) I the author will not be responsible for any damages (physical,79* financial, or otherwise) caused by the use if this library or any part80* of it.81*<br>82* 7) I the author do not guarantee, warrant, or make any representations,83* either expressed or implied, regarding the use of this library or any84* part of it.85* <br><br>86* Author: Paul Lamb87* <br>88* http://www.paulscode.com89* </b>90*/91public class SourceLWJGLOpenAL extends Source92{93/**94* The source's basic Channel type-cast to a ChannelLWJGLOpenAL.95*/96private ChannelLWJGLOpenAL channelOpenAL = (ChannelLWJGLOpenAL) channel;9798/**99* OpenAL IntBuffer sound-buffer identifier for this source if it is a normal100* source.101*/102private IntBuffer myBuffer;103104/**105* FloatBuffer containing the listener's 3D coordinates.106*/107private FloatBuffer listenerPosition;108109/**110* FloatBuffer containing the source's 3D coordinates.111*/112private FloatBuffer sourcePosition;113114/**115* FloatBuffer containing the source's velocity vector.116*/117private FloatBuffer sourceVelocity;118119/**120* Constructor: Creates a new source using the specified parameters.121* @param listenerPosition FloatBuffer containing the listener's 3D coordinates.122* @param myBuffer OpenAL IntBuffer sound-buffer identifier to use for a new normal source.123* @param priority Setting this to true will prevent other sounds from overriding this one.124* @param toStream Setting this to true will create a streaming source.125* @param toLoop Should this source loop, or play only once.126* @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.127* @param filenameURL Filename/URL of the sound file to play at this source.128* @param soundBuffer Buffer containing audio data, or null if not loaded yet.129* @param x X position for this source.130* @param y Y position for this source.131* @param z Z position for this source.132* @param attModel Attenuation model to use.133* @param distOrRoll Either the fading distance or rolloff factor, depending on the value of 'att'.134* @param temporary Whether or not to remove this source after it finishes playing.135*/136public SourceLWJGLOpenAL( FloatBuffer listenerPosition, IntBuffer myBuffer,137boolean priority, boolean toStream,138boolean toLoop, String sourcename,139FilenameURL filenameURL, SoundBuffer soundBuffer,140float x, float y, float z, int attModel,141float distOrRoll, boolean temporary )142{143super( priority, toStream, toLoop, sourcename, filenameURL, soundBuffer,144x, y, z, attModel, distOrRoll, temporary );145if( codec != null )146codec.reverseByteOrder( true );147this.listenerPosition = listenerPosition;148this.myBuffer = myBuffer;149libraryType = LibraryLWJGLOpenAL.class;150pitch = 1.0f;151resetALInformation();152}153154/**155* Constructor: Creates a new source matching the specified source.156* @param listenerPosition FloatBuffer containing the listener's 3D coordinates.157* @param myBuffer OpenAL IntBuffer sound-buffer identifier to use for a new normal source.158* @param old Source to copy information from.159* @param soundBuffer Buffer containing audio data, or null if not loaded yet.160*/161public SourceLWJGLOpenAL( FloatBuffer listenerPosition, IntBuffer myBuffer,162Source old, SoundBuffer soundBuffer )163{164super( old, soundBuffer );165if( codec != null )166codec.reverseByteOrder( true );167this.listenerPosition = listenerPosition;168this.myBuffer = myBuffer;169libraryType = LibraryLWJGLOpenAL.class;170pitch = 1.0f;171resetALInformation();172}173174/**175* Constructor: Creates a new streaming source that will be directly fed with176* raw audio data.177* @param listenerPosition FloatBuffer containing the listener's 3D coordinates.178* @param audioFormat Format that the data will be in.179* @param priority Setting this to true will prevent other sounds from overriding this one.180* @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.181* @param x X position for this source.182* @param y Y position for this source.183* @param z Z position for this source.184* @param attModel Attenuation model to use.185* @param distOrRoll Either the fading distance or rolloff factor, depending on the value of 'att'.186*/187public SourceLWJGLOpenAL( FloatBuffer listenerPosition,188AudioFormat audioFormat, boolean priority,189String sourcename, float x, float y, float z,190int attModel, float distOrRoll )191{192super( audioFormat, priority, sourcename, x, y, z, attModel,193distOrRoll );194this.listenerPosition = listenerPosition;195libraryType = LibraryLWJGLOpenAL.class;196pitch = 1.0f;197resetALInformation();198}199200/**201* Shuts the source down and removes references to all instantiated objects.202*/203@Override204public void cleanup()205{206207super.cleanup();208}209210/**211* Changes the peripheral information about the source using the specified212* parameters.213* @param listenerPosition FloatBuffer containing the listener's 3D coordinates.214* @param myBuffer OpenAL IntBuffer sound-buffer identifier to use for a new normal source.215* @param priority Setting this to true will prevent other sounds from overriding this one.216* @param toStream Setting this to true will create a streaming source.217* @param toLoop Should this source loop, or play only once.218* @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.219* @param filenameURL Filename/URL of the sound file to play at this source.220* @param soundBuffer Buffer containing audio data, or null if not loaded yet.221* @param x X position for this source.222* @param y Y position for this source.223* @param z Z position for this source.224* @param attModel Attenuation model to use.225* @param distOrRoll Either the fading distance or rolloff factor, depending on the value of 'att'.226* @param temporary Whether or not to remove this source after it finishes playing.227*/228public void changeSource( FloatBuffer listenerPosition, IntBuffer myBuffer,229boolean priority, boolean toStream,230boolean toLoop, String sourcename,231FilenameURL filenameURL, SoundBuffer soundBuffer,232float x, float y, float z, int attModel,233float distOrRoll, boolean temporary )234{235super.changeSource( priority, toStream, toLoop, sourcename,236filenameURL, soundBuffer, x, y, z, attModel,237distOrRoll, temporary );238this.listenerPosition = listenerPosition;239this.myBuffer = myBuffer;240pitch = 1.0f;241resetALInformation();242}243244/**245* Removes the next filename from the sound sequence queue and assigns it to246* this source. This method has no effect on non-streaming sources. This247* method is used internally by SoundSystem, and it is unlikely that the user248* will ever need to use it.249* @return True if there was something in the queue.250*/251@Override252public boolean incrementSoundSequence()253{254if( !toStream )255{256errorMessage( "Method 'incrementSoundSequence' may only be used " +257"for streaming sources." );258return false;259}260synchronized( soundSequenceLock )261{262if( soundSequenceQueue != null && soundSequenceQueue.size() > 0 )263{264filenameURL = soundSequenceQueue.remove( 0 );265if( codec != null )266codec.cleanup();267codec = SoundSystemConfig.getCodec( filenameURL.getFilename() );268if( codec != null )269{270codec.reverseByteOrder( true );271if( codec.getAudioFormat() == null )272codec.initialize( filenameURL.getURL() );273274AudioFormat audioFormat = codec.getAudioFormat();275276if( audioFormat == null )277{278errorMessage( "Audio Format null in method " +279"'incrementSoundSequence'" );280return false;281}282283int soundFormat = 0;284if( audioFormat.getChannels() == 1 )285{286if( audioFormat.getSampleSizeInBits() == 8 )287{288soundFormat = AL10.AL_FORMAT_MONO8;289}290else if( audioFormat.getSampleSizeInBits() == 16 )291{292soundFormat = AL10.AL_FORMAT_MONO16;293}294else295{296errorMessage( "Illegal sample size in method " +297"'incrementSoundSequence'" );298return false;299}300}301else if( audioFormat.getChannels() == 2 )302{303if( audioFormat.getSampleSizeInBits() == 8 )304{305soundFormat = AL10.AL_FORMAT_STEREO8;306}307else if( audioFormat.getSampleSizeInBits() == 16 )308{309soundFormat = AL10.AL_FORMAT_STEREO16;310}311else312{313errorMessage( "Illegal sample size in method " +314"'incrementSoundSequence'" );315return false;316}317}318else319{320errorMessage( "Audio data neither mono nor stereo in " +321"method 'incrementSoundSequence'" );322return false;323}324325// Let the channel know what format and sample rate to use:326channelOpenAL.setFormat( soundFormat,327(int) audioFormat.getSampleRate() );328preLoad = true;329}330return true;331}332}333return false;334}335336/**337* Called every time the listener's position or orientation changes.338*/339@Override340public void listenerMoved()341{342positionChanged();343}344345/**346* Moves the source to the specified position.347* @param x X coordinate to move to.348* @param y Y coordinate to move to.349* @param z Z coordinate to move to.350*/351@Override352public void setPosition( float x, float y, float z )353{354super.setPosition( x, y, z );355356// Make sure OpenAL information has been created357if( sourcePosition == null )358resetALInformation();359else360positionChanged();361362// put the new position information into the buffer:363sourcePosition.put( 0, x );364sourcePosition.put( 1, y );365sourcePosition.put( 2, z );366367// make sure we are assigned to a channel:368if( channel != null && channel.attachedSource == this &&369channelOpenAL != null && channelOpenAL.ALSource != null )370{371// move the source:372AL10.alSource( channelOpenAL.ALSource.get( 0 ), AL10.AL_POSITION,373sourcePosition );374checkALError();375}376}377378/**379* Recalculates the distance from the listner and the gain.380*/381@Override382public void positionChanged()383{384calculateDistance();385calculateGain();386387if( channel != null && channel.attachedSource == this &&388channelOpenAL != null && channelOpenAL.ALSource != null )389{390AL10.alSourcef( channelOpenAL.ALSource.get( 0 ),391AL10.AL_GAIN, (gain * sourceVolume392* (float) Math.abs( fadeOutGain )393* fadeInGain) );394checkALError();395}396checkPitch();397}398399/**400* Checks the source's pitch.401*/402private void checkPitch()403{404if( channel != null && channel.attachedSource == this &&405LibraryLWJGLOpenAL.alPitchSupported() && channelOpenAL != null &&406channelOpenAL.ALSource != null )407{408AL10.alSourcef( channelOpenAL.ALSource.get( 0 ),409AL10.AL_PITCH, pitch );410checkALError();411}412}413414/**415* Sets whether this source should loop or only play once.416* @param lp True or false.417*/418@Override419public void setLooping( boolean lp )420{421super.setLooping( lp );422423// make sure we are assigned to a channel:424if( channel != null && channel.attachedSource == this &&425channelOpenAL != null && channelOpenAL.ALSource != null )426{427if( lp )428AL10.alSourcei( channelOpenAL.ALSource.get( 0 ),429AL10.AL_LOOPING, AL10.AL_TRUE );430else431AL10.alSourcei( channelOpenAL.ALSource.get( 0 ),432AL10.AL_LOOPING, AL10.AL_FALSE );433checkALError();434}435}436437/**438* Sets this source's attenuation model.439* @param model Attenuation model to use.440*/441@Override442public void setAttenuation( int model )443{444super.setAttenuation( model );445// make sure we are assigned to a channel:446if( channel != null && channel.attachedSource == this &&447channelOpenAL != null && channelOpenAL.ALSource != null )448{449// attenuation changed, so update the rolloff factor accordingly450if( model == SoundSystemConfig.ATTENUATION_ROLLOFF )451AL10.alSourcef( channelOpenAL.ALSource.get( 0 ),452AL10.AL_ROLLOFF_FACTOR, distOrRoll );453else454AL10.alSourcef( channelOpenAL.ALSource.get( 0 ),455AL10.AL_ROLLOFF_FACTOR, 0.0f );456checkALError();457}458}459460/**461* Sets this source's fade distance or rolloff factor, depending on the462* attenuation model.463* @param dr New value for fade distance or rolloff factor.464*/465@Override466public void setDistOrRoll( float dr)467{468super.setDistOrRoll( dr );469// make sure we are assigned to a channel:470if( channel != null && channel.attachedSource == this &&471channelOpenAL != null && channelOpenAL.ALSource != null )472{473// if we are using rolloff attenuation, then dr is a rolloff factor:474if( attModel == SoundSystemConfig.ATTENUATION_ROLLOFF )475AL10.alSourcef( channelOpenAL.ALSource.get( 0 ),476AL10.AL_ROLLOFF_FACTOR, dr );477else478AL10.alSourcef( channelOpenAL.ALSource.get( 0 ),479AL10.AL_ROLLOFF_FACTOR, 0.0f );480checkALError();481}482}483484/**485* Sets this source's velocity, for use in Doppler effect.486* @param x Velocity along world x-axis.487* @param y Velocity along world y-axis.488* @param z Velocity along world z-axis.489*/490@Override491public void setVelocity( float x, float y, float z )492{493super.setVelocity( x, y, z );494495sourceVelocity = BufferUtils.createFloatBuffer( 3 ).put( new float[]496{ x, y, z } );497sourceVelocity.flip();498// make sure we are assigned to a channel:499if( channel != null && channel.attachedSource == this &&500channelOpenAL != null && channelOpenAL.ALSource != null )501{502AL10.alSource( channelOpenAL.ALSource.get( 0 ),503AL10.AL_VELOCITY, sourceVelocity );504checkALError();505}506}507508/**509* Manually sets this source's pitch.510* @param value A float value ( 0.5f - 2.0f ).511*/512@Override513public void setPitch( float value )514{515super.setPitch( value );516checkPitch();517}518519/**520* Plays the source on the specified channel.521* @param c Channel to play on.522*/523@Override524public void play( Channel c )525{526if( !active() )527{528if( toLoop )529toPlay = true;530return;531}532533if( c == null )534{535errorMessage( "Unable to play source, because channel was null" );536return;537}538539boolean newChannel = (channel != c);540if( channel != null && channel.attachedSource != this )541newChannel = true;542543boolean wasPaused = paused();544545super.play( c );546547channelOpenAL = (ChannelLWJGLOpenAL) channel;548549// Make sure the channel exists:550// check if we are already on this channel:551if( newChannel )552{553setPosition( position.x, position.y, position.z );554checkPitch();555556// Send the source's attributes to the channel:557if( channelOpenAL != null && channelOpenAL.ALSource != null )558{559if( LibraryLWJGLOpenAL.alPitchSupported() )560{561AL10.alSourcef( channelOpenAL.ALSource.get( 0 ),562AL10.AL_PITCH, pitch );563checkALError();564}565AL10.alSource( channelOpenAL.ALSource.get( 0 ),566AL10.AL_POSITION, sourcePosition );567checkALError();568569AL10.alSource( channelOpenAL.ALSource.get( 0 ),570AL10.AL_VELOCITY, sourceVelocity );571572checkALError();573574if( attModel == SoundSystemConfig.ATTENUATION_ROLLOFF )575AL10.alSourcef( channelOpenAL.ALSource.get( 0 ),576AL10.AL_ROLLOFF_FACTOR, distOrRoll );577else578AL10.alSourcef( channelOpenAL.ALSource.get( 0 ),579AL10.AL_ROLLOFF_FACTOR, 0.0f );580checkALError();581582if( toLoop && (!toStream) )583AL10.alSourcei( channelOpenAL.ALSource.get( 0 ),584AL10.AL_LOOPING, AL10.AL_TRUE );585else586AL10.alSourcei( channelOpenAL.ALSource.get( 0 ),587AL10.AL_LOOPING, AL10.AL_FALSE );588checkALError();589}590if( !toStream )591{592// This is not a streaming source, so make sure there is593// a sound buffer loaded to play:594if( myBuffer == null )595{596errorMessage( "No sound buffer to play" );597return;598}599600channelOpenAL.attachBuffer( myBuffer );601}602}603604// See if we are already playing:605if( !playing() )606{607if( toStream && !wasPaused )608{609if( codec == null )610{611errorMessage( "Decoder null in method 'play'" );612return;613}614if( codec.getAudioFormat() == null )615codec.initialize( filenameURL.getURL() );616617AudioFormat audioFormat = codec.getAudioFormat();618619if( audioFormat == null )620{621errorMessage( "Audio Format null in method 'play'" );622return;623}624625int soundFormat = 0;626if( audioFormat.getChannels() == 1 )627{628if( audioFormat.getSampleSizeInBits() == 8 )629{630soundFormat = AL10.AL_FORMAT_MONO8;631}632else if( audioFormat.getSampleSizeInBits() == 16 )633{634soundFormat = AL10.AL_FORMAT_MONO16;635}636else637{638errorMessage( "Illegal sample size in method 'play'" );639return;640}641}642else if( audioFormat.getChannels() == 2 )643{644if( audioFormat.getSampleSizeInBits() == 8 )645{646soundFormat = AL10.AL_FORMAT_STEREO8;647}648else if( audioFormat.getSampleSizeInBits() == 16 )649{650soundFormat = AL10.AL_FORMAT_STEREO16;651}652else653{654errorMessage( "Illegal sample size in method 'play'" );655return;656}657}658else659{660errorMessage( "Audio data neither mono nor stereo in " +661"method 'play'" );662return;663}664665// Let the channel know what format and sample rate to use:666channelOpenAL.setFormat( soundFormat,667(int) audioFormat.getSampleRate() );668preLoad = true;669}670channel.play();671if( pitch != 1.0f )672checkPitch();673}674}675676/**677* Queues up the initial stream-buffers for the stream.678* @return False if the end of the stream was reached.679*/680@Override681public boolean preLoad()682{683if( codec == null )684return false;685686codec.initialize( filenameURL.getURL() );687LinkedList<byte[]> preLoadBuffers = new LinkedList<byte[]>();688for( int i = 0; i < SoundSystemConfig.getNumberStreamingBuffers(); i++ )689{690soundBuffer = codec.read();691692if( soundBuffer == null || soundBuffer.audioData == null )693break;694695preLoadBuffers.add( soundBuffer.audioData );696}697positionChanged();698699channel.preLoadBuffers( preLoadBuffers );700701preLoad = false;702return true;703}704705/**706* Resets all the information OpenAL uses to play this source.707*/708private void resetALInformation()709{710// Create buffers for the source's position and velocity711sourcePosition = BufferUtils.createFloatBuffer( 3 ).put(712new float[] { position.x, position.y, position.z } );713sourceVelocity = BufferUtils.createFloatBuffer( 3 ).put(714new float[] { velocity.x, velocity.y, velocity.z } );715716// flip the buffers, so they can be used:717sourcePosition.flip();718sourceVelocity.flip();719720positionChanged();721}722723/**724* Calculates this source's distance from the listener.725*/726private void calculateDistance()727{728if( listenerPosition != null )729{730// Calculate the source's distance from the listener:731double dX = position.x - listenerPosition.get( 0 );732double dY = position.y - listenerPosition.get( 1 );733double dZ = position.z - listenerPosition.get( 2 );734distanceFromListener = (float) Math.sqrt( dX*dX + dY*dY + dZ*dZ );735}736}737738/**739* If using linear attenuation, calculates the gain for this source based on740* its distance from the listener.741*/742private void calculateGain()743{744// If using linear attenuation, calculate the source's gain:745if( attModel == SoundSystemConfig.ATTENUATION_LINEAR )746{747if( distanceFromListener <= 0 )748{749gain = 1.0f;750}751else if( distanceFromListener >= distOrRoll )752{753gain = 0.0f;754}755else756{757gain = 1.0f - (distanceFromListener / distOrRoll);758}759if( gain > 1.0f )760gain = 1.0f;761if( gain < 0.0f )762gain = 0.0f;763}764else765{766gain = 1.0f;767}768}769770/**771* Checks for OpenAL errors, and prints a message if there is an error.772* @return True if there was an error, False if not.773*/774private boolean checkALError()775{776switch( AL10.alGetError() )777{778case AL10.AL_NO_ERROR:779return false;780case AL10.AL_INVALID_NAME:781errorMessage( "Invalid name parameter." );782return true;783case AL10.AL_INVALID_ENUM:784errorMessage( "Invalid parameter." );785return true;786case AL10.AL_INVALID_VALUE:787errorMessage( "Invalid enumerated parameter value." );788return true;789case AL10.AL_INVALID_OPERATION:790errorMessage( "Illegal call." );791return true;792case AL10.AL_OUT_OF_MEMORY:793errorMessage( "Unable to allocate memory." );794return true;795default:796errorMessage( "An unrecognized error occurred." );797return true;798}799}800}801802803