summaryrefslogtreecommitdiffstats
path: root/src/classes/share/com/sun/j3d/audioengines
diff options
context:
space:
mode:
Diffstat (limited to 'src/classes/share/com/sun/j3d/audioengines')
-rw-r--r--src/classes/share/com/sun/j3d/audioengines/AudioEngine.java208
-rw-r--r--src/classes/share/com/sun/j3d/audioengines/AudioEngine3D.java497
-rw-r--r--src/classes/share/com/sun/j3d/audioengines/AudioEngine3DL2.java308
-rw-r--r--src/classes/share/com/sun/j3d/audioengines/AudioEngineThread.java275
-rw-r--r--src/classes/share/com/sun/j3d/audioengines/AuralParameters.java197
-rw-r--r--src/classes/share/com/sun/j3d/audioengines/Sample.java479
-rwxr-xr-xsrc/classes/share/com/sun/j3d/audioengines/javasound/JSAuralParameters.java79
-rw-r--r--src/classes/share/com/sun/j3d/audioengines/javasound/JSChannel.java435
-rwxr-xr-xsrc/classes/share/com/sun/j3d/audioengines/javasound/JSClip.java349
-rwxr-xr-xsrc/classes/share/com/sun/j3d/audioengines/javasound/JSDirectionalSample.java738
-rwxr-xr-xsrc/classes/share/com/sun/j3d/audioengines/javasound/JSMidi.java67
-rwxr-xr-xsrc/classes/share/com/sun/j3d/audioengines/javasound/JSPositionalSample.java1339
-rwxr-xr-xsrc/classes/share/com/sun/j3d/audioengines/javasound/JSSample.java362
-rwxr-xr-xsrc/classes/share/com/sun/j3d/audioengines/javasound/JSStream.java67
-rwxr-xr-xsrc/classes/share/com/sun/j3d/audioengines/javasound/JSThread.java855
-rwxr-xr-xsrc/classes/share/com/sun/j3d/audioengines/javasound/JavaSoundMixer.java959
16 files changed, 7214 insertions, 0 deletions
diff --git a/src/classes/share/com/sun/j3d/audioengines/AudioEngine.java b/src/classes/share/com/sun/j3d/audioengines/AudioEngine.java
new file mode 100644
index 0000000..8d4d36f
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/AudioEngine.java
@@ -0,0 +1,208 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+package com.sun.j3d.audioengines;
+
+import javax.media.j3d.*;
+
+/**
+ * The AudioEngine Class defines an audio output device that generates
+ * sound 'image' from scene graph.
+ * An AudioEngine object encapsulates the AudioDevice's basic information.
+ *
+ * <p>
+ * NOTE: AudioEngine developers should not subclass this class directly.
+ * Subclass AudioEngine3DL2 instead.
+ */
+public class AudioEngine implements AudioDevice {
+
+ /*
+ * This device's UNIX file descriptor
+ */
+ int fileDescriptor;
+
+ /*
+ * Type of audio output device J3D sound is played over:
+ * HEADPHONE, MONO_SPEAKER, STEREO_SPEAKERS
+ */
+ int audioPlaybackType = HEADPHONES;
+
+ /*
+ * Distance from center ear (midpoint between ears) to physical speaker.
+ * Default reflects distance for headphones.
+ * For two speakers it is assumed that the speakers are the same
+ * distance from the listener and that
+ */
+ float distanceToSpeaker = 0.0f;
+
+ /*
+ * Angle between the vector from center ear parallel to head coordiate
+ * Z axis and the vector from the center ear to the speaker.
+ * For two speakers it is assumed that the speakers are placed at the
+ * same angular offset from the listener.
+ */
+ float angleOffsetToSpeaker = 0.0f;
+
+ /*
+ * Channels currently available
+ */
+ int channelsAvailable = 8;
+
+ /*
+ * Total number of Channels ever available
+ */
+ int totalChannels = 8;
+
+ /**
+ * Construct a new AudioEngine with the specified P.E.
+ * @param physicalEnvironment the physical environment object where we
+ * want access to this device.
+ */
+ public AudioEngine(PhysicalEnvironment physicalEnvironment ) {
+ physicalEnvironment.setAudioDevice(this);
+ }
+
+ /**
+ * Code to initialize the device
+ * @return flag: true is initialized sucessfully, false if error
+ */
+ public boolean initialize() {
+ // Expected to be over-ridden by extending class
+ return true;
+ }
+
+ /**
+ * Code to close the device
+ * @return flag: true is closed sucessfully, false if error
+ */
+ public boolean close() {
+ // Expected to be over-ridden by extending class
+ return true;
+ }
+
+ /*
+ * Audio Playback Methods
+ */
+ /**
+ * Set Type of Audio Playback physical transducer(s) sound is output to.
+ * Valid types are HEADPHONE, MONO_SPEAKER, STEREO_SPEAKERS
+ * @param type of audio output device
+ */
+ public void setAudioPlaybackType(int type) {
+ audioPlaybackType = type;
+ }
+
+ /**
+ * Get Type of Audio Playback Output Device
+ * returns audio playback type to which sound is currently output
+ */
+ public int getAudioPlaybackType() {
+ return audioPlaybackType;
+ }
+
+ /**
+ * Set Distance from the Center Ear to a Speaker
+ * @param distance from the center ear and to the speaker
+ */
+ public void setCenterEarToSpeaker(float distance) {
+ distanceToSpeaker = distance;
+ }
+
+ /**
+ * Get Distance from Ear to Speaker
+ * returns value set as distance from listener's ear to speaker
+ */
+ public float getCenterEarToSpeaker() {
+ return distanceToSpeaker;
+ }
+
+ /**
+ * Set Angle Offset To Speaker
+ * @param angle in radian between head coordinate Z axis and vector to speaker */
+ public void setAngleOffsetToSpeaker(float angle) {
+ angleOffsetToSpeaker = angle;
+ }
+
+ /**
+ * Get Angle Offset To Speaker
+ * returns value set as angle between vector to speaker and Z head axis
+ */
+ public float getAngleOffsetToSpeaker() {
+ return angleOffsetToSpeaker;
+ }
+
+ /**
+ * Query total number of channels available for sound rendering
+ * for this audio device.
+ * returns number of maximum sound channels you can run with this
+ * library/device-driver.
+ */
+ public int getTotalChannels() {
+ // this method should be overridden by a device specific implementation
+ return (totalChannels);
+ }
+
+ /**
+ * Query number of channels currently available for use by the
+ * returns number of sound channels currently available (number
+ * not being used by active sounds.
+ */
+ public int getChannelsAvailable() {
+ return (channelsAvailable);
+ }
+
+ /**
+ * Query number of channels that would be used to render a particular
+ * sound node.
+ * @param sound refenence to sound node that query to be performed on
+ * returns number of sound channels used by a specific Sound node
+ * @deprecated This method is now part of the Sound class
+ */
+ public int getChannelsUsedForSound(Sound sound) {
+ if (sound != null)
+ return sound.getNumberOfChannelsUsed();
+ else
+ return -1;
+ }
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/AudioEngine3D.java b/src/classes/share/com/sun/j3d/audioengines/AudioEngine3D.java
new file mode 100644
index 0000000..2e495d5
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/AudioEngine3D.java
@@ -0,0 +1,497 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+package com.sun.j3d.audioengines;
+
+import javax.media.j3d.*;
+import javax.vecmath.*;
+import java.util.ArrayList;
+
+
+/**
+ * The AudioEngine3D Class defines an audio output device that generates
+ * sound 'image' from high-level sound parameters passed to it during
+ * scene graph.
+ *
+ * <P>
+ * The methods in this class are meant to be optionally overridden by an
+ * extended class. This extended class would provice device specific code.
+ *
+ * <P>
+ * Error checking on all parameters passed to these methods is already
+ * explicitly being done by the Java 3D core code that calls these methods.
+ *
+ * <p>
+ * NOTE: AudioEngine developers should not subclass this class directly.
+ * Subclass AudioEngine3DL2 instead.
+ */
+
+public class AudioEngine3D extends AudioEngine implements AudioDevice3D
+{
+ /*
+ * Identifiers of sample associated with sound source
+ * This array grows as the AudioDevice3D implementation requires it larger.
+ */
+ protected ArrayList samples = new ArrayList(64);
+
+ /**
+ * Current View sound is being rendered
+ */
+ protected View currentView = (View)null;
+
+ /*
+ * current Aural attribute Parameters
+ */
+ protected AuralParameters attribs = new AuralParameters();
+
+ /**
+ * Construct a new AudioEngine with the specified PhysicalEnvironment.
+ * @param physicalEnvironment the physical environment object where we
+ * want access to this device.
+ */
+ public AudioEngine3D(PhysicalEnvironment physicalEnvironment ) {
+ super(physicalEnvironment);
+ }
+
+ /*
+ *
+ * Methods that affect AudioEngine3D fields that are NOT associated
+ * with a specific sound sample
+ *
+ */
+
+ /**
+ * Save a reference to the current View object.
+ * @param reference to current view object
+ */
+ public void setView(View reference) {
+ currentView = reference;
+ return;
+ }
+ /**
+ * Get reference to the current View object.
+ * @return reference to current view object
+ */
+ public View getView() {
+ return (currentView);
+ }
+
+ /*
+ *
+ * Methods explicitly affect sound rendering and that require
+ * audio device specific methods that override this class.
+ *
+ */
+
+ /**
+ * Prepare Sound in device.
+ * Makes sound assessible to device - in this case attempts to load sound
+ * Stores sound type and data.
+ * @param soundType denotes type of sound: Background, Point or Cone
+ * @param soundData descrition of sound source data
+ * @return index into sample vector of Sample object for sound
+ */
+ public int prepareSound(int soundType, MediaContainer soundData) {
+ // This method must be overridden by device specific implementation
+ return Sample.NULL_SAMPLE;
+ }
+
+ /**
+ * Clear Sound.
+ * Removes/clears associated sound data with this sound source node
+ * @param index device specific reference number to device driver sample
+ */
+ public void clearSound(int index) {
+ // This method must be overridden by device specific implementation
+ return;
+ }
+
+ /**
+ * Set the transform for local to virtual world coordinate space
+ * @param index device specific reference number to device driver sample
+ * @param trans is a reference to virtual world composite transform
+ */
+ public void setVworldXfrm(int index, Transform3D trans) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ sample.vworldXfrm.set(trans);
+ return;
+ }
+ /**
+ * Start sample playing on audio device
+ * @param index device specific reference number to device driver sample
+ * @return status: < 0 denotes an error
+ */
+ public int startSample(int index) {
+ // This method must be overridden by device specific implementation
+ return -1; // error if not overridden
+ }
+
+ /**
+ * Stop sample playing on audio device
+ * @param index device specific reference number to device driver sample
+ * @return status: < 0 denotes an error
+ */
+ public int stopSample(int index) {
+ // This method must be overridden by device specific implementation
+ return -1; // error if not overridden
+ }
+
+ /**
+ * Update sample.
+ * Implies that some parameters affecting rendering have been modified.
+ * @param index device specific reference number to device driver sample
+ */
+ // TODO: The update method exists on a TEMPORARY basis.
+ public void updateSample(int index) {
+ // This method must be overridden by device specific implementation
+ return;
+ }
+
+ /**
+ * Mute sample.
+ * @param index device specific reference number to device driver sample
+ */
+ public void muteSample(int index) {
+ // This method must be overridden by device specific implementation
+ return;
+ }
+
+ /**
+ * Unmute sample.
+ * @param index device specific reference number to device driver sample
+ */
+ public void unmuteSample(int index) {
+ // This method must be overridden by device specific implementation
+ return;
+ }
+
+ /**
+ * Pause sample.
+ * @param index device specific reference number to device driver sample
+ */
+ public void pauseSample(int index) {
+ // This method must be overridden by device specific implementation
+ return;
+ }
+
+ /**
+ * Unpause sample.
+ * @param index device specific reference number to device driver sample
+ */
+ public void unpauseSample(int index) {
+ // This method must be overridden by device specific implementation
+ return;
+ }
+
+ /*
+ *
+ * Methods that affect fields associated with the sound sample
+ * and that may cause implicit rendering.
+ *
+ */
+ /**
+ * Set gain scale factor applied to sample.
+ * @param index device specific reference number to device driver sample
+ * @param scaleFactor floating point multiplier applied to sample amplitude
+ */
+ public void setSampleGain(int index, float scaleFactor) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ sample.setGain(scaleFactor);
+ return;
+ }
+
+ /**
+ * Set number of times sample is looped.
+ * @param index device specific reference number to device driver sample
+ * @param count number of times sample is repeated
+ */
+ public void setLoop(int index, int count) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ sample.setLoopCount(count);
+ return;
+ }
+
+ /**
+ * Set location of sample.
+ * @param index device specific reference number to device driver sample
+ * @param position point location in virtual world coordinate of sample
+ */
+ public void setPosition(int index, Point3d position) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ sample.setPosition(position);
+ return;
+ }
+
+ /* Set elliptical distance attenuation arrays applied to sample amplitude.
+ * @param index device specific reference number to device driver sample
+ * @param frontDistance defines an array of distance along the position axis
+ * thru which ellipses pass
+ * @param frontAttenuationScaleFactor gain scale factors
+ * @param backDistance defines an array of distance along the negative axis
+ * thru which ellipses pass
+ * @param backAttenuationScaleFactor gain scale factors
+ */
+ public void setDistanceGain(int index,
+ double[] frontDistance, float[] frontAttenuationScaleFactor,
+ double[] backDistance, float[] backAttenuationScaleFactor) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ sample.setDistanceGain(frontDistance, frontAttenuationScaleFactor,
+ backDistance, backAttenuationScaleFactor);
+ return;
+ }
+
+ /**
+ * Set direction vector of sample.
+ * @param index device specific reference number to device driver sample
+ * @param direction vector in virtual world coordinate.
+ */
+ public void setDirection(int index, Vector3d direction) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ sample.setDirection(direction);
+ return;
+ }
+
+ /**
+ * Set angular attenuation arrays affecting angular amplitude attenuation
+ * and angular distance filtering.
+ * @param index device specific reference number to device driver sample
+ * @param filterType denotes type of filtering (on no filtering) applied
+ * to sample.
+ * @param angle array containing angular distances from sound axis
+ * @param attenuationScaleFactor array containing gain scale factor
+ * @param filterCutoff array containing filter cutoff frequencies.
+ * The filter values for each tuples can be set to Sound.NO_FILTER.
+ */
+ public void setAngularAttenuation(int index, int filterType,
+ double[] angle, float[] attenuationScaleFactor, float[] filterCutoff) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ sample.setAngularAttenuation(filterType, angle,
+ attenuationScaleFactor, filterCutoff);
+ return;
+ }
+
+ /**
+ * Set rolloff value for current aural attribute applied to all samples.
+ * @param rolloff scale factor applied to standard speed of sound.
+ */
+ public void setRolloff(float rolloff) {
+ attribs.rolloff = rolloff;
+ return;
+ }
+
+ /**
+ * Set reverberation surface reflection coefficient value for current aural
+ * attribute applied to all samples.
+ * @param coefficient applied to amplitude of reverbation added at each
+ * iteration of reverb processing.
+ */
+ public void setReflectionCoefficient(float coefficient) {
+ attribs.reflectionCoefficient = coefficient;
+ return;
+ }
+
+ /**
+ * Set reverberation delay time for current aural attribute applied to
+ * all samples.
+ * @param reverbDelay amount of time in millisecond between each
+ * iteration of reverb processing.
+ */
+ public void setReverbDelay(float reverbDelay) {
+ attribs.reverbDelay = reverbDelay;
+ return;
+ }
+
+ /**
+ * Set reverberation order for current aural attribute applied to all
+ * samples.
+ * @param reverbOrder number of times reverb process loop is iterated.
+ */
+ public void setReverbOrder(int reverbOrder) {
+ attribs.reverbOrder = reverbOrder;
+ return;
+ }
+
+ /**
+ * Set distance filter for current aural attribute applied to all samples.
+ * @param filterType denotes type of filtering (on no filtering) applied
+ * to all sample based on distance between listener and sound.
+ * @param dist is an attenuation array of distance and low-pass filter values.
+ */
+ public void setDistanceFilter(int filterType,
+ double[] dist, float[] filterCutoff) {
+ attribs.setDistanceFilter(filterType, dist, filterCutoff);
+ return;
+ }
+
+ /**
+ * Set frequency scale factor for current aural attribute applied to all
+ * samples.
+ * @param scaleFactor frequency scale factor applied to samples normal
+ * playback rate.
+ */
+ public void setFrequencyScaleFactor(float scaleFactor) {
+ attribs.frequencyScaleFactor = scaleFactor;
+ return;
+ }
+ /**
+ * Set velocity scale factor for current aural attribute applied to all
+ * samples when Doppler is calculated.
+ * @param scaleFactor scale factor applied to postional samples'
+ * listener-to-soundSource velocity.
+ * playback rate.
+ */
+ public void setVelocityScaleFactor(float scaleFactor) {
+ attribs.velocityScaleFactor = scaleFactor;
+ return;
+ }
+
+ /**
+ * Get number of channels used by a particular sample on the audio device.
+ * @param index device specific reference number to device driver sample
+ * @return number of channels currently being used by this sample.
+ */
+ public int getNumberOfChannelsUsed(int index) {
+ // This method must be overridden by device specific implementation
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ return (sample.getNumberOfChannelsUsed());
+ else
+ return 0;
+ }
+
+ /**
+ * Get number of channels that would be used by a particular sample on
+ * the audio device given the mute flag passed in as a parameter.
+ * @param index device specific reference number to device driver sample
+ * @param muteFlag denotes the mute state to assume while executing this
+ * query. This mute value does not have to match the current mute state
+ * of the sample.
+ * @return number of channels that would be used by this sample if it
+ * were playing.
+ */
+ public int getNumberOfChannelsUsed(int index, boolean muteFlag) {
+ // This method must be overridden by device specific implementation
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ return (sample.getNumberOfChannelsUsed());
+ else
+ return 0;
+ }
+
+ /**
+ * Get length of time a sample would play if allowed to play to completion.
+ * @param index device specific reference number to device driver sample
+ * @return length of sample in milliseconds
+ */
+ public long getSampleDuration(int index) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ return (sample.getDuration());
+ else
+ return 0L;
+ }
+
+ /**
+ * Get time this sample begun playing on the audio device.
+ * @param index device specific reference number to device driver sample
+ * @return system clock time sample started
+ */
+ public long getStartTime(int index) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ return (sample.getStartTime());
+ else
+ return 0L;
+ }
+
+ /**
+ * Get reference to the array list of samples
+ * @return reference to samples list
+ * @deprecated unsafe to get reference to samples list with this method.
+ * It's better to directly reference samples list within a synchronized
+ * block which also contains calls to .getSample(index).
+ */
+ protected ArrayList getSampleList() {
+ return (samples);
+ }
+
+ public int getSampleListSize() {
+ return (samples.size());
+ }
+
+ /**
+ * Get specific sample from indexed sample list
+ * Checks for valid index before attempting to get sample from list.
+ * @param index device specific reference number to device driver sample
+ * @return reference to sample; returns null if index out of range.
+ *
+ * @since Java 3D 1.2.1
+ */
+ public Sample getSample(int index) {
+ synchronized(samples) {
+ if ((index >= 0) && (index < samples.size())) {
+ Sample sample = (Sample)samples.get(index);
+ return (sample);
+ }
+ else
+ return null;
+ }
+ }
+
+ /*
+ * Get reference to current aural attribute parameters associated with
+ * this audio device.
+ * @return reference to current aural attribute parameters
+ */
+ public AuralParameters getAuralParameters() {
+ return (attribs);
+ }
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/AudioEngine3DL2.java b/src/classes/share/com/sun/j3d/audioengines/AudioEngine3DL2.java
new file mode 100644
index 0000000..c8865c2
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/AudioEngine3DL2.java
@@ -0,0 +1,308 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+package com.sun.j3d.audioengines;
+
+import javax.media.j3d.*;
+import javax.vecmath.*;
+import java.util.ArrayList;
+
+
+/**
+ * The AudioEngine3DL2 Class defines an audio output device that generates
+ * sound 'image' from high-level sound parameters passed to it during
+ * scene graph.
+ *
+ * <P>
+ * The methods in this class are meant to be optionally overridden by an
+ * extended class. This extended class would provice device specific code.
+ *
+ * <P>
+ * Error checking on all parameters passed to these methods is already
+ * explicitly being done by the Java 3D core code that calls these methods.
+ *
+ * <P>
+ * These methods should NOT be called by any application if the audio engine
+ * is associated with a Physical Environment used by Java3D Core.
+ *
+ * @since Java 3D 1.3
+ */
+public class AudioEngine3DL2 extends AudioEngine3D implements AudioDevice3DL2 {
+ /**
+ * Construct a new AudioEngine3DL2 with the specified PhysicalEnvironment.
+ * @param physicalEnvironment the physical environment object where we
+ * want access to this device.
+ */
+ public AudioEngine3DL2(PhysicalEnvironment physicalEnvironment ) {
+ super(physicalEnvironment);
+ }
+
+ /*
+ *
+ * Methods that affect AudioEngine3DLD fields that are NOT associated
+ * with a specific sound sample
+ *
+ */
+
+ /**
+ * Pauses audio device engine without closing the device and associated
+ * threads.
+ * Causes all cached sounds to be paused and all streaming sounds to be
+ * stopped.
+ */
+ public void pause() {
+ // This method must be overridden by device specific implementation
+ return;
+ }
+ /**
+ * Resumes audio device engine (if previously paused) without
+ * reinitializing the device.
+ * Causes all paused cached sounds to be resumed and all streaming
+ * sounds restarted.
+ */
+ public void resume() {
+ // This method must be overridden by device specific implementation
+ return;
+ }
+
+ /**
+ * Set overall gain control of all sounds playing on the audio device.
+ * @param scaleFactor scale factor applied to calculated amplitudes for
+ * all sounds playing on this device
+ */
+ public void setGain(float scaleFactor) {
+ // This method must be overridden by device specific implementation
+ return;
+ }
+
+
+ /*
+ *
+ * Methods explicitly affect a particular sound rendering and that
+ * require audio device specific methods that override this class.
+ *
+ */
+
+ /**
+ * Set scale factor applied to sample playback rate for a particular sound
+ * associated with the audio device.
+ * Changing the device sample rate affects both the pitch and speed.
+ * This scale factor is applied to ALL sound types.
+ * Changes (scales) the playback rate of a sound independent of
+ * Doppler rate changes.
+ * @param index device specific reference to device driver sample
+ * @param scaleFactor non-negative factor applied to calculated
+ * amplitudes for all sounds playing on this device
+ * @see Sound#setRateScaleFactor
+ */
+ public void setRateScaleFactor(int index, float scaleFactor) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ sample.setRateScaleFactor(scaleFactor);
+ return;
+ }
+
+
+ /*
+ *
+ * Methods explicitly affect aural attributes of the listening space
+ * used to calculated reverberation during sound rendering.
+ * These require audio device specific methods that override this class.
+ *
+ */
+
+ /**
+ * Set late reflection (referred to as 'reverb') attenuation.
+ * This scale factor is applied to iterative, indistinguishable
+ * late reflections that constitute the tail of reverberated sound in
+ * the aural environment.
+ * This parameter, along with the early reflection coefficient, defines
+ * the reflective/absorptive characteristic of the surfaces in the
+ * current listening region.
+ * @param coefficient late reflection attenuation factor
+ * @see AuralAttributes#setReverbCoefficient
+ */
+ public void setReverbCoefficient(float coefficient) {
+ attribs.reverbCoefficient = coefficient;
+ return;
+ }
+
+
+ /**
+ * Sets the early reflection delay time.
+ * In this form, the parameter specifies the delay time between each order
+ * of reflection (while reverberation is being rendered) explicitly given
+ * in milliseconds.
+ * @param reflectionDelay time between each order of early reflection
+ * @see AuralAttributes#setReflectionDelay
+ */
+ public void setReflectionDelay(float reflectionDelay) {
+ attribs.reflectionDelay = reflectionDelay;
+ return;
+ }
+
+ /**
+ * Set reverb decay time.
+ * Defines the reverberation decay curve.
+ * @param time decay time in milliseconds
+ * @see AuralAttributes#setDecayTime
+ */
+ public void setDecayTime(float time) {
+ attribs.decayTime = time;
+ return;
+ }
+
+ /**
+ * Set reverb decay filter.
+ * This provides for frequencies above the given cutoff frequency to be
+ * attenuated during reverb decay at a different rate than frequencies
+ * below this value. Thus, defining a different reverb decay curve for
+ * frequencies above the cutoff value.
+ * @param frequencyCutoff value of frequencies in Hertz above which a
+ * low-pass filter is applied.
+ * @see AuralAttributes#setDecayFilter
+ */
+ public void setDecayFilter(float frequencyCutoff) {
+ attribs.decayFrequencyCutoff = frequencyCutoff;
+ return;
+ }
+
+ /**
+ * Set reverb diffusion.
+ * This defines the echo dispersement (also referred to as 'echo density').
+ * The value of this reverb parameter is expressed as a percent of the
+ * audio device's minimum-to-maximum values.
+ * @param diffusion percentage expressed within the range of 0.0 and 1.0
+ * @see AuralAttributes#setDiffusion
+ */
+ public void setDiffusion(float diffusion) {
+ attribs.diffusion = diffusion;
+ return;
+ }
+
+ /**
+ * Set reverb density.
+ * This defines the modal density (also referred to as 'spectral
+ * coloration').
+ * The value of this parameter is expressed as a percent of the audio
+ * device's minimum-to-maximum values for this reverb parameter.
+ * @param density reverb density expressed as a percentage,
+ * within the range of 0.0 and 1.0
+ * @see AuralAttributes#setDensity
+ */
+ public void setDensity(float density) {
+ attribs.density = density;
+ return;
+ }
+
+
+ /**
+ * Set the obstruction gain control. This method allows for attenuating
+ * sound waves traveling between the sound source and the listener
+ * obstructed by objects. Direct sound signals/waves for obstructed sound
+ * source are attenuated but not indirect (reflected) waves.
+ * There is no corresponding Core AuralAttributes method at this time.
+ * @param index device specific reference to device driver sample
+ * @param scaleFactor non-negative factor applied to direct sound gain
+ */
+ public void setObstructionGain(int index, float scaleFactor) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ sample.setObstructionGain(scaleFactor);
+ return;
+ }
+
+ /**
+ * Set the obstruction filter control.
+ * This provides for frequencies above the given cutoff frequency
+ * to be attenuated, during while the gain of an obstruction signal
+ * is being calculated, at a different rate than frequencies
+ * below this value.
+ * There is no corresponding Core AuralAttributes method at this time.
+ * @param index device specific reference to device driver sample
+ * @param frequencyCutoff value of frequencies in Hertz above which a
+ * low-pass filter is applied.
+ */
+
+ public void setObstructionFilter(int index, float frequencyCutoff) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ sample.setObstructionFilter(frequencyCutoff);
+ return;
+ }
+
+ /**
+ * Set the occlusion gain control. This method allows for attenuating
+ * sound waves traveling between the sound source and the listener
+ * occluded by objects. Both direct and indirect sound signals/waves
+ * for occluded sound sources are attenuated.
+ * There is no corresponding Core AuralAttributes method at this time.
+ * @param index device specific reference to device driver sample
+ * @param scaleFactor non-negative factor applied to direct sound gain
+ */
+ public void setOcclusionGain(int index, float scaleFactor) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ sample.setObstructionGain(scaleFactor);
+ return;
+ }
+
+ /**
+ * Set the occlusion filter control.
+ * This provides for frequencies above the given cutoff frequency
+ * to be attenuated, during while the gain of an occluded signal
+ * is being calculated, at a different rate than frequencies below
+ * this value.
+ * There is no corresponding Core AuralAttributes method at this time.
+ * @param index device specific reference to device driver sample
+ * @param frequencyCutoff value of frequencies in Hertz above which a
+ * low-pass filter is applied.
+ */
+ public void setOcclusionFilter(int index, float frequencyCutoff) {
+ Sample sample = (Sample)getSample(index);
+ if (sample != null)
+ sample.setObstructionFilter(frequencyCutoff);
+ return;
+ }
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/AudioEngineThread.java b/src/classes/share/com/sun/j3d/audioengines/AudioEngineThread.java
new file mode 100644
index 0000000..bf67e0a
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/AudioEngineThread.java
@@ -0,0 +1,275 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+package com.sun.j3d.audioengines;
+
+/*
+ * Audio Engine Thread
+ */
+
+import javax.media.j3d.*;
+
+/**
+ * The Thread Class extended for Audio Device engines that must process
+ * calls dynamically, in 'real-time" to asynchronously change engine
+ * parameters.
+ *
+ * <p>
+ * NOTE: this class is probably not needed for those Audio Device implementations
+ * that handle all dynamic parameters in the low-level audio library.
+ */
+public class AudioEngineThread extends Thread {
+
+ // Debug print flag
+ static final protected boolean debugFlag = false;
+
+
+ protected void debugPrint(String message) {
+ if (debugFlag)
+ System.out.println(message);
+ }
+
+ /**
+ * The classification types.
+ */
+ protected static final int WORK_THREAD = 0x01;
+ protected static final int UPDATE_THREAD = 0x02;
+
+ /**
+ * This runMonitor action puts the thread into an initial wait state
+ */
+ protected static final int WAIT = 0;
+
+ /**
+ * This runMonitor action notifies MasterControl that this thread
+ * has completed and wait.
+ */
+ protected static final int NOTIFY_AND_WAIT = 1;
+
+ /**
+ * This runMonitor action tells the thread to run N number of
+ * iterations.
+ */
+ protected static final int RUN = 2;
+
+ /**
+ * This runMonitor action tells the thread to stop running
+ */
+ protected static final int STOP = 3;
+
+ /**
+ * This indicates that this thread has been activated by MC
+ */
+ protected boolean active = false;
+
+ /**
+ * This indicates that this thread is alive and running
+ */
+ protected boolean running = true;
+
+
+ /**
+ * This indicates that this thread is ready
+ */
+ protected boolean started = false;
+
+ /**
+ * The time values passed into this thread
+ */
+ protected long referenceTime;
+
+ /**
+ * Use to assign threadOpts WAIT_ALL_THREADS
+ */
+ protected long lastWaitTimestamp = 0;
+
+ /**
+ * The type of this thread. It is one of the above constants.
+ */
+ protected int type;
+
+ /**
+ * The classification of this thread. It is one of the above constants.
+ */
+ protected int classification = WORK_THREAD;
+
+ /**
+ * The arguments passed in for this thread
+ */
+ protected Object[] args = null;
+
+ /**
+ * Flag to indicate that user initiate a thread stop
+ */
+ protected boolean userStop = false;
+
+ /**
+ * Flag to indicate that this thread is waiting to be notify
+ */
+ protected boolean waiting = false;
+
+ /**
+ * Some variables used to name threads correctly
+ */
+ protected static int numInstances = 0;
+ protected int instanceNum = -1;
+
+ /**
+ * This constructor simply assigns the given id.
+ */
+ public AudioEngineThread(ThreadGroup t, String threadName) {
+ super(t, threadName);
+ if (debugFlag)
+ debugPrint("AudioEngineThread.constructor("+threadName +")");
+ }
+
+ synchronized int newInstanceNum() {
+ return (++numInstances);
+ }
+
+ int getInstanceNum() {
+ if (instanceNum == -1)
+ instanceNum = newInstanceNum();
+ return instanceNum;
+ }
+
+ /**
+ * This method is defined by all slave threads to implement
+ * one iteration of work.
+ */
+ synchronized public void doWork() {
+ if (debugFlag)
+ debugPrint("AudioEngineThread.doWork()");
+ }
+
+ /**
+ * This initializes this thread. Once this method returns, the thread is
+ * ready to do work.
+ */
+ public void initialize() {
+ if (debugFlag)
+ debugPrint("AudioEngineThread.initialize()");
+ this.start();
+ while (!started) {
+ try {
+ Thread.currentThread().sleep(1, 0);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ /**
+ * This causes the threads run method to exit.
+ */
+ public void finish() {
+ while (!waiting) {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {}
+ }
+ runMonitor(STOP, 0,null);
+ }
+
+ /*
+ * This thread controls the syncing of all the canvases attached to
+ * this view.
+ */
+ public void run() {
+ if (debugFlag)
+ debugPrint("AudioEngineThread.run");
+ runMonitor(WAIT, 0, null);
+ while (running) {
+ doWork();
+ runMonitor(WAIT, 0, null);
+ }
+ // resource clean up
+ shutdown();
+ }
+
+ synchronized public void runMonitor(int action, long referenceTime, Object[] args){
+ switch (action) {
+ case WAIT:
+ if (debugFlag)
+ debugPrint("AudioEngineThread.runMonitor(WAIT)");
+ try {
+ started = true;
+ waiting = true;
+ wait();
+ } catch (InterruptedException e) {
+ System.err.println(e);
+ }
+ waiting = false;
+ break;
+ case RUN:
+ if (debugFlag)
+ debugPrint("AudioEngineThread.runMonitor(RUN)");
+ this.referenceTime = referenceTime;
+ this.args = args;
+ notify();
+ break;
+ case STOP:
+ if (debugFlag)
+ debugPrint("AudioEngineThread.runMonitor(STOP)");
+ running = false;
+ notify();
+ break;
+ }
+ }
+
+ public void shutdown() {
+ }
+
+ // default resource clean up method
+ public void cleanup() {
+ active = false;
+ running = true;
+ started = true;
+ lastWaitTimestamp = 0;
+ classification = WORK_THREAD;
+ args = null;
+ userStop = false;
+ referenceTime = 0;
+
+ }
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/AuralParameters.java b/src/classes/share/com/sun/j3d/audioengines/AuralParameters.java
new file mode 100644
index 0000000..6c29164
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/AuralParameters.java
@@ -0,0 +1,197 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+package com.sun.j3d.audioengines;
+
+import javax.media.j3d.*;
+import javax.vecmath.*;
+
+/**
+ * The AuralParameters Class defines a set of fields that define the
+ * Aural listening environment. Many of the parameters correspond to
+ * AuralAttribute fields.
+ *
+ * <p>
+ * Error checking on all parameters passed to these methods is already
+ * explicitly being done by the Java 3D core code that calls these methods.
+ */
+
+public class AuralParameters
+{
+ // Speed of Sound in meters/milliseconds
+ public static final float SPEED_OF_SOUND = 0.344f;
+ public static final int NO_FILTERING = -1;
+
+ public float rolloff = 1.0f;
+ public float reflectionCoefficient = 0.0f;
+ public float reverbDelay = 40.0f;
+ public int reverbOrder = 0;
+ public float frequencyScaleFactor = 1.0f;
+ public float velocityScaleFactor = 0.0f;
+ int filterType = NO_FILTERING;
+ double[] filterDistance = null;
+ float[] filterCutoff = null;
+
+ /*
+ * @since Java 3D 1.3
+ */
+ public float reverbCoefficient = 1.0f;
+ public float reflectionDelay = 20.0f;
+ public float decayTime = 1000.0f;
+ public float decayFrequencyCutoff = 5000.0f;
+ public float diffusion = 1.0f; // 100%
+ public float density = 1.0f; // 100%
+
+ /**
+ * Construct a new AuralParameters object
+ */
+ public AuralParameters() {
+ frequencyScaleFactor = 1.0f;
+ velocityScaleFactor = 0.0f;
+ rolloff = 1.0f;
+ reflectionCoefficient = 0.0f;
+ reflectionDelay = 20.0f;
+ reverbCoefficient = 1.0f;
+ reverbDelay = 40.0f;
+ reverbOrder = 0;
+ filterType = NO_FILTERING;
+ filterDistance = new double[2]; // start out with array of two
+ filterCutoff = new float[2]; // start out with array of two
+ decayTime = 1000.0f;
+ decayFrequencyCutoff = 5000.0f;
+ diffusion = 1.0f; // 100%
+ density = 1.0f; // 100%
+ }
+
+ public void setDistanceFilter(int filterType, double[] distance,
+ float[] filterCutoff) {
+ boolean error = false;
+ boolean allocate = false;
+ int attenuationLength = 0;
+ if (distance == null || filterCutoff == null) {
+ error = true;
+ }
+ else {
+ attenuationLength = distance.length;
+ if (attenuationLength == 0 || filterType == NO_FILTERING) {
+ error = true;
+ }
+ }
+ if (error) {
+ this.filterType = NO_FILTERING;
+ this.filterDistance = null;
+ this.filterCutoff = null;
+ if (debugFlag)
+ debugPrint("setDistanceFilter NO_FILTERING");
+ return;
+ }
+ this.filterType = filterType;
+ if (debugFlag)
+ debugPrint("setDistanceFilter type = " + filterType);
+ if ((filterDistance == null) || (filterCutoff == null)) {
+ allocate = true;
+ }
+ else if (attenuationLength > filterDistance.length) {
+ allocate = true;
+ }
+ if (allocate) {
+ if (debugFlag)
+ debugPrint("setDistanceFilter length = " + attenuationLength);
+ this.filterDistance = new double[attenuationLength];
+ this.filterCutoff = new float[attenuationLength];
+ }
+ System.arraycopy(distance, 0, this.filterDistance, 0,
+ attenuationLength);
+ System.arraycopy(filterCutoff, 0, this.filterCutoff, 0,
+ attenuationLength);
+
+ if (debugFlag) {
+ debugPrint("setDistanceFilter arrays = ");
+ for (int i=0; i<attenuationLength; i++)
+ debugPrint(this.filterDistance[i] + "," + this.filterCutoff[i]);
+ debugPrint("setDistanceFilter passed in = ");
+ for (int i=0; i<attenuationLength; i++)
+ debugPrint((float)(filterDistance[i]) + "," + filterCutoff[i]);
+ }
+ return;
+ }
+ public int getDistanceFilterLength() {
+ if (filterDistance != null)
+ return filterDistance.length;
+ return 0;
+ }
+
+ public int getDistanceFilterType() {
+ return filterType;
+ }
+
+ public void getDistanceFilter(double[] distance, float[] filterCutoff) {
+ if (distance == null || filterCutoff == null)
+ return;
+ int attenuationLength = distance.length;
+ if (attenuationLength == 0 ||
+ (filterDistance==null) || (filterCutoff==null))
+ return;
+ if (attenuationLength > filterDistance.length)
+ attenuationLength = filterDistance.length;
+ System.arraycopy(this.filterDistance, 0, distance, 0,
+ attenuationLength);
+ System.arraycopy(this.filterCutoff, 0, filterCutoff, 0,
+ attenuationLength);
+ return;
+ }
+
+ // Debug print flags
+ static final boolean debugFlag = false;
+ static final boolean internalErrors = false;
+
+ /**
+ * Debug print method for Sound nodes
+ */
+ protected void debugPrint(String message) {
+ if (debugFlag)
+ System.out.println(message);
+ }
+
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/Sample.java b/src/classes/share/com/sun/j3d/audioengines/Sample.java
new file mode 100644
index 0000000..620c28e
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/Sample.java
@@ -0,0 +1,479 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+package com.sun.j3d.audioengines;
+
+import javax.media.j3d.*;
+import javax.vecmath.*;
+
+/**
+ * The Sample class defines the data and methods associated with a sound
+ * sample played through the AudioDevice.
+ * This contains all the data fields for non-spatialized and spatialized
+ * (positional and directional) sound samples.
+ */
+public class Sample {
+
+ // Debug print flags and methods
+ static final protected boolean debugFlag = false;
+ static final protected boolean internalErrors = false;
+
+ protected void debugPrint(String message) {
+ if (debugFlag)
+ System.out.println(message);
+ }
+
+ protected void debugPrintln(String message) {
+ if (debugFlag)
+ System.out.println(message);
+ }
+
+ /**
+ * Null Sound identifier denotes sound is not created or initialized
+ */
+ public static final int NULL_SAMPLE = -1;
+
+ /**
+ * sound data associated with sound source
+ */
+ protected MediaContainer soundData = null;
+
+ /**
+ * sound data associated with sound source
+ */
+ protected int soundType = -1;
+
+ /**
+ * Overall Scale Factor applied to sound gain.
+ */
+ protected float gain = 1.0f; // Valid values are >= 0.0.
+
+ /**
+ * Overall Scale Factor applied to sound.
+ * @since Java 3D 1.3
+ */
+ protected float rateScaleFactor = 1.0f; // Valid values are >= 0.0.
+
+ /**
+ * Number of times sound is looped/repeated during play
+ */
+ protected int loopCount = 0; // Range from 0 to POSITIVE_INFINITY(-1)
+
+
+ /*
+ * Duration of sample
+ * This should match the Sound node constant of same name
+ */
+ public static final int DURATION_UNKNOWN = -1;
+ protected long duration = DURATION_UNKNOWN;
+
+ protected int numberOfChannels = 0;
+ protected boolean mute = false; // denotes if sample is muted
+ // (playing with zero gain)
+
+ /*
+ *
+ * Fields associated with positional sound samples
+ *
+ */
+ /*
+ * Local to Vworld transform
+ */
+ protected Transform3D vworldXfrm = new Transform3D();
+ protected boolean vwXfrmFlag = false;
+
+ /*
+ * Origin of Sound source in Listener's space.
+ */
+ protected Point3f position = new Point3f(0.0f, 0.0f, 0.0f);
+
+ /*
+ * Pairs of distances and gain scale factors that define piecewise linear
+ * gain attenuation between each pair.
+ */
+ protected double[] attenuationDistance = null;
+ protected float[] attenuationGain = null;;
+
+ /**
+ * dirty flags denoting what has changed since last rendering
+ */
+ protected int dirtyFlags = 0xFFFF;
+
+ /*
+ *
+ * Direction sample fields
+ *
+ */
+ /**
+ * The Cone Sound's direction vector. This is the cone axis.
+ */
+ protected Vector3f direction = new Vector3f(0.0f, 0.0f, 1.0f);
+
+ /**
+ * Pairs of distances and gain scale factors that define piecewise linear
+ * gain BACK attenuation between each pair.
+ * These are used for defining elliptical attenuation regions.
+ */
+ protected double[] backAttenuationDistance = null;
+ protected float[] backAttenuationGain = null;
+
+ /**
+ * Directional Sound's gain can be attenuated based on the listener's
+ * location off-angle from the source source direction.
+ * This can be set by three parameters:
+ * angular distance in radians
+ * gain scale factor
+ * filtering (currently the only filtering supported is lowpass)
+ */
+ protected double[] angularDistance = {0.0, (Math.PI * 0.5)};
+ protected float[] angularGain = {1.0f, 0.0f};
+
+ /**
+ * Distance Filter
+ * Each sound source is attenuated by a filter based on it's distance
+ * from the listener.
+ * For now the only supported filterType will be LOW_PASS frequency
+ * cutoff.
+ * At some time full FIR filtering will be supported.
+ */
+ public static final int NO_FILTERING = -1;
+ public static final int LOW_PASS = 1;
+
+ protected int angularFilterType = NO_FILTERING;
+ protected float[] angularFilterCutoff = {Sound.NO_FILTER, Sound.NO_FILTER};
+
+ /*
+ * Obstruction and Occlusion parameters
+ * For now the only type of filtering supported is a low-pass filter
+ * defined by a frequency cutoff value.
+ * @since Java 3D 1.3
+ */
+ protected float obstructionGain = 1.0f; // scale factor
+ protected int obstructionFilterType = NO_FILTERING;
+ protected float obstructionFilterCutoff = Sound.NO_FILTER;
+ protected float occlusionGain = 1.0f; // scale factor
+ protected int occlusionFilterType = NO_FILTERING;
+ protected float occlusionFilterCutoff = Sound.NO_FILTER;
+
+ /*
+ * Construct a new audio device Sample object
+ */
+ public Sample() {
+ if (debugFlag)
+ debugPrintln("Sample constructor");
+ }
+
+ public long getDuration() {
+ return 0;
+ }
+
+ public long getStartTime() {
+ return 0;
+ }
+
+ public int getNumberOfChannelsUsed() {
+ return 0;
+ }
+
+ public void setDirtyFlags(int flags) {
+ dirtyFlags = flags;
+ }
+
+ public int getDirtyFlags() {
+ return dirtyFlags;
+ }
+
+ public void setSoundType(int type) {
+ soundType = type;
+ }
+
+ public int getSoundType() {
+ return soundType;
+ }
+
+ public void setSoundData(MediaContainer ref) {
+ soundData = ref;
+ }
+
+ public MediaContainer getSoundData() {
+ return soundData;
+ }
+
+ public void setMuteFlag(boolean flag) {
+ mute = flag;
+ }
+
+ public boolean getMuteFlag() {
+ return mute;
+ }
+
+ public void setVWrldXfrmFlag(boolean flag) {
+ // this flag is ONLY true if the VirtualWorld Transform is ever set
+ vwXfrmFlag = flag;
+ }
+
+ public boolean getVWrldXfrmFlag() {
+ return vwXfrmFlag;
+ }
+
+ public void setGain(float scaleFactor) {
+ gain = scaleFactor;
+ }
+
+ public float getGain() {
+ return gain;
+ }
+
+ public void setLoopCount(int count) {
+ loopCount = count;
+ }
+
+ public int getLoopCount() {
+ return loopCount;
+ }
+
+
+ public void setPosition(Point3d position) {
+ this.position.set(position);
+ return;
+ }
+
+ // TODO: no get method for Position
+
+
+ public void setDistanceGain(
+ double[] frontDistance, float[] frontAttenuationScaleFactor,
+ double[] backDistance, float[] backAttenuationScaleFactor) {
+ if (frontDistance != null) {
+ int size = frontDistance.length;
+ attenuationDistance = new double[size];
+ attenuationGain = new float[size];
+ for (int i=0; i<size; i++) {
+ attenuationDistance[i] = frontDistance[i];
+ attenuationGain[i] = frontAttenuationScaleFactor[i];
+ }
+ }
+ else {
+ attenuationDistance = null;
+ attenuationGain = null;
+ }
+ if (backDistance != null && frontDistance != null) {
+ int size = backDistance.length;
+ backAttenuationDistance = new double[size];
+ backAttenuationGain = new float[size];
+ for (int i=0; i<size; i++) {
+ backAttenuationDistance[i] = backDistance[i];
+ backAttenuationGain[i] = backAttenuationScaleFactor[i];
+ }
+ }
+ else {
+ backAttenuationDistance = null;
+ backAttenuationGain = null;
+ }
+ return;
+ }
+
+ // TODO: no get method for Back Attenuation
+
+
+ public void setDirection(Vector3d direction) {
+ this.direction.set(direction);
+ return;
+ }
+
+ // TODO: no get method for Direction
+
+
+ public void setAngularAttenuation(int filterType, double[] angle,
+ float[] attenuationScaleFactor, float[] filterCutoff) {
+ if (angle != null) {
+ int size = angle.length;
+ angularDistance = new double[size];
+ angularGain = new float[size];
+ if (filterType != NO_FILTERING && filterCutoff != null)
+ angularFilterCutoff = new float[size];
+ else
+ angularFilterCutoff = null;
+ for (int i=0; i<size; i++) {
+ angularDistance[i] = angle[i];
+ angularGain[i] = attenuationScaleFactor[i];
+ if (filterType != NO_FILTERING)
+ angularFilterCutoff[i] = filterCutoff[i];
+ }
+ angularFilterType = filterType;
+ }
+ else {
+ angularDistance = null;
+ angularGain = null;
+ angularFilterCutoff = null;
+ angularFilterType = NO_FILTERING;
+ }
+ }
+
+ // TODO: no get method for Angular Attenuation
+
+
+ /*
+ * Set Rate ScaleFactor
+ * @since Java 3D 1.3
+ */
+ public void setRateScaleFactor(float scaleFactor) {
+ rateScaleFactor = scaleFactor;
+ }
+
+ /*
+ * Get Rate ScaleFactor
+ * @since Java 3D 1.3
+ */
+ public float getRateScaleFactor() {
+ return rateScaleFactor;
+ }
+
+
+ /*
+ * Set Obstruction Gain
+ * @since Java 3D 1.3
+ */
+ public void setObstructionGain(float scaleFactor) {
+ obstructionGain = scaleFactor;
+ }
+
+ /*
+ * Get Obstruction Gain
+ * @since Java 3D 1.3
+ */
+ public float getObstructionGain() {
+ return obstructionGain;
+ }
+
+ /*
+ * Set Obstruction Filter Cutoff Frequency
+ * @since Java 3D 1.3
+ */
+ public void setObstructionFilter(float cutoffFrequency) {
+ obstructionFilterType = LOW_PASS;
+ obstructionFilterCutoff = cutoffFrequency;
+ }
+
+ // TODO: no get method for Obstruction Filtering
+
+
+ /*
+ * Set Occlusion Gain
+ * @since Java 3D 1.3
+ */
+ public void setOcclusionGain(float scaleFactor) {
+ occlusionGain = scaleFactor;
+ }
+
+ /*
+ * Get Occlusion Gain
+ * @since Java 3D 1.3
+ */
+ public float getOcclusionGain() {
+ return occlusionGain;
+ }
+
+ /*
+ * Set Occlusion Filter Cutoff Frequency
+ * @since Java 3D 1.3
+ */
+ public void setOcclusionFilter(float cutoffFrequency) {
+ occlusionFilterType = LOW_PASS;
+ occlusionFilterCutoff = cutoffFrequency;
+ }
+
+ // TODO: no get method for Occlusion Filtering
+
+
+ /**
+ * Clears/re-initialize fields associated with sample data
+ * for this sound,
+ * and frees any device specific data associated with this sample.
+ */
+ public void clear() {
+ if (debugFlag)
+ debugPrintln("Sample.clear() entered");
+ soundData = (MediaContainer)null;
+ soundType = NULL_SAMPLE;
+ gain = 1.0f;
+ loopCount = 0;
+ duration = DURATION_UNKNOWN;
+ numberOfChannels = 0;
+ vworldXfrm.setIdentity();
+ vwXfrmFlag = false;
+ position.set(0.0f, 0.0f, 0.0f);
+ attenuationDistance = null;
+ attenuationGain = null;
+ direction.set(0.0f, 0.0f, 1.0f);
+ backAttenuationDistance = null;
+ backAttenuationGain = null;
+ if (angularDistance != null) {
+ angularDistance[0] = 0.0f;
+ angularDistance[1] = (float)(Math.PI) * 0.5f;
+ }
+ if (angularGain != null) {
+ angularGain[0] = 1.0f;
+ angularGain[1] = 0.0f;
+ }
+ angularFilterType = NO_FILTERING;
+ if (angularFilterCutoff != null) {
+ angularFilterCutoff[0] = Sound.NO_FILTER;
+ angularFilterCutoff[1] = Sound.NO_FILTER;
+ }
+ obstructionGain = 1.0f;
+ obstructionFilterType = NO_FILTERING;
+ obstructionFilterCutoff = Sound.NO_FILTER;
+ occlusionGain = 1.0f;
+ occlusionFilterType = NO_FILTERING;
+ occlusionFilterCutoff = Sound.NO_FILTER;
+ }
+
+ /*
+ * Render
+ */
+ public void render(int dirtyFlags, View view, AuralParameters attribs) {
+ // meant to be overridden
+ }
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSAuralParameters.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSAuralParameters.java
new file mode 100755
index 0000000..58ef551
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSAuralParameters.java
@@ -0,0 +1,79 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+package com.sun.j3d.audioengines.javasound;
+
+import java.lang.String;
+import java.io.*;
+
+/**
+ * The AudioDevice dependent sound node and aural attribute node parameters.
+ * These are explicitly maintained for HaeSoundMixer
+ */
+
+public class JSAuralParameters extends com.sun.j3d.audioengines.AuralParameters {
+
+ /**
+ * Reverb Parameters
+ *
+ * dirty flag checked and cleared by render()
+ */
+ static int REFLECTION_COEFF_CHANGED = 1;
+ static int REVERB_DELAY_CHANGED = 2;
+ static int REVERB_ORDER_CHANGED = 4;
+
+ int reverbDirty = 0xFFFF;
+ int lastReverbSpeed = 0; // TODO: NOT used yet
+ boolean reverbFlag = false; // previously refered to as reverbOn
+ int reverbType = 1; // Reverb type 1 equals NONE in JavaSound engine
+
+
+ JSAuralParameters() {
+ super();
+ reverbDirty = 0xFFFF;
+ lastReverbSpeed = 0;
+ reverbType = 1;
+ reverbFlag = false;
+ }
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSChannel.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSChannel.java
new file mode 100644
index 0000000..4fb4912
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSChannel.java
@@ -0,0 +1,435 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+/*
+ * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs
+ * to be rewritten.
+ */
+
+package com.sun.j3d.audioengines.javasound;
+
+// import java.applet.*;
+import java.util.*;
+import java.lang.String;
+import java.net.*;
+import java.io.*;
+import java.io.InputStream;
+// import sun.applet.*;
+import javax.sound.sampled.*;
+import com.sun.j3d.audioengines.*;
+// import javax.media.j3d.*;
+
+/**
+ * The JSChannel Class defines an audio output methods that call JavaSound
+ * API methods common for all data line types: streams, clip and MIDI lines.
+ */
+
+class JSChannel {
+
+ AudioInputStream ais = null;
+ long startTime = 0;
+ URL url = null;
+ InputStream inputStream = null;
+ AudioFormat audioFormat = null;
+ // WORKAROUND for (possibly old) bug in JavaSound
+ // JavaSound has left and right flipped
+ // TODO: verify whether this is still true
+ static double panLeft = 1.0;
+ static double panRight = -1.0;
+ float rateInHz = 0.0f;
+
+ /**
+ * Debug print mechanism for Sound nodes
+ */
+ static final boolean debugFlag = false;
+
+ static void debugPrint(String message) {
+ if (debugFlag)
+ System.out.print(message);
+ }
+
+ static void debugPrintln(String message) {
+ if (debugFlag)
+ System.out.println(message);
+ }
+
+
+ /**
+ * Code to initialize the device
+ * @return flag: true is initialized sucessfully, false if error
+ */
+ boolean initialize() {
+ // for now do nothing
+ return true;
+ }
+
+ /**
+ * @return reference to newly created AudioInputStream
+ */
+ AudioInputStream initAudioInputStream(InputStream inputStream, boolean cacheFlag) {
+ ais = null;
+ if (inputStream == null) {
+ if (debugFlag) {
+ debugPrint("JSChannel: Internal Error initAudioInputStream ");
+ debugPrintln("input stream given is null");
+ }
+ this.inputStream = null;
+ return null;
+ }
+ try {
+ if (debugFlag)
+ debugPrintln("JSChannel: initAudioInputStream - try getting stream ");
+ // open the sound data as an 'audio input stream'
+ // and read the header information at the start of the data.
+ ais = AudioSystem.getAudioInputStream(inputStream);
+ // add this new stream to vector list of streams
+ }
+ catch (Exception e) {
+ if (debugFlag) {
+ debugPrint("JSChannel: Internal Error initAudioInputStream ");
+ debugPrintln("get stream failed");
+ }
+ e.printStackTrace();
+ this.inputStream = null;
+ return null;
+ }
+ // success, so save new inputStream and nullify url field
+ this.inputStream = inputStream;
+ url = null;
+/******
+// QUESTION: HOW do I figure out the data type of the file/url/inputStream????
+ if (ais instanceof AudioMidiInputStream ||
+ ais instanceof AudioRmfInputStream )
+ // QUESTION: can non-cached MIDI files ever be supported ?
+*******/
+ return ais;
+ } // initAudioInputStream
+
+
+ /**
+ * @return reference to newly created AudioInputStream
+ */
+ AudioInputStream initAudioInputStream(URL path, boolean cacheFlag) {
+ ais = null;
+ if (path == null) {
+ if (debugFlag) {
+ debugPrint("JSChannel: Internal Error initAudioInputStream ");
+ debugPrintln("URL given is null");
+ }
+ this.url = null;
+ return null;
+ }
+ try {
+ if (debugFlag)
+ debugPrintln("JSChannel: initAudioInputStream - try getting stream ");
+ ais = AudioSystem.getAudioInputStream(path.openStream());
+ }
+ catch (Exception e) {
+ if (debugFlag) {
+ debugPrint("JSChannel: Internal Error initAudioInputStream ");
+ debugPrintln("get stream failed");
+ }
+ e.printStackTrace();
+ this.url = null;
+ return null;
+ }
+ // success, so save new url path and nullify input stream field
+ this.url = path;
+ inputStream = null;
+ return ais;
+ } // initAudioInputStream
+
+
+ AudioInputStream reinitAudioInputStream(URL path) {
+/*****
+ if (path == null) {
+ if (debugFlag) {
+ debugPrint("JSChannel: Internal Error reinitAudioInputStream ");
+ debugPrintln("URL given is null");
+ }
+ return null;
+ }
+ try {
+ if (debugFlag)
+ debugPrintln("JSChannel: reinitAudioInputStream - try getting stream ");
+ ais = AudioSystem.getAudioInputStream(path.openStream());
+ }
+ catch (Exception e) {
+ if (debugFlag) {
+ debugPrint("JSChannel: Internal Error reinitAudioInputStream ");
+ debugPrintln("get stream failed");
+ }
+ e.printStackTrace();
+ return null;
+ }
+ // Parametes stay the same except for start time which is changed later
+ return ais;
+******/
+ return null; // TODO: implement this
+
+ } // reinitAudioInputStream
+
+ AudioInputStream reinitAudioInputStream(InputStream inputStream) {
+/******
+ AudioInputStream ais;
+ if (inputStream == null) {
+ if (debugFlag) {
+ debugPrint("JSChannel: Internal Error reinitAudioInputStream ");
+ debugPrintln("InputStream given is null");
+ }
+ return null;
+ }
+ try {
+// Couldn't get this method to work!!!
+ if (debugFlag)
+ debugPrintln("JSChannel: reintAudioContainer - try closing stream ");
+ inputStream.close();
+
+ if (debugFlag)
+ debugPrintln("JSChannel: reinitAudioInputStream - try getting stream ");
+ ais = AudioSystem.getAudioInputStream(inputStream);
+ }
+ catch (Exception e) {
+ if (debugFlag) {
+ debugPrint("JSChannel: Internal Error reinitAudioInputStream ");
+ debugPrintln("get stream failed");
+ }
+ e.printStackTrace();
+ return null;
+ }
+ // Parametes stay the same except for start time which is changed later
+ return ais; // >=0 if everythings OK
+**************/
+ return null; // TODO: implement this
+
+ } // reinitAudioInputStream
+
+
+ DataLine initDataLine(AudioInputStream ais) {
+ if (debugFlag) {
+ debugPrintln("JSChannel: initDataLine(" + ais + ")");
+ debugPrintln(" must be overridden");
+ }
+ return null;
+ }
+
+ long getDuration() {
+ // TODO: how should this really be done??
+ if (debugFlag)
+ debugPrintln("JSChannel:getDuration");
+
+ if (ais == null || audioFormat == null ) {
+ if (debugFlag)
+ debugPrintln("JSChannel: Internal Error getDuration");
+ return (long)Sample.DURATION_UNKNOWN;
+ }
+ // Otherwise we'll assume that we can calculate this duration
+
+ // get "duration" of audio stream (wave file)
+ // TODO: For True STREAMing audio the size is unknown...
+ long numFrames = ais.getFrameLength();
+ if (debugFlag)
+ debugPrintln(" frame length = " + numFrames);
+ if (numFrames <= 0)
+ return (long)Sample.DURATION_UNKNOWN;
+
+ float rateInFrames = audioFormat.getFrameRate();
+ rateInHz = audioFormat.getSampleRate();
+ if (debugFlag)
+ debugPrintln(" rate in Frames = " + rateInFrames);
+ if (numFrames <= 0)
+ return (long)Sample.DURATION_UNKNOWN;
+ long duration = (long)((float)numFrames/rateInFrames);
+ if (debugFlag)
+ debugPrintln(" duration(based on ais) = " + duration);
+ return duration;
+ }
+
+ /**
+ * Start TWO Samples
+ */
+ boolean startSamples(int loopCount, float leftGain, float rightGain,
+ int leftDelay, int rightDelay) {
+ if (debugFlag)
+ debugPrint("JSChannel: startSamples must be overridden");
+ return false;
+ } // end of start Samples
+
+ /*
+ * Starts a Sample
+ */
+ boolean startSample(int loopCount, float gain, int delay) {
+ if (debugFlag)
+ debugPrint("JSChannel: startSample must be overridden");
+ return false;
+ } // end of start (single) Sample
+
+ int stopSample() {
+// This will tell thread to stop reading and writing
+ // reload with old URL
+ // reloadSample
+ if (debugFlag)
+ debugPrint("JSChannel: stopSample must be overridden");
+ startTime = 0;
+ return 0;
+ }
+
+ int stopSamples() {
+// This will tell thread to stop reading and writing
+ // TODO: For muting, stop sound but don't clear startTime...
+ // QUESTION: what does it mean for replaying that .stop "frees memory"
+ if (debugFlag)
+ debugPrint("JSChannel: stopSample must be overridden");
+// reloadSample
+
+ startTime = 0;
+ return 0;
+ }
+
+ void setSampleGain(float gain) {
+// TODO: Must be done in thread
+ if (debugFlag)
+ debugPrint("JSChannel: setSampleGain must be overridden");
+ }
+
+ void setSampleDelay(int delay) {
+ if (debugFlag)
+ debugPrint("JSChannel: setSampleDelay must be overridden");
+ /*
+ * null method
+ */
+ // dynamic changes to sample delay while playing is not implemented
+ }
+
+ void setSampleReverb(int type, boolean on) {
+ if (debugFlag)
+ debugPrint("JSChannel: setSampleReverb must be overridden");
+ }
+
+ void setSampleRate() {
+ if (debugFlag)
+ debugPrint("JSChannel: setSampleRate must be overridden");
+ }
+ void scaleSampleRate(float scaleFactor) {
+ /**
+ * Change rate for Doppler affect or pitch shifting.
+ * Engine maximum sample rate is 48kHz so clamp to that
+ * max value.
+ */
+ if (debugFlag)
+ debugPrintln("JSChannel: scaleSampleRate");
+ if (ais == null) {
+ if (debugFlag) {
+ debugPrint("JSChannel: Internal Error scaleSampleRate: ");
+ debugPrintln("ais is null");
+ }
+ return;
+ }
+
+ AudioFormat audioFormat = ais.getFormat();
+ float rate = audioFormat.getSampleRate();
+
+ double newRate = rate * scaleFactor;
+ if (newRate > 48000.0) // clamp to 48K max
+ newRate = 48000.0;
+/****
+// NOTE: This doesn't work...
+/// audioStream.setSampleRate(newRate);
+
+// need to set FloatControl.Type(SAMPLE_RATE) to new value somehow...
+
+ if (debugFlag) {
+ debugPrintln("JSChannel: scaleSampleRate: new rate = " +
+ rate * scaleFactor);
+ debugPrintln(" >>>>>>>>>>>>>>> using scaleFactor = " +
+ scaleFactor);
+ }
+****/
+ }
+
+ int pauseSamples() {
+ /**
+ * Pause playing samples
+ */
+// TODO: Notify thread
+ return 0;
+ }
+
+ int pauseSample() {
+ /**
+ * Pause playing a sample
+ */
+// TODO: Notify thread
+ return 0;
+ }
+
+ int unpauseSamples() {
+ /**
+ * Resume playing samples
+ */
+// TODO: Notify thread
+ return 0;
+ }
+
+ int unpauseSample() {
+ /**
+ * Resume playing a sample
+ */
+// TODO: Notify thread
+ return 0;
+ }
+
+ void setSampleFiltering(boolean filterFlag, float cutoffFreq) {
+ /**
+ * Set or clear low-pass filtering
+ */
+/****
+// QUESTION: how will this be done if data is written out one channel/sample at
+ a time??
+****/
+ // QUESTION: should filtering of Midi data be performed?
+// ais.setFiltering(filterFlag, cutoffFreq);
+ }
+
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSClip.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSClip.java
new file mode 100755
index 0000000..a1558fa
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSClip.java
@@ -0,0 +1,349 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+/*
+ * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs
+ * to be rewritten.
+ */
+
+package com.sun.j3d.audioengines.javasound;
+
+import java.applet.*;
+import java.util.*;
+import java.lang.String;
+import java.net.*;
+import java.io.*;
+import java.io.InputStream;
+import javax.sound.sampled.*;
+
+/**
+ * The JSClip Class defines an audio output methods that call JavaSound
+ * Hae mixer methods.
+ */
+
+class JSClip extends JSChannel {
+
+ Clip line;
+
+// TODO: separate left and right channel required until I write into
+// stereo buffer!
+ Clip otherChannel = null;
+
+// TODO: Reverb channel that is centered and not delayed is maintained separately
+// until a way to set stereo reverb send (panned and attenuated to give
+// the same affect) is implemented
+ Clip reverbChannel = null;
+
+
+ /**
+ * Create data line for outputting audio input stream.
+ * for a stream that is a sourceDataline
+ * @return true is successful in initiallizing DataLine
+ */
+ DataLine initDataLine(AudioInputStream ais) {
+ if (debugFlag)
+ debugPrintln("JSClip: initDataLine(" + ais + ")");
+
+ try {
+ if (debugFlag)
+ debugPrintln("JSClip: loadSample - try getting new line ");
+ /*
+ * From the AudioInputStream fetch information about the format
+ * of the audio data - including sampling frequency, number of
+ * channels, size of samples,...
+ */
+ audioFormat = ais.getFormat();
+
+ /*
+ * we can't yet open the device for ALAW/ULAW playback,
+ * convert ALAW/ULAW to PCM
+ */
+ if ((audioFormat.getEncoding() == AudioFormat.Encoding.ULAW) ||
+ (audioFormat.getEncoding() == AudioFormat.Encoding.ALAW)) {
+
+ AudioFormat tmp =
+ new AudioFormat(
+ AudioFormat.Encoding.PCM_SIGNED,
+ audioFormat.getSampleRate(),
+ audioFormat.getSampleSizeInBits() * 2,
+ audioFormat.getChannels(),
+ audioFormat.getFrameSize() * 2,
+ audioFormat.getFrameRate(),
+ true);
+ ais = AudioSystem.getAudioInputStream(tmp, ais);
+ audioFormat = tmp;
+ }
+
+ /*
+ * ask JavaSound for outline with a format suitable for our
+ * AudioInputStream. In order to ask for a line, a Info object
+ * with the desired properties must be constructed.
+ * Clip is used for outputing buffered data.
+ * We have to pass the line the AudioFormat object so it knows
+ * format will be.
+ *
+ * TODO: we could give JavaSound a hint about how big the
+ * internal buffer for the line should be, rather than use the
+ * default.
+ */
+ DataLine.Info info = new DataLine.Info(Clip.class,
+ audioFormat);
+ line = (Clip)AudioSystem.getLine(info);
+/*****
+// TODO: JSClip can't be a listener (do we need to do this in the thread?)
+ if (debugFlag)
+ debugPrintln("JSClip: addLineListener for clip");
+ line.addLineListener(this);
+******/
+
+ if (debugFlag)
+ debugPrintln("JSClip: open sound Clip");
+
+ // Make line ready to receive data.
+ line.open(ais);
+
+ // Line can now receive data but still needs to be
+ // activated (opened) so it will pass data on to the
+ // audio device. This is done at "startSample" time.
+ }
+ catch (Exception e) {
+ if (debugFlag) {
+ debugPrint("JSClip: Internal Error loadSample ");
+ debugPrintln("get stream failed");
+ }
+ e.printStackTrace();
+ // TODO: clean up vector elements that were set up for
+ // failed sample
+ return null;
+ }
+ return (DataLine)line;
+ } // initDataLine
+
+ /**
+ * Start TWO Samples
+ *
+ * used when two samples are associated with a single Point or Cone
+ * sound. This method handles starting both samples, rather than
+ * forcing the caller to make two calls to startSample, so that the
+ * actual Java Sound start methods called are as immediate (without
+ * delay between as possible.
+ */
+ boolean startSamples(int loopCount, float leftGain, float rightGain,
+ int leftDelay, int rightDelay) {
+ // loop count is ignored for Stream and MIDI
+ // TODO: loop count isn't implemented for MIDI yet
+
+ // left and rightDelay parameters are in terms of Samples
+ if (debugFlag) {
+ debugPrint("JSClip: startSamples ");
+ debugPrintln("start stream for Left called with ");
+ debugPrintln(" gain = " + leftGain +
+ " delay = " + leftDelay);
+ debugPrintln("start stream for Right called with ");
+ debugPrintln(" gain = " + rightGain +
+ " delay = " + rightDelay);
+ }
+
+ // This is called assuming that the Stream is allocated for a
+ // Positional sample, but if it is not then fall back to
+ // starting the single sample associated with this Stream
+ if (otherChannel == null || reverbChannel == null)
+ startSample(loopCount, leftGain, leftDelay);
+
+ /*
+ * ais for Left and Right streams should be same so just get ais
+ * left stream
+ */
+ if (ais == null) {
+ if (debugFlag) {
+ debugPrint("JSClip: Internal Error startSamples: ");
+ debugPrintln("either left or right ais is null");
+ }
+ return false;
+ }
+ Clip leftLine;
+ Clip rightLine;
+ leftLine = line;
+ rightLine = otherChannel;
+// left line only for background sounds...
+// TODO:
+/***********
+for now just care about the left
+ if (leftLine == null || rightLine == null) {
+ if (debugFlag) {
+ debugPrint("JSClip: startSamples Internal Error: ");
+ debugPrintln("either left or right line null");
+ }
+ return false;
+ }
+************/
+
+ // we know that were processing TWO channels
+ double ZERO_EPS = 0.0039; // approx 1/256 - twice MIDI precision
+ double leftVolume = (double)leftGain;
+ double rightVolume = (double)rightGain;
+
+// TODO: if not reading/writing done for Clips then I can't do
+// stereo trick (reading mono file and write to stereo buffer)
+ // Save time sound started, only in left
+ startTime = System.currentTimeMillis();
+ if (debugFlag)
+ debugPrintln("*****start Stream with new start time " +
+ startTime);
+ try {
+ // QUESTION: Offset clip is done how???
+/*******
+// TODO:
+offset delayed sound
+set volume
+set pan??
+set reverb
+ boolean reverbLeft = false; // off; reverb has it own channel
+ boolean reverbRight = reverbLeft;
+
+ if (leftDelay < rightDelay) {
+XXXX audioLeftStream.start(leftVolume, panLeft, reverbLeft);
+XXXX audioRightStream.start(rightVolume, panRight, reverbRight);
+ }
+ else {
+XXXX audioRightStream.start(rightVolume, panRight, reverbRight);
+XXXX audioLeftStream.start(leftVolume, panLeft, reverbLeft);
+ }
+******/
+ line.setLoopPoints(0, -1); // Loop the entire sound sample
+ line.loop(loopCount); // plays clip loopCount + 1 times
+ line.start(); // start the sound
+ }
+ catch (Exception e) {
+ if (debugFlag) {
+ debugPrint("JSClip: startSamples ");
+ debugPrintln("audioInputStream.read failed");
+ }
+ e.printStackTrace();
+ startTime = 0;
+ return false;
+ }
+
+ if (debugFlag)
+ debugPrintln("JSClip: startSamples returns");
+ return true;
+ } // end of startSamples
+
+
+ /*
+ * This method is called specifically for BackgroundSounds.
+ * There is exactly ONE sample (mono or stereo) associated with
+ * this type of sound. Consequently, delay is not applicable.
+ * Since the sound has no auralAttributes applied to it reverb
+ * is not applied to the sample.
+ */
+ boolean startSample(int loopCount, float gain, int delay) {
+ /*
+ if (debugFlag) {
+ debugPrint("JSClip: startSample ");
+ debugPrintln("start stream called with ");
+ debugPrintln(" gain = " + gain + ", delay is zero");
+ }
+
+ // Since only one sample is processed in startSample, just call
+ // this more general method passing duplicate information
+ // We don't really want to do this in the long term.
+ return startSamples(loopCount, gain, gain, 0, 0);
+ */
+
+ // TODO: The following is temporary until we have fully
+ // functional startSample and startSamples methods
+ if (debugFlag)
+ debugPrintln("JSClip.startSample(): starting sound Clip");
+ line.setFramePosition(0); // Start playing from the beginning
+ line.setLoopPoints(0, -1); // Loop the entire sound sample
+ line.loop(loopCount);
+ line.start();
+ return true;
+ } // end of start (single) Sample
+
+ int stopSample() {
+ // This will tell thread to stop reading and writing
+ // reload with old URL - reloadSample()???
+
+ if (debugFlag)
+ debugPrintln("JSClip.stopSample(): stopping sound Clip");
+ line.stop();
+
+ startTime = 0;
+ return 0;
+ }
+
+ int stopSamples() {
+ // This will tell thread to stop reading and writing
+ // TODO: For muting, stop sound but don't clear startTime...
+ // QUESTION: what does it mean for replaying that .stop "frees memory"
+
+ // reloadSample
+ // QUESTION: set stop state WHERE??!!
+
+ if (debugFlag)
+ debugPrintln("JSClip.stopSample(): stopping sound Clip");
+ line.stop();
+
+ startTime = 0;
+ return 0;
+ }
+
+ /*
+ * called by LineListener class
+ */
+ public void update(LineEvent event) {
+ if (event.getType().equals(LineEvent.Type.STOP)) {
+ line.close(); // really a stop??
+ }
+ else if (event.getType().equals(LineEvent.Type.CLOSE)) {
+ // this forces a system exit in example code
+ // TODO: what should be done to close line
+ if (debugFlag)
+ debugPrint("JSClip.update(CLOSE) entered ");
+ }
+ }
+
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSDirectionalSample.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSDirectionalSample.java
new file mode 100755
index 0000000..ce8a97f
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSDirectionalSample.java
@@ -0,0 +1,738 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+/*
+ * DirectionalSample object
+ *
+ * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs
+ * to be rewritten.
+ */
+
+package com.sun.j3d.audioengines.javasound;
+
+import javax.media.j3d.*;
+import com.sun.j3d.audioengines.*;
+import javax.vecmath.*;
+
+/**
+ * The PostionalSample Class defines the data and methods associated with a
+ * PointSound sample played through the AudioDevice.
+ */
+
+class JSDirectionalSample extends JSPositionalSample
+{
+ // The transformed direction of this sound
+ Vector3f xformDirection = new Vector3f(0.0f, 0.0f, 1.0f);
+
+ public JSDirectionalSample() {
+ super();
+ if (debugFlag)
+ debugPrintln("JSDirectionalSample constructor");
+ }
+
+ void setXformedDirection() {
+ if (debugFlag)
+ debugPrint("*** setXformedDirection");
+ if (!getVWrldXfrmFlag()) {
+ if (debugFlag)
+ debugPrint(" Transform NOT set yet, so dir => xformDir");
+ xformDirection.set(direction);
+ }
+ else {
+ if (debugFlag)
+ debugPrint(" Transform dir => xformDir");
+ vworldXfrm.transform(direction, xformDirection);
+ }
+ if (debugFlag)
+ debugPrint(" xform(sound)Direction <= "+xformDirection.x+
+ ", " + xformDirection.y + ", " + xformDirection.z);
+ }
+
+
+ /* ***********************************
+ *
+ * Intersect ray to head with Ellipse
+ *
+ * ***********************************/
+ /*
+ * An ellipse is defined using:
+ * (1) the ConeSound's direction vector as the major axis of the ellipse;
+ * (2) the max parameter (a front distance attenuation value) along the
+ * cone's position axis; and
+ * (3) the min parameter (a back distance attenuation value) along the
+ * cone's negative axis
+ * This method calculates the distance from the sound source to the
+ * Intersection of the Ellipse with the ray from the sound source to the
+ * listener's head.
+ * This method returns the resulting distance.
+ * If an error occurs, -1.0 is returned.
+ *
+ * A calculation are done in 'Cone' space:
+ * The origin is defined as being the sound source position.
+ * The ConeSound source axis is the X-axis of this Cone's space.
+ * Since this ConeSound source defines a prolate spheroid (obtained
+ * by revolving an ellipsoid about the major axis) we can define the
+ * Y-axis of this Cone space as being in the same plane as the X-axis
+ * and the vector from the origin to the head.
+ * All calculations in Cone space can be generalized in this two-
+ * dimensional space without loss of precision.
+ * Location of the head, H, in Cone space can then be defined as:
+ * H'(x,y) = (cos @, sin @) * | H |
+ * where @ is the angle between the X-axis and the ray to H.
+ * Using the equation of the line thru the origin and H', and the
+ * equation of ellipse defined with min and max, find the
+ * intersection by solving for x and then y.
+ *
+ * (I) The equation of the line thru the origin and H', and the
+ * | H'(y) - S(y) |
+ * y - S(y) = | ----------- | * [x - S(x)]
+ * | H'(x) - S(x) |
+ * and since S(x,y) is the origin of ConeSpace:
+ * | H'(y) |
+ * y = | ----- | x
+ * | H'(x) |
+ *
+ * (II) The equation of ellipse:
+ * x**2 y**2
+ * ---- + ---- = 1
+ * a**2 b**2
+ * given a is length from origin to ellipse along major, X-axis, and
+ * b is length from origin to ellipse along minor, Y-axis;
+ * where a**2 = [(max+min)/2]**2 , since 2a = min+max;
+ * where b**2 = min*max , since the triangle abc is made is defined by the
+ * the points: S(x,y), origin, and (0,b),
+ * thus b**2 = a**2 - S(x,y) = a**2 - ((a-min)**2) = 2a*min - min**2
+ * b**2 = ((min+max)*min) - min**2 = min*max.
+ * so the equation of the ellipse becomes:
+ * x**2 y**2
+ * ---------------- + ------- = 1
+ * [(max+min)/2]**2 min*max
+ *
+ * Substuting for y from Eq.(I) into Eq.(II) gives
+ * x**2 [(H'(y)/H'(x))*x]**2
+ * ---------------- + -------------------- = 1
+ * [(max+min)/2]**2 min*max
+ *
+ * issolating x**2 gives
+ * | 1 [H'(y)/H'(x)]**2 |
+ * x**2 | ---------------- + ---------------- | = 1
+ * | [(max+min)/2]**2 min*max |
+ *
+ *
+ * | 4 [(sin @ * |H|)/(cos @ * |H|)]**2 |
+ * x**2 | -------------- + -------------------------------- | = 1
+ * | [(max+min)]**2 min*max |
+ *
+ * | |
+ * | 1 |
+ * | |
+ * x**2 = | --------------------------------------- |
+ * | | 4 [sin @/cos @]**2 | |
+ * | | -------------- + ---------------- | |
+ * | | [(max+min)]**2 min*max | |
+ *
+ * substitute tan @ for [sin @/cos @], and take the square root and you have
+ * the equation for x as calculated below.
+ *
+ * Then solve for y by plugging x into Eq.(I).
+ *
+ * Return the distance from the origin in Cone space to this intersection
+ * point: square_root(x**2 + y**2).
+ *
+ */
+ double intersectEllipse(double max, double min ) {
+
+ if (debugFlag)
+ debugPrint(" intersectEllipse entered with min/max = " + min + "/" + max);
+ /*
+ * First find angle '@' between the X-axis ('A') and the ray to Head ('H').
+ * In local coordinates, use Dot Product of those two vectors to get cos @:
+ * A(u)*H(u) + A(v)*H(v) + A(w)*H(v)
+ * cos @ = --------------------------------
+ * |A|*|H|
+ * then since domain of @ is { 0 <= @ <= PI }, arccos can be used to get @.
+ */
+ Vector3f xAxis = this.direction; // axis is sound direction vector
+ // Get the already calculated vector from sound source position to head
+ Vector3f sourceToHead = this.sourceToCenterEar;
+ // error check vectors not empty
+ if (xAxis == null || sourceToHead == null) {
+ if (debugFlag)
+ debugPrint( " one or both of the vectors are null" );
+ return (-1.0f); // denotes an error occurred
+ }
+
+ // Dot Product
+ double dotProduct = (double)( (sourceToHead.dot(xAxis)) /
+ (sourceToHead.length() * xAxis.length()));
+ if (debugFlag)
+ debugPrint( " dot product = " + dotProduct );
+ // since theta angle is in the range between 0 and PI, arccos can be used
+ double theta = (float)(Math.acos(dotProduct));
+ if (debugFlag)
+ debugPrint( " theta = " + theta );
+
+ /*
+ * Solve for X using Eq.s (I) and (II) from above.
+ */
+ double minPlusMax = (double)(min + max);
+ double tangent = Math.tan(theta);
+ double xSquared = 1.0 /
+ ( ( 4.0 / (minPlusMax * minPlusMax) ) +
+ ( (tangent * tangent) / (min * max) ) );
+ double x = Math.sqrt(xSquared);
+ if (debugFlag)
+ debugPrint( " X = " + x );
+ /*
+ * Solve for y, given the result for x:
+ * | H'(y) | | sin @ |
+ * y = | ----- | x = | ----- | x
+ * | H'(x) | | cos @ |
+ */
+ double y = tangent * x;
+ if (debugFlag)
+ debugPrint( " Y = " + y );
+ double ySquared = y * y;
+
+ /*
+ * Now return distance from origin to intersection point (x,y)
+ */
+ float distance = (float)(Math.sqrt(xSquared + ySquared));
+ if (debugFlag)
+ debugPrint( " distance to intersection = " + distance );
+ return (distance);
+ }
+
+ /* *****************
+ *
+ * Find Factor
+ *
+ * *****************/
+ /*
+ * Interpolates the correct attenuation scale factor given a 'distance'
+ * value. This version used both front and back attenuation distance
+ * and scale factor arrays (if non-null) in its calculation of the
+ * the distance attenuation.
+ * If the back attenuation arrays are null then this executes the
+ * PointSoundRetained version of this method.
+ * This method finds the intesection of the ray from the sound source
+ * to the center-ear, with the ellipses defined by the two sets (front
+ * and back) of distance attenuation arrays.
+ * This method looks at pairs of intersection distance values to find
+ * which pair the input distance argument is between:
+ * [intersectionDistance[index] and intersectionDistance[index+1]
+ * The index is used to get factorArray[index] and factorArray[index+1].
+ * Then the ratio of the 'distance' between this pair of intersection
+ * values is used to scale the two found factorArray values proportionally.
+ */
+ float findFactor(double distanceToHead,
+ double[] maxDistanceArray, float[] maxFactorArray,
+ double[] minDistanceArray, float[] minFactorArray) {
+ int index, lowIndex, highIndex, indexMid;
+ double returnValue;
+
+ if (debugFlag) {
+ debugPrint("JSDirectionalSample.findFactor entered:");
+ debugPrint(" distance to head = " + distanceToHead);
+ }
+
+ if (minDistanceArray == null || minFactorArray == null) {
+ /*
+ * Execute the PointSoundRetained version of this method.
+ * Assume it will check for other error conditions.
+ */
+ return ( this.findFactor(distanceToHead,
+ maxDistanceArray, maxFactorArray) );
+ }
+
+ /*
+ * Error checking
+ */
+ if (maxDistanceArray == null || maxFactorArray == null) {
+ if (debugFlag)
+ debugPrint(" findFactor: arrays null");
+ return -1.0f;
+ }
+ // Assuming length > 1 already tested in set attenuation arrays methods
+ int arrayLength = maxDistanceArray.length;
+ if (arrayLength < 2) {
+ if (debugFlag)
+ debugPrint(" findFactor: arrays length < 2");
+ return -1.0f;
+ }
+ int largestIndex = arrayLength - 1;
+ /*
+ * Calculate distanceGain scale factor
+ */
+ /*
+ * distanceToHead is larger than greatest distance in maxDistanceArray
+ * so head is beyond the outer-most ellipse.
+ */
+ if (distanceToHead >= maxDistanceArray[largestIndex]) {
+ if (debugFlag)
+ debugPrint(" findFactor: distance > " +
+ maxDistanceArray[largestIndex]);
+ if (debugFlag)
+ debugPrint(" maxDistanceArray length = " +
+ maxDistanceArray.length);
+ if (debugFlag)
+ debugPrint(" findFactor returns ****** " +
+ maxFactorArray[largestIndex] + " ******");
+ return maxFactorArray[largestIndex];
+ }
+
+ /*
+ * distanceToHead is smaller than least distance in minDistanceArray
+ * so head is inside the inner-most ellipse.
+ */
+ if (distanceToHead <= minDistanceArray[0]) {
+ if (debugFlag)
+ debugPrint(" findFactor: distance < " +
+ maxDistanceArray[0]);
+ if (debugFlag)
+ debugPrint(" findFactor returns ****** " +
+ minFactorArray[0] + " ******");
+ return minFactorArray[0];
+ }
+
+ /*
+ * distanceToHead is between points within attenuation arrays.
+ * Use binary halfing of distance attenuation arrays.
+ */
+ {
+ double[] distanceArray = new double[arrayLength];
+ float[] factorArray = new float[arrayLength];
+ boolean[] intersectionCalculated = new boolean[arrayLength];
+ // initialize intersection calculated array flags to false
+ for (int i=0; i<arrayLength; i++)
+ intersectionCalculated[i] = false;
+ boolean intersectionOnEllipse = false;
+ int factorIndex = -1;
+
+ /*
+ * Using binary halving to find the two index values in the
+ * front and back distance arrays that the distanceToHead
+ * parameter (from sound source position to head) fails between.
+ * Changing the the current low and high index values
+ * calculate the intesection of ellipses (defined by this
+ * min/max distance values) with the ray (sound source to
+ * head). Put the resulting value into the distanceArray.
+ */
+ /*
+ * initialize the lowIndex to first index of distance arrays.
+ * initialize the highIndex to last index of distance arrays.
+ */
+ lowIndex = 0;
+ highIndex = largestIndex;
+
+ if (debugFlag)
+ debugPrint(" while loop to find index that's closest: ");
+ while (lowIndex < (highIndex-1)) {
+ if (debugFlag)
+ debugPrint(" lowIndex " + lowIndex +
+ ", highIndex " + highIndex);
+ /*
+ * Calculate the Intersection of Ellipses (defined by this
+ * min/max values) with the ray from the sound source to the
+ * head. Put the resulting value into the distanceArray.
+ */
+ if (!intersectionCalculated[lowIndex]) {
+ distanceArray[lowIndex] = this.intersectEllipse(
+ maxDistanceArray[lowIndex], minDistanceArray[lowIndex]);
+ // If return intersection distance is < 0 an error occurred.
+ if (distanceArray[lowIndex] >= 0.0)
+ intersectionCalculated[lowIndex] = true;
+ else {
+ /*
+ * Error in ellipse intersection calculation. Use
+ * average of max/min difference for intersection value.
+ */
+ distanceArray[lowIndex] = (minDistanceArray[lowIndex] +
+ maxDistanceArray[lowIndex])*0.5;
+ if (internalErrors)
+ debugPrint(
+ "Internal Error in intersectEllipse; use " +
+ distanceArray[lowIndex] +
+ " for intersection value " );
+ // Rather than aborting, just use average and go on...
+ intersectionCalculated[lowIndex] = true;
+ }
+ } // end of if intersection w/ lowIndex not already calculated
+
+ if (!intersectionCalculated[highIndex]) {
+ distanceArray[highIndex] = this.intersectEllipse(
+ maxDistanceArray[highIndex],minDistanceArray[highIndex]);
+ // If return intersection distance is < 0 an error occurred.
+ if (distanceArray[highIndex] >= 0.0f)
+ intersectionCalculated[highIndex] = true;
+ else {
+ /*
+ * Error in ellipse intersection calculation. Use
+ * average of max/min difference for intersection value.
+ */
+ distanceArray[highIndex] = (minDistanceArray[highIndex]+
+ maxDistanceArray[highIndex])*0.5f;
+ if (internalErrors)
+ debugPrint(
+ "Internal Error in intersectEllipse; use " +
+ distanceArray[highIndex] +
+ " for intersection value " );
+ // Rather than aborting, just use average and go on...
+ intersectionCalculated[highIndex] = true;
+ }
+ } // end of if intersection w/ highIndex not already calculated
+
+ /*
+ * Test for intersection points being the same as head position
+ * distanceArray[lowIndex] and distanceArray[highIndex], if so
+ * return factor value directly from array
+ */
+ if (distanceArray[lowIndex] >= distanceToHead) {
+ if ((lowIndex != 0) &&
+ (distanceToHead < distanceArray[lowIndex])) {
+ if (internalErrors)
+ debugPrint(
+ "Internal Error: binary halving in " +
+ "findFactor failed; distance < low " +
+ "index value");
+ }
+ if (debugFlag) {
+ debugPrint(" distanceArray[lowIndex] >= " +
+ "distanceToHead" );
+ debugPrint( " factorIndex = " + lowIndex);
+ }
+ intersectionOnEllipse = true;
+ factorIndex = lowIndex;
+ break;
+ }
+ else if (distanceArray[highIndex] <= distanceToHead) {
+ if ((highIndex != largestIndex) &&
+ (distanceToHead > distanceArray[highIndex])) {
+ if (internalErrors)
+ debugPrint(
+ "Internal Error: binary halving in " +
+ "findFactor failed; distance > high " +
+ "index value");
+ }
+ if (debugFlag) {
+ debugPrint(" distanceArray[highIndex] >= " +
+ "distanceToHead" );
+ debugPrint( " factorIndex = " + highIndex);
+ }
+ intersectionOnEllipse = true;
+ factorIndex = highIndex;
+ break;
+ }
+
+ if (distanceToHead > distanceArray[lowIndex] &&
+ distanceToHead < distanceArray[highIndex] ) {
+ indexMid = lowIndex + ((highIndex - lowIndex) / 2);
+ if (distanceToHead <= distanceArray[indexMid])
+ // value of distance in lower "half" of list
+ highIndex = indexMid;
+ else // value if distance in upper "half" of list
+ lowIndex = indexMid;
+ }
+ } /* of while */
+
+ /*
+ * First check to see if distanceToHead is beyond min or max
+ * ellipses, or on an ellipse.
+ * If so, factor is calculated using the distance Ratio
+ * (distanceToHead - min) / (max-min)
+ * where max = maxDistanceArray[factorIndex], and
+ * min = minDistanceArray[factorIndex]
+ */
+ if (intersectionOnEllipse && factorIndex >= 0) {
+ if (debugFlag) {
+ debugPrint( " ratio calculated using factorIndex " +
+ factorIndex);
+ debugPrint( " d.A. max pair for factorIndex " +
+ maxDistanceArray[factorIndex] + ", " +
+ maxFactorArray[factorIndex]);
+ debugPrint( " d.A. min pair for lowIndex " +
+ minDistanceArray[factorIndex] + ", " +
+ minFactorArray[factorIndex]);
+ }
+ returnValue = (
+ ( (distanceArray[factorIndex] -
+ minDistanceArray[factorIndex]) /
+ (maxDistanceArray[factorIndex] -
+ minDistanceArray[factorIndex]) ) *
+ (maxFactorArray[factorIndex] -
+ minFactorArray[factorIndex]) ) +
+ minFactorArray[factorIndex] ;
+ if (debugFlag)
+ debugPrint(" findFactor returns ****** " +
+ returnValue + " ******");
+ return (float)returnValue;
+ }
+
+ /* Otherwise, for distanceToHead between distance intersection
+ * values, we need to calculate two factors - one for the
+ * ellipse defined by lowIndex min/max factor arrays, and
+ * the other by highIndex min/max factor arrays. Then the
+ * distance Ratio (defined above) is applied, using these
+ * two factor values, to get the final return value.
+ */
+ double highFactorValue = 1.0;
+ double lowFactorValue = 0.0;
+ highFactorValue =
+ ( ((distanceArray[highIndex] - minDistanceArray[highIndex]) /
+ (maxDistanceArray[highIndex]-minDistanceArray[highIndex])) *
+ (maxFactorArray[highIndex] - minFactorArray[highIndex]) ) +
+ minFactorArray[highIndex] ;
+ if (debugFlag) {
+ debugPrint( " highFactorValue calculated w/ highIndex " +
+ highIndex);
+ debugPrint( " d.A. max pair for highIndex " +
+ maxDistanceArray[highIndex] + ", " +
+ maxFactorArray[highIndex]);
+ debugPrint( " d.A. min pair for lowIndex " +
+ minDistanceArray[highIndex] + ", " +
+ minFactorArray[highIndex]);
+ debugPrint( " highFactorValue " + highFactorValue);
+ }
+ lowFactorValue =
+ ( ((distanceArray[lowIndex] - minDistanceArray[lowIndex]) /
+ (maxDistanceArray[lowIndex] - minDistanceArray[lowIndex])) *
+ (maxFactorArray[lowIndex] - minFactorArray[lowIndex]) ) +
+ minFactorArray[lowIndex] ;
+ if (debugFlag) {
+ debugPrint( " lowFactorValue calculated w/ lowIndex " +
+ lowIndex);
+ debugPrint( " d.A. max pair for lowIndex " +
+ maxDistanceArray[lowIndex] + ", " +
+ maxFactorArray[lowIndex]);
+ debugPrint( " d.A. min pair for lowIndex " +
+ minDistanceArray[lowIndex] + ", " +
+ minFactorArray[lowIndex]);
+ debugPrint( " lowFactorValue " + lowFactorValue);
+ }
+ /*
+ * calculate gain scale factor based on the ratio distance
+ * between ellipses the distanceToHead lies between.
+ */
+ /*
+ * ratio: distance from listener to sound source
+ * between lowIndex and highIndex times
+ * attenuation value between lowIndex and highIndex
+ * gives linearly interpolationed attenuation value
+ */
+ if (debugFlag) {
+ debugPrint( " ratio calculated using distanceArray" +
+ lowIndex + ", highIndex " + highIndex);
+ debugPrint( " calculated pair for lowIndex " +
+ distanceArray[lowIndex]+", "+ lowFactorValue);
+ debugPrint( " calculated pair for highIndex " +
+ distanceArray[highIndex]+", "+ highFactorValue );
+ }
+
+ returnValue =
+ ( ( (distanceToHead - distanceArray[lowIndex]) /
+ (distanceArray[highIndex] - distanceArray[lowIndex]) ) *
+ (highFactorValue - lowFactorValue) ) +
+ factorArray[lowIndex] ;
+ if (debugFlag)
+ debugPrint(" findFactor returns ******" +
+ returnValue + " ******");
+ return (float)returnValue;
+ }
+
+ }
+
+ /**
+ * CalculateDistanceAttenuation
+ *
+ * Simply calls ConeSound specific 'findFactor()' with
+ * both front and back attenuation linear distance and gain scale factor
+ * arrays.
+ */
+ float calculateDistanceAttenuation(float distance) {
+ float factor = findFactor(distance, this.attenuationDistance,
+ this.attenuationGain, this.backAttenuationDistance,
+ this.backAttenuationGain);
+ if (factor < 0.0f)
+ return 1.0f;
+ else
+ return factor;
+ }
+ /**
+ * CalculateAngularGain
+ *
+ * Simply calls generic (for PointSound) 'findFactor()' with
+ * a single set of angular attenuation distance and gain scalefactor arrays.
+ */
+ float calculateAngularGain() {
+ float angle = findAngularOffset();
+ float factor = findFactor(angle, this.angularDistance, this.angularGain);
+ if (factor < 0.0f)
+ return 1.0f;
+ else
+ return factor;
+ }
+
+ /* *****************
+ *
+ * Find Angular Offset
+ *
+ * *****************/
+ /*
+ * Calculates the angle from the sound's direction axis and the ray from
+ * the sound origin to the listener'center ear.
+ * For Cone Sounds this value is the arc cosine of dot-product between
+ * the sound direction vector and the vector (sound position,centerEar)
+ * all in Virtual World coordinates space.
+ * Center ear position is in Virtual World coordinates.
+ * Assumes that calculation done in VWorld Space...
+ * Assumes that xformPosition is already calculated...
+ */
+ float findAngularOffset() {
+ Vector3f unitToEar = new Vector3f();
+ Vector3f unitDirection = new Vector3f();
+ Point3f xformPosition = positions[currentIndex];
+ Point3f xformCenterEar = centerEars[currentIndex];
+ float dotProduct;
+ float angle;
+ /*
+ * TODO: (Question) is assumption that xformed values available O.K.
+ * TODO: (Performance) save this angular offset and only recalculate
+ * if centerEar or sound position have changed.
+ */
+ unitToEar.x = xformCenterEar.x - xformPosition.x;
+ unitToEar.y = xformCenterEar.y - xformPosition.y;
+ unitToEar.z = xformCenterEar.z - xformPosition.z;
+ unitToEar.normalize();
+ unitDirection.normalize(this.direction);
+ dotProduct = unitToEar.dot(unitDirection);
+ angle = (float)(Math.acos((double)dotProduct));
+ if (debugFlag)
+ debugPrint(" angle from cone direction = " + angle);
+ return(angle);
+ }
+
+ /************
+ *
+ * Calculate Filter
+ *
+ * *****************/
+ /*
+ * Calculates the low-pass cutoff frequency filter value applied to the
+ * a sound based on both:
+ * Distance Filter (from Aural Attributes) based on distance
+ * between the sound and the listeners position
+ * Angular Filter (for Directional Sounds) based on the angle
+ * between a sound's projected direction and the
+ * vector between the sounds position and center ear.
+ * The lowest of these two filter is used.
+ * This filter value is stored into the sample's filterFreq field.
+ */
+ void calculateFilter(float distance, AuralParameters attribs) {
+ // setting filter cutoff freq to 44.1kHz which, in this
+ // implementation, is the same as not performing filtering
+ float distanceFilter = 44100.0f;
+ float angularFilter = 44100.0f;
+ int arrayLength = attribs.getDistanceFilterLength();
+ int filterType = attribs.getDistanceFilterType();
+
+ boolean distanceFilterFound = false;
+ boolean angularFilterFound = false;
+ if ((filterType == AuralParameters.NO_FILTERING) && arrayLength > 0) {
+ double[] distanceArray = new double[arrayLength];
+ float[] cutoffArray = new float[arrayLength];
+ attribs.getDistanceFilter(distanceArray, cutoffArray);
+
+ if (debugFlag) {
+ debugPrint("distanceArray cutoffArray");
+ for (int i=0; i<arrayLength; i++)
+ debugPrint((float)distanceArray[i] + ", " + cutoffArray[i]);
+ }
+
+ // Calculate angle from direction axis towards listener
+ float angle = findAngularOffset();
+ distanceFilter = findFactor((double)angle,
+ angularDistance, angularFilterCutoff);
+ if (distanceFilter < 0.0f)
+ distanceFilterFound = false;
+ else
+ distanceFilterFound = true;
+ }
+ else {
+ distanceFilterFound = false;
+ distanceFilter = -1.0f;
+ }
+
+ if (debugFlag)
+ debugPrint(" calculateFilter arrayLength = " + arrayLength);
+
+ // Angular filter of directional sound sources.
+ arrayLength = angularDistance.length;
+ filterType = angularFilterType;
+ if ((filterType != AuralParameters.NO_FILTERING) && arrayLength > 0) {
+ angularFilter = findFactor((double)distance,
+ angularDistance, angularFilterCutoff);
+ if (angularFilter < 0.0f)
+ angularFilterFound = false;
+ else
+ angularFilterFound = true;
+ }
+ else {
+ angularFilterFound = false;
+ angularFilter = -1.0f;
+ }
+
+ filterFlag = distanceFilterFound || angularFilterFound;
+ if (distanceFilter < 0.0f)
+ filterFreq = angularFilter;
+ else if (angularFilter < 0.0f)
+ filterFreq = distanceFilter;
+ else // both filter frequencies are > 0
+ filterFreq = Math.min(distanceFilter, angularFilter);
+
+ if (debugFlag)
+ debugPrint(" calculateFilter flag,freq = " + filterFlag +
+ "," + filterFreq );
+ }
+
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSMidi.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSMidi.java
new file mode 100755
index 0000000..0ec687e
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSMidi.java
@@ -0,0 +1,67 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+package com.sun.j3d.audioengines.javasound;
+
+/**
+ * The JSMidi class defines audio output methods that call the JavaSound
+ * API methods for MIDI sounds.
+ *
+ * <p>
+ * NOTE: This class is not yet implemented.
+ */
+
+class JSMidi extends JSChannel {
+ private static boolean warningReported = false;
+
+ JSMidi() {
+ // Report a "not implemented" warning message
+ if (!warningReported) {
+ System.err.println("***");
+ System.err.println("*** WARNING: JavaSoundMixer: MIDI sound not implemented");
+ System.err.println("***");
+ warningReported = true;
+ }
+ }
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSPositionalSample.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSPositionalSample.java
new file mode 100755
index 0000000..580c060
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSPositionalSample.java
@@ -0,0 +1,1339 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+/*
+ * Java Sound PositionalSample object
+ *
+ * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs
+ * to be rewritten.
+ */
+
+package com.sun.j3d.audioengines.javasound;
+
+import javax.media.j3d.*;
+import javax.vecmath.*;
+import com.sun.j3d.audioengines.*;
+
+/**
+ * The PostionalSample Class defines the data and methods associated with a
+ * PointSound sample played thru the AudioDevice.
+ */
+
+class JSPositionalSample extends JSSample
+{
+
+ // maintain fields for stereo channel rendering
+ float leftGain = 1.0f; // scale factor
+ float rightGain = 1.0f; // scale factor
+ int leftDelay = 0; // left InterauralTimeDifference in millisec
+ int rightDelay = 0; // right ITD in millisec
+ // fields for reverb channel
+
+ // debug flag for the verbose Doppler calculation methods
+ static final
+ protected boolean dopplerFlag = true;
+
+ /**
+ * For positional and directional sounds, TWO Hae streams or clips
+ * are allocated, one each for the left and right channels, played at
+ * a different (delayed) time and with a different gain value.
+ */
+ int secondIndex = NULL_SAMPLE;
+ /**
+ * A third sample for control of reverb of the stream/clip is openned
+ * and maintained for all directional/positional sounds.
+ * For now, even if no aural attributes (using reverb) are active,
+ * a reverb channel is always started with the other two. A sound could
+ * be started without reverb and then reverb added later, but since there
+ * is no way to offset properly into all sounds (considering non-cached
+ * and nconsistent rate-changes during playing) this third sound is
+ * always allocated and started.
+ */
+ int reverbIndex = NULL_SAMPLE;
+
+ /**
+ * Save ear positions transformed into VirtualWorld coords from Head coords
+ * These default positions are used when the real values cannot queried
+ */
+ Point3f xformLeftEar = new Point3f(-0.09f, -0.03f, 0.095f);
+ Point3f xformRightEar = new Point3f(0.09f, -0.03f, 0.095f);
+ // Z axis in head space - looking into the screen
+ Vector3f xformHeadZAxis = new Vector3f(0.0f, 0.0f, -1.0f); // Va
+
+ /**
+ * Save vectors from source source position to transformed ear parameters
+ */
+ Vector3f sourceToCenterEar = new Vector3f(); // Vh
+ Vector3f sourceToRightEar = new Vector3f(); // Vf or Vc
+ Vector3f sourceToLeftEar = new Vector3f(); // Vf or Vc
+
+ boolean averageDistances = false;
+ long deltaTime = 0;
+ double sourcePositionChange = -1.0;
+ double headPositionChange = -1.0;
+
+ /*
+ * Maintain the last locations of sound and head as well as time the
+ * sound was last processed.
+ * Process delta distance and time as part of Doppler calculations.
+ */
+ static int MAX_DISTANCES = 4;
+ int numDistances = 0;
+// TODO: time is based on changes to position!!! only
+// TODO: must shap shot when either Position OR ear changes!!!
+// TODO: must grab all changes to VIEW parameters (could change ear)!!!
+// not just when pointer to View changes!!
+ long[] times = new long[MAX_DISTANCES];
+ Point3f[] positions = new Point3f[MAX_DISTANCES]; // xformed sound source positions
+ Point3f[] centerEars = new Point3f[MAX_DISTANCES]; // xformed center ear positions
+ /*
+ * a set of indices (first, last, and current) are maintained to point
+ * into the above arrays
+ */
+ int firstIndex = 0;
+ int lastIndex = 0;
+ int currentIndex = 0;
+
+ /*
+ * Allow changes in Doppler rate only small incremental values otherwise
+ * you hear skips in the pitch of a sound during playback.
+ * When playback is faster, allow delta changes:
+ * (diff in Factor for octave (1.0))/(12 1/2-steps))*(1/4) of half-step
+ * When playback is slower, allow delta changes:
+ * (diff in Factor for octave (0.5))/(12 1/2-steps))*(1/4) of half-step
+ */
+ double lastRequestedDopplerRateRatio = -1.0f;
+ double lastActualDopplerRateRatio = -1.0f;
+ static double maxRatio = 256.0f; // 8 times higher/lower
+ /*
+ * denotes movement of sound away or towards listener
+ */
+ static int TOWARDS = 1;
+ static int NO_CHANGE = 0;
+ static int AWAY = -1;
+
+ /*
+ * Process request for Filtering fields
+ */
+ boolean filterFlag = false;
+ float filterFreq = -1.0f;
+
+ /*
+ * Construct a new audio device Sample object
+ */
+ public JSPositionalSample() {
+ super();
+ if (debugFlag)
+ debugPrint("JSPositionalSample constructor");
+ // initiallize circular buffer for averaging distance values
+ for (int i=0; i<MAX_DISTANCES; i++) {
+ positions[i] = new Point3f();
+ centerEars[i] = new Point3f(0.09f, -0.03f, 0.095f);
+ }
+ clear();
+ }
+
+ // TODO: get/set secondChannel to JSStream/Clip/MIDI
+ // TODO: get/set reverbChannel to JSStream/Clip/MIDI
+ /*
+ * Process request for Filtering fields
+ */
+ boolean getFilterFlag() {
+ return filterFlag;
+ }
+ float getFilterFreq() {
+ return filterFreq;
+ }
+
+
+ /**
+ * Clears the fields associated with sample data for this sound, and
+ * frees any device specific data associated with this sample.
+ */
+ public void clear() {
+ if (debugFlag)
+ debugPrint("JSPositionalSample.clear() enter");
+ super.clear();
+ leftGain = 1.0f;
+ rightGain = 1.0f;
+ leftDelay = 0;
+ rightDelay = 0;
+ xformLeftEar.set(-0.09f, -0.03f, 0.095f);
+ xformRightEar.set(0.09f, -0.03f, 0.095f);
+ // Z axis in head space - looking into the screen
+ xformHeadZAxis.set(0.0f, 0.0f, -1.0f); // Va
+ sourceToCenterEar.set(0.0f, 0.0f, 0.0f); // Vh
+ sourceToRightEar.set(0.0f, 0.0f, 0.0f); // Vf or Vc
+ sourceToLeftEar.set(0.0f, 0.0f, 0.0f); // Vf or Vc
+ reset();
+ if (debugFlag)
+ debugPrint("JSPositionalSample.clear() exit");
+ }
+
+ /**
+ * Reset time and count based fields associated with sample data
+ * for this sound
+ */
+ void reset() {
+ if (debugFlag)
+ debugPrint("JSPositionalSample.reset() enter");
+ super.reset();
+ averageDistances = false; // denotes not previously processed
+ deltaTime = 0;
+ sourcePositionChange = -1.0;
+ headPositionChange = -1.0;
+ rateRatio = 1.0f;
+ numDistances = 0;
+ averageDistances = false;
+ if (debugFlag)
+ debugPrint("JSPositionalSample.reset() exit");
+ }
+ // increments index counters and bumps index numbers if the end of
+ // the circular buffer is reached
+ void incrementIndices() {
+ int maxIndex = MAX_DISTANCES - 1;
+ if (numDistances < maxIndex) {
+ averageDistances = false;
+ currentIndex = numDistances;
+ lastIndex = currentIndex - 1;
+ firstIndex = 0;
+ numDistances++;
+ }
+ else if (numDistances == maxIndex) {
+ // we filled the data buffers completely and are ready to
+ // calculate averages
+ averageDistances = true;
+ currentIndex = maxIndex;
+ lastIndex = currentIndex - 1;
+ firstIndex = 0;
+ numDistances++;
+ }
+ else if (numDistances > maxIndex) {
+ // increment each counter and loop around
+ averageDistances = true;
+ currentIndex++;
+ lastIndex++;
+ firstIndex++;
+ currentIndex %= MAX_DISTANCES;
+ lastIndex %= MAX_DISTANCES;
+ firstIndex %= MAX_DISTANCES;
+ }
+ }
+
+ // Not only do we transform position but delta time is calculated and
+ // old transformed position is saved
+ // Average the last MAX_DISTANCES delta time and change in position using
+ // an array for both and circlularly storing the time and distance values
+ // into this array.
+ // Current transformed position and time in stored into maxIndex of their
+ // respective arrays.
+ void setXformedPosition() {
+ Point3f newPosition = new Point3f();
+ if (debugFlag)
+ debugPrint("*** setXformedPosition");
+ // xform Position
+ if (getVWrldXfrmFlag()) {
+ if (debugFlag)
+ debugPrint(" Transform set so transform pos");
+ vworldXfrm.transform(position, newPosition);
+ }
+ else {
+ if (debugFlag)
+ debugPrint(" Transform NOT set so pos => xformPos");
+ newPosition.set(position);
+ }
+ // store position and increment indices ONLY if theres an actual change
+ if (newPosition.x == positions[currentIndex].x &&
+ newPosition.y == positions[currentIndex].y &&
+ newPosition.z == positions[currentIndex].z ) {
+ if (debugFlag)
+ debugPrint(" No change in pos, so don't reset");
+ return;
+ }
+
+ incrementIndices();
+ // store new transformed position
+ times[currentIndex] = System.currentTimeMillis();
+ positions[currentIndex].set(newPosition);
+ if (debugFlag)
+ debugPrint(" xform(sound)Position -" +
+ " positions[" + currentIndex + "] = (" +
+ positions[currentIndex].x + ", " +
+ positions[currentIndex].y + ", " +
+ positions[currentIndex].z + ")");
+
+ // since this is a change to the sound position and not the
+ // head save the last head position into the current element
+ if (numDistances > 1)
+ centerEars[currentIndex].set(centerEars[lastIndex]);
+
+ }
+
+ /**
+ * Set Doppler effect Rate
+ *
+ * Calculate the rate of change in for the head and sound
+ * between the two time stamps (last two times position or
+ * VirtualWorld transform was updated).
+ * First determine if the head and sound source are moving
+ * towards each other (distance between them is decreasing),
+ * moving away from each other (distance between them is
+ * increasing), or no change (distance is the same, not moving
+ * or moving the same speed/direction).
+ * The following equation is used for determining the change in frequency -
+ * If there has been a change in the distance between the head and sound:
+ *
+ * f' = f * frequencyScaleFactor * velocityRatio
+ *
+ * For no change in the distance bewteen head and sound, velocityRatio is 1:
+ *
+ * f' = f
+ *
+ * For head and sound moving towards each other, velocityRatio (> 1.0) is:
+ *
+ * | speedOfSound*rollOff + velocityOfHead*velocityScaleFactor |
+ * | ------------------------------------------------------------- |
+ * | speedOfSound*rollOff - velocityOfSource*velocityScaleFactor |
+ *
+ * For head and sound moving away from each other, velocityRatio (< 1.0) is:
+ *
+ * | speedOfSound*rollOff - velocityOfHead*velocityScaleFactor |
+ * | ------------------------------------------------------------- |
+ * | speedOfSound*rollOff + velocityOfSource*velocityScaleFactor |
+ *
+ * where frequencyScaleFactor, rollOff, velocityScaleFactor all come from
+ * the active AuralAttributes parameters.
+ * The following special cases must be test for AuralAttribute parameters:
+ * rolloff
+ * Value MUST be > zero for any sound to be heard!
+ * If value is zero, all sounds affected by AuralAttribute region are silent.
+ * velocityScaleFactor
+ * Value MUST be > zero for any sound to be heard!
+ * If value is zero, all sounds affected by AuralAttribute region are paused.
+ * frequencyScaleFactor
+ * Value of zero disables Doppler calculations:
+ * Sfreq' = Sfreq * frequencyScaleFactor
+ *
+ * This rate is passed to device drive as a change to playback sample
+ * rate, in this case the frequency need not be known.
+ *
+ * Return value of zero denotes no change
+ * Return value of -1 denotes ERROR
+ */
+ float calculateDoppler(AuralParameters attribs) {
+ double sampleRateRatio = 1.0;
+ double headVelocity = 0.0; // in milliseconds
+ double soundVelocity = 0.0; // in milliseconds
+ double distanceSourceToHead = 0.0; // in meters
+ double lastDistanceSourceToHead = 0.0; // in meters
+ float speedOfSound = attribs.SPEED_OF_SOUND;
+ double numerator = 1.0;
+ double denominator = 1.0;
+ int direction = NO_CHANGE; // sound movement away or towards listener
+
+ Point3f lastXformPosition;
+ Point3f lastXformCenterEar;
+ Point3f xformPosition;
+ Point3f xformCenterEar;
+ float averagedSoundDistances = 0.0f;
+ float averagedEarsDistances = 0.0f;
+
+ /*
+ * Average the differences between the last MAX_DISTANCE
+ * sound positions and head positions
+ */
+ if (!averageDistances) {
+ // TODO: Use some EPSilion to do 'equals' test against
+ if (dopplerFlag)
+ debugPrint("JSPositionalSample.calculateDoppler - " +
+ "not enough distance data collected, " +
+ "dopplerRatio set to zero");
+ // can't calculate change in direction
+ return 0.0f; // sample rate ratio is zero
+ }
+
+ lastXformPosition = positions[lastIndex];
+ lastXformCenterEar = centerEars[lastIndex];
+ xformPosition = positions[currentIndex];
+ xformCenterEar = centerEars[currentIndex];
+ distanceSourceToHead = xformPosition.distance(xformCenterEar);
+ lastDistanceSourceToHead = lastXformPosition.distance(lastXformCenterEar);
+ if (dopplerFlag) {
+ debugPrint("JSPositionalSample.calculateDoppler - distances: " +
+ "current,last = " + distanceSourceToHead + ", " +
+ lastDistanceSourceToHead );
+ debugPrint(" " +
+ "current position = " +
+ xformPosition.x + ", " + xformPosition.y +
+ ", " + xformPosition.z);
+ debugPrint(" " +
+ "current ear = " +
+ xformCenterEar.x + ", " + xformCenterEar.y +
+ ", " + xformCenterEar.z);
+ debugPrint(" " +
+ "last position = " +
+ lastXformPosition.x + ", " + lastXformPosition.y +
+ ", " + lastXformPosition.z);
+ debugPrint(" " +
+ "last ear = " +
+ lastXformCenterEar.x + ", " + lastXformCenterEar.y +
+ ", " + lastXformCenterEar.z);
+ }
+ if (distanceSourceToHead == lastDistanceSourceToHead) {
+ // TODO: Use some EPSilion to do 'equals' test against
+ if (dopplerFlag)
+ debugPrint("JSPositionalSample.calculateDoppler - " +
+ "distance diff = 0, dopplerRatio set to zero");
+ // can't calculate change in direction
+ return 0.0f; // sample rate ratio is zero
+ }
+
+ deltaTime = times[currentIndex] - times[firstIndex];
+ for (int i=0; i<(MAX_DISTANCES-1); i++) {
+ averagedSoundDistances += positions[i+1].distance(positions[i]);
+ averagedEarsDistances += centerEars[i+1].distance(centerEars[i]);
+ }
+ averagedSoundDistances /= (MAX_DISTANCES-1);
+ averagedEarsDistances /= (MAX_DISTANCES-1);
+ soundVelocity = averagedSoundDistances/deltaTime;
+ headVelocity = averagedEarsDistances/deltaTime;
+ if (dopplerFlag) {
+ debugPrint(" " +
+ "delta time = " + deltaTime );
+ debugPrint(" " +
+ "soundPosition delta = " +
+ xformPosition.distance(lastXformPosition));
+ debugPrint(" " +
+ "soundVelocity = " + soundVelocity);
+ debugPrint(" " +
+ "headPosition delta = " +
+ xformCenterEar.distance(lastXformCenterEar));
+ debugPrint(" " +
+ "headVelocity = " + headVelocity);
+ }
+ if (attribs != null) {
+
+ float rolloff = attribs.rolloff;
+ float velocityScaleFactor = attribs.velocityScaleFactor;
+ if (rolloff != 1.0f) {
+ speedOfSound *= rolloff;
+ if (dopplerFlag)
+ debugPrint(" " +
+ "attrib rollof = " + rolloff);
+ }
+ if (velocityScaleFactor != 1.0f) {
+ soundVelocity *= velocityScaleFactor;
+ headVelocity *= velocityScaleFactor;
+ if (dopplerFlag) {
+ debugPrint(" " +
+ "attrib velocity scale factor = " +
+ velocityScaleFactor );
+ debugPrint(" " +
+ "new soundVelocity = " + soundVelocity);
+ debugPrint(" " +
+ "new headVelocity = " + headVelocity);
+ }
+ }
+ }
+ if (distanceSourceToHead < lastDistanceSourceToHead) {
+ // sound and head moving towards each other
+ if (dopplerFlag)
+ debugPrint(" " +
+ "moving towards...");
+ direction = TOWARDS;
+ numerator = speedOfSound + headVelocity;
+ denominator = speedOfSound - soundVelocity;
+ }
+ else {
+ // sound and head moving away from each other
+ // note: no change in distance case covered above
+ if (dopplerFlag)
+ debugPrint(" " +
+ "moving away...");
+ direction = AWAY;
+ numerator = speedOfSound - headVelocity;
+ denominator = speedOfSound + soundVelocity;
+ }
+ if (numerator <= 0.0) {
+ if (dopplerFlag)
+ debugPrint("JSPositionalSample.calculateDoppler: " +
+ "BOOM!! - velocity of head > speed of sound");
+ return -1.0f;
+ }
+ else if (denominator <= 0.0) {
+ if (dopplerFlag)
+ debugPrint("JSPositionalSample.calculateDoppler: " +
+ "BOOM!! - velocity of sound source negative");
+ return -1.0f;
+ }
+ else {
+ if (dopplerFlag)
+ debugPrint("JSPositionalSample.calculateDoppler: " +
+ "numerator = " + numerator +
+ ", denominator = " + denominator );
+ sampleRateRatio = numerator / denominator;
+ }
+
+/********
+ IF direction WERE important to calling method...
+ * Return value greater than 0 denotes direction of sound source is
+ * towards the listener
+ * Return value less than 0 denotes direction of sound source is
+ * away from the listener
+ if (direction == AWAY)
+ return -((float)sampleRateRatio);
+ else
+ return (float)sampleRateRatio;
+*********/
+ return (float)sampleRateRatio;
+ }
+
+ void updateEar(int dirtyFlags, View view) {
+ if (debugFlag)
+ debugPrint("*** updateEar fields");
+ // xform Ear
+ Point3f xformCenterEar = new Point3f();
+ if (!calculateNewEar(dirtyFlags, view, xformCenterEar)) {
+ if (debugFlag)
+ debugPrint("calculateNewEar returned false");
+ return;
+ }
+ // store ear and increment indices ONLY if there is an actual change
+ if (xformCenterEar.x == centerEars[currentIndex].x &&
+ xformCenterEar.y == centerEars[currentIndex].y &&
+ xformCenterEar.z == centerEars[currentIndex].z ) {
+ if (debugFlag)
+ debugPrint(" No change in ear, so don't reset");
+ return;
+ }
+ // store xform Ear
+ incrementIndices();
+ times[currentIndex] = System.currentTimeMillis();
+ centerEars[currentIndex].set(xformCenterEar);
+ // since this is a change to the head position and not the sound
+ // position save the last sound position into the current element
+ if (numDistances > 1)
+ positions[currentIndex].set(positions[lastIndex]);
+ }
+
+ boolean calculateNewEar(int dirtyFlags, View view, Point3f xformCenterEar) {
+ /*
+ * Transform ear position (from Head) into Virtual World Coord space
+ */
+ Point3d earPosition = new Point3d(); // temporary double Point
+
+ // TODO: check dirty flags coming in
+ // For now, recalculate ear positions by forcing earsXformed false
+ boolean earsXformed = false;
+ if (!earsXformed) {
+ if (view != null) {
+ PhysicalBody body = view.getPhysicalBody();
+ if (body != null) {
+
+ // Get Head Coord. to Virtual World transform
+ // TODO: re-enable this when userHeadToVworld is
+ // implemented correctly!!!
+ Transform3D headToVwrld = new Transform3D();
+ view.getUserHeadToVworld(headToVwrld);
+ if (debugFlag) {
+ debugPrint("user head to Vwrld colum-major:");
+ double[] matrix = new double[16];
+ headToVwrld.get(matrix);
+ debugPrint("JSPosSample " + matrix[0]+", " +
+ matrix[1]+", "+matrix[2]+", "+matrix[3]);
+ debugPrint("JSPosSample " + matrix[4]+", " +
+ matrix[5]+", "+matrix[6]+", "+matrix[7]);
+ debugPrint("JSPosSample " + matrix[8]+", " +
+ matrix[9]+", "+matrix[10]+", "+matrix[11]);
+ debugPrint("JSPosSample " + matrix[12]+", " +
+ matrix[13]+", "+matrix[14]+", "+matrix[15]);
+ }
+
+ // Get left and right ear positions in Head Coord.s
+ // Transforms left and right ears to Virtual World coord.s
+ body.getLeftEarPosition(earPosition);
+ xformLeftEar.x = (float)earPosition.x;
+ xformLeftEar.y = (float)earPosition.y;
+ xformLeftEar.z = (float)earPosition.z;
+ body.getRightEarPosition(earPosition);
+ xformRightEar.x = (float)earPosition.x;
+ xformRightEar.y = (float)earPosition.y;
+ xformRightEar.z = (float)earPosition.z;
+ headToVwrld.transform(xformRightEar);
+ headToVwrld.transform(xformLeftEar);
+ // Transform head viewing (Z) axis to Virtual World coord.s
+ xformHeadZAxis.set(0.0f, 0.0f, -1.0f); // Va
+ headToVwrld.transform(xformHeadZAxis);
+
+ // calculate the new (current) mid-point between the ears
+ // find the mid point between left and right ear positions
+ xformCenterEar.x = xformLeftEar.x +
+ ((xformRightEar.x - xformLeftEar.x)*0.5f);
+ xformCenterEar.y = xformLeftEar.y +
+ ((xformRightEar.y - xformLeftEar.y)*0.5f);
+ xformCenterEar.z = xformLeftEar.z +
+ ((xformRightEar.z - xformLeftEar.z)*0.5f);
+ // TODO: when head changes earDirty should be set!
+ // earDirty = false;
+ if (debugFlag) {
+ debugPrint(" earXformed CALCULATED");
+ debugPrint(" xformCenterEar = " +
+ xformCenterEar.x + " " +
+ xformCenterEar.y + " " +
+ xformCenterEar.z );
+ }
+ earsXformed = true;
+ } // end of body NOT null
+ } // end of view NOT null
+ } // end of earsDirty
+ else {
+ // TODO: use existing transformed ear positions
+ }
+
+ if (!earsXformed) {
+ // uses the default head position of (0.0, -0.03, 0.095)
+ if (debugFlag)
+ debugPrint(" earXformed NOT calculated");
+ }
+ return earsXformed;
+ }
+
+ /**
+ * Render this sample
+ *
+ * Calculate the audiodevice parameters necessary to spatially play this
+ * sound.
+ */
+ public void render(int dirtyFlags, View view, AuralParameters attribs) {
+ if (debugFlag)
+ debugPrint("JSPositionalSample.render");
+ updateEar(dirtyFlags, view);
+
+ /*
+ * Time to check velocities and change the playback rate if necessary...
+ *
+ * Rolloff value MUST be > zero for any sound to be heard!
+ * If rolloff is zero, all sounds affected by AuralAttribute region
+ * are silent.
+ * FrequencyScaleFactor value MUST be > zero for any sound to be heard!
+ * since Sfreq' = Sfreq * frequencyScaleFactor.
+ * If FrequencyScaleFactor is zero, all sounds affected by
+ * AuralAttribute region are paused.
+ * VelocityScaleFactor value of zero disables Doppler calculations.
+ *
+ * Scale 'Doppler' rate (or lack of Doppler) by frequencyScaleFactor.
+ */
+ float dopplerRatio = 1.0f;
+ if (attribs != null) {
+ float rolloff = attribs.rolloff;
+ float frequencyScaleFactor = attribs.frequencyScaleFactor;
+ float velocityScaleFactor = attribs.velocityScaleFactor;
+ if (debugFlag || dopplerFlag)
+ debugPrint("JSPositionalSample: attribs NOT null");
+ if (rolloff <= 0.0f) {
+ if (debugFlag)
+ debugPrint(" rolloff = " + rolloff + " <= 0.0" );
+ // TODO: Make sound silent
+ // return ???
+ }
+ else if (frequencyScaleFactor <= 0.0f) {
+ if (debugFlag)
+ debugPrint(" freqScaleFactor = " + frequencyScaleFactor +
+ " <= 0.0" );
+ // TODO: Pause sound silent
+ // return ???
+ }
+ else if (velocityScaleFactor > 0.0f) {
+ if (debugFlag || dopplerFlag)
+ debugPrint(" velocityScaleFactor = " +
+ velocityScaleFactor);
+/*******
+ if (deltaTime > 0) {
+*******/
+ // Doppler can be calculated after the second time
+ // updateXformParams() is executed
+ dopplerRatio = calculateDoppler(attribs);
+
+ if (dopplerRatio == 0.0f) {
+ // dopplerRatio zeroo denotes no changed
+ // TODO: But what if frequencyScaleFactor has changed
+ if (debugFlag) {
+ debugPrint("JSPositionalSample: render: " +
+ "dopplerRatio returned zero; no change");
+ }
+ }
+ else if (dopplerRatio == -1.0f) {
+ // error returned by calculateDoppler
+ if (debugFlag) {
+ debugPrint("JSPositionalSample: render: " +
+ "dopplerRatio returned = " +
+ dopplerRatio + "< 0");
+ }
+ // TODO: Make sound silent
+ // return ???
+ }
+ else if (dopplerRatio > 0.0f) {
+ // rate could be changed
+ rateRatio = dopplerRatio * frequencyScaleFactor *
+ getRateScaleFactor();
+ if (debugFlag) {
+ debugPrint(" scaled by frequencyScaleFactor = " +
+ frequencyScaleFactor );
+ }
+ }
+/******
+ }
+ else {
+ if (debugFlag)
+ debugPrint("deltaTime <= 0 - skip Doppler calc");
+ }
+******/
+ }
+ else { // auralAttributes not null but velocityFactor <= 0
+ // Doppler is disabled
+ rateRatio = frequencyScaleFactor * getRateScaleFactor();
+ }
+ }
+ /*
+ * since aural attributes undefined, default values are used,
+ * thus no Doppler calculated
+ */
+ else {
+ if (debugFlag || dopplerFlag)
+ debugPrint("JSPositionalSample: attribs null");
+ rateRatio = 1.0f;
+ }
+
+ this.panSample(attribs);
+ }
+
+ /* *****************
+ *
+ * Calculate Angular Gain
+ *
+ * *****************/
+ /*
+ * Calculates the Gain scale factor applied to the overall gain for
+ * a sound based on angle between a sound's projected direction and the
+ * vector between the sounds position and center ear.
+ *
+ * For Point Sounds this value is always 1.0f.
+ */
+ float calculateAngularGain() {
+ return(1.0f);
+ }
+
+ /* *****************
+ *
+ * Calculate Filter
+ *
+ * *****************/
+ /*
+ * Calculates the low-pass cutoff frequency filter value applied to the
+ * a sound based on both:
+ * Distance Filter (from Aural Attributes) based on distance
+ * between the sound and the listeners position
+ * Angular Filter (for Directional Sounds) based on the angle
+ * between a sound's projected direction and the
+ * vector between the sounds position and center ear.
+ * The lowest of these two filter is used.
+ * This filter value is stored into the sample's filterFreq field.
+ */
+ void calculateFilter(float distance, AuralParameters attribs) {
+ // setting filter cutoff freq to 44.1kHz which, in this
+ // implementation, is the same as not performing filtering
+ float distanceFilter = 44100.0f;
+ float angularFilter = 44100.0f;
+ int arrayLength = attribs.getDistanceFilterLength();
+ int filterType = attribs.getDistanceFilterType();
+ boolean distanceFilterFound = false;
+ boolean angularFilterFound = false;
+ if ((filterType != AuralParameters.NO_FILTERING) && arrayLength > 0) {
+ double[] distanceArray = new double[arrayLength];
+ float[] cutoffArray = new float[arrayLength];
+ attribs.getDistanceFilter(distanceArray, cutoffArray);
+
+ if (debugFlag) {
+ debugPrint("distanceArray cutoffArray");
+ for (int i=0; i<arrayLength; i++)
+ debugPrint((float)(distanceArray[i]) + ", " + cutoffArray[i]);
+ }
+ distanceFilter = findFactor((double)distance,
+ distanceArray, cutoffArray);
+ if (distanceFilter < 0.0f)
+ distanceFilterFound = false;
+ else
+ distanceFilterFound = true;
+ }
+ else {
+ distanceFilterFound = false;
+ distanceFilter = -1.0f;
+ }
+
+ if (debugFlag)
+ debugPrint(" calculateFilter arrayLength = " + arrayLength);
+
+ // Angular filter only applies to directional sound sources.
+ angularFilterFound = false;
+ angularFilter = -1.0f;
+
+ filterFlag = distanceFilterFound || angularFilterFound;
+ filterFreq = distanceFilter;
+ if (debugFlag)
+ debugPrint(" calculateFilter flag,freq = " + filterFlag +
+ "," + filterFreq );
+ }
+
+ /* *****************
+ *
+ * Find Factor
+ *
+ * *****************/
+ /*
+ * Interpolates the correct output factor given a 'distance' value
+ * and references to the distance array and factor array used in
+ * the calculation. These array parameters could be either linear or
+ * angular distance arrays, or filter arrays.
+ * The values in the distance array are monotonically increasing.
+ * This method looks at pairs of distance array values to find which
+ * pair the input distance argument is between distanceArray[index] and
+ * distanceArray[index+1].
+ * The index is used to get factorArray[index] and factorArray[index+1].
+ * Then the ratio of the 'distance' between this pair of distanceArray
+ * values is used to scale the two found factorArray values proportionally.
+ * The resulting factor is returned, unless there is an error, then -1.0
+ * is returned.
+ */
+ float findFactor(double distance,
+ double[] distanceArray, float[] factorArray) {
+ int index, lowIndex, highIndex, indexMid;
+
+ if (debugFlag)
+ debugPrint("JSPositionalSample.findFactor entered");
+
+ /*
+ * Error checking
+ */
+ if (distanceArray == null || factorArray == null) {
+ if (debugFlag)
+ debugPrint(" findFactor: arrays null");
+ return -1.0f; // no value
+ }
+ int arrayLength = distanceArray.length;
+ if (arrayLength < 2) {
+ if (debugFlag)
+ debugPrint(" findFactor: arrays length < 2");
+ return -1.0f; // no value
+ }
+ int largestIndex = arrayLength - 1;
+
+ /*
+ * Calculate distanceGain scale factor
+ */
+ if (distance >= distanceArray[largestIndex]) {
+ if (debugFlag) {
+ debugPrint(" findFactor: distance > " +
+ distanceArray[largestIndex]);
+ debugPrint(" distanceArray length = "+ arrayLength);
+ }
+ return factorArray[largestIndex];
+ }
+ else if (distance <= distanceArray[0]) {
+ if (debugFlag)
+ debugPrint(" findFactor: distance < " +
+ distanceArray[0]);
+ return factorArray[0];
+ }
+ /*
+ * Distance between points within attenuation array.
+ * Use binary halfing of distance array
+ */
+ else {
+ lowIndex = 0;
+ highIndex = largestIndex;
+ if (debugFlag)
+ debugPrint(" while loop to find index: ");
+ while (lowIndex < (highIndex-1)) {
+ if (debugFlag) {
+ debugPrint(" lowIndex " + lowIndex +
+ ", highIndex " + highIndex);
+ debugPrint(" d.A. pair for lowIndex " +
+ distanceArray[lowIndex] + ", " + factorArray[lowIndex] );
+ debugPrint(" d.A. pair for highIndex " +
+ distanceArray[highIndex] + ", " + factorArray[highIndex] );
+ }
+ /*
+ * we can assume distance is between distance atttenuation vals
+ * distanceArray[lowIndex] and distanceArray[highIndex]
+ * calculate gain scale factor based on distance
+ */
+ if (distanceArray[lowIndex] >= distance) {
+ if (distance < distanceArray[lowIndex]) {
+ if (internalErrors)
+ debugPrint("Internal Error: binary halving in " +
+ " findFactor failed; distance < index value");
+ }
+ if (debugFlag) {
+ debugPrint( " index == distanceGain " +
+ lowIndex);
+ debugPrint(" findFactor returns [LOW=" +
+ lowIndex + "] " + factorArray[lowIndex]);
+ }
+ // take value of scale factor directly from factorArray
+ return factorArray[lowIndex];
+ }
+ else if (distanceArray[highIndex] <= distance) {
+ if (distance > distanceArray[highIndex]) {
+ if (internalErrors)
+ debugPrint("Internal Error: binary halving in " +
+ " findFactor failed; distance > index value");
+ }
+ if (debugFlag) {
+ debugPrint( " index == distanceGain " +
+ highIndex);
+ debugPrint(" findFactor returns [HIGH=" +
+ highIndex + "] " + factorArray[highIndex]);
+ }
+ // take value of scale factor directly from factorArray
+ return factorArray[highIndex];
+ }
+ if (distance > distanceArray[lowIndex] &&
+ distance < distanceArray[highIndex] ) {
+ indexMid = lowIndex + ((highIndex - lowIndex) / 2);
+ if (distance <= distanceArray[indexMid])
+ // value of distance in lower "half" of list
+ highIndex = indexMid;
+ else // value if distance in upper "half" of list
+ lowIndex = indexMid;
+ }
+ } /* of while */
+
+ /*
+ * ratio: distance from listener to sound source
+ * between lowIndex and highIndex times
+ * attenuation value between lowIndex and highIndex
+ * gives linearly interpolationed attenuation value
+ */
+ if (debugFlag) {
+ debugPrint( " ratio calculated using lowIndex " +
+ lowIndex + ", highIndex " + highIndex);
+ debugPrint( " d.A. pair for lowIndex " +
+ distanceArray[lowIndex]+", "+factorArray[lowIndex] );
+ debugPrint( " d.A. pair for highIndex " +
+ distanceArray[highIndex]+", "+factorArray[highIndex] );
+ }
+
+ float outputFactor =
+ ((float)(((distance - distanceArray[lowIndex])/
+ (distanceArray[highIndex] - distanceArray[lowIndex]) ) ) *
+ (factorArray[highIndex] - factorArray[lowIndex]) ) +
+ factorArray[lowIndex] ;
+ if (debugFlag)
+ debugPrint(" findFactor returns " + outputFactor);
+ return outputFactor;
+ }
+ }
+
+ /**
+ * CalculateDistanceAttenuation
+ *
+ * Simply calls generic (for PointSound) 'findFactor()' with
+ * a single set of attenuation distance and gain scale factor arrays.
+ */
+ float calculateDistanceAttenuation(float distance) {
+ float factor = 1.0f;
+ factor = findFactor((double)distance, this.attenuationDistance,
+ this.attenuationGain);
+ if (factor >= 0.0)
+ return (factor);
+ else
+ return (1.0f);
+ }
+
+ /* ******************
+ *
+ * Pan Sample
+ *
+ * ******************/
+ /*
+ * Sets pan and delay for a single sample associated with this Sound.
+ * Front and Back quadrants are treated the same.
+ */
+ void panSample(AuralParameters attribs) {
+ int quadrant = 1;
+ float intensityHigh = 1.0f;
+ float intensityLow = 0.125f;
+ float intensityDifference = intensityHigh - intensityLow;
+
+ //TODO: time around "average" default head
+ // int delayHigh = 32; // 32.15 samples = .731 ms
+ // int delayLow = 0;
+
+ float intensityOffset; // 0.0 -> 1.0 then 1.0 -> 0.0 for full rotation
+ float halfX;
+ int id;
+ int err;
+
+ float nearZero = 0.000001f;
+ float nearOne = 0.999999f;
+ float nearNegativeOne = -nearOne;
+ float halfPi = (float)Math.PI * 0.5f;
+ /*
+ * Parameters used for IID and ITD equations.
+ * Name of parameters (as used in Guide, E.3) are denoted in comments.
+ */
+ float distanceSourceToCenterEar = 0.0f; // Dh
+ float lastDistanceSourceToCenterEar = 0.0f;
+ float distanceSourceToRightEar = 0.0f; // Ef or Ec
+ float distanceSourceToLeftEar = 0.0f; // Ef or Ec
+ float distanceBetweenEars = 0.18f; // De
+ float radiusOfHead = 0.0f; // De/2
+ float radiusOverDistanceToSource = 0.0f; // De/2 * 1/Dh
+
+ float alpha = 0.0f; // 'alpha'
+ float sinAlpha = 0.0f; // sin(alpha);
+ float gamma = 0.0f; // 'gamma'
+
+ // Speed of Sound (unaffected by rolloff) in millisec/meters
+ float speedOfSound = attribs.SPEED_OF_SOUND;
+ float invSpeedOfSound = 1.0f / attribs.SPEED_OF_SOUND;
+
+ float sampleRate = 44.1f; // 44 samples/millisec
+
+ boolean rightEarClosest = false;
+ boolean soundFromBehind = false;
+
+ float distanceGain = 1.0f;
+ float allGains = this.gain; // product of gain scale factors
+
+ Point3f workingPosition = new Point3f();
+ Point3f workingCenterEar = new Point3f();
+
+ // Asuumes that head and ear positions can be retrieved from universe
+
+ Vector3f mixScale = new Vector3f(); // for mix*Samples code
+
+ // Use transformed position of this sound
+ workingPosition.set(positions[currentIndex]);
+ workingCenterEar.set(centerEars[currentIndex]);
+ if (debugFlag) {
+ debugPrint("panSample:workingPosition from" +
+ " positions["+currentIndex+"] -> " +
+ workingPosition.x + ", " + workingPosition.y + ", " +
+ workingPosition.z + " for pointSound " + this);
+ debugPrint("panSample:workingCenterEar " +
+ workingCenterEar.x + " " + workingCenterEar.y + " " +
+ workingCenterEar.z);
+ debugPrint("panSample:xformLeftEar " +
+ xformLeftEar.x + " " + xformLeftEar.y + " " +
+ xformLeftEar.z);
+ debugPrint("panSample:xformRightEar " +
+ xformRightEar.x + " " + xformRightEar.y + " " +
+ xformRightEar.z);
+ }
+
+ // Create the vectors from the sound source to head positions
+ sourceToCenterEar.x = workingCenterEar.x - workingPosition.x;
+ sourceToCenterEar.y = workingCenterEar.y - workingPosition.y;
+ sourceToCenterEar.z = workingCenterEar.z - workingPosition.z;
+ sourceToRightEar.x = xformRightEar.x - workingPosition.x;
+ sourceToRightEar.y = xformRightEar.y - workingPosition.y;
+ sourceToRightEar.z = xformRightEar.z - workingPosition.z;
+ sourceToLeftEar.x = xformLeftEar.x - workingPosition.x;
+ sourceToLeftEar.y = xformLeftEar.y - workingPosition.y;
+ sourceToLeftEar.z = xformLeftEar.z - workingPosition.z;
+
+ /*
+ * get distances from SoundSource to
+ * (i) head origin
+ * (ii) right ear
+ * (iii) left ear
+ */
+ distanceSourceToCenterEar = workingPosition.distance(workingCenterEar);
+ distanceSourceToRightEar = workingPosition.distance(xformRightEar);
+ distanceSourceToLeftEar = workingPosition.distance(xformLeftEar);
+ distanceBetweenEars = xformRightEar.distance(xformLeftEar);
+ if (debugFlag)
+ debugPrint(" distance from left,right ears to source: = (" +
+ distanceSourceToLeftEar + ", " + distanceSourceToRightEar + ")");
+
+ radiusOfHead = distanceBetweenEars * 0.5f;
+ if (debugFlag)
+ debugPrint(" radius of head = " + radiusOfHead );
+ radiusOverDistanceToSource = // De/2 * 1/Dh
+ radiusOfHead/distanceSourceToCenterEar;
+ if (debugFlag)
+ debugPrint(" radius over distance = " + radiusOverDistanceToSource );
+ if (debugFlag) {
+ debugPrint("panSample:source to center ear " +
+ sourceToCenterEar.x + " " + sourceToCenterEar.y + " " +
+ sourceToCenterEar.z );
+ debugPrint("panSample:xform'd Head ZAxis " +
+ xformHeadZAxis.x + " " + xformHeadZAxis.y + " " +
+ xformHeadZAxis.z );
+ debugPrint("panSample:length of sourceToCenterEar " +
+ sourceToCenterEar.length());
+ debugPrint("panSample:length of xformHeadZAxis " +
+ xformHeadZAxis.length());
+ }
+
+ // Dot Product
+ double dotProduct = (double)(
+ (sourceToCenterEar.dot(xformHeadZAxis))/
+ (sourceToCenterEar.length() * xformHeadZAxis.length()));
+ if (debugFlag)
+ debugPrint( " dot product = " + dotProduct );
+ alpha = (float)(Math.acos(dotProduct));
+ if (debugFlag)
+ debugPrint( " alpha = " + alpha );
+
+ if (alpha > halfPi) {
+ if (debugFlag)
+ debugPrint(" sound from behind");
+ soundFromBehind = true;
+ alpha = (float)Math.PI - alpha;
+ if (debugFlag)
+ debugPrint( " PI minus alpha =>" + alpha );
+ }
+ else {
+ soundFromBehind = false;
+ if (debugFlag)
+ debugPrint(" sound from in front");
+ }
+
+ gamma = (float)(Math.acos(radiusOverDistanceToSource));
+ if (debugFlag)
+ debugPrint( " gamma " + gamma );
+
+ rightEarClosest =
+ (distanceSourceToRightEar>distanceSourceToLeftEar) ? false : true ;
+ /*
+ * Determine the quadrant sound is in
+ */
+ if (rightEarClosest) {
+ if (debugFlag)
+ debugPrint( " right ear closest");
+ if (soundFromBehind)
+ quadrant = 4;
+ else
+ quadrant = 1;
+ }
+ else {
+ if (debugFlag)
+ debugPrint( " left ear closest");
+ if (soundFromBehind)
+ quadrant = 3;
+ else
+ quadrant = 2;
+ }
+ sinAlpha = (float)(Math.sin((double)alpha));
+ if (sinAlpha < 0.0) sinAlpha = -sinAlpha;
+ if (debugFlag)
+ debugPrint( " sin(alpha) " + sinAlpha );
+
+ /*
+ * The path from sound source to the farthest ear is always indirect
+ * (it wraps around part of the head).
+ * Calculate distance wrapped around the head for farthest ear
+ */
+ float DISTANCE = (float)Math.sqrt((double)
+ distanceSourceToCenterEar * distanceSourceToCenterEar +
+ radiusOfHead * radiusOfHead);
+ if (debugFlag)
+ debugPrint( " partial distance from edge of head to source = "
+ + distanceSourceToCenterEar);
+ if (rightEarClosest) {
+ distanceSourceToLeftEar =
+ DISTANCE + radiusOfHead * (halfPi+alpha-gamma);
+ if (debugFlag)
+ debugPrint(" new distance from left ear to source = "
+ + distanceSourceToLeftEar);
+ }
+ else {
+ distanceSourceToRightEar =
+ DISTANCE + radiusOfHead * (halfPi+alpha-gamma);
+ if (debugFlag)
+ debugPrint(" new distance from right ear to source = "
+ + distanceSourceToRightEar);
+ }
+ /*
+ * The path from the source source to the closest ear could either
+ * be direct or indirect (wraps around part of the head).
+ * if sinAlpha >= radiusOverDistance path of sound to closest ear
+ * is direct, otherwise it is indirect
+ */
+ if (sinAlpha < radiusOverDistanceToSource) {
+ if (debugFlag)
+ debugPrint(" closest path is also indirect ");
+ // Path of sound to closest ear is indirect
+
+ if (rightEarClosest) {
+ distanceSourceToRightEar =
+ DISTANCE + radiusOfHead * (halfPi-alpha-gamma);
+ if (debugFlag)
+ debugPrint(" new distance from right ear to source = "
+ + distanceSourceToRightEar);
+ }
+ else {
+ distanceSourceToLeftEar =
+ DISTANCE + radiusOfHead * (halfPi-alpha-gamma);
+ if (debugFlag)
+ debugPrint(" new distance from left ear to source = "
+ + distanceSourceToLeftEar);
+ }
+ }
+ else {
+ if (debugFlag)
+ debugPrint(" closest path is direct ");
+ if (rightEarClosest) {
+ if (debugFlag)
+ debugPrint(" direct distance from right ear to source = "
+ + distanceSourceToRightEar);
+ }
+ else {
+ if (debugFlag)
+ debugPrint(" direct distance from left ear to source = "
+ + distanceSourceToLeftEar);
+ }
+ }
+
+ /**
+ * Short-cut taken. Rather than using actual delays from source
+ * (where the overall distances would be taken into account in
+ * determining delay) the difference in the left and right delay
+ * are applied.
+ * This approach will be preceptibly wrong for sound sources that
+ * are very far away from the listener so both ears would have
+ * large delay.
+ */
+ sampleRate = channel.rateInHz * (0.001f); // rate in milliseconds
+ if (rightEarClosest) {
+ rightDelay = 0;
+ leftDelay = (int)((distanceSourceToLeftEar - distanceSourceToRightEar) *
+ invSpeedOfSound * sampleRate);
+ }
+ else {
+ leftDelay = 0;
+ rightDelay = (int)((distanceSourceToRightEar - distanceSourceToLeftEar) *
+ invSpeedOfSound * sampleRate);
+ }
+
+ if (debugFlag) {
+ debugPrint(" using inverted SoS = " + invSpeedOfSound);
+ debugPrint(" and sample rate = " + sampleRate);
+ debugPrint(" left and right delay = ("
+ + leftDelay + ", " + rightDelay + ")");
+ }
+
+ // What should the gain be for the different ears???
+ // TODO: now using a hack that sets gain based on a unit circle!!!
+ workingPosition.sub(workingCenterEar); // offset sound pos. by head origin
+ // normalize; put Sound on unit sphere around head origin
+ workingPosition.scale(1.0f/distanceSourceToCenterEar);
+ if (debugFlag)
+ debugPrint(" workingPosition after unitization " +
+ workingPosition.x+" "+workingPosition.y+" "+workingPosition.z );
+
+ /*
+ * Get the correct distance gain scale factor from attenuation arrays.
+ * This requires that sourceToCenterEar vector has been calculated.
+ */
+ // TODO: now using distance from center ear to source
+ // Using distances from each ear to source would be more accurate
+ distanceGain = calculateDistanceAttenuation(distanceSourceToCenterEar);
+
+ allGains *= distanceGain;
+
+ /*
+ * Add angular gain (for Cone sound)
+ */
+ if (debugFlag)
+ debugPrint(" all Gains (without angular gain) " + allGains);
+ // assume that transfromed Position is already calculated
+ allGains *= this.calculateAngularGain();
+ if (debugFlag)
+ debugPrint(" (incl. angular gain) " + allGains);
+
+ halfX = workingPosition.x/2.0f;
+ if (halfX >= 0)
+ intensityOffset = (intensityDifference * (0.5f - halfX));
+ else
+ intensityOffset = (intensityDifference * (0.5f + halfX));
+
+ /*
+ * For now have delay constant for front back sound for now
+ */
+ if (debugFlag)
+ debugPrint("panSample: quadrant " + quadrant);
+ switch (quadrant) {
+ case 1:
+ // Sound from front, right of center of head
+ case 4:
+ // Sound from back, right of center of head
+ rightGain = allGains * (intensityHigh - intensityOffset);
+ leftGain = allGains * (intensityLow + intensityOffset);
+ break;
+
+ case 2:
+ // Sound from front, left of center of head
+ case 3:
+ // Sound from back, right of center of head
+ leftGain = allGains * (intensityHigh - intensityOffset);
+ rightGain = allGains * (intensityLow + intensityOffset);
+ break;
+ } /* switch */
+ if (debugFlag)
+ debugPrint("panSample: left/rightGain " + leftGain +
+ ", " + rightGain);
+
+ // Combines distance and angular filter to set this sample's current
+ // frequency cutoff value
+ calculateFilter(distanceSourceToCenterEar, attribs);
+
+ } /* panSample() */
+
+// NOTE: setGain in audioengines.Sample is used to set/get user suppled factor
+// this class uses this single gain value to calculate the left and
+// right gain values
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSSample.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSSample.java
new file mode 100755
index 0000000..cc19fda
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSSample.java
@@ -0,0 +1,362 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+/*
+ * Java Sound Sample object
+ *
+ * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs
+ * to be rewritten.
+ */
+
+package com.sun.j3d.audioengines.javasound;
+
+import java.net.URL;
+import java.io.InputStream;
+import javax.media.j3d.*;
+import javax.sound.sampled.*;
+import com.sun.j3d.audioengines.*;
+
+/**
+ * The Sample Class extended for Java Sound Mixer specific audio device.
+ */
+
+class JSSample extends com.sun.j3d.audioengines.Sample
+{
+ /*
+ * NOTE: for this device type there is exactly one sample associated
+ * with each sound.
+ */
+
+ /**
+ * Sound Data Types
+ *
+ * Samples can be processed as streaming or buffered data.
+ * Fully spatializing sound sources may require data to be buffered.
+ *
+ * Sound data specified as Streaming is not copied by the AudioDevice
+ * driver implementation. It is up the application to ensure that
+ * this data is continuously accessible during sound rendering.
+ * Futhermore, full sound spatialization may not be possible, for
+ * all AudioDevice implementations on unbuffered sound data.
+ */
+ static final int STREAMING_AUDIO_DATA = 1;
+ /**
+ * Sound data specified as Buffered is copied by the AudioDevice
+ * driver implementation.
+ */
+ static final int BUFFERED_AUDIO_DATA = 2;
+ /**
+ * MIDI data
+ * TODO: differentiate between STREAMING and BUFFERED MIDI data
+ * right now all MIDI data is buffered
+ */
+ static final int STREAMING_MIDI_DATA = 3;
+ static final int BUFFERED_MIDI_DATA = 3;
+ static final int UNSUPPORTED_DATA_TYPE = -1;
+
+ static final int NULL_SAMPLE = -1;
+
+ /**
+ * sound data types: BUFFERED (cached) or STREAMING (non-cached)
+ */
+ int dataType = BUFFERED_AUDIO_DATA;
+
+ JSChannel channel = null;
+
+ /**
+ * Offset pointer within currently playing sample data
+ */
+ long dataOffset = 0;
+
+ /*
+ * Maintain continuously playing silent sound sources.
+ */
+ long timeDeactivated = 0;
+ long positionDeactivated = 0;
+
+ long sampleLength = 0;
+ long loopStartOffset = 0; // for most this will be 0
+ long loopLength = 0; // for most this is end sample - sampleLength
+ long attackLength = 0; // portion of sample before loop section
+ long releaseLength = 0; // portion of sample after loop section
+
+ float rateRatio = 1.0f;
+ float currentRateRatio = -1.0f; // last actual rate ratio send to device
+ float targetRateRatio = -1.0f;
+ boolean rampRateFlag = false;
+
+ public JSSample() {
+ super();
+ if (debugFlag)
+ debugPrintln("JSSample constructor");
+ }
+
+ // the only public methods are those declared in the audioengines
+ // package as public
+
+ /*
+ * This excutes code necessary to set current fields to their current
+ * correct values before JavaSoundMixer either start or updates the
+ * sample thru calls to JSThread.
+ */
+ public void render(int dirtyFlags, View view, AuralParameters attribs) {
+ if (debugFlag)
+ debugPrint("JSSample.render ");
+ // if this is starting set gain, delay (for Pos), freq rate ...
+ // TODO: NOT SURE - leaving this in for now
+ float freqScaleFactor = attribs.frequencyScaleFactor;
+ if (attribs != null) {
+ if (freqScaleFactor <= 0.0f) {
+ // TODO: Pause Sample
+ }
+ else
+ rateRatio = currentRateRatio * freqScaleFactor;
+ }
+ else
+ rateRatio = currentRateRatio;
+ }
+
+ /**
+ * Clears/re-initialize fields associated with sample data for
+ * this sound,
+ * and frees any device specific data associated with this sample.
+ */
+ public void clear() {
+ super.clear();
+ if (debugFlag)
+ debugPrintln("JSSample.clear() entered");
+ // TODO: unload sound data at device
+// null out samples element that points to this?
+// would this cause samples list size to shrink?
+// if sample elements are never freed then does this mean
+// a have a memory leak?
+ dataType = UNSUPPORTED_DATA_TYPE;
+ dataOffset = 0;
+ timeDeactivated = 0;
+ positionDeactivated = 0;
+ sampleLength = 0;
+ loopStartOffset = 0;
+ loopLength = 0;
+ attackLength = 0;
+ releaseLength = 0;
+ rateRatio = 1.0f;
+ channel = null;
+ if (debugFlag)
+ debugPrintln("JSSample.clear() exited");
+ }
+
+ // @return error true if error occurred
+ boolean load(MediaContainer soundData) {
+ /**
+ * Get the AudioInputStream first.
+ * MediaContiner passed to method assumed to be a clone of the
+ * application node with the query capability bits set on.
+ */
+ String path = soundData.getURLString();
+ URL url = soundData.getURLObject();
+ InputStream inputStream = soundData.getInputStream();
+ boolean cacheFlag = soundData.getCacheEnable();
+ AudioInputStream ais = null;
+ DataLine dataLine = null;
+
+ // TODO: How do we determine if the file is a MIDI file???
+ // for now set dataType to BUFFERED_ or STREAMING_AUDIO_DATA
+ // used to test for ais instanceof AudioMidiInputStream ||
+ // ais instanceof AudioRmfInputStream )
+ // then set dataType = JSSample.BUFFERED_MIDI_DATA;
+ // QUESTION: can non-cached MIDI files ever be supported ?
+ /****************
+ // TODO: when we have a way to determine data type use code below
+ if (dataType==UNSUPPORTED_DATA_TYPE OR error_occurred)
+ clearSound(index);
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer.prepareSound get dataType failed");
+ return true;
+ }
+ *****************/
+ // ...for now just check cacheFlag
+ if (cacheFlag)
+ dataType = BUFFERED_AUDIO_DATA;
+ else
+ dataType = STREAMING_AUDIO_DATA;
+
+ if ((url == null) && (inputStream == null) && (path == null)) {
+ if (debugFlag)
+ debugPrint("JavaSoundMixer.loadSound null data - return error");
+ return true;
+ }
+
+ // get ais
+ if (path != null) {
+ // generate url from string, and pass url to driver
+ if (debugFlag) {
+ debugPrint("JavaSoundMixer.loadSound with path = " + path);
+ }
+ try {
+ url = new URL(path);
+ }
+ catch (Exception e) {
+ // do not throw an exception while rendering
+ return true;
+ }
+ }
+
+ // get DataLine channel based on data type
+ if (dataType == BUFFERED_AUDIO_DATA) {
+ if (debugFlag)
+ debugPrintln("JSSample.load dataType = BUFFERED ");
+ channel = new JSClip();
+ if (debugFlag)
+ debugPrintln(" calls JSClip.initAudioInputStream");
+ if (url != null)
+ ais = channel.initAudioInputStream(url, cacheFlag);
+ else if (inputStream != null)
+ ais = channel.initAudioInputStream(inputStream, cacheFlag);
+ if (ais == null) {
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer.prepareSound " +
+ "initAudioInputStream() failed");
+ return true;
+ }
+ if (debugFlag)
+ debugPrintln(" calls JSClip.initDataLine");
+ dataLine = channel.initDataLine(ais);
+ }
+ else if (dataType == STREAMING_AUDIO_DATA) {
+ if (debugFlag)
+ debugPrintln("JSSample.load dataType = STREAMING ");
+ channel = new JSStream();
+ if (debugFlag)
+ debugPrintln(" calls JSStream.initAudioInputStream");
+ if (url != null)
+ ais = channel.initAudioInputStream(url, cacheFlag);
+ else if (inputStream != null)
+ ais = channel.initAudioInputStream(inputStream, cacheFlag);
+ if (ais == null) {
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer.prepareSound " +
+ "initAudioInputStream() failed");
+ return true;
+ }
+ if (debugFlag)
+ debugPrintln(" calls JSStream.initDataLine");
+ dataLine = channel.initDataLine(ais);
+ }
+ else {
+ if (debugFlag)
+ debugPrintln("JSSample.load doesn't support MIDI yet");
+ }
+ if (dataLine == null) {
+ if (debugFlag)
+ debugPrint("JSSample.load initDataLine failed ");
+ channel = null;
+ return true;
+ }
+ duration = channel.getDuration();
+ if (debugFlag)
+ debugPrint("JSSample.load channel duration = " + duration);
+ /*
+ * Since no error occurred while loading, save all the characteristics
+ * for the sound in the sample.
+ */
+ setDirtyFlags(0xFFFF);
+ setSoundType(soundType);
+ setSoundData(soundData);
+
+ if (debugFlag)
+ debugPrintln("JSSample.load returned without error");
+ return false;
+ }
+
+ void reset() {
+ if (debugFlag)
+ debugPrint("JSSample.reset() exit");
+ rateRatio = 1.0f;
+ }
+
+// TODO: NEED methods for any field accessed by both JSThread and
+// JavaSoundMixer so that we can make these MT safe??
+ /*
+ * Process request for Filtering fields
+ */
+ boolean getFilterFlag() {
+ return false;
+ }
+ float getFilterFreq() {
+ return -1.0f;
+ }
+
+ void setCurrentRateRatio(float ratio) {
+ currentRateRatio = ratio;
+ }
+
+ float getCurrentRateRatio() {
+ return currentRateRatio;
+ }
+
+ void setTargetRateRatio(float ratio) {
+ targetRateRatio = ratio;
+ }
+
+ float getTargetRateRatio() {
+ return targetRateRatio;
+ }
+
+ void setRampRateFlag(boolean flag) {
+ rampRateFlag = flag;
+ }
+
+ boolean getRampRateFlag() {
+ return rampRateFlag;
+ }
+
+ void setDataType(int type) {
+ dataType = type;
+ }
+
+ int getDataType() {
+ return dataType;
+ }
+
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSStream.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSStream.java
new file mode 100755
index 0000000..340e1af
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSStream.java
@@ -0,0 +1,67 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+package com.sun.j3d.audioengines.javasound;
+
+/**
+ * The JSStream class defines audio output methods that call the JavaSound
+ * API methods for streams.
+ *
+ * <p>
+ * NOTE: This class is not yet implemented.
+ */
+
+class JSStream extends JSChannel {
+ private static boolean warningReported = false;
+
+ JSStream() {
+ // Report a "not implemented" warning message
+ if (!warningReported) {
+ System.err.println("***");
+ System.err.println("*** WARNING: JavaSoundMixer: Streaming (uncached) audio not implemented");
+ System.err.println("***");
+ warningReported = true;
+ }
+ }
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSThread.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSThread.java
new file mode 100755
index 0000000..ed91910
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSThread.java
@@ -0,0 +1,855 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+package com.sun.j3d.audioengines.javasound;
+
+/*
+ * JavaSound engine Thread
+ *
+ * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs
+ * to be rewritten. When this is done, we may or may not need this class.
+ */
+
+import javax.media.j3d.*;
+import com.sun.j3d.audioengines.*;
+
+/**
+ * The Thread Class extended for JavaSound Mixer specific audio device
+ * calls that dynamically, in 'real-time" change engine parameters
+ * such as volume/gain and sample-rate/frequency(pitch).
+ */
+
+class JSThread extends com.sun.j3d.audioengines.AudioEngineThread {
+
+ /**
+ * The thread data for this thread
+ */
+ int totalChannels = 0;
+ /**
+ * flags denoting if dynamic gain or rate interpolation is to be performed
+ */
+ boolean rampGain = false;
+
+ // global thread flat rampRate set true only when setTargetRate called
+ // for any sample. but it is cleared only by doWork when no sample
+ // has a need for the rate to be ramped any further.
+ boolean rampRate = false;
+
+/*** TODO:
+ *
+ * scalefactors applied to current sample rate to determine delta changes
+ * in rate (in Hz)
+ *
+ float currentGain = 1.0f;
+ float targetGain = 1.0f;
+***********/
+
+ // reference to engine that created this thread
+ AudioEngine3D audioEngine = null;
+
+ /**
+ * This constructor simply assigns the given id.
+ */
+ JSThread(ThreadGroup t, AudioEngine3DL2 engine) {
+ super(t, "J3D-JavaSoundThread");
+ audioEngine = engine;
+ // TODO: really get total JavaSound channels
+ totalChannels = 32;
+ if (debugFlag)
+ debugPrint("JSThread.constructor("+t+")");
+ }
+
+
+
+ /**
+ * This method performs one iteration of pending work to do
+ *
+ * Wildly "garbled" sounds was caused by unequal changes in delta
+ * time verses delta distances (resulting in jumps in rate factors
+ * calculated for Doppler. This work thread is meant to smoothly
+ * increment/decrement changes in rate (and other future parameters)
+ * until the target value is reached.
+ */
+ synchronized public void doWork() {
+ if (debugFlag)
+ debugPrint("JSThread.doWork()");
+/*******
+ while (rampRate || rampGain) {
+*********/
+/****** DESIGN
+// Loop while sound is playing, reget attributes and gains/reverb,... params
+// update lowlevel params then read modify then copy to line(s)
+
+can keep my own loop count for streams??? not really
+
+*******/
+ // QUESTION: will size ever get smaller after get performed???
+ int numSamples = audioEngine.getSampleListSize();
+ JSSample sample = null;
+ int numRateRamps = 0;
+ for (int index = 0; index < numSamples; index++) {
+ // loop thru samples looking for ones needing rate incremented
+ sample = (JSSample)audioEngine.getSample(index);
+ if (sample == null)
+ continue;
+ if (sample.getRampRateFlag()) {
+ if (debugFlag)
+ debugPrint(" rampRate true");
+ boolean endOfRampReached = adjustRate(sample);
+ sample.setRampRateFlag(!endOfRampReached);
+ if (!endOfRampReached)
+ numRateRamps++;
+ }
+ // TODO: support changes in gain this way as well
+ }
+ if (numRateRamps > 0) {
+ rampRate = true;
+runMonitor(RUN, 0, null);
+ }
+ else
+ rampRate = false;
+/*********
+ try {
+ Thread.sleep(4);
+ } catch (InterruptedException e){}
+*********/
+/********
+ } // while
+*********/
+ // otherwise do nothing
+ }
+
+ int getTotalChannels() {
+ return (totalChannels);
+ }
+
+ /**
+ * Gradually change rate scale factor
+ *
+ * If the rate change is too great suddenly, it sounds like a
+ * jump, so we need to change gradually over time.
+ * Since an octive delta change up is 2.0 but down is 0.5, forced
+ * "max" rate of change is different for both.
+ * @return true if target rate value was reached
+ */
+ boolean adjustRate(JSSample sample) {
+ // QUESTION: what should max delta rate changes be
+ // Using 1/32 of a half-step (1/12 of an octive)???
+ double maxRateChangeDown = 0.00130213;
+ double maxRateChangeUp = 0.00260417;
+
+ double lastActualRateRatio = sample.getCurrentRateRatio();
+ double requestedRateRatio = sample.getTargetRateRatio();
+ boolean endOfRamp = false; // flag denotes if target rate reached
+ if ( lastActualRateRatio > 0 ) {
+ double sampleRateRatio = requestedRateRatio; // in case diff = 0
+ double diff = 0.0;
+ if (debugFlag) {
+ debugPrint("JSThread.adjustRate: between " +
+ lastActualRateRatio + " & " + requestedRateRatio);
+ }
+ diff = requestedRateRatio - lastActualRateRatio;
+ if (diff > 0.0) { // direction of movement is towards listener
+ // inch up towards the requested target rateRatio
+ if (diff >= maxRateChangeUp) {
+ sampleRateRatio = lastActualRateRatio + maxRateChangeUp;
+ if (debugFlag) {
+ debugPrint(" adjustRate: " +
+ "diff >= maxRateChangeUp so ");
+ debugPrint(" adjustRate: " +
+ " sampleRateRatio incremented up by max");
+ }
+ endOfRamp = false; // target value not reached
+ }
+ /*
+ * otherwise delta change is within tolerance
+ * so use requested RateRatio as calculated w/out change
+ */
+ else {
+ sampleRateRatio = requestedRateRatio;
+ if (debugFlag) {
+ debugPrint(" adjustRate: " +
+ " requestedRateRatio reached");
+ }
+ endOfRamp = true; // reached
+ }
+ }
+ else if (diff < 0.0) { // movement is away from listener
+ // inch down towards the requested target rateRatio
+ if ((-diff) >= maxRateChangeDown) {
+ sampleRateRatio = lastActualRateRatio - maxRateChangeDown;
+ if (debugFlag) {
+ debugPrint(" adjustRate: " +
+ "-(diff) >= maxRateChangeUp so ");
+ debugPrint(" adjustRate: " +
+ " sampleRateRatio incremented down by max ");
+ }
+ endOfRamp = false; // target value not reached
+ }
+ /*
+ * otherwise negitive delta change is within tolerance so
+ * use sampleRateRatio as calculated w/out change
+ */
+ else {
+ sampleRateRatio = requestedRateRatio;
+ if (debugFlag) {
+ debugPrint(" adjustRate: " +
+ " requestedRateRatio reached");
+ }
+ endOfRamp = true; // reached
+ }
+ }
+ else // there is no difference between last set and requested rates
+ return true;
+
+ this.setSampleRate(sample, (float)sampleRateRatio);
+ }
+ else {
+ // this is the first time thru with a rate change
+ if (debugFlag) {
+ debugPrint(" adjustRate: " +
+ "last requested rateRatio not set yet " +
+ "so sampleRateRatio left unchanged");
+ }
+ this.setSampleRate(sample, (float)requestedRateRatio);
+ endOfRamp = false; // target value not reached
+ }
+ return endOfRamp;
+ } // adjustRate
+
+ void setSampleRate(JSSample sample, JSAuralParameters attribs) {
+// TODO:
+ }
+
+ // gain set at start sample time as well
+ void setSampleGain(JSSample sample, JSAuralParameters attribs) {
+/*******
+ // take fields as already set in sample and updates gain
+ // called after sample.render performed
+ if (debugFlag)
+ debugPrint("JSThread.setSampleGain()");
+leftGain, rightGain
+ if (debugFlag) {
+ debugPrint(" " +
+ "StereoGain during update " + leftGain +
+ ", " + rightGain);
+ debugPrint(" " +
+ "StereoDelay during update " + leftDelay +
+ ", " + rightDelay);
+ }
+ int dataType = sample.getDataType();
+ int soundType = sample.getSoundType();
+ boolean muted = sample.getMuteFlag();
+
+ if (debugFlag)
+ debugPrint("setStereoGain for sample "+sample+" " + leftGain +
+ ", " + rightGain);
+ if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA ||
+ dataType == JSAuralParameters.BUFFERED_AUDIO_DATA ) {
+ thread.setSampleGain(sample, leftGain);
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ thread.setSampleGain(
+ ((JSPositionalSample)sample).getSecondIndex(), rightGain); thread.setSampleGain(
+ ((JSPositionalSample)sample).getReverbIndex(), reverbGain);
+ }
+ }
+ // TODO: JavaSound does not support MIDI song panning yet
+ else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA ||
+
+ dataType == JSAuralParameters.BUFFERED_MIDI_DATA) {
+ // Stereo samples not used for Midi Song playback
+ thread.setSampleGain(sample, (leftGain+rightGain) );
+ ******
+ // -1.0 far left, 0.0 center, 1.0 far right
+ position = (leftGain - rightGain) / (leftGain + rightGain);
+ JSMidi.setSamplePan(sample, position);
+
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ JSMidi.setSampleGain(
+ ((JSPositionalSample)sample).getSecondIndex(), rightGain); JSMidi.setSampleGain(
+ ((JSPositionalSample)sample).getReverbIndex(), reverbGain);
+ }
+ ******
+ }
+ else {
+ if (debugFlag)
+ debugPrint( "JSThread: Internal Error setSampleGain dataType " +
+ dataType + " invalid");
+ return;
+ }
+ *****
+ // force specific gain
+ // go ahead and set gain immediately
+ this.setSampleGain(sample, scaleFactor);
+ rampGain = false; // disable ramping of gain
+******/
+ }
+
+ void setSampleDelay(JSSample sample, JSAuralParameters attribs) {
+/******
+ // take fields as already set in sample and updates delay
+ // called after sample.render performed
+ // adjust by attrib rolloff
+ float delayTime = attribs.reverbDelay * attribs.rolloff;
+
+ leftDelay = (int)(sample.leftDelay * attribs.rolloff);
+ rightDelay = (int)(sample.rightDelay * attribs.rolloff);
+leftDelay, rightDelay
+ int dataType = sample.getDataType();
+ int soundType = sample.getSoundType();
+ if (debugFlag)
+ debugPrint("setStereoDelay for sample "+sample+" " + leftDelay +
+ ", " + rightDelay);
+ if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA) {
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ JSStream.setSampleDelay(
+ sample, leftDelay);
+ JSStream.setSampleDelay(
+ ((JSPositionalSample)sample).getSecondIndex(), rightDelay);
+ }
+ else
+ JSStream.setSampleDelay(sample, 0);
+ }
+ else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) {
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ JSClip.setSampleDelay(
+ sample, leftDelay);
+ JSClip.setSampleDelay(
+ ((JSPositionalSample)sample).getSecondIndex(), rightDelay);
+ }
+ else
+ JSClip.setSampleDelay(sample, 0);
+ }
+ else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA ||
+
+ dataType == JSAuralParameters.BUFFERED_MIDI_DATA) {
+ ********
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ JSMidi.setSampleDelay(
+ sample, leftDelay);
+ JSMidi.setSampleDelay(
+ ((JSPositionalSample)sample).getSecondIndex(), rightDelay);
+ }
+ else
+ ********
+ JSMidi.setSampleDelay(sample, 0);
+ }
+ else {
+ if (debugFlag)
+ debugPrint( "JSThread: Internal Error setSampleDelay dataType " +
+ dataType + " invalid");
+ return;
+ }
+******/
+ }
+
+ void setTargetGain(JSSample sample, float scaleFactor) {
+/**********
+// TODO: implement this
+ // current gain is used as starting scalefactor for ramp
+// TEMPORARY: for now just set gain
+ this.setSampleGain(sample, scaleFactor);
+ rampGain = false;
+ rampGain = true;
+ targetGain = scaleFactor;
+ runMonitor(RUN, 0, null);
+**********/
+ }
+
+ void setRate(JSSample sample, float rateScaleFactor) {
+ // force specific rate
+ // go ahead and set rate immediately
+ // take fields as already set in sample and updates rate
+ // called after sample.render performed
+ this.setSampleRate(sample, rateScaleFactor);
+ // disables rate from being gradually increased or decreased
+ // don't set global thread flat rampRate false just because
+ // one sample's rate is set to a specific value.
+ sample.setRampRateFlag(false);
+ }
+
+ void setTargetRate(JSSample sample, float rateScaleFactor) {
+ // make gradual change in rate factors up or down to target rate
+ sample.setRampRateFlag(true);
+ sample.setTargetRateRatio(rateScaleFactor);
+ rampRate = true;
+ runMonitor(RUN, 0, null);
+ }
+
+// TODO: should have methods for delay and pan as well
+
+ void setSampleGain(JSSample sample, float gain) {
+/***********
+// QUESTION: What needs to be synchronized???
+ if (debugFlag)
+ debugPrint("JSThread.setSampleGain for sample "+sample+" " + gain );
+ int dataType = sample.getDataType();
+ int soundType = sample.getSoundType();
+ boolean muted = sample.getMuteFlag();
+// TODO:
+ if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA)
+{
+ com.sun.j3d.audio.J3DHaeStream.setSampleGain(index, gain);
+ }
+ else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) {
+ com.sun.j3d.audio.J3DHaeClip.setSampleGain(index, gain);
+ }
+ else {
+ // dataType==JSAuralParameters.STREAMING_MIDI_DATA
+ // dataType==JSAuralParameters.BUFFERED_MIDI_DATA
+ com.sun.j3d.audio.J3DHaeMidi.setSampleGain(index, gain);
+ }
+***************/
+ }
+
+ void setSampleRate(JSSample sample, float scaleFactor) {
+/*********
+// QUESTION: What needs to be synchronized???
+ // TODO: use sample.rateRatio??
+ if (debugFlag)
+ debugPrint("JSThread.setSampleRate sample " +
+ sample + ", scale factor = " + scaleFactor);
+ int dataType = sample.getDataType();
+ int soundType = sample.getSoundType();
+
+// TODO:
+ if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA) {
+ com.sun.j3d.audio.J3DHaeStream.scaleSampleRate(index, scaleFactor);
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ com.sun.j3d.audio.J3DHaeStream.scaleSampleRate(
+ ((JSPositionalSample)sample).getSecondIndex(),
+ scaleFactor);
+ com.sun.j3d.audio.J3DHaeStream.scaleSampleRate(
+ ((JSPositionalSample)sample).getReverbIndex(),
+ scaleFactor);
+ }
+ }
+ else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) {
+ com.sun.j3d.audio.J3DHaeClip.scaleSampleRate(index, scaleFactor);
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ com.sun.j3d.audio.J3DHaeClip.scaleSampleRate(
+ ((JSPositionalSample)sample).getSecondIndex(),
+ scaleFactor);
+ com.sun.j3d.audio.J3DHaeClip.scaleSampleRate(
+ ((JSPositionalSample)sample).getReverbIndex(),
+ scaleFactor);
+ }
+ }
+ else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA ||
+ dataType == JSAuralParameters.BUFFERED_MIDI_DATA) {
+ com.sun.j3d.audio.J3DHaeMidi.scaleSampleRate(index, scaleFactor);
+ // TODO: MIDI only supported for Background sounds
+ }
+***********/
+ sample.setCurrentRateRatio(scaleFactor);
+ }
+
+ boolean startSample(JSSample sample) {
+/**********
+// QUESTION: should this have a return values - error - or not??
+
+ int returnValue = 0;
+ AuralParameters attribs = audioEngine.getAuralParameters();
+ int soundType = sample.getSoundType();
+ boolean muted = sample.getMuteFlag();
+ int dataType = sample.getDataType();
+ int loopCount = sample.getLoopCount();
+ float leftGain = sample.leftGain;
+ float rightGain = sample.rightGain;
+ int leftDelay = (int)(sample.leftDelay * attribs.rolloff);
+ int rightDelay = (int)(sample.rightDelay * attribs.rolloff);
+ if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA) {
+ if (soundType == AudioDevice3D.BACKGROUND_SOUND) {
+ returnValue = JSStream.startSample(sample,
+ loopCount, leftGain);
+ if (debugFlag)
+ debugPrint("JSThread " +
+ "start stream backgroundSound with gain " + leftGain);
+ }
+ else { // soundType is POINT_SOUND or CONE_SOUND
+ // start up main left and right channels for spatial rendered sound
+ returnValue = JSStream.startSamples(sample,
+ ((JSPositionalSample)sample).getSecondIndex(),
+ loopCount, leftGain, rightGain, leftDelay, rightDelay);
+ //
+ // start up reverb channel w/out delay even if reverb not on now //
+ float reverbGain = 0.0f;
+ if (!muted && auralParams.reverbFlag) {
+ reverbGain = sample.getGain() *
+ attribs.reflectionCoefficient;
+ }
+ int reverbRtrnVal = JSStream.startSample(
+ ((JSPositionalSample)sample).getReverbIndex(), loopCount, reverbGain);
+ if (debugFlag)
+ debugPrint("JSThread " +
+ "start stream positionalSound with gain "+ leftGain +
+ ", " + rightGain);
+ }
+ }
+
+ else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) {
+ if (soundType == AudioDevice3D.BACKGROUND_SOUND) {
+ returnValue = JSClip.startSample(sample,
+ loopCount, leftGain );
+ if (debugFlag)
+ debugPrint("JSThread " +
+ "start buffer backgroundSound with gain " + leftGain);
+ }
+ else { // soundType is POINT_SOUND or CONE_SOUND
+ // start up main left and right channels for spatial rendered sound
+ returnValue = JSClip.startSamples(sample,
+ ((JSPositionalSample)sample).getSecondIndex(),
+ loopCount, leftGain, rightGain, leftDelay, rightDelay);
+ //
+ // start up reverb channel w/out delay even if reverb not on now //
+ float reverbGain = 0.0f;
+ if (!muted && auralParams.reverbFlag) {
+ reverbGain = sample.getGain() *
+ attribs.reflectionCoefficient;
+ }
+ int reverbRtrnVal = JSClip.startSample(
+ ((JSPositionalSample)sample).getReverbIndex(),
+ loopCount, reverbGain);
+
+ if (debugFlag)
+ debugPrint("JSThread " +
+ "start stream positionalSound with gain " + leftGain
+ + ", " + rightGain);
+ }
+ }
+ else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA ||
+ dataType == JSAuralParameters.BUFFERED_MIDI_DATA) {
+ if (soundType == AudioDevice3D.BACKGROUND_SOUND) {
+ returnValue = JSMidi.startSample(sample,
+ loopCount, leftGain);
+ if (debugFlag)
+ debugPrint("JSThread " +
+ "start Midi backgroundSound with gain " + leftGain);
+ }
+ else { // soundType is POINT_SOUND or CONE_SOUND
+ // start up main left and right channels for spatial rendered sound
+ returnValue = JSMidi.startSamples(sample,
+ ((JSPositionalSample)sample).getSecondIndex(),
+ loopCount, leftGain, rightGain, leftDelay, rightDelay);
+ *******
+ // TODO: positional MIDI sounds not supported.
+ // The above startSamples really just start on sample
+ // Don't bother with reverb channel for now.
+
+ //
+ // start up reverb channel w/out delay even if reverb not on now //
+ float reverbGain = 0.0f;
+ if (!muted && auralParams.reverbFlag) {
+ reverbGain = sample.getGain() *
+ attribs.reflectionCoefficient;
+ }
+ int reverbRtrnVal = JSMidi.startSample(
+ ((JSPositionalSample)sample).getReverbIndex(), loopCount, reverbGain);
+ *******
+ if (debugFlag)
+ debugPrint("JSThread " +
+ "start Midi positionalSound with gain "+ leftGain +
+ ", " + rightGain);
+ }
+ }
+
+ else {
+ if (debugFlag)
+ debugPrint(
+ "JSThread: Internal Error startSample dataType " +
+ dataType + " invalid");
+ return false;
+ }
+ // TODO: have to look at return values and conditionally return 'success'
+**********/
+ return true;
+ }
+
+ boolean stopSample(JSSample sample) {
+/***********
+// QUESTION: should this have a return values - error - or not??
+ int dataType = sample.getDataType();
+ int soundType = sample.getSoundType();
+
+ int returnValue = 0;
+ if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA) {
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ returnValue = JSStream.stopSamples(sample,
+ ((JSPositionalSample)sample).getSecondIndex());
+ returnValue = JSStream.stopSample(
+ ((JSPositionalSample)sample).getReverbIndex());
+ }
+ else
+ returnValue = JSStream.stopSample(sample);
+ }
+ else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) {
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ returnValue = JSClip.stopSamples(sample,
+ ((JSPositionalSample)sample).getSecondIndex());
+ returnValue = JSClip.stopSample(
+ ((JSPositionalSample)sample).getReverbIndex());
+ }
+ else
+ returnValue = JSClip.stopSample(sample);
+ }
+ else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA ||
+ dataType == JSAuralParameters.BUFFERED_MIDI_DATA) {
+
+ *****
+ // TODO: positional sounds NOT supported yet
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ returnValue = JSMidi.stopSamples(sample,
+ ((JSPositionalSample)sample).getSecondIndex());
+ returnValue = JSMidi.stopSample(
+ ((JSPositionalSample)sample).getReverbIndex());
+ }
+ else
+ *****
+ returnValue = JSMidi.stopSample(sample);
+ }
+ else {
+ if (debugFlag)
+ debugPrint( "JSThread: Internal Error stopSample dataType " +
+ dataType + " invalid");
+ return -1;
+ }
+
+************/
+ return true;
+ }
+
+
+ void pauseSample(JSSample sample) {
+/**********
+ int dataType = sample.getDataType();
+ int soundType = sample.getSoundType();
+ int returnValue = 0;
+ if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA) {
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ returnValue = JSStream.pauseSamples(sample,
+ ((JSPositionalSample)sample).getSecondIndex());
+ returnValue = JSStream.pauseSample(
+ ((JSPositionalSample)sample).getReverbIndex());
+ }
+ else
+ returnValue = JSStream.pauseSample(sample);
+ }
+ else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) {
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ returnValue = JSClip.pauseSamples(sample,
+ ((JSPositionalSample)sample).getSecondIndex());
+ returnValue = JSClip.pauseSample(
+ ((JSPositionalSample)sample).getReverbIndex());
+ }
+ else
+ returnValue = JSClip.pauseSample(sample);
+ }
+ else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA ||
+
+ dataType == JSAuralParameters.BUFFERED_MIDI_DATA) {
+ *******
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ returnValue = JSMidi.pauseSamples(sample,
+ ((JSPositionalSample)sample).getSecondIndex());
+ returnValue = JSMidi.pauseSample(
+ ((JSPositionalSample)sample).getReverbIndex());
+ }
+ else
+ *****
+ returnValue = JSMidi.pauseSample(sample);
+ }
+ else {
+ if (debugFlag)
+ debugPrint(
+ "JSThread: Internal Error pauseSample dataType " +
+ dataType + " invalid");
+ }
+ if (returnValue < 0) {
+ if (debugFlag)
+ debugPrint( "JSThread: Internal Error pauseSample " +
+ "for sample " + sample + " failed");
+ }
+// QUESTION: return value or not???
+ return;
+*************/
+ }
+
+ void unpauseSample(JSSample sample) {
+/**************
+ int dataType = sample.getDataType();
+ int soundType = sample.getSoundType();
+ int returnValue = 0;
+ if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA) {
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ returnValue = JSStream.unpauseSamples(sample,
+ ((JSPositionalSample)sample).getSecondIndex());
+ returnValue = JSStream.unpauseSample(
+ ((JSPositionalSample)sample).getReverbIndex());
+ }
+ else
+ returnValue = JSStream.unpauseSample(sample);
+ }
+ else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) {
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ returnValue = JSClip.unpauseSamples(sample,
+ ((JSPositionalSample)sample).getSecondIndex());
+ returnValue = JSClip.unpauseSample(
+ ((JSPositionalSample)sample).getReverbIndex());
+ }
+ else
+ returnValue = JSClip.unpauseSample(sample);
+ }
+ else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA ||
+
+ dataType == JSAuralParameters.BUFFERED_MIDI_DATA) {
+ *********
+ // TODO: positional Midi sounds
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ returnValue = JSMidi.unpauseSamples(sample,
+ ((JSPositionalSample)sample).getSecondIndex());
+ returnValue = JSMidi.unpauseSample(
+ ((JSPositionalSample)sample).getReverbIndex());
+ }
+ else
+ *********
+ returnValue = JSMidi.unpauseSample(sample);
+ }
+ else {
+ if (debugFlag)
+ debugPrint(
+ "JSThread: Internal Error unpauseSample dataType " + dataType + " invalid");
+ }
+ if (returnValue < 0) {
+ if (debugFlag)
+ debugPrint( "JSThread: Internal Error unpauseSample " +
+ "for sample " + sample + " failed");
+
+ }
+// QUESTION: return value or not???
+ return;
+*************/
+ }
+
+// TODO:
+ void muteSample(JSSample sample) {
+ // is this already muted? if so don't do anytning
+
+ // This determines if mute is done as a zero gain or
+ // as a stop, advance restart...
+ }
+
+// TODO:
+ void unmuteSample(JSSample sample) {
+ if (debugFlag)
+ debugPrint( "JSThread.unmuteSample not implemented");
+ }
+
+ int startStreams() {
+// QUESTION: return value or not???
+ return 0;
+ }
+ int startStream() {
+// QUESTION: return value or not???
+ return 0;
+ }
+ int startClips() {
+// QUESTION: return value or not???
+ return 0;
+ }
+ int startClip() {
+// QUESTION: return value or not???
+ return 0;
+ }
+
+ /**
+ * This initializes this thread. Once this method returns, the thread is
+ * ready to do work.
+ */
+ public void initialize() {
+ super.initialize();
+ // this.setPriority(Thread.MAX_PRIORITY);
+ // TODO: init values of fields???
+ if (debugFlag)
+ debugPrint("JSThread.initialize()");
+ // TODO: doesn't do anything yet
+ }
+
+ /**
+ * Code to close the device
+ * @return flag: true is closed sucessfully, false if error
+ */
+ boolean close() {
+ // TODO: for now do nothing
+ return false;
+ }
+
+ public void shutdown() {
+ }
+
+
+
+
+ // default resource clean up method
+ public void cleanup() {
+ super.cleanup();
+ if (debugFlag)
+ debugPrint("JSThread.cleanup()");
+ }
+}
diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JavaSoundMixer.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JavaSoundMixer.java
new file mode 100755
index 0000000..2d5bc73
--- /dev/null
+++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JavaSoundMixer.java
@@ -0,0 +1,959 @@
+/*
+ * $RCSfile$
+ *
+ * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any
+ * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+ * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+ * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+ * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+ * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+ * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+ * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+ * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ *
+ * $Revision$
+ * $Date$
+ * $State$
+ */
+
+/*
+ * Audio device driver using Java Sound Mixer Engine.
+ *
+ * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs
+ * to be rewritten.
+ */
+
+package com.sun.j3d.audioengines.javasound;
+
+import java.net.URL;
+import java.io.InputStream;
+import javax.vecmath.*;
+import javax.media.j3d.*;
+import com.sun.j3d.audioengines.*;
+import java.util.ArrayList;
+import java.lang.Thread;
+
+/**
+ * The JavaSoundMixer Class defines an audio output device that accesses
+ * JavaSound functionality stream data.
+ */
+public class JavaSoundMixer extends AudioEngine3DL2 {
+
+ // Debug print flags and methods
+ static final boolean debugFlag = false;
+ static final boolean internalErrors = false;
+
+ void debugPrint(String message) {
+ if (debugFlag)
+ System.out.println(message);
+ }
+
+ void debugPrintln(String message) {
+ if (debugFlag)
+ System.out.println(message);
+ }
+
+ // Determines method to call for added or setting sound into ArrayList
+ static final int ADD_TO_LIST = 1;
+ static final int SET_INTO_LIST = 2;
+
+ // current Aural Parameters = Aural Attributes from core + JavaSound
+ // specific fields, including reverberation parameters.
+ JSAuralParameters auralParams = null;
+
+ // thread for dynamically changing audio parameters such as volume
+ // and sample rate.
+ JSThread thread = null;
+
+ /*
+ * new fields in extended class
+ */
+ protected float deviceGain = 1.0f;
+
+ protected static final int NOT_PAUSED = 0;
+ protected static final int PAUSE_PENDING = 1;
+ protected static final int PAUSED = 2;
+ protected static final int RESUME_PENDING = 3;
+ protected int pause = NOT_PAUSED;
+
+ /*
+ * Construct a new JavaSoundMixer with the specified P.E.
+ * @param physicalEnvironment the physical environment object where we
+ * want access to this device.
+ */
+ public JavaSoundMixer(PhysicalEnvironment physicalEnvironment ) {
+ super(physicalEnvironment);
+ thread = new JSThread(Thread.currentThread().getThreadGroup(), this);
+ }
+
+ /**
+ * Query total number of channels available for sound rendering
+ * for this audio device.
+ * Overridden method from AudioEngine.
+ * @return number of maximum voices play simultaneously on JavaSound Mixer.
+ */
+ public int getTotalChannels() {
+ if (thread != null)
+ return thread.getTotalChannels();
+ else
+ return 32;
+ }
+
+ /**
+ * Code to initialize the device
+ * New interface to mixer/engine specific methods
+ * @return flag: true is initialized sucessfully, false if error
+ */
+ public boolean initialize() {
+ if (thread == null) {
+ return false;
+ }
+ // init JavaSound dynamic thread
+ thread.initialize();
+ auralParams = new JSAuralParameters();
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: JSStream.initialize returned true");
+ return true;
+ }
+
+ /**
+ * Code to close the device.
+ * New interface to mixer/engine specific methods
+ * @return flag: true is closed sucessfully, false if error
+ */
+ public boolean close() {
+ if (thread == null)
+ return false;
+ if (thread.close()) {
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: JSStream.close returned true");
+ return true;
+ }
+ else {
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: JSStream.close returned false");
+ return false;
+ }
+ }
+
+
+ /**
+ * Code to load sound data into a channel of device mixer.
+ * Load sound as one or mores sample into the Java Sound Mixer:
+ * a) as either a STREAM or CLIP based on whether cached is enabled
+ * b) positional and directional sounds use three samples per
+ * sound
+ * Overriden method from AudioEngine3D.
+ *
+ * Sound type determines if this is a Background, Point or Cone
+ * sound source and thus the JSXxxxSample object type
+ * Call JSXxxxxSample.loadSample()
+ * If no error
+ * Get the next free index in the samples list.
+ * Store a reference to JSXxxxSample object in samples list.
+ * @return index to the sample in samples list.
+ */
+ public int prepareSound(int soundType, MediaContainer soundData) {
+ int index = JSSample.NULL_SAMPLE;
+ int methodType = ADD_TO_LIST;
+ if (soundData == null)
+ return JSSample.NULL_SAMPLE;
+ synchronized(samples) {
+ // for now force to just add to end of samples list
+ int samplesSize = samples.size();
+ index = samplesSize;
+ samples.ensureCapacity(index+1);
+ boolean error = false;
+
+ if (soundType == AudioDevice3D.CONE_SOUND) {
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer.prepareSound type=CONE");
+ JSDirectionalSample dirSample = new JSDirectionalSample();
+ error = dirSample.load(soundData);
+ if (error)
+ return JSSample.NULL_SAMPLE;
+ if (methodType == SET_INTO_LIST)
+ samples.set(index, dirSample);
+ else
+ samples.add(index, dirSample);
+ /*
+ * Since no error occurred while loading, save all the
+ * characterstics for the sound in the sample.
+ */
+ dirSample.setDirtyFlags(0xFFFF);
+ dirSample.setSoundType(soundType);
+ dirSample.setSoundData(soundData);
+
+ }
+ else if (soundType == AudioDevice3D.POINT_SOUND) {
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer.prepareSound type=POINT");
+ JSPositionalSample posSample = new JSPositionalSample();
+ error = posSample.load(soundData);
+ if (error)
+ return JSSample.NULL_SAMPLE;
+ if (methodType == SET_INTO_LIST)
+ samples.set(index, posSample);
+ else
+ samples.add(index, posSample);
+ posSample.setDirtyFlags(0xFFFF);
+ posSample.setSoundType(soundType);
+ posSample.setSoundData(soundData);
+ }
+ else { // soundType == AudioDevice3D.BACKGROUND_SOUND
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer.prepareSound type=BACKGROUND");
+ JSSample sample = null;
+ sample = new JSSample();
+ error = sample.load(soundData);
+ if (error)
+ return JSSample.NULL_SAMPLE;
+ if (methodType == SET_INTO_LIST)
+ samples.set(index, sample);
+ else
+ samples.add(index, sample);
+ sample.setDirtyFlags(0xFFFF);
+ sample.setSoundType(soundType);
+ sample.setSoundData(soundData);
+ }
+ }
+
+ if (debugFlag) {
+ debugPrint(" prepareSound type = "+soundType);
+ debugPrintln("JavaSoundMixer.prepareSound returned "+index);
+ }
+ return index;
+ }
+
+ /**
+ * Clears the fields associated with sample data for this sound.
+ * Overriden method from AudioEngine3D.
+ */
+ public void clearSound(int index) {
+ // TODO: call JSXXXX clear method
+ JSSample sample = null;
+ if ( (sample = (JSSample)getSample(index)) == null)
+ return;
+ sample.clear();
+ synchronized(samples) {
+ samples.set(index, null);
+ }
+ }
+
+ /**
+ * Save a reference to the local to virtual world coordinate space
+ * Overriden method from AudioEngine3D.
+ */
+ public void setVworldXfrm(int index, Transform3D trans) {
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: setVworldXfrm for index " + index);
+ super.setVworldXfrm(index, trans);
+ if (debugFlag) {
+ double[] matrix = new double[16];
+ trans.get(matrix);
+ debugPrintln("JavaSoundMixer column-major transform ");
+ debugPrintln("JavaSoundMixer " + matrix[0]+", "+matrix[1]+
+ ", "+matrix[2]+", "+matrix[3]);
+ debugPrintln("JavaSoundMixer " + matrix[4]+", "+matrix[5]+
+ ", "+matrix[6]+", "+matrix[7]);
+ debugPrintln("JavaSoundMixer " + matrix[8]+", "+matrix[9]+
+ ", "+matrix[10]+", "+matrix[11]);
+ debugPrintln("JavaSoundMixer " + matrix[12]+", "+matrix[13]+
+ ", "+matrix[14]+", "+matrix[15]);
+ }
+ JSSample sample = null;
+ if ((sample = (JSSample)getSample(index)) == null)
+ return;
+ int soundType = sample.getSoundType();
+
+ if (soundType == AudioDevice3D.CONE_SOUND) {
+ JSDirectionalSample dirSample = null;
+ if ((dirSample = (JSDirectionalSample)getSample(index)) == null)
+ return;
+ dirSample.setXformedDirection();
+ dirSample.setXformedPosition();
+ // flag that VirtualWorld transform set
+ dirSample.setVWrldXfrmFlag(true);
+ }
+ else if (soundType == AudioDevice3D.POINT_SOUND) {
+ JSPositionalSample posSample = null;
+ if ((posSample = (JSPositionalSample)getSample(index)) == null)
+ return;
+ posSample.setXformedPosition();
+ // flag that VirtualWorld transform set
+ posSample.setVWrldXfrmFlag(true);
+ }
+ return;
+ }
+ /*
+ * Overriden method from AudioEngine3D.
+ */
+ public void setPosition(int index, Point3d position) {
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: setPosition for index " + index);
+ super.setPosition(index, position);
+ JSPositionalSample posSample = null;
+ if ((posSample = (JSPositionalSample)getSample(index)) == null)
+ return;
+ int soundType = posSample.getSoundType();
+ if ( (soundType == AudioDevice3D.POINT_SOUND) ||
+ (soundType == AudioDevice3D.CONE_SOUND) ) {
+ posSample.setXformedPosition();
+ }
+ return;
+ }
+
+ /*
+ * Overriden method from AudioEngine3D.
+ */
+ public void setDirection(int index, Vector3d direction) {
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: setDirection for index " + index);
+ super.setDirection(index, direction);
+ JSDirectionalSample dirSample = null;
+ if ((dirSample = (JSDirectionalSample)getSample(index)) == null)
+ return;
+ int soundType = dirSample.getSoundType();
+ if (soundType == AudioDevice3D.CONE_SOUND) {
+ dirSample.setXformedDirection();
+ }
+ return;
+ }
+
+ /*
+ * Overriden method from AudioEngine3D.
+ */
+ public void setReflectionCoefficient(float coefficient) {
+ super.setReflectionCoefficient(coefficient);
+ auralParams.reverbDirty |= JSAuralParameters.REFLECTION_COEFF_CHANGED;
+ return;
+ }
+
+ /*
+ * Overriden method from AudioEngine3D.
+ */
+ public void setReverbDelay(float reverbDelay) {
+ super.setReverbDelay(reverbDelay);
+ auralParams.reverbDirty |= JSAuralParameters.REVERB_DELAY_CHANGED;
+ return;
+ }
+
+ /*
+ * Overriden method from AudioEngine3D.
+ */
+ public void setReverbOrder(int reverbOrder) {
+ super.setReverbOrder(reverbOrder);
+ auralParams.reverbDirty |= JSAuralParameters.REVERB_ORDER_CHANGED;
+ return;
+ }
+
+ /*
+ * QUESTION: if this is used, for now, exclusively, to start a Background
+ * or any single sampled Sounds, why are there if-else cases to handle
+ * Point and Cone sounds??
+ *
+ * For now background sounds are not reverberated
+ *
+ * Overriden method from AudioEngine3D.
+ */
+ public int startSample(int index) {
+ // TODO: Rewrite this function
+
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: STARTSample for index " + index);
+
+ JSSample sample = null;
+ if ( ( (sample = (JSSample)getSample(index)) == null) ||
+ thread == null )
+ return JSSample.NULL_SAMPLE;
+
+ int soundType = sample.getSoundType();
+ boolean muted = sample.getMuteFlag();
+ if (muted) {
+ if (debugFlag)
+ debugPrintln(" MUTEd start");
+ thread.muteSample(sample);
+ if (soundType != AudioDevice3D.BACKGROUND_SOUND)
+ setFilter(index, false, Sound.NO_FILTER);
+ }
+ else {
+ sample.render(sample.getDirtyFlags(), getView(), auralParams);
+ this.scaleSampleRate(index, sample.rateRatio);
+ // filtering
+ if (soundType != AudioDevice3D.BACKGROUND_SOUND)
+ setFilter(index, sample.getFilterFlag(), sample.getFilterFreq());
+ }
+
+ boolean startSuccessful;
+ startSuccessful = thread.startSample(sample);
+
+ sample.channel.startSample(sample.getLoopCount(), sample.getGain(), 0);
+
+ if (!startSuccessful) {
+ if (internalErrors)
+ debugPrintln(
+ "JavaSoundMixer: Internal Error startSample for index " +
+ index + " failed");
+ return JSSample.NULL_SAMPLE;
+ }
+ else {
+ if (debugFlag)
+ debugPrintln(" startSample worked, " +
+ "returning " + startSuccessful);
+ // NOTE: Set AuralParameters AFTER sound started
+ // Setting AuralParameters before you start sound doesn't work
+ if (!muted) {
+ if (auralParams.reverbDirty > 0) {
+ if (debugFlag) {
+ debugPrintln("startSample: reverb settings are:");
+ debugPrintln(" coeff = "+
+ auralParams.reflectionCoefficient +
+ ", delay = " + auralParams.reverbDelay +
+ ", order = " + auralParams.reverbOrder);
+ }
+ float delayTime = auralParams.reverbDelay * auralParams.rolloff;
+ calcReverb(sample);
+ }
+ // NOTE: it apprears that reverb has to be reset in
+ // JavaSound engine when sound re-started??
+ // force reset of reverb parameters when sound is started
+ setReverb(sample);
+ }
+ return index;
+ }
+ }
+
+ /*
+ * Overriden method from AudioEngine3D.
+ */
+ public int stopSample(int index) {
+ // TODO: Rewrite this function
+
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: STOPSample for index " + index);
+ JSSample sample = null;
+ if ((sample = (JSSample)getSample(index)) == null)
+ return -1;
+
+ int dataType = sample.getDataType();
+ int soundType = sample.getSoundType();
+
+ boolean stopSuccessful = true;
+ stopSuccessful = thread.stopSample(sample);
+
+ sample.channel.stopSample();
+
+ if (!stopSuccessful) {
+ if (internalErrors)
+ debugPrintln( "JavaSoundMixer: Internal Error stopSample(s) for index " +
+ index + " failed");
+ return -1;
+ }
+ else {
+ // set fields in sample to reset for future start
+ sample.reset();
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: stopSample for index " +
+ index + " worked, returning " + stopSuccessful);
+ return 0;
+ }
+ }
+
+ /*
+ * Overriden method from AudioEngine3D.
+ */
+ public void pauseSample(int index) {
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: PAUSESample for index " + index);
+ JSSample sample = null;
+ if ((sample = (JSSample)getSample(index)) == null)
+ return;
+ // check thread != null
+ thread.pauseSample(sample);
+ }
+
+ /*
+ * Overriden method from AudioEngine3D.
+ */
+ public void unpauseSample(int index) {
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: UNPAUSESample for index " + index);
+ JSSample sample = null;
+ if ((sample = (JSSample)getSample(index)) == null)
+ return;
+ thread.unpauseSample(sample);
+ }
+
+ /*
+ * Force thread to update sample.
+ * Overriden method from AudioEngine3D.
+ */
+
+ public void updateSample(int index) {
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: UPDATESample for index " + index);
+ JSSample sample = null;
+ if ( ( (sample = (JSSample)getSample(index)) == null) ||
+ thread == null )
+ return;
+
+ int soundType = sample.getSoundType();
+ boolean muted = sample.getMuteFlag();
+
+ if (muted) {
+ if (soundType != AudioDevice3D.BACKGROUND_SOUND)
+ setFilter(index, false, Sound.NO_FILTER);
+ thread.muteSample(sample);
+ if (debugFlag)
+ debugPrintln(" Mute during update");
+ }
+ else {
+ // If reverb parameters changed resend to audio device
+ if (auralParams.reverbDirty > 0) {
+ if (debugFlag) {
+ debugPrintln("updateSample: reverb settings are:");
+ debugPrintln(" coeff = " + auralParams.reflectionCoefficient+
+ ", delay = " + auralParams.reverbDelay +
+ ", order = " + auralParams.reverbOrder);
+ }
+ float delayTime = auralParams.reverbDelay * auralParams.rolloff;
+ calcReverb(sample);
+ }
+ // TODO: Only re-set reverb if values different
+ // For now force reset to ensure that reverb is currently correct
+ setReverb(sample); // ensure reverb is current/correct
+
+ // TODO: For now sum left & rightGains for reverb gain
+ float reverbGain = 0.0f;
+ if (!muted && auralParams.reverbFlag) {
+ reverbGain = sample.getGain() * auralParams.reflectionCoefficient;
+ }
+
+ sample.render(sample.getDirtyFlags(), getView(), auralParams);
+
+ // filtering
+ if (soundType != AudioDevice3D.BACKGROUND_SOUND)
+ setFilter(index, sample.getFilterFlag(), sample.getFilterFreq());
+ thread.setSampleGain(sample, auralParams);
+ thread.setSampleRate(sample, auralParams);
+ thread.setSampleDelay(sample, auralParams);
+ }
+ return;
+ }
+
+ /*
+ * Overriden method from AudioEngine3D.
+ */
+ public void muteSample(int index) {
+ JSSample sample = null;
+ if ((sample = (JSSample)getSample(index)) == null)
+ return;
+
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: muteSample");
+ sample.setMuteFlag(true);
+ thread.muteSample(sample);
+ return;
+ }
+
+ /*
+ * Overriden method from AudioEngine3D.
+ */
+ public void unmuteSample(int index) {
+ JSSample sample = null;
+ if ((sample = (JSSample)getSample(index)) == null)
+ return;
+
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: unmuteSample");
+ sample.setMuteFlag(false);
+
+ // since while mute the reverb type and state was not updated...
+ // Reverb has to be recalculated when sound is unmuted .
+ auralParams.reverbDirty = 0xFFFF; // force an update of reverb params
+ sample.setDirtyFlags(0xFFFF); // heavy weight forcing of gain/delay update
+
+ // TODO: force an update of ALL parameters that could have changed
+ // while muting disabled...
+
+ thread.unmuteSample(sample);
+ return;
+ }
+
+ /*
+ * Overriden method from AudioEngine3D.
+ */
+ public long getSampleDuration(int index) {
+ JSSample sample = null;
+ if ((sample = (JSSample)getSample(index)) == null)
+ return Sample.DURATION_UNKNOWN;
+ long duration;
+
+ if (sample != null)
+ duration = sample.getDuration();
+ else
+ duration = Sample.DURATION_UNKNOWN;
+ if (debugFlag)
+ debugPrintln(" return duration " + duration);
+ return duration;
+ }
+
+ /*
+ * Overriden method from AudioEngine3D.
+ */
+ public int getNumberOfChannelsUsed(int index) {
+ /*
+ * Calls same method with different signature containing the
+ * sample's mute flag passed as the 2nd parameter.
+ */
+ JSSample sample = null;
+ if ((sample = (JSSample)getSample(index)) == null)
+ return 0;
+ else
+ return getNumberOfChannelsUsed(index, sample.getMuteFlag());
+ }
+
+ /**
+ * Overriden method from AudioEngine3D.
+ */
+ public int getNumberOfChannelsUsed(int index, boolean muted) {
+ /*
+ * The JavaSoundMixer implementation uses THREE channels to render
+ * the stereo image of each Point and Cone Sounds:
+ * Two for rendering the right and left portions of the rendered
+ * spatialized sound image - panned hard right or left respectively.
+ * This implementation uses one channel to render Background sounds
+ * whether the sample is mono or stereo.
+ *
+ * TODO: When muted is implemented, that flag should be check
+ * so that zero is returned.
+ */
+ JSSample sample = null;
+ if ((sample = (JSSample)getSample(index)) == null)
+ return 0;
+
+ int soundType = sample.getSoundType();
+ int dataType = sample.getDataType();
+
+ // TODO: for now positional Midi sound used only 1 sample
+ if (dataType == JSSample.STREAMING_MIDI_DATA ||
+ dataType == JSSample.BUFFERED_MIDI_DATA)
+ return 1;
+
+ if (soundType == BACKGROUND_SOUND)
+ return 1;
+ else // for Point and Cone sounds
+ return 3;
+ }
+
+ /*
+ * Overriden method from AudioEngine3D.
+ */
+ public long getStartTime(int index) {
+ JSSample sample = null;
+ if ((sample = (JSSample)getSample(index)) == null)
+ return 0L;
+ if (sample.channel == null)
+ return 0L;
+ return (long)sample.channel.startTime;
+ }
+
+ /*
+ * Methods called during rendering
+ */
+ void scaleSampleRate(int index, float scaleFactor) {
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: scaleSampleRate index " +
+ index + ", scale factor = " + scaleFactor);
+ JSSample sample = null;
+ if ((sample = (JSSample)getSample(index)) == null ||
+ thread == null)
+ return;
+ int dataType = sample.getDataType();
+ if (debugFlag)
+ debugPrintln(" scaleSampleRate.dataType = " + dataType +
+ "using sample " + sample + " from samples[" +
+ index +"]");
+ int soundType = sample.getSoundType();
+
+ if (dataType == JSSample.STREAMING_AUDIO_DATA ||
+ dataType == JSSample.BUFFERED_AUDIO_DATA) {
+ thread.setSampleRate(sample, scaleFactor);
+ /**********
+ // TODO:
+ if (soundType != AudioDevice3D.BACKGROUND_SOUND) {
+ thread.setSampleRate( ((JSPositionalSample)sample).getSecondIndex(),
+ scaleFactor);
+ thread.setSampleRate(((JSPositionalSample)sample).getReverbIndex(),
+ scaleFactor);
+ }
+ **********/
+ }
+ else if (dataType == JSSample.STREAMING_MIDI_DATA ||
+ dataType == JSSample.BUFFERED_MIDI_DATA) {
+ thread.setSampleRate(sample, scaleFactor);
+ /**********
+ if (soundType != AudioDevice3D.BACKGROUND_SOUND) {
+ thread.setSampleRate(((JSPositionalSample)sample).getSecondIndex(),
+ scaleFactor);
+ thread.setSampleRate(((JSPositionalSample)sample).getReverbIndex(),
+ scaleFactor);
+ }
+ **********/
+ }
+ else {
+ if (internalErrors)
+ debugPrintln(
+ "JavaSoundMixer: Internal Error scaleSampleRate dataType " +
+ dataType + " invalid");
+ }
+ }
+
+ /*
+ * Methods called during rendering
+ */
+ void calcReverb(JSSample sample) {
+ /*
+ * Java Sound reverb parameters are a subset of Java 3D parameters
+ */
+ int dataType = sample.getDataType();
+ int soundType = sample.getSoundType();
+ float decay = auralParams.decayTime;
+ float delay = auralParams.reverbDelay * auralParams.rolloff;
+ float reflection = auralParams.reflectionCoefficient;
+ int order = auralParams.reverbOrder;
+ /*
+ * Remember Coeff change is choosen over Order change if BOTH made
+ * otherwise the last one changed take precidence.
+ */
+ if (auralParams.reflectionCoefficient == 0.0f ||
+ auralParams.reverbCoefficient == 0.0f)
+ auralParams.reverbFlag = false;
+ else {
+ auralParams.reverbFlag = true;
+ if (order > 0) {
+ // clamp reverb decay time to order*delay
+ float clampedTime = order * delay;
+ if ( clampedTime < decay)
+ decay = clampedTime;
+ }
+ if (delay < 100.0f) {
+ // "small" reverberant space
+ if (decay <= 1500.0f)
+ auralParams.reverbType = 2;
+ else
+ auralParams.reverbType = 4;
+ }
+ else if (delay < 500.0f) {
+ // "medium" reverberant space
+ if (decay <= 1500.0f)
+ auralParams.reverbType = 3;
+ else
+ auralParams.reverbType = 6;
+ }
+ else { // delay >= 500.0f
+ // "large" reverberant space
+ if (decay <= 1500.0f)
+ auralParams.reverbType = 6;
+ else
+ auralParams.reverbType = 5;
+ }
+ }
+
+ if (debugFlag)
+ debugPrintln("JavaSoundMixer: setReverb for " +
+ sample + ", type = " + auralParams.reverbType + ", flag = " + auralParams.reverbFlag);
+
+ auralParams.reverbDirty = 0; // clear the attribute reverb dirty flags
+ }
+
+ /*
+ * Interal method for setting reverb parameters called during rendering.
+ * This not called by SoundScheduler.
+ */
+ void setReverb(JSSample sample) {
+ /*
+ * Only third sample of multisample sounds has reverb parameters set.
+ * For now, only positional and directional sounds are reverberated.
+ */
+ int soundType = sample.getSoundType();
+ int dataType = sample.getDataType();
+
+ // QUESTION: Should reverb be applied to background sounds?
+ if ( (soundType == AudioDevice3D.CONE_SOUND) ||
+ (soundType == AudioDevice3D.POINT_SOUND) ) {
+ if (debugFlag)
+ debugPrintln("setReverb called with type, on = " +
+ auralParams.reverbType + ", " + auralParams.reverbFlag);
+ if (sample == null)
+ return;
+ JSPositionalSample posSample = (JSPositionalSample)sample;
+ if (posSample.channel == null)
+ return;
+
+ /**********
+ // NOTE: no support for reverb channel yet...
+ int reverbIndex = posSample.getReverbIndex();
+ **********/
+ if (dataType == JSSample.STREAMING_AUDIO_DATA) {
+ JSStream stream = (JSStream)posSample.channel;
+ stream.setSampleReverb(auralParams.reverbType, auralParams.reverbFlag);
+ }
+ else if (dataType == JSSample.BUFFERED_AUDIO_DATA) {
+ JSClip clip = (JSClip)posSample.channel;
+ clip.setSampleReverb(auralParams.reverbType, auralParams.reverbFlag);
+ }
+ /**********
+ // TODO:
+ else if (dataType == JSSample.STREAMING_MIDI_DATA ||
+ dataType == JSSample.BUFFERED_MIDI_DATA) {
+ JSMidi.setSampleReverb(reverbIndex,
+ auralParams.reverbType, auralParams.reverbFlag);
+ }
+ **********/
+ else {
+ if (internalErrors)
+ debugPrintln( "JavaSoundMixer: Internal Error setReverb " +
+ "dataType " + dataType + " invalid");
+ }
+ }
+ }
+
+ // TEMPORARY: Override of method due to bug in Java Sound
+ public void setLoop(int index, int count) {
+ JSSample sample = null;
+ if ((sample = (JSSample)getSample(index)) == null)
+ return;
+ int dataType = sample.getDataType();
+
+ // WORKAROUND:
+ // Bug in Java Sound engine hangs when INFINITE_LOOP count
+ // for Audio Wave data. Leave count unchanged for Midi data.
+ if (dataType==JSSample.STREAMING_AUDIO_DATA ||
+ dataType==JSSample.BUFFERED_AUDIO_DATA) {
+ if (count == Sound.INFINITE_LOOPS) {
+ // LoopCount of 'loop Infinitely' forced to largest positive int
+ count = 0x7FFFFFF;
+ }
+ }
+ super.setLoop(index, count);
+ return;
+ }
+
+ // Perform device specific filtering
+ // Assumes that this is called for positional and directional sounds
+ // not background sounds, so there are at lease two samples assigned
+ // per sound.
+ // TODO: remove assumption from method
+ void setFilter(int index, boolean filterFlag, float filterFreq) {
+ JSPositionalSample posSample = null;
+ if ((posSample = (JSPositionalSample)getSample(index)) == null)
+ return;
+ if (posSample.channel == null)
+ return;
+ int dataType = posSample.getDataType();
+
+ // Filtering can NOT be performed on MIDI Songs
+ if (dataType == JSSample.STREAMING_MIDI_DATA ||
+ dataType == JSSample.BUFFERED_MIDI_DATA) {
+ return;
+ }
+
+ /****
+ // TODO: multiple clips per channel
+ int secondIndex = posSample.getSecondIndex();
+ *****/
+ if (dataType == JSSample.BUFFERED_AUDIO_DATA) {
+ JSClip clip = (JSClip)posSample.channel;
+ clip.setSampleFiltering(filterFlag,filterFreq);
+ /*****
+ JSClip.setSampleFiltering(econdIndex, filterFlag, filterFreq);
+ ******/
+ }
+ else { // dataType == JSSample.STREAMING_AUDIO_DATA
+ JSStream stream = (JSStream)posSample.channel;
+ stream.setSampleFiltering(filterFlag,filterFreq);
+ /*****
+ JSStream.setSampleFiltering(secondIndex, ilterFlag, filterFreq);
+ ******/
+ }
+ // QUESTION: should reverb channel be filtered???
+
+ if (debugFlag) {
+ debugPrintln("JavaSoundMixer:setFilter " +
+ "of non-backgroundSound by (" +
+ filterFlag + ", " + filterFreq + ")");
+ }
+ }
+ //
+ // Set overall gain for device
+ // @since Java 3D 1.3
+ //
+ public void setGain(float scaleFactor) {
+ float oldDeviceGain = deviceGain;
+ float gainFactor = scaleFactor/oldDeviceGain;
+ // TODO: for each sample, change gain by gainFactor
+ deviceGain = scaleFactor; // set given scalefactor as new device gain
+ return;
+ }
+
+ /*
+ * Set sample specific sample rate scale factor gain
+ * @since Java 3D 1.3
+ */
+ public void setRateScaleFactor(int index, float rateScaleFactor) {
+ JSSample sample = null;
+ if ((sample = (JSSample)getSample(index)) == null)
+ return;
+ sample.setRateScaleFactor(rateScaleFactor);
+ this.scaleSampleRate(index, rateScaleFactor);
+ }
+
+ /**
+ * Pauses audio device engine without closing the device and associated
+ * threads.
+ * Causes all cached sounds to be paused and all streaming sounds to be
+ * stopped.
+ */
+ public void pause() {
+ pause = PAUSE_PENDING;
+ // TODO: pause all sounds
+ return;
+ }
+ /**
+ * Resumes audio device engine (if previously paused) without reinitializing * the device.
+ * Causes all paused cached sounds to be resumed and all streaming sounds
+ * restarted.
+ */
+ public void resume() {
+ pause = RESUME_PENDING;
+ // TODO: unpause all sounds
+ return;
+ }
+}