org.scijava.java3d.audioengines.javasound.JSPositionalSample Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of j3dutils Show documentation
Show all versions of j3dutils Show documentation
Utility functions for the Java 3D Graphics API
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 org.scijava.java3d.audioengines.javasound;
import org.scijava.java3d.PhysicalBody;
import org.scijava.java3d.Transform3D;
import org.scijava.java3d.View;
import org.scijava.vecmath.Point3d;
import org.scijava.vecmath.Point3f;
import org.scijava.vecmath.Vector3f;
import org.scijava.java3d.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
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy