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;
}
}
}