com.sun.j3d.audioengines.javasound.JSPositionalSample Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java3d-core Show documentation
Show all versions of java3d-core Show documentation
Java3D Core And Java3D Util Libraries
The newest version!
/*
* Copyright (c) 2007 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.
*
*/
/*
* 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.PhysicalBody;
import javax.media.j3d.Transform3D;
import javax.media.j3d.View;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;
import com.sun.j3d.audioengines.AuralParameters;
/**
* 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 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.
*/
@Override
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= 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
}