Path: blob/main/src/lwjgl/java/paulscode/sound/libraries/ChannelLWJGLOpenAL.java
8650 views
package paulscode.sound.libraries;12import java.nio.ByteBuffer;3import java.nio.IntBuffer;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;10import org.lwjgl.openal.AL11;1112import paulscode.sound.Channel;13import paulscode.sound.SoundSystemConfig;1415/**16* The ChannelLWJGLOpenAL class is used to reserve a sound-card voice using the17* lwjgl binding of OpenAL. Channels can be either normal or streaming18* channels.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 ChannelLWJGLOpenAL extends Channel92{93/**94* OpenAL's IntBuffer identifier for this channel.95*/96public IntBuffer ALSource;9798/**99* OpenAL data format to use when playing back the assigned source.100*/101public int ALformat; // OpenAL data format102103/**104* Sample rate (speed) to use for play-back.105*/106public int sampleRate; // sample rate107108/**109* Miliseconds of buffers previously played (streaming sources).110*/111public float millisPreviouslyPlayed = 0;112113/**114* Constructor: takes channelType identifier and a handle to the OpenAL115* IntBuffer identifier to use for this channel. Possible values for channel116* type can be found in the117* {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} class.118* @param type Type of channel (normal or streaming).119* @param src Handle to the OpenAL source identifier.120*/121public ChannelLWJGLOpenAL( int type, IntBuffer src )122{123super( type );124libraryType = LibraryLWJGLOpenAL.class;125ALSource = src;126}127128/**129* Empties the streamBuffers list, stops and deletes the ALSource, shuts the130* channel down, and removes references to all instantiated objects.131*/132@Override133public void cleanup()134{135if( ALSource != null )136{137try138{139// Stop playing the source:140AL10.alSourceStop( ALSource );141AL10.alGetError();142}143catch( Exception e )144{}145try146{147// Delete the source:148AL10.alDeleteSources( ALSource );149AL10.alGetError();150}151catch( Exception e )152{}153ALSource.clear();154}155ALSource = null;156157super.cleanup();158}159160/**161* Attaches an OpenAL sound-buffer identifier for the sound data to be played162* back for a normal source.163* @param buf Intbuffer identifier for the sound data to play.164* @return False if an error occurred.165*/166public boolean attachBuffer( IntBuffer buf )167{168// A sound buffer can only be attached to a normal source:169if( errorCheck( channelType != SoundSystemConfig.TYPE_NORMAL,170"Sound buffers may only be attached to normal " +171"sources." ) )172return false;173174// send the sound buffer to the channel:175AL10.alSourcei( ALSource.get( 0 ), AL10.AL_BUFFER,176buf.get(0) );177178179// save the format for later, for determining milliseconds played180if( attachedSource != null && attachedSource.soundBuffer != null &&181attachedSource.soundBuffer.audioFormat != null )182setAudioFormat( attachedSource.soundBuffer.audioFormat );183184// Check for errors and return:185return checkALError();186}187/**188* Sets the channel up to receive the specified audio format.189* @param audioFormat Format to use when playing the stream data.190*/191@Override192public void setAudioFormat( AudioFormat audioFormat )193{194int soundFormat = 0;195if( audioFormat.getChannels() == 1 )196{197if( audioFormat.getSampleSizeInBits() == 8 )198{199soundFormat = AL10.AL_FORMAT_MONO8;200}201else if( audioFormat.getSampleSizeInBits() == 16 )202{203soundFormat = AL10.AL_FORMAT_MONO16;204}205else206{207errorMessage( "Illegal sample size in method " +208"'setAudioFormat'" );209return;210}211}212else if( audioFormat.getChannels() == 2 )213{214if( audioFormat.getSampleSizeInBits() == 8 )215{216soundFormat = AL10.AL_FORMAT_STEREO8;217}218else if( audioFormat.getSampleSizeInBits() == 16 )219{220soundFormat = AL10.AL_FORMAT_STEREO16;221}222else223{224errorMessage( "Illegal sample size in method " +225"'setAudioFormat'" );226return;227}228}229else230{231errorMessage( "Audio data neither mono nor stereo in " +232"method 'setAudioFormat'" );233return;234}235ALformat = soundFormat;236sampleRate = (int) audioFormat.getSampleRate();237}238/**239* Sets the channel up to receive the specified OpenAL audio format and sample240* rate.241* @param format Format to use.242* @param rate Sample rate (speed) to use.243*/244public void setFormat( int format, int rate )245{246ALformat = format;247sampleRate = rate;248}249250/**251* Queues up the initial byte[] buffers of data to be streamed.252* @param bufferList List of the first buffers to be played for a streaming source.253* @return False if problem occurred or if end of stream was reached.254*/255@Override256public boolean preLoadBuffers( LinkedList<byte[]> bufferList )257{258// Stream buffers can only be queued for streaming sources:259if( errorCheck( channelType != SoundSystemConfig.TYPE_STREAMING,260"Buffers may only be queued for streaming sources." ) )261return false;262263if( errorCheck( bufferList == null,264"Buffer List null in method 'preLoadBuffers'" ) )265return false;266267IntBuffer streamBuffers;268269// Remember if the channel was playing:270boolean playing = playing();271// stop the channel if it is playing:272if( playing )273{274AL10.alSourceStop( ALSource.get( 0 ) );275checkALError();276}277// Clear out any previously queued buffers:278int processed = AL10.alGetSourcei( ALSource.get( 0 ),279AL10.AL_BUFFERS_PROCESSED );280if( processed > 0 )281{282streamBuffers = BufferUtils.createIntBuffer( processed );283AL10.alGenBuffers( streamBuffers );284if( errorCheck( checkALError(),285"Error clearing stream buffers in method 'preLoadBuffers'" ) )286return false;287AL10.alSourceUnqueueBuffers( ALSource.get( 0 ), streamBuffers );288if( errorCheck( checkALError(),289"Error unqueuing stream buffers in method 'preLoadBuffers'" ) )290return false;291}292293// restart the channel if it was previously playing:294if( playing )295{296AL10.alSourcePlay( ALSource.get( 0 ) );297checkALError();298}299300streamBuffers = BufferUtils.createIntBuffer( bufferList.size() );301AL10.alGenBuffers( streamBuffers );302if( errorCheck( checkALError(),303"Error generating stream buffers in method 'preLoadBuffers'" ) )304return false;305306ByteBuffer byteBuffer = null;307for( int i = 0; i < bufferList.size(); i++ )308{309//byteBuffer = ByteBuffer.wrap( bufferList.get(i), 0,310// bufferList.get(i).length );311byteBuffer = (ByteBuffer) BufferUtils.createByteBuffer(312bufferList.get(i).length ).put( bufferList.get( i ) ).flip();313314try315{316AL10.alBufferData( streamBuffers.get(i), ALformat, byteBuffer,317sampleRate );318}319catch( Exception e )320{321errorMessage( "Error creating buffers in method " +322"'preLoadBuffers'" );323printStackTrace( e );324return false;325}326if( errorCheck( checkALError(),327"Error creating buffers in method 'preLoadBuffers'" ) )328return false;329330}331332try333{334AL10.alSourceQueueBuffers( ALSource.get( 0 ), streamBuffers );335}336catch( Exception e )337{338errorMessage( "Error queuing buffers in method 'preLoadBuffers'" );339printStackTrace( e );340return false;341}342if( errorCheck( checkALError(),343"Error queuing buffers in method 'preLoadBuffers'" ) )344return false;345346AL10.alSourcePlay( ALSource.get( 0 ) );347if( errorCheck( checkALError(),348"Error playing source in method 'preLoadBuffers'" ) )349return false;350351// Success:352return true;353}354355/**356* Queues up a byte[] buffer of data to be streamed.357* @param buffer The next buffer to be played for a streaming source.358* @return False if an error occurred or if the channel is shutting down.359*/360@Override361public boolean queueBuffer( byte[] buffer )362{363// Stream buffers can only be queued for streaming sources:364if( errorCheck( channelType != SoundSystemConfig.TYPE_STREAMING,365"Buffers may only be queued for streaming sources." ) )366return false;367368//ByteBuffer byteBuffer = ByteBuffer.wrap( buffer, 0, buffer.length );369ByteBuffer byteBuffer = (ByteBuffer) BufferUtils.createByteBuffer(370buffer.length ).put( buffer ).flip();371372IntBuffer intBuffer = BufferUtils.createIntBuffer( 1 );373374AL10.alSourceUnqueueBuffers( ALSource.get( 0 ), intBuffer );375if( checkALError() )376return false;377378if( AL10.alIsBuffer( intBuffer.get( 0 ) ) )379millisPreviouslyPlayed += millisInBuffer( intBuffer.get( 0 ) );380checkALError();381382AL10.alBufferData( intBuffer.get(0), ALformat, byteBuffer, sampleRate );383if( checkALError() )384return false;385386AL10.alSourceQueueBuffers( ALSource.get( 0 ), intBuffer );387if( checkALError() )388return false;389390return true;391}392393/**394* Feeds raw data to the stream.395* @param buffer Buffer containing raw audio data to stream.396* @return Number of prior buffers that have been processed., or -1 if error.397*/398@Override399public int feedRawAudioData( byte[] buffer )400{401// Stream buffers can only be queued for streaming sources:402if( errorCheck( channelType != SoundSystemConfig.TYPE_STREAMING,403"Raw audio data can only be fed to streaming sources." ) )404return -1;405406//ByteBuffer byteBuffer = ByteBuffer.wrap( buffer, 0, buffer.length );407ByteBuffer byteBuffer = (ByteBuffer) BufferUtils.createByteBuffer(408buffer.length ).put( buffer ).flip();409410IntBuffer intBuffer;411412// Clear out any previously queued buffers:413int processed = AL10.alGetSourcei( ALSource.get( 0 ),414AL10.AL_BUFFERS_PROCESSED );415if( processed > 0 )416{417intBuffer = BufferUtils.createIntBuffer( processed );418AL10.alGenBuffers( intBuffer );419if( errorCheck( checkALError(),420"Error clearing stream buffers in method 'feedRawAudioData'" ) )421return -1;422AL10.alSourceUnqueueBuffers( ALSource.get( 0 ), intBuffer );423if( errorCheck( checkALError(),424"Error unqueuing stream buffers in method 'feedRawAudioData'" ) )425return -1;426int i;427intBuffer.rewind();428while( intBuffer.hasRemaining() )429{430i = intBuffer.get();431if( AL10.alIsBuffer( i ) )432{433millisPreviouslyPlayed += millisInBuffer( i );434}435checkALError();436}437AL10.alDeleteBuffers( intBuffer );438checkALError();439}440intBuffer = BufferUtils.createIntBuffer( 1 );441AL10.alGenBuffers( intBuffer );442if( errorCheck( checkALError(),443"Error generating stream buffers in method 'preLoadBuffers'" ) )444return -1;445446AL10.alBufferData( intBuffer.get(0), ALformat, byteBuffer, sampleRate );447if( checkALError() )448return -1;449450AL10.alSourceQueueBuffers( ALSource.get( 0 ), intBuffer );451if( checkALError() )452return -1;453454if( attachedSource != null && attachedSource.channel == this &&455attachedSource.active() )456{457// restart the channel if it was previously playing:458if( !playing() )459{460AL10.alSourcePlay( ALSource.get( 0 ) );461checkALError();462}463}464465return processed;466}467468/**469* Returns the number of milliseconds of audio contained in specified buffer.470* @return milliseconds, or 0 if unable to calculate.471*/472public float millisInBuffer( int alBufferi )473{474return( ( (float) AL10.alGetBufferi( alBufferi, AL10.AL_SIZE ) /475(float) AL10.alGetBufferi( alBufferi, AL10.AL_CHANNELS ) /476( (float) AL10.alGetBufferi( alBufferi, AL10.AL_BITS ) / 8.0f ) /477(float) sampleRate ) * 1000 );478}479480/**481* Calculates the number of milliseconds since the channel began playing.482* @return Milliseconds, or -1 if unable to calculate.483*/484@Override485public float millisecondsPlayed()486{487// get number of samples played in current buffer488float offset = (float)AL10.alGetSourcei( ALSource.get( 0 ),489AL11.AL_BYTE_OFFSET );490491float bytesPerFrame = 1f;492switch( ALformat )493{494case AL10.AL_FORMAT_MONO8 :495bytesPerFrame = 1f;496break;497case AL10.AL_FORMAT_MONO16 :498bytesPerFrame = 2f;499break;500case AL10.AL_FORMAT_STEREO8 :501bytesPerFrame = 2f;502break;503case AL10.AL_FORMAT_STEREO16 :504bytesPerFrame = 4f;505break;506default :507break;508}509510offset = ( ( (float) offset / bytesPerFrame ) / (float) sampleRate )511* 1000;512513// add the milliseconds from stream-buffers that played previously514if( channelType == SoundSystemConfig.TYPE_STREAMING )515offset += millisPreviouslyPlayed;516517// Return millis played:518return( offset );519}520521/**522* Returns the number of queued byte[] buffers that have finished playing.523* @return Number of buffers processed.524*/525@Override526public int buffersProcessed()527{528// Only streaming sources process buffers:529if( channelType != SoundSystemConfig.TYPE_STREAMING )530return 0;531532// determine how many have been processed:533int processed = AL10.alGetSourcei( ALSource.get( 0 ),534AL10.AL_BUFFERS_PROCESSED );535536// Check for errors:537if( checkALError() )538return 0;539540// Return how many were processed:541return processed;542}543544/**545* Dequeues all previously queued data.546*/547@Override548public void flush()549{550// Only a streaming source can be flushed, because only streaming551// sources have queued buffers:552if( channelType != SoundSystemConfig.TYPE_STREAMING )553return;554555// determine how many buffers have been queued:556int queued = AL10.alGetSourcei( ALSource.get( 0 ),557AL10.AL_BUFFERS_QUEUED );558// Check for errors:559if( checkALError() )560return;561562IntBuffer intBuffer = BufferUtils.createIntBuffer( 1 );563while( queued > 0 )564{565try566{567AL10.alSourceUnqueueBuffers( ALSource.get( 0 ), intBuffer );568}569catch( Exception e )570{571return;572}573if( checkALError() )574return;575queued--;576}577millisPreviouslyPlayed = 0;578}579580/**581* Stops the channel, dequeues any queued data, and closes the channel.582*/583@Override584public void close()585{586try587{588AL10.alSourceStop( ALSource.get( 0 ) );589AL10.alGetError();590}591catch( Exception e )592{}593594if( channelType == SoundSystemConfig.TYPE_STREAMING )595flush();596}597598/**599* Plays the currently attached normal source, opens this channel up for600* streaming, or resumes playback if this channel was paused.601*/602@Override603public void play()604{605AL10.alSourcePlay( ALSource.get( 0 ) );606checkALError();607}608609/**610* Temporarily stops playback for this channel.611*/612@Override613public void pause()614{615AL10.alSourcePause( ALSource.get( 0 ) );616checkALError();617}618619/**620* Stops playback for this channel and rewinds the attached source to the621* beginning.622*/623@Override624public void stop()625{626AL10.alSourceStop( ALSource.get( 0 ) );627if( !checkALError() )628millisPreviouslyPlayed = 0;629}630631/**632* Rewinds the attached source to the beginning. Stops the source if it was633* paused.634*/635@Override636public void rewind()637{638// rewinding for streaming sources is handled elsewhere639if( channelType == SoundSystemConfig.TYPE_STREAMING )640return;641642AL10.alSourceRewind( ALSource.get( 0 ) );643if( !checkALError() )644millisPreviouslyPlayed = 0;645}646647648/**649* Used to determine if a channel is actively playing a source. This method650* will return false if the channel is paused or stopped and when no data is651* queued to be streamed.652* @return True if this channel is playing a source.653*/654@Override655public boolean playing()656{657int state = AL10.alGetSourcei( ALSource.get( 0 ),658AL10.AL_SOURCE_STATE );659if( checkALError() )660return false;661662return( state == AL10.AL_PLAYING );663}664665/**666* Checks for OpenAL errors, and prints a message if there is an error.667* @return True if there was an error, False if not.668*/669private boolean checkALError()670{671switch( AL10.alGetError() )672{673case AL10.AL_NO_ERROR:674return false;675case AL10.AL_INVALID_NAME:676errorMessage( "Invalid name parameter." );677return true;678case AL10.AL_INVALID_ENUM:679errorMessage( "Invalid parameter." );680return true;681case AL10.AL_INVALID_VALUE:682errorMessage( "Invalid enumerated parameter value." );683return true;684case AL10.AL_INVALID_OPERATION:685errorMessage( "Illegal call." );686return true;687case AL10.AL_OUT_OF_MEMORY:688errorMessage( "Unable to allocate memory." );689return true;690default:691errorMessage( "An unrecognized error occurred." );692return true;693}694}695}696697698