package paulscode.sound.libraries;
import java.util.LinkedList;
import javax.sound.sampled.AudioFormat;
import paulscode.sound.Channel;
import paulscode.sound.FilenameURL;
import paulscode.sound.ListenerData;
import paulscode.sound.SoundBuffer;
import paulscode.sound.Source;
import paulscode.sound.SoundSystemConfig;
import paulscode.sound.Vector3D;
/**
* The SourceJavaSound class provides an interface to the JavaSound library.
* For more information about the Java Sound API, please visit
* http://java.sun.com/products/java-media/sound/
*<br><br>
*<b><i> SoundSystem LibraryJavaSound License:</b></i><br><b><br>
* 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 may not falsely claim to be the author of this library or any
* unmodified portion of it.
*<br>
* 2) You may not copyright this library or a modified version of it and then
* sue me for copyright infringement.
*<br>
* 3) 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>
* 4) 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>
* 5) 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>
* 6) 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 SourceJavaSound extends Source
{
/**
* The source's basic Channel type-cast to a ChannelJavaSound.
*/
protected ChannelJavaSound channelJavaSound = (ChannelJavaSound) channel;
/**
* Handle to the listener information.
*/
public ListenerData listener;
/**
* Panning between left and right speaker (float between -1.0 and 1.0).
*/
private float pan = 0.0f;
/**
* Constructor: Creates a new source using the specified parameters.
* @param listener Handle to information about the listener.
* @param priority Setting this to true will prevent other sounds from overriding this one.
* @param toStream Setting this to true will create a streaming source.
* @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 soundBuffer Sound buffer to use if creating a new normal 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 'att'.
* @param temporary Whether or not to remove this source after it finishes playing.
*/
public SourceJavaSound( ListenerData listener, boolean priority,
boolean toStream, boolean toLoop, String sourcename,
FilenameURL filenameURL, SoundBuffer soundBuffer,
float x, float y, float z, int attModel,
float distOrRoll, boolean temporary )
{
super( priority, toStream, toLoop, sourcename, filenameURL, soundBuffer,
x, y, z, attModel, distOrRoll, temporary );
libraryType = LibraryJavaSound.class;
// point handle to the listener information:
this.listener = listener;
positionChanged();
}
/**
* Constructor: Creates a new source matching the specified source.
* @param listener Handle to information about the listener.
* @param old Source to copy information from.
* @param soundBuffer Sound buffer to use if creating a new normal source.
*/
public SourceJavaSound( ListenerData listener, Source old,
SoundBuffer soundBuffer )
{
super( old, soundBuffer );
libraryType = LibraryJavaSound.class;
// point handle to the listener information:
this.listener = listener;
positionChanged();
}
/**
* Constructor: Creates a new streaming source that will be directly fed with
* raw audio data.
* @param listener Handle to information about the listener.
* @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 'att'.
*/
public SourceJavaSound( ListenerData listener, AudioFormat audioFormat,
boolean priority, String sourcename, float x,
float y, float z, int attModel, float distOrRoll )
{
super( audioFormat, priority, sourcename, x, y, z, attModel,
distOrRoll );
libraryType = LibraryJavaSound.class;
// point handle to the listener information:
this.listener = listener;
positionChanged();
}
/**
* Shuts the source down and removes references to all instantiated objects.
*/
@Override
public void cleanup()
{
super.cleanup();
}
/**
* Changes the peripheral information about the source using the specified
* parameters.
* @param priority Setting this to true will prevent other sounds from overriding this one.
* @param toStream Setting this to true will create a streaming source.
* @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 soundBuffer Sound buffer to use if creating a new normal 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 'att'.
* @param temporary Whether or not to remove this source after it finishes playing.
*/
@Override
public void changeSource( boolean priority, boolean toStream,
boolean toLoop, String sourcename,
FilenameURL filenameURL, SoundBuffer soundBuffer,
float x, float y, float z, int attModel,
float distOrRoll, boolean temporary )
{
super.changeSource( priority, toStream, toLoop, sourcename, filenameURL,
soundBuffer, x, y, z, attModel, distOrRoll,
temporary );
if( channelJavaSound != null )
channelJavaSound.setLooping( toLoop );
positionChanged();
}
/**
* Called every time the listener's position or orientation changes.
*/
@Override
public void listenerMoved()
{
positionChanged();
}
/**
* Sets this source'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 setVelocity( float x, float y, float z )
{
super.setVelocity( x, y, z );
positionChanged();
}
/**
* Moves the source to the specified position.
* @param x X coordinate to move to.
* @param y Y coordinate to move to.
* @param z Z coordinate to move to.
*/
@Override
public void setPosition( float x, float y, float z )
{
super.setPosition( x, y, z );
positionChanged();
}
/**
* Updates the pan and gain.
*/
@Override
public void positionChanged()
{
calculateGain();
calculatePan();
calculatePitch();
}
/**
* Manually sets this source's pitch.
* @param value A float value ( 0.5f - 2.0f ).
*/
@Override
public void setPitch( float value )
{
super.setPitch( value );
calculatePitch();
}
/**
* Sets this source's attenuation model.
* @param model Attenuation model to use.
*/
@Override
public void setAttenuation( int model )
{
super.setAttenuation( model );
calculateGain();
}
/**
* Sets this source's fade distance or rolloff factor, depending on the
* attenuation model.
* @param dr New value for fade distance or rolloff factor.
*/
@Override
public void setDistOrRoll( float dr)
{
super.setDistOrRoll( dr );
calculateGain();
}
/**
* Plays the source on the specified channel.
* @param c Channel to play on.
*/
@Override
public void play( Channel c )
{
if( !active() )
{
if( toLoop )
toPlay = true;
return;
}
if( c == null )
{
errorMessage( "Unable to play source, because channel was null" );
return;
}
boolean newChannel = (channel != c);
if( channel != null && channel.attachedSource != this )
newChannel = true;
boolean wasPaused = paused();
boolean wasStopped = stopped();
super.play( c );
channelJavaSound = (ChannelJavaSound) channel;
// Make sure the channel exists:
// check if we are already on this channel:
if( newChannel )
{
if( channelJavaSound != null )
channelJavaSound.setLooping( toLoop );
if( !toStream )
{
// This is not a streaming source, so make sure there is
// a sound buffer loaded to play:
if( soundBuffer == null )
{
errorMessage( "No sound buffer to play" );
return;
}
channelJavaSound.attachBuffer( soundBuffer );
}
}
positionChanged(); // set new pan and gain
// See if we are already playing:
if( wasStopped || !playing() )
{
if( toStream && !wasPaused )
{
preLoad = true;
}
channel.play();
}
}
/**
* Queues up the initial stream-buffers for the stream.
* @return False if the end of the stream was reached.
*/
@Override
public boolean preLoad()
{
if( codec == null )
{
return false;
}
codec.initialize( filenameURL.getURL() );
LinkedList<byte[]> preLoadBuffers = new LinkedList<byte[]>();
for( int i = 0; i < SoundSystemConfig.getNumberStreamingBuffers(); i++ )
{
soundBuffer = codec.read();
if( soundBuffer == null || soundBuffer.audioData == null )
break;
preLoadBuffers.add( soundBuffer.audioData );
}
channelJavaSound.resetStream( codec.getAudioFormat() );
positionChanged(); // set new pan and gain
channel.preLoadBuffers( preLoadBuffers );
preLoad = false;
return true;
}
/**
* Calculates the gain for this source based on its attenuation model and
* distance from the listener.
*/
public void calculateGain()
{
float distX = position.x - listener.position.x;
float distY = position.y - listener.position.y;
float distZ = position.z - listener.position.z;
distanceFromListener = (float) Math.sqrt( distX*distX + distY*distY
+ distZ*distZ );
// Calculate the source's gain using the specified attenuation model:
switch( attModel )
{
case SoundSystemConfig.ATTENUATION_LINEAR:
if( distanceFromListener <= 0 )
{
gain = 1.0f;
}
else if( distanceFromListener >= distOrRoll )
{
gain = 0.0f;
}
else
{
gain = 1.0f - ( distanceFromListener / distOrRoll );
}
break;
case SoundSystemConfig.ATTENUATION_ROLLOFF:
if( distanceFromListener <= 0 )
{
gain = 1.0f;
}
else
{
float tweakFactor = 0.0005f;
float attenuationFactor = distOrRoll * distanceFromListener
* distanceFromListener
* tweakFactor;
// Make sure we don't do a division by zero:
// (rolloff should NEVER be negative)
if( attenuationFactor < 0 )
attenuationFactor = 0;
gain = 1.0f / ( 1 + attenuationFactor );
}
break;
default:
gain = 1.0f;
break;
}
// make sure gain is between 0 and 1:
if( gain > 1.0f )
gain = 1.0f;
if( gain < 0.0f )
gain = 0.0f;
gain *= sourceVolume * SoundSystemConfig.getMasterGain()
* (float) Math.abs( fadeOutGain ) * fadeInGain;
// update the channel's gain:
if( channel != null && channel.attachedSource == this &&
channelJavaSound != null )
channelJavaSound.setGain( gain );
}
/**
* Calculates the panning for this source based on its position in relation to
* the listener.
*/
public void calculatePan()
{
Vector3D side = listener.up.cross( listener.lookAt );
side.normalize();
float x = position.dot( position.subtract( listener.position ), side );
float z = position.dot( position.subtract( listener.position ),
listener.lookAt );
side = null;
float angle = (float) Math.atan2( x, z );
pan = (float) - Math.sin( angle );
if( channel != null && channel.attachedSource == this &&
channelJavaSound != null )
{
if( attModel == SoundSystemConfig.ATTENUATION_NONE )
channelJavaSound.setPan( 0 );
else
channelJavaSound.setPan( pan );
}
}
/**
* Calculates the pitch for this source based on its position in relation to
* the listener.
*/
public void calculatePitch()
{
if( channel != null && channel.attachedSource == this &&
channelJavaSound != null )
{
// If not using Doppler effect, save some calculations:
if( SoundSystemConfig.getDopplerFactor() == 0 )
{
channelJavaSound.setPitch( pitch );
}
else
{
float SS = 343.3f;
Vector3D SV = velocity;
Vector3D LV = listener.velocity;
float DV = SoundSystemConfig.getDopplerVelocity();
float DF = SoundSystemConfig.getDopplerFactor();
Vector3D SL = listener.position.subtract( position );
float vls = SL.dot( LV ) / SL.length();
float vss = SL.dot( SV ) / SL.length();
vss = min( vss, SS / DF );
vls = min( vls, SS / DF );
float newPitch = pitch * ( SS * DV - DF * vls ) /
(SS * DV - DF * vss );
if( newPitch < 0.5f )
newPitch = 0.5f;
else if( newPitch > 2.0f )
newPitch = 2.0f;
channelJavaSound.setPitch( newPitch );
}
}
}
public float min( float a, float b )
{
if( a < b )
return a;
return b;
}
}