diff options
Diffstat (limited to 'src/classes/share/com/sun/j3d/audioengines')
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; + } +} |