package paulscode.sound.libraries; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.util.LinkedList; import javax.sound.sampled.AudioFormat; // From the lwjgl library, http://www.lwjgl.org import org.lwjgl.BufferUtils; import org.lwjgl.openal.AL10; import org.lwjgl.openal.AL11; import paulscode.sound.Channel; import paulscode.sound.SoundSystemConfig; /** * The ChannelLWJGLOpenAL class is used to reserve a sound-card voice using the * lwjgl binding of OpenAL. Channels can be either normal or streaming * channels. *<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 ChannelLWJGLOpenAL extends Channel { /** * OpenAL's IntBuffer identifier for this channel. */ public IntBuffer ALSource; /** * OpenAL data format to use when playing back the assigned source. */ public int ALformat; // OpenAL data format /** * Sample rate (speed) to use for play-back. */ public int sampleRate; // sample rate /** * Miliseconds of buffers previously played (streaming sources). */ public float millisPreviouslyPlayed = 0; /** * Constructor: takes channelType identifier and a handle to the OpenAL * IntBuffer identifier to use for this channel. Possible values for channel * type can be found in the * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} class. * @param type Type of channel (normal or streaming). * @param src Handle to the OpenAL source identifier. */ public ChannelLWJGLOpenAL( int type, IntBuffer src ) { super( type ); libraryType = LibraryLWJGLOpenAL.class; ALSource = src; } /** * Empties the streamBuffers list, stops and deletes the ALSource, shuts the * channel down, and removes references to all instantiated objects. */ @Override public void cleanup() { if( ALSource != null ) { try { // Stop playing the source: AL10.alSourceStop( ALSource ); AL10.alGetError(); } catch( Exception e ) {} try { // Delete the source: AL10.alDeleteSources( ALSource ); AL10.alGetError(); } catch( Exception e ) {} ALSource.clear(); } ALSource = null; super.cleanup(); } /** * Attaches an OpenAL sound-buffer identifier for the sound data to be played * back for a normal source. * @param buf Intbuffer identifier for the sound data to play. * @return False if an error occurred. */ public boolean attachBuffer( IntBuffer buf ) { // A sound buffer can only be attached to a normal source: if( errorCheck( channelType != SoundSystemConfig.TYPE_NORMAL, "Sound buffers may only be attached to normal " + "sources." ) ) return false; // send the sound buffer to the channel: AL10.alSourcei( ALSource.get( 0 ), AL10.AL_BUFFER, buf.get(0) ); // save the format for later, for determining milliseconds played if( attachedSource != null && attachedSource.soundBuffer != null && attachedSource.soundBuffer.audioFormat != null ) setAudioFormat( attachedSource.soundBuffer.audioFormat ); // Check for errors and return: return checkALError(); } /** * Sets the channel up to receive the specified audio format. * @param audioFormat Format to use when playing the stream data. */ @Override public void setAudioFormat( AudioFormat 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 " + "'setAudioFormat'" ); return; } } 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 " + "'setAudioFormat'" ); return; } } else { errorMessage( "Audio data neither mono nor stereo in " + "method 'setAudioFormat'" ); return; } ALformat = soundFormat; sampleRate = (int) audioFormat.getSampleRate(); } /** * Sets the channel up to receive the specified OpenAL audio format and sample * rate. * @param format Format to use. * @param rate Sample rate (speed) to use. */ public void setFormat( int format, int rate ) { ALformat = format; sampleRate = rate; } /** * Queues up the initial byte[] buffers of data to be streamed. * @param bufferList List of the first buffers to be played for a streaming source. * @return False if problem occurred or if end of stream was reached. */ @Override public boolean preLoadBuffers( LinkedList<byte[]> bufferList ) { // Stream buffers can only be queued for streaming sources: if( errorCheck( channelType != SoundSystemConfig.TYPE_STREAMING, "Buffers may only be queued for streaming sources." ) ) return false; if( errorCheck( bufferList == null, "Buffer List null in method 'preLoadBuffers'" ) ) return false; IntBuffer streamBuffers; // Remember if the channel was playing: boolean playing = playing(); // stop the channel if it is playing: if( playing ) { AL10.alSourceStop( ALSource.get( 0 ) ); checkALError(); } // Clear out any previously queued buffers: int processed = AL10.alGetSourcei( ALSource.get( 0 ), AL10.AL_BUFFERS_PROCESSED ); if( processed > 0 ) { streamBuffers = BufferUtils.createIntBuffer( processed ); AL10.alGenBuffers( streamBuffers ); if( errorCheck( checkALError(), "Error clearing stream buffers in method 'preLoadBuffers'" ) ) return false; AL10.alSourceUnqueueBuffers( ALSource.get( 0 ), streamBuffers ); if( errorCheck( checkALError(), "Error unqueuing stream buffers in method 'preLoadBuffers'" ) ) return false; } // restart the channel if it was previously playing: if( playing ) { AL10.alSourcePlay( ALSource.get( 0 ) ); checkALError(); } streamBuffers = BufferUtils.createIntBuffer( bufferList.size() ); AL10.alGenBuffers( streamBuffers ); if( errorCheck( checkALError(), "Error generating stream buffers in method 'preLoadBuffers'" ) ) return false; ByteBuffer byteBuffer = null; for( int i = 0; i < bufferList.size(); i++ ) { //byteBuffer = ByteBuffer.wrap( bufferList.get(i), 0, // bufferList.get(i).length ); byteBuffer = (ByteBuffer) BufferUtils.createByteBuffer( bufferList.get(i).length ).put( bufferList.get( i ) ).flip(); try { AL10.alBufferData( streamBuffers.get(i), ALformat, byteBuffer, sampleRate ); } catch( Exception e ) { errorMessage( "Error creating buffers in method " + "'preLoadBuffers'" ); printStackTrace( e ); return false; } if( errorCheck( checkALError(), "Error creating buffers in method 'preLoadBuffers'" ) ) return false; } try { AL10.alSourceQueueBuffers( ALSource.get( 0 ), streamBuffers ); } catch( Exception e ) { errorMessage( "Error queuing buffers in method 'preLoadBuffers'" ); printStackTrace( e ); return false; } if( errorCheck( checkALError(), "Error queuing buffers in method 'preLoadBuffers'" ) ) return false; AL10.alSourcePlay( ALSource.get( 0 ) ); if( errorCheck( checkALError(), "Error playing source in method 'preLoadBuffers'" ) ) return false; // Success: return true; } /** * Queues up a byte[] buffer of data to be streamed. * @param buffer The next buffer to be played for a streaming source. * @return False if an error occurred or if the channel is shutting down. */ @Override public boolean queueBuffer( byte[] buffer ) { // Stream buffers can only be queued for streaming sources: if( errorCheck( channelType != SoundSystemConfig.TYPE_STREAMING, "Buffers may only be queued for streaming sources." ) ) return false; //ByteBuffer byteBuffer = ByteBuffer.wrap( buffer, 0, buffer.length ); ByteBuffer byteBuffer = (ByteBuffer) BufferUtils.createByteBuffer( buffer.length ).put( buffer ).flip(); IntBuffer intBuffer = BufferUtils.createIntBuffer( 1 ); AL10.alSourceUnqueueBuffers( ALSource.get( 0 ), intBuffer ); if( checkALError() ) return false; if( AL10.alIsBuffer( intBuffer.get( 0 ) ) ) millisPreviouslyPlayed += millisInBuffer( intBuffer.get( 0 ) ); checkALError(); AL10.alBufferData( intBuffer.get(0), ALformat, byteBuffer, sampleRate ); if( checkALError() ) return false; AL10.alSourceQueueBuffers( ALSource.get( 0 ), intBuffer ); if( checkALError() ) return false; return true; } /** * Feeds raw data to the stream. * @param buffer Buffer containing raw audio data to stream. * @return Number of prior buffers that have been processed., or -1 if error. */ @Override public int feedRawAudioData( byte[] buffer ) { // Stream buffers can only be queued for streaming sources: if( errorCheck( channelType != SoundSystemConfig.TYPE_STREAMING, "Raw audio data can only be fed to streaming sources." ) ) return -1; //ByteBuffer byteBuffer = ByteBuffer.wrap( buffer, 0, buffer.length ); ByteBuffer byteBuffer = (ByteBuffer) BufferUtils.createByteBuffer( buffer.length ).put( buffer ).flip(); IntBuffer intBuffer; // Clear out any previously queued buffers: int processed = AL10.alGetSourcei( ALSource.get( 0 ), AL10.AL_BUFFERS_PROCESSED ); if( processed > 0 ) { intBuffer = BufferUtils.createIntBuffer( processed ); AL10.alGenBuffers( intBuffer ); if( errorCheck( checkALError(), "Error clearing stream buffers in method 'feedRawAudioData'" ) ) return -1; AL10.alSourceUnqueueBuffers( ALSource.get( 0 ), intBuffer ); if( errorCheck( checkALError(), "Error unqueuing stream buffers in method 'feedRawAudioData'" ) ) return -1; if( AL10.alIsBuffer( intBuffer.get( 0 ) ) ) millisPreviouslyPlayed += millisInBuffer( intBuffer.get( 0 ) ); checkALError(); } else { intBuffer = BufferUtils.createIntBuffer( 1 ); AL10.alGenBuffers( intBuffer ); if( errorCheck( checkALError(), "Error generating stream buffers in method 'preLoadBuffers'" ) ) return -1; } AL10.alBufferData( intBuffer.get(0), ALformat, byteBuffer, sampleRate ); if( checkALError() ) return -1; AL10.alSourceQueueBuffers( ALSource.get( 0 ), intBuffer ); if( checkALError() ) return -1; if( attachedSource != null && attachedSource.channel == this && attachedSource.active() ) { // restart the channel if it was previously playing: if( !playing() ) { AL10.alSourcePlay( ALSource.get( 0 ) ); checkALError(); } } return processed; } /** * Returns the number of milliseconds of audio contained in specified buffer. * @return milliseconds, or 0 if unable to calculate. */ public float millisInBuffer( int alBufferi ) { return( ( (float) AL10.alGetBufferi( alBufferi, AL10.AL_SIZE ) / (float) AL10.alGetBufferi( alBufferi, AL10.AL_CHANNELS ) / ( (float) AL10.alGetBufferi( alBufferi, AL10.AL_BITS ) / 8.0f ) / (float) sampleRate ) * 1000 ); } /** * Calculates the number of milliseconds since the channel began playing. * @return Milliseconds, or -1 if unable to calculate. */ @Override public float millisecondsPlayed() { // get number of samples played in current buffer float offset = (float)AL10.alGetSourcei( ALSource.get( 0 ), AL11.AL_BYTE_OFFSET ); float bytesPerFrame = 1f; switch( ALformat ) { case AL10.AL_FORMAT_MONO8 : bytesPerFrame = 1f; break; case AL10.AL_FORMAT_MONO16 : bytesPerFrame = 2f; break; case AL10.AL_FORMAT_STEREO8 : bytesPerFrame = 2f; break; case AL10.AL_FORMAT_STEREO16 : bytesPerFrame = 4f; break; default : break; } offset = ( ( (float) offset / bytesPerFrame ) / (float) sampleRate ) * 1000; // add the milliseconds from stream-buffers that played previously if( channelType == SoundSystemConfig.TYPE_STREAMING ) offset += millisPreviouslyPlayed; // Return millis played: return( offset ); } /** * Returns the number of queued byte[] buffers that have finished playing. * @return Number of buffers processed. */ @Override public int buffersProcessed() { // Only streaming sources process buffers: if( channelType != SoundSystemConfig.TYPE_STREAMING ) return 0; // determine how many have been processed: int processed = AL10.alGetSourcei( ALSource.get( 0 ), AL10.AL_BUFFERS_PROCESSED ); // Check for errors: if( checkALError() ) return 0; // Return how many were processed: return processed; } /** * Dequeues all previously queued data. */ @Override public void flush() { // Only a streaming source can be flushed, because only streaming // sources have queued buffers: if( channelType != SoundSystemConfig.TYPE_STREAMING ) return; // determine how many buffers have been queued: int queued = AL10.alGetSourcei( ALSource.get( 0 ), AL10.AL_BUFFERS_QUEUED ); // Check for errors: if( checkALError() ) return; IntBuffer intBuffer = BufferUtils.createIntBuffer( 1 ); while( queued > 0 ) { try { AL10.alSourceUnqueueBuffers( ALSource.get( 0 ), intBuffer ); } catch( Exception e ) { return; } if( checkALError() ) return; queued--; } millisPreviouslyPlayed = 0; } /** * Stops the channel, dequeues any queued data, and closes the channel. */ @Override public void close() { try { AL10.alSourceStop( ALSource.get( 0 ) ); AL10.alGetError(); } catch( Exception e ) {} if( channelType == SoundSystemConfig.TYPE_STREAMING ) flush(); } /** * Plays the currently attached normal source, opens this channel up for * streaming, or resumes playback if this channel was paused. */ @Override public void play() { AL10.alSourcePlay( ALSource.get( 0 ) ); checkALError(); } /** * Temporarily stops playback for this channel. */ @Override public void pause() { AL10.alSourcePause( ALSource.get( 0 ) ); checkALError(); } /** * Stops playback for this channel and rewinds the attached source to the * beginning. */ @Override public void stop() { AL10.alSourceStop( ALSource.get( 0 ) ); if( !checkALError() ) millisPreviouslyPlayed = 0; } /** * Rewinds the attached source to the beginning. Stops the source if it was * paused. */ @Override public void rewind() { // rewinding for streaming sources is handled elsewhere if( channelType == SoundSystemConfig.TYPE_STREAMING ) return; AL10.alSourceRewind( ALSource.get( 0 ) ); if( !checkALError() ) millisPreviouslyPlayed = 0; } /** * Used to determine if a channel is actively playing a source. This method * will return false if the channel is paused or stopped and when no data is * queued to be streamed. * @return True if this channel is playing a source. */ @Override public boolean playing() { int state = AL10.alGetSourcei( ALSource.get( 0 ), AL10.AL_SOURCE_STATE ); if( checkALError() ) return false; return( state == AL10.AL_PLAYING ); } /** * 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; } } }