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