package com.paulscode.sound.libraries;
import java.util.LinkedList;
import javax.sound.sampled.AudioFormat;
import com.paulscode.sound.Channel;
import com.paulscode.sound.FilenameURL;
import com.paulscode.sound.ListenerData;
import com.paulscode.sound.SoundBuffer;
import com.paulscode.sound.Source;
import com.paulscode.sound.SoundSystemConfig;
import org.lwjgl.util.vector.Vector3f;
/**
* 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;
}
boolean noNextBuffers = false;
synchronized (soundSequenceLock) {
if (nextBuffers == null || nextBuffers.isEmpty())
noNextBuffers = true;
}
LinkedList<byte[]> preLoadBuffers = new LinkedList<byte[]>();
if (nextCodec != null && !noNextBuffers) {
codec = nextCodec;
nextCodec = null;
synchronized (soundSequenceLock) {
while (!nextBuffers.isEmpty()) {
soundBuffer = nextBuffers.remove(0);
if (soundBuffer != null && soundBuffer.audioData != null)
preLoadBuffers.add(soundBuffer.audioData);
}
}
} else {
codec.initialize(filenameURL.getURL());
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();
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() {
Vector3f side = Vector3f.cross(listener.up, listener.lookAt, null);
// Vector3f side = listener.up.cross(listener.lookAt);
side.normalise();
float x = Vector3f.dot(Vector3f.sub(position, listener.position, null),
side);
float z = Vector3f.dot(Vector3f.sub(position, listener.position, null),
listener.lookAt);
// 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;
Vector3f SV = velocity;
Vector3f LV = listener.velocity;
float DV = SoundSystemConfig.getDopplerVelocity();
float DF = SoundSystemConfig.getDopplerFactor();
// Vector3f SL = listener.position.subtract(position);
Vector3f SL = Vector3f.sub(listener.position, position, null);
float vls = Vector3f.dot(SL, LV);
float vss = Vector3f.dot(SL, SV);
// 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;
}
}