0001: /*
0002: * $RCSfile: JSPositionalSample.java,v $
0003: *
0004: * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * Redistribution and use in source and binary forms, with or without
0007: * modification, are permitted provided that the following conditions
0008: * are met:
0009: *
0010: * - Redistribution of source code must retain the above copyright
0011: * notice, this list of conditions and the following disclaimer.
0012: *
0013: * - Redistribution in binary form must reproduce the above copyright
0014: * notice, this list of conditions and the following disclaimer in
0015: * the documentation and/or other materials provided with the
0016: * distribution.
0017: *
0018: * Neither the name of Sun Microsystems, Inc. or the names of
0019: * contributors may be used to endorse or promote products derived
0020: * from this software without specific prior written permission.
0021: *
0022: * This software is provided "AS IS," without a warranty of any
0023: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
0024: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
0025: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
0026: * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
0027: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
0028: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
0029: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
0030: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
0031: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
0032: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
0033: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
0034: * POSSIBILITY OF SUCH DAMAGES.
0035: *
0036: * You acknowledge that this software is not designed, licensed or
0037: * intended for use in the design, construction, operation or
0038: * maintenance of any nuclear facility.
0039: *
0040: * $Revision: 1.4 $
0041: * $Date: 2007/02/09 17:20:03 $
0042: * $State: Exp $
0043: */
0044:
0045: /*
0046: * Java Sound PositionalSample object
0047: *
0048: * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs
0049: * to be rewritten.
0050: */
0051:
0052: package com.sun.j3d.audioengines.javasound;
0053:
0054: import javax.media.j3d.*;
0055: import javax.vecmath.*;
0056: import com.sun.j3d.audioengines.*;
0057:
0058: /**
0059: * The PostionalSample Class defines the data and methods associated with a
0060: * PointSound sample played thru the AudioDevice.
0061: */
0062:
0063: class JSPositionalSample extends JSSample {
0064:
0065: // maintain fields for stereo channel rendering
0066: float leftGain = 1.0f; // scale factor
0067: float rightGain = 1.0f; // scale factor
0068: int leftDelay = 0; // left InterauralTimeDifference in millisec
0069: int rightDelay = 0; // right ITD in millisec
0070: // fields for reverb channel
0071:
0072: // debug flag for the verbose Doppler calculation methods
0073: static final protected boolean dopplerFlag = true;
0074:
0075: /**
0076: * For positional and directional sounds, TWO Hae streams or clips
0077: * are allocated, one each for the left and right channels, played at
0078: * a different (delayed) time and with a different gain value.
0079: */
0080: int secondIndex = NULL_SAMPLE;
0081: /**
0082: * A third sample for control of reverb of the stream/clip is openned
0083: * and maintained for all directional/positional sounds.
0084: * For now, even if no aural attributes (using reverb) are active,
0085: * a reverb channel is always started with the other two. A sound could
0086: * be started without reverb and then reverb added later, but since there
0087: * is no way to offset properly into all sounds (considering non-cached
0088: * and nconsistent rate-changes during playing) this third sound is
0089: * always allocated and started.
0090: */
0091: int reverbIndex = NULL_SAMPLE;
0092:
0093: /**
0094: * Save ear positions transformed into VirtualWorld coords from Head coords
0095: * These default positions are used when the real values cannot queried
0096: */
0097: Point3f xformLeftEar = new Point3f(-0.09f, -0.03f, 0.095f);
0098: Point3f xformRightEar = new Point3f(0.09f, -0.03f, 0.095f);
0099: // Z axis in head space - looking into the screen
0100: Vector3f xformHeadZAxis = new Vector3f(0.0f, 0.0f, -1.0f); // Va
0101:
0102: /**
0103: * Save vectors from source source position to transformed ear parameters
0104: */
0105: Vector3f sourceToCenterEar = new Vector3f(); // Vh
0106: Vector3f sourceToRightEar = new Vector3f(); // Vf or Vc
0107: Vector3f sourceToLeftEar = new Vector3f(); // Vf or Vc
0108:
0109: boolean averageDistances = false;
0110: long deltaTime = 0;
0111: double sourcePositionChange = -1.0;
0112: double headPositionChange = -1.0;
0113:
0114: /*
0115: * Maintain the last locations of sound and head as well as time the
0116: * sound was last processed.
0117: * Process delta distance and time as part of Doppler calculations.
0118: */
0119: static int MAX_DISTANCES = 4;
0120: int numDistances = 0;
0121: // TODO: time is based on changes to position!!! only
0122: // TODO: must shap shot when either Position OR ear changes!!!
0123: // TODO: must grab all changes to VIEW parameters (could change ear)!!!
0124: // not just when pointer to View changes!!
0125: long[] times = new long[MAX_DISTANCES];
0126: Point3f[] positions = new Point3f[MAX_DISTANCES]; // xformed sound source positions
0127: Point3f[] centerEars = new Point3f[MAX_DISTANCES]; // xformed center ear positions
0128: /*
0129: * a set of indices (first, last, and current) are maintained to point
0130: * into the above arrays
0131: */
0132: int firstIndex = 0;
0133: int lastIndex = 0;
0134: int currentIndex = 0;
0135:
0136: /*
0137: * Allow changes in Doppler rate only small incremental values otherwise
0138: * you hear skips in the pitch of a sound during playback.
0139: * When playback is faster, allow delta changes:
0140: * (diff in Factor for octave (1.0))/(12 1/2-steps))*(1/4) of half-step
0141: * When playback is slower, allow delta changes:
0142: * (diff in Factor for octave (0.5))/(12 1/2-steps))*(1/4) of half-step
0143: */
0144: double lastRequestedDopplerRateRatio = -1.0f;
0145: double lastActualDopplerRateRatio = -1.0f;
0146: static double maxRatio = 256.0f; // 8 times higher/lower
0147: /*
0148: * denotes movement of sound away or towards listener
0149: */
0150: static int TOWARDS = 1;
0151: static int NO_CHANGE = 0;
0152: static int AWAY = -1;
0153:
0154: /*
0155: * Process request for Filtering fields
0156: */
0157: boolean filterFlag = false;
0158: float filterFreq = -1.0f;
0159:
0160: /*
0161: * Construct a new audio device Sample object
0162: */
0163: public JSPositionalSample() {
0164: super ();
0165: if (debugFlag)
0166: debugPrint("JSPositionalSample constructor");
0167: // initiallize circular buffer for averaging distance values
0168: for (int i = 0; i < MAX_DISTANCES; i++) {
0169: positions[i] = new Point3f();
0170: centerEars[i] = new Point3f(0.09f, -0.03f, 0.095f);
0171: }
0172: clear();
0173: }
0174:
0175: // TODO: get/set secondChannel to JSStream/Clip/MIDI
0176: // TODO: get/set reverbChannel to JSStream/Clip/MIDI
0177: /*
0178: * Process request for Filtering fields
0179: */
0180: boolean getFilterFlag() {
0181: return filterFlag;
0182: }
0183:
0184: float getFilterFreq() {
0185: return filterFreq;
0186: }
0187:
0188: /**
0189: * Clears the fields associated with sample data for this sound, and
0190: * frees any device specific data associated with this sample.
0191: */
0192: public void clear() {
0193: if (debugFlag)
0194: debugPrint("JSPositionalSample.clear() enter");
0195: super .clear();
0196: leftGain = 1.0f;
0197: rightGain = 1.0f;
0198: leftDelay = 0;
0199: rightDelay = 0;
0200: xformLeftEar.set(-0.09f, -0.03f, 0.095f);
0201: xformRightEar.set(0.09f, -0.03f, 0.095f);
0202: // Z axis in head space - looking into the screen
0203: xformHeadZAxis.set(0.0f, 0.0f, -1.0f); // Va
0204: sourceToCenterEar.set(0.0f, 0.0f, 0.0f); // Vh
0205: sourceToRightEar.set(0.0f, 0.0f, 0.0f); // Vf or Vc
0206: sourceToLeftEar.set(0.0f, 0.0f, 0.0f); // Vf or Vc
0207: reset();
0208: if (debugFlag)
0209: debugPrint("JSPositionalSample.clear() exit");
0210: }
0211:
0212: /**
0213: * Reset time and count based fields associated with sample data
0214: * for this sound
0215: */
0216: void reset() {
0217: if (debugFlag)
0218: debugPrint("JSPositionalSample.reset() enter");
0219: super .reset();
0220: averageDistances = false; // denotes not previously processed
0221: deltaTime = 0;
0222: sourcePositionChange = -1.0;
0223: headPositionChange = -1.0;
0224: rateRatio = 1.0f;
0225: numDistances = 0;
0226: averageDistances = false;
0227: if (debugFlag)
0228: debugPrint("JSPositionalSample.reset() exit");
0229: }
0230:
0231: // increments index counters and bumps index numbers if the end of
0232: // the circular buffer is reached
0233: void incrementIndices() {
0234: int maxIndex = MAX_DISTANCES - 1;
0235: if (numDistances < maxIndex) {
0236: averageDistances = false;
0237: currentIndex = numDistances;
0238: lastIndex = currentIndex - 1;
0239: firstIndex = 0;
0240: numDistances++;
0241: } else if (numDistances == maxIndex) {
0242: // we filled the data buffers completely and are ready to
0243: // calculate averages
0244: averageDistances = true;
0245: currentIndex = maxIndex;
0246: lastIndex = currentIndex - 1;
0247: firstIndex = 0;
0248: numDistances++;
0249: } else if (numDistances > maxIndex) {
0250: // increment each counter and loop around
0251: averageDistances = true;
0252: currentIndex++;
0253: lastIndex++;
0254: firstIndex++;
0255: currentIndex %= MAX_DISTANCES;
0256: lastIndex %= MAX_DISTANCES;
0257: firstIndex %= MAX_DISTANCES;
0258: }
0259: }
0260:
0261: // Not only do we transform position but delta time is calculated and
0262: // old transformed position is saved
0263: // Average the last MAX_DISTANCES delta time and change in position using
0264: // an array for both and circlularly storing the time and distance values
0265: // into this array.
0266: // Current transformed position and time in stored into maxIndex of their
0267: // respective arrays.
0268: void setXformedPosition() {
0269: Point3f newPosition = new Point3f();
0270: if (debugFlag)
0271: debugPrint("*** setXformedPosition");
0272: // xform Position
0273: if (getVWrldXfrmFlag()) {
0274: if (debugFlag)
0275: debugPrint(" Transform set so transform pos");
0276: vworldXfrm.transform(position, newPosition);
0277: } else {
0278: if (debugFlag)
0279: debugPrint(" Transform NOT set so pos => xformPos");
0280: newPosition.set(position);
0281: }
0282: // store position and increment indices ONLY if theres an actual change
0283: if (newPosition.x == positions[currentIndex].x
0284: && newPosition.y == positions[currentIndex].y
0285: && newPosition.z == positions[currentIndex].z) {
0286: if (debugFlag)
0287: debugPrint(" No change in pos, so don't reset");
0288: return;
0289: }
0290:
0291: incrementIndices();
0292: // store new transformed position
0293: times[currentIndex] = System.currentTimeMillis();
0294: positions[currentIndex].set(newPosition);
0295: if (debugFlag)
0296: debugPrint(" xform(sound)Position -"
0297: + " positions[" + currentIndex + "] = ("
0298: + positions[currentIndex].x + ", "
0299: + positions[currentIndex].y + ", "
0300: + positions[currentIndex].z + ")");
0301:
0302: // since this is a change to the sound position and not the
0303: // head save the last head position into the current element
0304: if (numDistances > 1)
0305: centerEars[currentIndex].set(centerEars[lastIndex]);
0306:
0307: }
0308:
0309: /**
0310: * Set Doppler effect Rate
0311: *
0312: * Calculate the rate of change in for the head and sound
0313: * between the two time stamps (last two times position or
0314: * VirtualWorld transform was updated).
0315: * First determine if the head and sound source are moving
0316: * towards each other (distance between them is decreasing),
0317: * moving away from each other (distance between them is
0318: * increasing), or no change (distance is the same, not moving
0319: * or moving the same speed/direction).
0320: * The following equation is used for determining the change in frequency -
0321: * If there has been a change in the distance between the head and sound:
0322: *
0323: * f' = f * frequencyScaleFactor * velocityRatio
0324: *
0325: * For no change in the distance bewteen head and sound, velocityRatio is 1:
0326: *
0327: * f' = f
0328: *
0329: * For head and sound moving towards each other, velocityRatio (> 1.0) is:
0330: *
0331: * | speedOfSound*rollOff + velocityOfHead*velocityScaleFactor |
0332: * | ------------------------------------------------------------- |
0333: * | speedOfSound*rollOff - velocityOfSource*velocityScaleFactor |
0334: *
0335: * For head and sound moving away from each other, velocityRatio (< 1.0) is:
0336: *
0337: * | speedOfSound*rollOff - velocityOfHead*velocityScaleFactor |
0338: * | ------------------------------------------------------------- |
0339: * | speedOfSound*rollOff + velocityOfSource*velocityScaleFactor |
0340: *
0341: * where frequencyScaleFactor, rollOff, velocityScaleFactor all come from
0342: * the active AuralAttributes parameters.
0343: * The following special cases must be test for AuralAttribute parameters:
0344: * rolloff
0345: * Value MUST be > zero for any sound to be heard!
0346: * If value is zero, all sounds affected by AuralAttribute region are silent.
0347: * velocityScaleFactor
0348: * Value MUST be > zero for any sound to be heard!
0349: * If value is zero, all sounds affected by AuralAttribute region are paused.
0350: * frequencyScaleFactor
0351: * Value of zero disables Doppler calculations:
0352: * Sfreq' = Sfreq * frequencyScaleFactor
0353: *
0354: * This rate is passed to device drive as a change to playback sample
0355: * rate, in this case the frequency need not be known.
0356: *
0357: * Return value of zero denotes no change
0358: * Return value of -1 denotes ERROR
0359: */
0360: float calculateDoppler(AuralParameters attribs) {
0361: double sampleRateRatio = 1.0;
0362: double headVelocity = 0.0; // in milliseconds
0363: double soundVelocity = 0.0; // in milliseconds
0364: double distanceSourceToHead = 0.0; // in meters
0365: double lastDistanceSourceToHead = 0.0; // in meters
0366: float speedOfSound = attribs.SPEED_OF_SOUND;
0367: double numerator = 1.0;
0368: double denominator = 1.0;
0369: int direction = NO_CHANGE; // sound movement away or towards listener
0370:
0371: Point3f lastXformPosition;
0372: Point3f lastXformCenterEar;
0373: Point3f xformPosition;
0374: Point3f xformCenterEar;
0375: float averagedSoundDistances = 0.0f;
0376: float averagedEarsDistances = 0.0f;
0377:
0378: /*
0379: * Average the differences between the last MAX_DISTANCE
0380: * sound positions and head positions
0381: */
0382: if (!averageDistances) {
0383: // TODO: Use some EPSilion to do 'equals' test against
0384: if (dopplerFlag)
0385: debugPrint("JSPositionalSample.calculateDoppler - "
0386: + "not enough distance data collected, "
0387: + "dopplerRatio set to zero");
0388: // can't calculate change in direction
0389: return 0.0f; // sample rate ratio is zero
0390: }
0391:
0392: lastXformPosition = positions[lastIndex];
0393: lastXformCenterEar = centerEars[lastIndex];
0394: xformPosition = positions[currentIndex];
0395: xformCenterEar = centerEars[currentIndex];
0396: distanceSourceToHead = xformPosition.distance(xformCenterEar);
0397: lastDistanceSourceToHead = lastXformPosition
0398: .distance(lastXformCenterEar);
0399: if (dopplerFlag) {
0400: debugPrint("JSPositionalSample.calculateDoppler - distances: "
0401: + "current,last = "
0402: + distanceSourceToHead
0403: + ", "
0404: + lastDistanceSourceToHead);
0405: debugPrint(" "
0406: + "current position = " + xformPosition.x + ", "
0407: + xformPosition.y + ", " + xformPosition.z);
0408: debugPrint(" "
0409: + "current ear = " + xformCenterEar.x + ", "
0410: + xformCenterEar.y + ", " + xformCenterEar.z);
0411: debugPrint(" "
0412: + "last position = " + lastXformPosition.x + ", "
0413: + lastXformPosition.y + ", " + lastXformPosition.z);
0414: debugPrint(" "
0415: + "last ear = " + lastXformCenterEar.x + ", "
0416: + lastXformCenterEar.y + ", "
0417: + lastXformCenterEar.z);
0418: }
0419: if (distanceSourceToHead == lastDistanceSourceToHead) {
0420: // TODO: Use some EPSilion to do 'equals' test against
0421: if (dopplerFlag)
0422: debugPrint("JSPositionalSample.calculateDoppler - "
0423: + "distance diff = 0, dopplerRatio set to zero");
0424: // can't calculate change in direction
0425: return 0.0f; // sample rate ratio is zero
0426: }
0427:
0428: deltaTime = times[currentIndex] - times[firstIndex];
0429: for (int i = 0; i < (MAX_DISTANCES - 1); i++) {
0430: averagedSoundDistances += positions[i + 1]
0431: .distance(positions[i]);
0432: averagedEarsDistances += centerEars[i + 1]
0433: .distance(centerEars[i]);
0434: }
0435: averagedSoundDistances /= (MAX_DISTANCES - 1);
0436: averagedEarsDistances /= (MAX_DISTANCES - 1);
0437: soundVelocity = averagedSoundDistances / deltaTime;
0438: headVelocity = averagedEarsDistances / deltaTime;
0439: if (dopplerFlag) {
0440: debugPrint(" "
0441: + "delta time = " + deltaTime);
0442: debugPrint(" "
0443: + "soundPosition delta = "
0444: + xformPosition.distance(lastXformPosition));
0445: debugPrint(" "
0446: + "soundVelocity = " + soundVelocity);
0447: debugPrint(" "
0448: + "headPosition delta = "
0449: + xformCenterEar.distance(lastXformCenterEar));
0450: debugPrint(" "
0451: + "headVelocity = " + headVelocity);
0452: }
0453: if (attribs != null) {
0454:
0455: float rolloff = attribs.rolloff;
0456: float velocityScaleFactor = attribs.velocityScaleFactor;
0457: if (rolloff != 1.0f) {
0458: speedOfSound *= rolloff;
0459: if (dopplerFlag)
0460: debugPrint(" "
0461: + "attrib rollof = " + rolloff);
0462: }
0463: if (velocityScaleFactor != 1.0f) {
0464: soundVelocity *= velocityScaleFactor;
0465: headVelocity *= velocityScaleFactor;
0466: if (dopplerFlag) {
0467: debugPrint(" "
0468: + "attrib velocity scale factor = "
0469: + velocityScaleFactor);
0470: debugPrint(" "
0471: + "new soundVelocity = " + soundVelocity);
0472: debugPrint(" "
0473: + "new headVelocity = " + headVelocity);
0474: }
0475: }
0476: }
0477: if (distanceSourceToHead < lastDistanceSourceToHead) {
0478: // sound and head moving towards each other
0479: if (dopplerFlag)
0480: debugPrint(" "
0481: + "moving towards...");
0482: direction = TOWARDS;
0483: numerator = speedOfSound + headVelocity;
0484: denominator = speedOfSound - soundVelocity;
0485: } else {
0486: // sound and head moving away from each other
0487: // note: no change in distance case covered above
0488: if (dopplerFlag)
0489: debugPrint(" "
0490: + "moving away...");
0491: direction = AWAY;
0492: numerator = speedOfSound - headVelocity;
0493: denominator = speedOfSound + soundVelocity;
0494: }
0495: if (numerator <= 0.0) {
0496: if (dopplerFlag)
0497: debugPrint("JSPositionalSample.calculateDoppler: "
0498: + "BOOM!! - velocity of head > speed of sound");
0499: return -1.0f;
0500: } else if (denominator <= 0.0) {
0501: if (dopplerFlag)
0502: debugPrint("JSPositionalSample.calculateDoppler: "
0503: + "BOOM!! - velocity of sound source negative");
0504: return -1.0f;
0505: } else {
0506: if (dopplerFlag)
0507: debugPrint("JSPositionalSample.calculateDoppler: "
0508: + "numerator = " + numerator
0509: + ", denominator = " + denominator);
0510: sampleRateRatio = numerator / denominator;
0511: }
0512:
0513: /********
0514: IF direction WERE important to calling method...
0515: * Return value greater than 0 denotes direction of sound source is
0516: * towards the listener
0517: * Return value less than 0 denotes direction of sound source is
0518: * away from the listener
0519: if (direction == AWAY)
0520: return -((float)sampleRateRatio);
0521: else
0522: return (float)sampleRateRatio;
0523: *********/
0524: return (float) sampleRateRatio;
0525: }
0526:
0527: void updateEar(int dirtyFlags, View view) {
0528: if (debugFlag)
0529: debugPrint("*** updateEar fields");
0530: // xform Ear
0531: Point3f xformCenterEar = new Point3f();
0532: if (!calculateNewEar(dirtyFlags, view, xformCenterEar)) {
0533: if (debugFlag)
0534: debugPrint("calculateNewEar returned false");
0535: return;
0536: }
0537: // store ear and increment indices ONLY if there is an actual change
0538: if (xformCenterEar.x == centerEars[currentIndex].x
0539: && xformCenterEar.y == centerEars[currentIndex].y
0540: && xformCenterEar.z == centerEars[currentIndex].z) {
0541: if (debugFlag)
0542: debugPrint(" No change in ear, so don't reset");
0543: return;
0544: }
0545: // store xform Ear
0546: incrementIndices();
0547: times[currentIndex] = System.currentTimeMillis();
0548: centerEars[currentIndex].set(xformCenterEar);
0549: // since this is a change to the head position and not the sound
0550: // position save the last sound position into the current element
0551: if (numDistances > 1)
0552: positions[currentIndex].set(positions[lastIndex]);
0553: }
0554:
0555: boolean calculateNewEar(int dirtyFlags, View view,
0556: Point3f xformCenterEar) {
0557: /*
0558: * Transform ear position (from Head) into Virtual World Coord space
0559: */
0560: Point3d earPosition = new Point3d(); // temporary double Point
0561:
0562: // TODO: check dirty flags coming in
0563: // For now, recalculate ear positions by forcing earsXformed false
0564: boolean earsXformed = false;
0565: if (!earsXformed) {
0566: if (view != null) {
0567: PhysicalBody body = view.getPhysicalBody();
0568: if (body != null) {
0569:
0570: // Get Head Coord. to Virtual World transform
0571: // TODO: re-enable this when userHeadToVworld is
0572: // implemented correctly!!!
0573: Transform3D headToVwrld = new Transform3D();
0574: view.getUserHeadToVworld(headToVwrld);
0575: if (debugFlag) {
0576: debugPrint("user head to Vwrld colum-major:");
0577: double[] matrix = new double[16];
0578: headToVwrld.get(matrix);
0579: debugPrint("JSPosSample " + matrix[0] + ", "
0580: + matrix[1] + ", " + matrix[2] + ", "
0581: + matrix[3]);
0582: debugPrint("JSPosSample " + matrix[4] + ", "
0583: + matrix[5] + ", " + matrix[6] + ", "
0584: + matrix[7]);
0585: debugPrint("JSPosSample " + matrix[8] + ", "
0586: + matrix[9] + ", " + matrix[10] + ", "
0587: + matrix[11]);
0588: debugPrint("JSPosSample " + matrix[12]
0589: + ", " + matrix[13] + ", " + matrix[14]
0590: + ", " + matrix[15]);
0591: }
0592:
0593: // Get left and right ear positions in Head Coord.s
0594: // Transforms left and right ears to Virtual World coord.s
0595: body.getLeftEarPosition(earPosition);
0596: xformLeftEar.x = (float) earPosition.x;
0597: xformLeftEar.y = (float) earPosition.y;
0598: xformLeftEar.z = (float) earPosition.z;
0599: body.getRightEarPosition(earPosition);
0600: xformRightEar.x = (float) earPosition.x;
0601: xformRightEar.y = (float) earPosition.y;
0602: xformRightEar.z = (float) earPosition.z;
0603: headToVwrld.transform(xformRightEar);
0604: headToVwrld.transform(xformLeftEar);
0605: // Transform head viewing (Z) axis to Virtual World coord.s
0606: xformHeadZAxis.set(0.0f, 0.0f, -1.0f); // Va
0607: headToVwrld.transform(xformHeadZAxis);
0608:
0609: // calculate the new (current) mid-point between the ears
0610: // find the mid point between left and right ear positions
0611: xformCenterEar.x = xformLeftEar.x
0612: + ((xformRightEar.x - xformLeftEar.x) * 0.5f);
0613: xformCenterEar.y = xformLeftEar.y
0614: + ((xformRightEar.y - xformLeftEar.y) * 0.5f);
0615: xformCenterEar.z = xformLeftEar.z
0616: + ((xformRightEar.z - xformLeftEar.z) * 0.5f);
0617: // TODO: when head changes earDirty should be set!
0618: // earDirty = false;
0619: if (debugFlag) {
0620: debugPrint(" earXformed CALCULATED");
0621: debugPrint(" xformCenterEar = "
0622: + xformCenterEar.x + " "
0623: + xformCenterEar.y + " "
0624: + xformCenterEar.z);
0625: }
0626: earsXformed = true;
0627: } // end of body NOT null
0628: } // end of view NOT null
0629: } // end of earsDirty
0630: else {
0631: // TODO: use existing transformed ear positions
0632: }
0633:
0634: if (!earsXformed) {
0635: // uses the default head position of (0.0, -0.03, 0.095)
0636: if (debugFlag)
0637: debugPrint(" earXformed NOT calculated");
0638: }
0639: return earsXformed;
0640: }
0641:
0642: /**
0643: * Render this sample
0644: *
0645: * Calculate the audiodevice parameters necessary to spatially play this
0646: * sound.
0647: */
0648: public void render(int dirtyFlags, View view,
0649: AuralParameters attribs) {
0650: if (debugFlag)
0651: debugPrint("JSPositionalSample.render");
0652: updateEar(dirtyFlags, view);
0653:
0654: /*
0655: * Time to check velocities and change the playback rate if necessary...
0656: *
0657: * Rolloff value MUST be > zero for any sound to be heard!
0658: * If rolloff is zero, all sounds affected by AuralAttribute region
0659: * are silent.
0660: * FrequencyScaleFactor value MUST be > zero for any sound to be heard!
0661: * since Sfreq' = Sfreq * frequencyScaleFactor.
0662: * If FrequencyScaleFactor is zero, all sounds affected by
0663: * AuralAttribute region are paused.
0664: * VelocityScaleFactor value of zero disables Doppler calculations.
0665: *
0666: * Scale 'Doppler' rate (or lack of Doppler) by frequencyScaleFactor.
0667: */
0668: float dopplerRatio = 1.0f;
0669: if (attribs != null) {
0670: float rolloff = attribs.rolloff;
0671: float frequencyScaleFactor = attribs.frequencyScaleFactor;
0672: float velocityScaleFactor = attribs.velocityScaleFactor;
0673: if (debugFlag || dopplerFlag)
0674: debugPrint("JSPositionalSample: attribs NOT null");
0675: if (rolloff <= 0.0f) {
0676: if (debugFlag)
0677: debugPrint(" rolloff = " + rolloff + " <= 0.0");
0678: // TODO: Make sound silent
0679: // return ???
0680: } else if (frequencyScaleFactor <= 0.0f) {
0681: if (debugFlag)
0682: debugPrint(" freqScaleFactor = "
0683: + frequencyScaleFactor + " <= 0.0");
0684: // TODO: Pause sound silent
0685: // return ???
0686: } else if (velocityScaleFactor > 0.0f) {
0687: if (debugFlag || dopplerFlag)
0688: debugPrint(" velocityScaleFactor = "
0689: + velocityScaleFactor);
0690: /*******
0691: if (deltaTime > 0) {
0692: *******/
0693: // Doppler can be calculated after the second time
0694: // updateXformParams() is executed
0695: dopplerRatio = calculateDoppler(attribs);
0696:
0697: if (dopplerRatio == 0.0f) {
0698: // dopplerRatio zeroo denotes no changed
0699: // TODO: But what if frequencyScaleFactor has changed
0700: if (debugFlag) {
0701: debugPrint("JSPositionalSample: render: "
0702: + "dopplerRatio returned zero; no change");
0703: }
0704: } else if (dopplerRatio == -1.0f) {
0705: // error returned by calculateDoppler
0706: if (debugFlag) {
0707: debugPrint("JSPositionalSample: render: "
0708: + "dopplerRatio returned = "
0709: + dopplerRatio + "< 0");
0710: }
0711: // TODO: Make sound silent
0712: // return ???
0713: } else if (dopplerRatio > 0.0f) {
0714: // rate could be changed
0715: rateRatio = dopplerRatio * frequencyScaleFactor
0716: * getRateScaleFactor();
0717: if (debugFlag) {
0718: debugPrint(" scaled by frequencyScaleFactor = "
0719: + frequencyScaleFactor);
0720: }
0721: }
0722: /******
0723: }
0724: else {
0725: if (debugFlag)
0726: debugPrint("deltaTime <= 0 - skip Doppler calc");
0727: }
0728: ******/
0729: } else { // auralAttributes not null but velocityFactor <= 0
0730: // Doppler is disabled
0731: rateRatio = frequencyScaleFactor * getRateScaleFactor();
0732: }
0733: }
0734: /*
0735: * since aural attributes undefined, default values are used,
0736: * thus no Doppler calculated
0737: */
0738: else {
0739: if (debugFlag || dopplerFlag)
0740: debugPrint("JSPositionalSample: attribs null");
0741: rateRatio = 1.0f;
0742: }
0743:
0744: this .panSample(attribs);
0745: }
0746:
0747: /* *****************
0748: *
0749: * Calculate Angular Gain
0750: *
0751: * *****************/
0752: /*
0753: * Calculates the Gain scale factor applied to the overall gain for
0754: * a sound based on angle between a sound's projected direction and the
0755: * vector between the sounds position and center ear.
0756: *
0757: * For Point Sounds this value is always 1.0f.
0758: */
0759: float calculateAngularGain() {
0760: return (1.0f);
0761: }
0762:
0763: /* *****************
0764: *
0765: * Calculate Filter
0766: *
0767: * *****************/
0768: /*
0769: * Calculates the low-pass cutoff frequency filter value applied to the
0770: * a sound based on both:
0771: * Distance Filter (from Aural Attributes) based on distance
0772: * between the sound and the listeners position
0773: * Angular Filter (for Directional Sounds) based on the angle
0774: * between a sound's projected direction and the
0775: * vector between the sounds position and center ear.
0776: * The lowest of these two filter is used.
0777: * This filter value is stored into the sample's filterFreq field.
0778: */
0779: void calculateFilter(float distance, AuralParameters attribs) {
0780: // setting filter cutoff freq to 44.1kHz which, in this
0781: // implementation, is the same as not performing filtering
0782: float distanceFilter = 44100.0f;
0783: float angularFilter = 44100.0f;
0784: int arrayLength = attribs.getDistanceFilterLength();
0785: int filterType = attribs.getDistanceFilterType();
0786: boolean distanceFilterFound = false;
0787: boolean angularFilterFound = false;
0788: if ((filterType != AuralParameters.NO_FILTERING)
0789: && arrayLength > 0) {
0790: double[] distanceArray = new double[arrayLength];
0791: float[] cutoffArray = new float[arrayLength];
0792: attribs.getDistanceFilter(distanceArray, cutoffArray);
0793:
0794: if (debugFlag) {
0795: debugPrint("distanceArray cutoffArray");
0796: for (int i = 0; i < arrayLength; i++)
0797: debugPrint((float) (distanceArray[i]) + ", "
0798: + cutoffArray[i]);
0799: }
0800: distanceFilter = findFactor((double) distance,
0801: distanceArray, cutoffArray);
0802: if (distanceFilter < 0.0f)
0803: distanceFilterFound = false;
0804: else
0805: distanceFilterFound = true;
0806: } else {
0807: distanceFilterFound = false;
0808: distanceFilter = -1.0f;
0809: }
0810:
0811: if (debugFlag)
0812: debugPrint(" calculateFilter arrayLength = "
0813: + arrayLength);
0814:
0815: // Angular filter only applies to directional sound sources.
0816: angularFilterFound = false;
0817: angularFilter = -1.0f;
0818:
0819: filterFlag = distanceFilterFound || angularFilterFound;
0820: filterFreq = distanceFilter;
0821: if (debugFlag)
0822: debugPrint(" calculateFilter flag,freq = " + filterFlag
0823: + "," + filterFreq);
0824: }
0825:
0826: /* *****************
0827: *
0828: * Find Factor
0829: *
0830: * *****************/
0831: /*
0832: * Interpolates the correct output factor given a 'distance' value
0833: * and references to the distance array and factor array used in
0834: * the calculation. These array parameters could be either linear or
0835: * angular distance arrays, or filter arrays.
0836: * The values in the distance array are monotonically increasing.
0837: * This method looks at pairs of distance array values to find which
0838: * pair the input distance argument is between distanceArray[index] and
0839: * distanceArray[index+1].
0840: * The index is used to get factorArray[index] and factorArray[index+1].
0841: * Then the ratio of the 'distance' between this pair of distanceArray
0842: * values is used to scale the two found factorArray values proportionally.
0843: * The resulting factor is returned, unless there is an error, then -1.0
0844: * is returned.
0845: */
0846: float findFactor(double distance, double[] distanceArray,
0847: float[] factorArray) {
0848: int index, lowIndex, highIndex, indexMid;
0849:
0850: if (debugFlag)
0851: debugPrint("JSPositionalSample.findFactor entered");
0852:
0853: /*
0854: * Error checking
0855: */
0856: if (distanceArray == null || factorArray == null) {
0857: if (debugFlag)
0858: debugPrint(" findFactor: arrays null");
0859: return -1.0f; // no value
0860: }
0861: int arrayLength = distanceArray.length;
0862: if (arrayLength < 2) {
0863: if (debugFlag)
0864: debugPrint(" findFactor: arrays length < 2");
0865: return -1.0f; // no value
0866: }
0867: int largestIndex = arrayLength - 1;
0868:
0869: /*
0870: * Calculate distanceGain scale factor
0871: */
0872: if (distance >= distanceArray[largestIndex]) {
0873: if (debugFlag) {
0874: debugPrint(" findFactor: distance > "
0875: + distanceArray[largestIndex]);
0876: debugPrint(" distanceArray length = " + arrayLength);
0877: }
0878: return factorArray[largestIndex];
0879: } else if (distance <= distanceArray[0]) {
0880: if (debugFlag)
0881: debugPrint(" findFactor: distance < "
0882: + distanceArray[0]);
0883: return factorArray[0];
0884: }
0885: /*
0886: * Distance between points within attenuation array.
0887: * Use binary halfing of distance array
0888: */
0889: else {
0890: lowIndex = 0;
0891: highIndex = largestIndex;
0892: if (debugFlag)
0893: debugPrint(" while loop to find index: ");
0894: while (lowIndex < (highIndex - 1)) {
0895: if (debugFlag) {
0896: debugPrint(" lowIndex " + lowIndex
0897: + ", highIndex " + highIndex);
0898: debugPrint(" d.A. pair for lowIndex "
0899: + distanceArray[lowIndex] + ", "
0900: + factorArray[lowIndex]);
0901: debugPrint(" d.A. pair for highIndex "
0902: + distanceArray[highIndex] + ", "
0903: + factorArray[highIndex]);
0904: }
0905: /*
0906: * we can assume distance is between distance atttenuation vals
0907: * distanceArray[lowIndex] and distanceArray[highIndex]
0908: * calculate gain scale factor based on distance
0909: */
0910: if (distanceArray[lowIndex] >= distance) {
0911: if (distance < distanceArray[lowIndex]) {
0912: if (internalErrors)
0913: debugPrint("Internal Error: binary halving in "
0914: + " findFactor failed; distance < index value");
0915: }
0916: if (debugFlag) {
0917: debugPrint(" index == distanceGain "
0918: + lowIndex);
0919: debugPrint(" findFactor returns [LOW="
0920: + lowIndex + "] "
0921: + factorArray[lowIndex]);
0922: }
0923: // take value of scale factor directly from factorArray
0924: return factorArray[lowIndex];
0925: } else if (distanceArray[highIndex] <= distance) {
0926: if (distance > distanceArray[highIndex]) {
0927: if (internalErrors)
0928: debugPrint("Internal Error: binary halving in "
0929: + " findFactor failed; distance > index value");
0930: }
0931: if (debugFlag) {
0932: debugPrint(" index == distanceGain "
0933: + highIndex);
0934: debugPrint(" findFactor returns [HIGH="
0935: + highIndex + "] "
0936: + factorArray[highIndex]);
0937: }
0938: // take value of scale factor directly from factorArray
0939: return factorArray[highIndex];
0940: }
0941: if (distance > distanceArray[lowIndex]
0942: && distance < distanceArray[highIndex]) {
0943: indexMid = lowIndex + ((highIndex - lowIndex) / 2);
0944: if (distance <= distanceArray[indexMid])
0945: // value of distance in lower "half" of list
0946: highIndex = indexMid;
0947: else
0948: // value if distance in upper "half" of list
0949: lowIndex = indexMid;
0950: }
0951: } /* of while */
0952:
0953: /*
0954: * ratio: distance from listener to sound source
0955: * between lowIndex and highIndex times
0956: * attenuation value between lowIndex and highIndex
0957: * gives linearly interpolationed attenuation value
0958: */
0959: if (debugFlag) {
0960: debugPrint(" ratio calculated using lowIndex "
0961: + lowIndex + ", highIndex " + highIndex);
0962: debugPrint(" d.A. pair for lowIndex "
0963: + distanceArray[lowIndex] + ", "
0964: + factorArray[lowIndex]);
0965: debugPrint(" d.A. pair for highIndex "
0966: + distanceArray[highIndex] + ", "
0967: + factorArray[highIndex]);
0968: }
0969:
0970: float outputFactor = ((float) (((distance - distanceArray[lowIndex]) / (distanceArray[highIndex] - distanceArray[lowIndex]))) * (factorArray[highIndex] - factorArray[lowIndex]))
0971: + factorArray[lowIndex];
0972: if (debugFlag)
0973: debugPrint(" findFactor returns " + outputFactor);
0974: return outputFactor;
0975: }
0976: }
0977:
0978: /**
0979: * CalculateDistanceAttenuation
0980: *
0981: * Simply calls generic (for PointSound) 'findFactor()' with
0982: * a single set of attenuation distance and gain scale factor arrays.
0983: */
0984: float calculateDistanceAttenuation(float distance) {
0985: float factor = 1.0f;
0986: factor = findFactor((double) distance,
0987: this .attenuationDistance, this .attenuationGain);
0988: if (factor >= 0.0)
0989: return (factor);
0990: else
0991: return (1.0f);
0992: }
0993:
0994: /* ******************
0995: *
0996: * Pan Sample
0997: *
0998: * ******************/
0999: /*
1000: * Sets pan and delay for a single sample associated with this Sound.
1001: * Front and Back quadrants are treated the same.
1002: */
1003: void panSample(AuralParameters attribs) {
1004: int quadrant = 1;
1005: float intensityHigh = 1.0f;
1006: float intensityLow = 0.125f;
1007: float intensityDifference = intensityHigh - intensityLow;
1008:
1009: //TODO: time around "average" default head
1010: // int delayHigh = 32; // 32.15 samples = .731 ms
1011: // int delayLow = 0;
1012:
1013: float intensityOffset; // 0.0 -> 1.0 then 1.0 -> 0.0 for full rotation
1014: float halfX;
1015: int id;
1016: int err;
1017:
1018: float nearZero = 0.000001f;
1019: float nearOne = 0.999999f;
1020: float nearNegativeOne = -nearOne;
1021: float halfPi = (float) Math.PI * 0.5f;
1022: /*
1023: * Parameters used for IID and ITD equations.
1024: * Name of parameters (as used in Guide, E.3) are denoted in comments.
1025: */
1026: float distanceSourceToCenterEar = 0.0f; // Dh
1027: float lastDistanceSourceToCenterEar = 0.0f;
1028: float distanceSourceToRightEar = 0.0f; // Ef or Ec
1029: float distanceSourceToLeftEar = 0.0f; // Ef or Ec
1030: float distanceBetweenEars = 0.18f; // De
1031: float radiusOfHead = 0.0f; // De/2
1032: float radiusOverDistanceToSource = 0.0f; // De/2 * 1/Dh
1033:
1034: float alpha = 0.0f; // 'alpha'
1035: float sinAlpha = 0.0f; // sin(alpha);
1036: float gamma = 0.0f; // 'gamma'
1037:
1038: // Speed of Sound (unaffected by rolloff) in millisec/meters
1039: float speedOfSound = attribs.SPEED_OF_SOUND;
1040: float invSpeedOfSound = 1.0f / attribs.SPEED_OF_SOUND;
1041:
1042: float sampleRate = 44.1f; // 44 samples/millisec
1043:
1044: boolean rightEarClosest = false;
1045: boolean soundFromBehind = false;
1046:
1047: float distanceGain = 1.0f;
1048: float allGains = this .gain; // product of gain scale factors
1049:
1050: Point3f workingPosition = new Point3f();
1051: Point3f workingCenterEar = new Point3f();
1052:
1053: // Asuumes that head and ear positions can be retrieved from universe
1054:
1055: Vector3f mixScale = new Vector3f(); // for mix*Samples code
1056:
1057: // Use transformed position of this sound
1058: workingPosition.set(positions[currentIndex]);
1059: workingCenterEar.set(centerEars[currentIndex]);
1060: if (debugFlag) {
1061: debugPrint("panSample:workingPosition from" + " positions["
1062: + currentIndex + "] -> " + workingPosition.x + ", "
1063: + workingPosition.y + ", " + workingPosition.z
1064: + " for pointSound " + this );
1065: debugPrint("panSample:workingCenterEar "
1066: + workingCenterEar.x + " " + workingCenterEar.y
1067: + " " + workingCenterEar.z);
1068: debugPrint("panSample:xformLeftEar " + xformLeftEar.x + " "
1069: + xformLeftEar.y + " " + xformLeftEar.z);
1070: debugPrint("panSample:xformRightEar " + xformRightEar.x
1071: + " " + xformRightEar.y + " " + xformRightEar.z);
1072: }
1073:
1074: // Create the vectors from the sound source to head positions
1075: sourceToCenterEar.x = workingCenterEar.x - workingPosition.x;
1076: sourceToCenterEar.y = workingCenterEar.y - workingPosition.y;
1077: sourceToCenterEar.z = workingCenterEar.z - workingPosition.z;
1078: sourceToRightEar.x = xformRightEar.x - workingPosition.x;
1079: sourceToRightEar.y = xformRightEar.y - workingPosition.y;
1080: sourceToRightEar.z = xformRightEar.z - workingPosition.z;
1081: sourceToLeftEar.x = xformLeftEar.x - workingPosition.x;
1082: sourceToLeftEar.y = xformLeftEar.y - workingPosition.y;
1083: sourceToLeftEar.z = xformLeftEar.z - workingPosition.z;
1084:
1085: /*
1086: * get distances from SoundSource to
1087: * (i) head origin
1088: * (ii) right ear
1089: * (iii) left ear
1090: */
1091: distanceSourceToCenterEar = workingPosition
1092: .distance(workingCenterEar);
1093: distanceSourceToRightEar = workingPosition
1094: .distance(xformRightEar);
1095: distanceSourceToLeftEar = workingPosition
1096: .distance(xformLeftEar);
1097: distanceBetweenEars = xformRightEar.distance(xformLeftEar);
1098: if (debugFlag)
1099: debugPrint(" distance from left,right ears to source: = ("
1100: + distanceSourceToLeftEar
1101: + ", "
1102: + distanceSourceToRightEar + ")");
1103:
1104: radiusOfHead = distanceBetweenEars * 0.5f;
1105: if (debugFlag)
1106: debugPrint(" radius of head = " + radiusOfHead);
1107: radiusOverDistanceToSource = // De/2 * 1/Dh
1108: radiusOfHead / distanceSourceToCenterEar;
1109: if (debugFlag)
1110: debugPrint(" radius over distance = "
1111: + radiusOverDistanceToSource);
1112: if (debugFlag) {
1113: debugPrint("panSample:source to center ear "
1114: + sourceToCenterEar.x + " " + sourceToCenterEar.y
1115: + " " + sourceToCenterEar.z);
1116: debugPrint("panSample:xform'd Head ZAxis "
1117: + xformHeadZAxis.x + " " + xformHeadZAxis.y + " "
1118: + xformHeadZAxis.z);
1119: debugPrint("panSample:length of sourceToCenterEar "
1120: + sourceToCenterEar.length());
1121: debugPrint("panSample:length of xformHeadZAxis "
1122: + xformHeadZAxis.length());
1123: }
1124:
1125: // Dot Product
1126: double dotProduct = (double) ((sourceToCenterEar
1127: .dot(xformHeadZAxis)) / (sourceToCenterEar.length() * xformHeadZAxis
1128: .length()));
1129: if (debugFlag)
1130: debugPrint(" dot product = " + dotProduct);
1131: alpha = (float) (Math.acos(dotProduct));
1132: if (debugFlag)
1133: debugPrint(" alpha = " + alpha);
1134:
1135: if (alpha > halfPi) {
1136: if (debugFlag)
1137: debugPrint(" sound from behind");
1138: soundFromBehind = true;
1139: alpha = (float) Math.PI - alpha;
1140: if (debugFlag)
1141: debugPrint(" PI minus alpha =>" + alpha);
1142: } else {
1143: soundFromBehind = false;
1144: if (debugFlag)
1145: debugPrint(" sound from in front");
1146: }
1147:
1148: gamma = (float) (Math.acos(radiusOverDistanceToSource));
1149: if (debugFlag)
1150: debugPrint(" gamma " + gamma);
1151:
1152: rightEarClosest = (distanceSourceToRightEar > distanceSourceToLeftEar) ? false
1153: : true;
1154: /*
1155: * Determine the quadrant sound is in
1156: */
1157: if (rightEarClosest) {
1158: if (debugFlag)
1159: debugPrint(" right ear closest");
1160: if (soundFromBehind)
1161: quadrant = 4;
1162: else
1163: quadrant = 1;
1164: } else {
1165: if (debugFlag)
1166: debugPrint(" left ear closest");
1167: if (soundFromBehind)
1168: quadrant = 3;
1169: else
1170: quadrant = 2;
1171: }
1172: sinAlpha = (float) (Math.sin((double) alpha));
1173: if (sinAlpha < 0.0)
1174: sinAlpha = -sinAlpha;
1175: if (debugFlag)
1176: debugPrint(" sin(alpha) " + sinAlpha);
1177:
1178: /*
1179: * The path from sound source to the farthest ear is always indirect
1180: * (it wraps around part of the head).
1181: * Calculate distance wrapped around the head for farthest ear
1182: */
1183: float DISTANCE = (float) Math
1184: .sqrt((double) distanceSourceToCenterEar
1185: * distanceSourceToCenterEar + radiusOfHead
1186: * radiusOfHead);
1187: if (debugFlag)
1188: debugPrint(" partial distance from edge of head to source = "
1189: + distanceSourceToCenterEar);
1190: if (rightEarClosest) {
1191: distanceSourceToLeftEar = DISTANCE + radiusOfHead
1192: * (halfPi + alpha - gamma);
1193: if (debugFlag)
1194: debugPrint(" new distance from left ear to source = "
1195: + distanceSourceToLeftEar);
1196: } else {
1197: distanceSourceToRightEar = DISTANCE + radiusOfHead
1198: * (halfPi + alpha - gamma);
1199: if (debugFlag)
1200: debugPrint(" new distance from right ear to source = "
1201: + distanceSourceToRightEar);
1202: }
1203: /*
1204: * The path from the source source to the closest ear could either
1205: * be direct or indirect (wraps around part of the head).
1206: * if sinAlpha >= radiusOverDistance path of sound to closest ear
1207: * is direct, otherwise it is indirect
1208: */
1209: if (sinAlpha < radiusOverDistanceToSource) {
1210: if (debugFlag)
1211: debugPrint(" closest path is also indirect ");
1212: // Path of sound to closest ear is indirect
1213:
1214: if (rightEarClosest) {
1215: distanceSourceToRightEar = DISTANCE + radiusOfHead
1216: * (halfPi - alpha - gamma);
1217: if (debugFlag)
1218: debugPrint(" new distance from right ear to source = "
1219: + distanceSourceToRightEar);
1220: } else {
1221: distanceSourceToLeftEar = DISTANCE + radiusOfHead
1222: * (halfPi - alpha - gamma);
1223: if (debugFlag)
1224: debugPrint(" new distance from left ear to source = "
1225: + distanceSourceToLeftEar);
1226: }
1227: } else {
1228: if (debugFlag)
1229: debugPrint(" closest path is direct ");
1230: if (rightEarClosest) {
1231: if (debugFlag)
1232: debugPrint(" direct distance from right ear to source = "
1233: + distanceSourceToRightEar);
1234: } else {
1235: if (debugFlag)
1236: debugPrint(" direct distance from left ear to source = "
1237: + distanceSourceToLeftEar);
1238: }
1239: }
1240:
1241: /**
1242: * Short-cut taken. Rather than using actual delays from source
1243: * (where the overall distances would be taken into account in
1244: * determining delay) the difference in the left and right delay
1245: * are applied.
1246: * This approach will be preceptibly wrong for sound sources that
1247: * are very far away from the listener so both ears would have
1248: * large delay.
1249: */
1250: sampleRate = channel.rateInHz * (0.001f); // rate in milliseconds
1251: if (rightEarClosest) {
1252: rightDelay = 0;
1253: leftDelay = (int) ((distanceSourceToLeftEar - distanceSourceToRightEar)
1254: * invSpeedOfSound * sampleRate);
1255: } else {
1256: leftDelay = 0;
1257: rightDelay = (int) ((distanceSourceToRightEar - distanceSourceToLeftEar)
1258: * invSpeedOfSound * sampleRate);
1259: }
1260:
1261: if (debugFlag) {
1262: debugPrint(" using inverted SoS = "
1263: + invSpeedOfSound);
1264: debugPrint(" and sample rate = " + sampleRate);
1265: debugPrint(" left and right delay = ("
1266: + leftDelay + ", " + rightDelay + ")");
1267: }
1268:
1269: // What should the gain be for the different ears???
1270: // TODO: now using a hack that sets gain based on a unit circle!!!
1271: workingPosition.sub(workingCenterEar); // offset sound pos. by head origin
1272: // normalize; put Sound on unit sphere around head origin
1273: workingPosition.scale(1.0f / distanceSourceToCenterEar);
1274: if (debugFlag)
1275: debugPrint(" workingPosition after unitization "
1276: + workingPosition.x + " " + workingPosition.y + " "
1277: + workingPosition.z);
1278:
1279: /*
1280: * Get the correct distance gain scale factor from attenuation arrays.
1281: * This requires that sourceToCenterEar vector has been calculated.
1282: */
1283: // TODO: now using distance from center ear to source
1284: // Using distances from each ear to source would be more accurate
1285: distanceGain = calculateDistanceAttenuation(distanceSourceToCenterEar);
1286:
1287: allGains *= distanceGain;
1288:
1289: /*
1290: * Add angular gain (for Cone sound)
1291: */
1292: if (debugFlag)
1293: debugPrint(" all Gains (without angular gain) "
1294: + allGains);
1295: // assume that transfromed Position is already calculated
1296: allGains *= this .calculateAngularGain();
1297: if (debugFlag)
1298: debugPrint(" (incl. angular gain) "
1299: + allGains);
1300:
1301: halfX = workingPosition.x / 2.0f;
1302: if (halfX >= 0)
1303: intensityOffset = (intensityDifference * (0.5f - halfX));
1304: else
1305: intensityOffset = (intensityDifference * (0.5f + halfX));
1306:
1307: /*
1308: * For now have delay constant for front back sound for now
1309: */
1310: if (debugFlag)
1311: debugPrint("panSample: quadrant "
1312: + quadrant);
1313: switch (quadrant) {
1314: case 1:
1315: // Sound from front, right of center of head
1316: case 4:
1317: // Sound from back, right of center of head
1318: rightGain = allGains * (intensityHigh - intensityOffset);
1319: leftGain = allGains * (intensityLow + intensityOffset);
1320: break;
1321:
1322: case 2:
1323: // Sound from front, left of center of head
1324: case 3:
1325: // Sound from back, right of center of head
1326: leftGain = allGains * (intensityHigh - intensityOffset);
1327: rightGain = allGains * (intensityLow + intensityOffset);
1328: break;
1329: } /* switch */
1330: if (debugFlag)
1331: debugPrint("panSample: left/rightGain "
1332: + leftGain + ", " + rightGain);
1333:
1334: // Combines distance and angular filter to set this sample's current
1335: // frequency cutoff value
1336: calculateFilter(distanceSourceToCenterEar, attribs);
1337:
1338: } /* panSample() */
1339:
1340: // NOTE: setGain in audioengines.Sample is used to set/get user suppled factor
1341: // this class uses this single gain value to calculate the left and
1342: // right gain values
1343: }
|