001: /*
002: * $RCSfile: JSDirectionalSample.java,v $
003: *
004: * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * - Redistribution of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * - Redistribution in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * Neither the name of Sun Microsystems, Inc. or the names of
019: * contributors may be used to endorse or promote products derived
020: * from this software without specific prior written permission.
021: *
022: * This software is provided "AS IS," without a warranty of any
023: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
024: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
025: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
026: * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
027: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
028: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
029: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
030: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
031: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
032: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
033: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
034: * POSSIBILITY OF SUCH DAMAGES.
035: *
036: * You acknowledge that this software is not designed, licensed or
037: * intended for use in the design, construction, operation or
038: * maintenance of any nuclear facility.
039: *
040: * $Revision: 1.4 $
041: * $Date: 2007/02/09 17:20:03 $
042: * $State: Exp $
043: */
044:
045: /*
046: * DirectionalSample object
047: *
048: * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs
049: * to be rewritten.
050: */
051:
052: package com.sun.j3d.audioengines.javasound;
053:
054: import javax.media.j3d.*;
055: import com.sun.j3d.audioengines.*;
056: import javax.vecmath.*;
057:
058: /**
059: * The PostionalSample Class defines the data and methods associated with a
060: * PointSound sample played through the AudioDevice.
061: */
062:
063: class JSDirectionalSample extends JSPositionalSample {
064: // The transformed direction of this sound
065: Vector3f xformDirection = new Vector3f(0.0f, 0.0f, 1.0f);
066:
067: public JSDirectionalSample() {
068: super ();
069: if (debugFlag)
070: debugPrintln("JSDirectionalSample constructor");
071: }
072:
073: void setXformedDirection() {
074: if (debugFlag)
075: debugPrint("*** setXformedDirection");
076: if (!getVWrldXfrmFlag()) {
077: if (debugFlag)
078: debugPrint(" Transform NOT set yet, so dir => xformDir");
079: xformDirection.set(direction);
080: } else {
081: if (debugFlag)
082: debugPrint(" Transform dir => xformDir");
083: vworldXfrm.transform(direction, xformDirection);
084: }
085: if (debugFlag)
086: debugPrint(" xform(sound)Direction <= "
087: + xformDirection.x + ", " + xformDirection.y + ", "
088: + xformDirection.z);
089: }
090:
091: /* ***********************************
092: *
093: * Intersect ray to head with Ellipse
094: *
095: * ***********************************/
096: /*
097: * An ellipse is defined using:
098: * (1) the ConeSound's direction vector as the major axis of the ellipse;
099: * (2) the max parameter (a front distance attenuation value) along the
100: * cone's position axis; and
101: * (3) the min parameter (a back distance attenuation value) along the
102: * cone's negative axis
103: * This method calculates the distance from the sound source to the
104: * Intersection of the Ellipse with the ray from the sound source to the
105: * listener's head.
106: * This method returns the resulting distance.
107: * If an error occurs, -1.0 is returned.
108: *
109: * A calculation are done in 'Cone' space:
110: * The origin is defined as being the sound source position.
111: * The ConeSound source axis is the X-axis of this Cone's space.
112: * Since this ConeSound source defines a prolate spheroid (obtained
113: * by revolving an ellipsoid about the major axis) we can define the
114: * Y-axis of this Cone space as being in the same plane as the X-axis
115: * and the vector from the origin to the head.
116: * All calculations in Cone space can be generalized in this two-
117: * dimensional space without loss of precision.
118: * Location of the head, H, in Cone space can then be defined as:
119: * H'(x,y) = (cos @, sin @) * | H |
120: * where @ is the angle between the X-axis and the ray to H.
121: * Using the equation of the line thru the origin and H', and the
122: * equation of ellipse defined with min and max, find the
123: * intersection by solving for x and then y.
124: *
125: * (I) The equation of the line thru the origin and H', and the
126: * | H'(y) - S(y) |
127: * y - S(y) = | ----------- | * [x - S(x)]
128: * | H'(x) - S(x) |
129: * and since S(x,y) is the origin of ConeSpace:
130: * | H'(y) |
131: * y = | ----- | x
132: * | H'(x) |
133: *
134: * (II) The equation of ellipse:
135: * x**2 y**2
136: * ---- + ---- = 1
137: * a**2 b**2
138: * given a is length from origin to ellipse along major, X-axis, and
139: * b is length from origin to ellipse along minor, Y-axis;
140: * where a**2 = [(max+min)/2]**2 , since 2a = min+max;
141: * where b**2 = min*max , since the triangle abc is made is defined by the
142: * the points: S(x,y), origin, and (0,b),
143: * thus b**2 = a**2 - S(x,y) = a**2 - ((a-min)**2) = 2a*min - min**2
144: * b**2 = ((min+max)*min) - min**2 = min*max.
145: * so the equation of the ellipse becomes:
146: * x**2 y**2
147: * ---------------- + ------- = 1
148: * [(max+min)/2]**2 min*max
149: *
150: * Substuting for y from Eq.(I) into Eq.(II) gives
151: * x**2 [(H'(y)/H'(x))*x]**2
152: * ---------------- + -------------------- = 1
153: * [(max+min)/2]**2 min*max
154: *
155: * issolating x**2 gives
156: * | 1 [H'(y)/H'(x)]**2 |
157: * x**2 | ---------------- + ---------------- | = 1
158: * | [(max+min)/2]**2 min*max |
159: *
160: *
161: * | 4 [(sin @ * |H|)/(cos @ * |H|)]**2 |
162: * x**2 | -------------- + -------------------------------- | = 1
163: * | [(max+min)]**2 min*max |
164: *
165: * | |
166: * | 1 |
167: * | |
168: * x**2 = | --------------------------------------- |
169: * | | 4 [sin @/cos @]**2 | |
170: * | | -------------- + ---------------- | |
171: * | | [(max+min)]**2 min*max | |
172: *
173: * substitute tan @ for [sin @/cos @], and take the square root and you have
174: * the equation for x as calculated below.
175: *
176: * Then solve for y by plugging x into Eq.(I).
177: *
178: * Return the distance from the origin in Cone space to this intersection
179: * point: square_root(x**2 + y**2).
180: *
181: */
182: double intersectEllipse(double max, double min) {
183:
184: if (debugFlag)
185: debugPrint(" intersectEllipse entered with min/max = "
186: + min + "/" + max);
187: /*
188: * First find angle '@' between the X-axis ('A') and the ray to Head ('H').
189: * In local coordinates, use Dot Product of those two vectors to get cos @:
190: * A(u)*H(u) + A(v)*H(v) + A(w)*H(v)
191: * cos @ = --------------------------------
192: * |A|*|H|
193: * then since domain of @ is { 0 <= @ <= PI }, arccos can be used to get @.
194: */
195: Vector3f xAxis = this .direction; // axis is sound direction vector
196: // Get the already calculated vector from sound source position to head
197: Vector3f sourceToHead = this .sourceToCenterEar;
198: // error check vectors not empty
199: if (xAxis == null || sourceToHead == null) {
200: if (debugFlag)
201: debugPrint(" one or both of the vectors are null");
202: return (-1.0f); // denotes an error occurred
203: }
204:
205: // Dot Product
206: double dotProduct = (double) ((sourceToHead.dot(xAxis)) / (sourceToHead
207: .length() * xAxis.length()));
208: if (debugFlag)
209: debugPrint(" dot product = " + dotProduct);
210: // since theta angle is in the range between 0 and PI, arccos can be used
211: double theta = (float) (Math.acos(dotProduct));
212: if (debugFlag)
213: debugPrint(" theta = " + theta);
214:
215: /*
216: * Solve for X using Eq.s (I) and (II) from above.
217: */
218: double minPlusMax = (double) (min + max);
219: double tangent = Math.tan(theta);
220: double xSquared = 1.0 / ((4.0 / (minPlusMax * minPlusMax)) + ((tangent * tangent) / (min * max)));
221: double x = Math.sqrt(xSquared);
222: if (debugFlag)
223: debugPrint(" X = " + x);
224: /*
225: * Solve for y, given the result for x:
226: * | H'(y) | | sin @ |
227: * y = | ----- | x = | ----- | x
228: * | H'(x) | | cos @ |
229: */
230: double y = tangent * x;
231: if (debugFlag)
232: debugPrint(" Y = " + y);
233: double ySquared = y * y;
234:
235: /*
236: * Now return distance from origin to intersection point (x,y)
237: */
238: float distance = (float) (Math.sqrt(xSquared + ySquared));
239: if (debugFlag)
240: debugPrint(" distance to intersection = "
241: + distance);
242: return (distance);
243: }
244:
245: /* *****************
246: *
247: * Find Factor
248: *
249: * *****************/
250: /*
251: * Interpolates the correct attenuation scale factor given a 'distance'
252: * value. This version used both front and back attenuation distance
253: * and scale factor arrays (if non-null) in its calculation of the
254: * the distance attenuation.
255: * If the back attenuation arrays are null then this executes the
256: * PointSoundRetained version of this method.
257: * This method finds the intesection of the ray from the sound source
258: * to the center-ear, with the ellipses defined by the two sets (front
259: * and back) of distance attenuation arrays.
260: * This method looks at pairs of intersection distance values to find
261: * which pair the input distance argument is between:
262: * [intersectionDistance[index] and intersectionDistance[index+1]
263: * The index is used to get factorArray[index] and factorArray[index+1].
264: * Then the ratio of the 'distance' between this pair of intersection
265: * values is used to scale the two found factorArray values proportionally.
266: */
267: float findFactor(double distanceToHead, double[] maxDistanceArray,
268: float[] maxFactorArray, double[] minDistanceArray,
269: float[] minFactorArray) {
270: int index, lowIndex, highIndex, indexMid;
271: double returnValue;
272:
273: if (debugFlag) {
274: debugPrint("JSDirectionalSample.findFactor entered:");
275: debugPrint(" distance to head = " + distanceToHead);
276: }
277:
278: if (minDistanceArray == null || minFactorArray == null) {
279: /*
280: * Execute the PointSoundRetained version of this method.
281: * Assume it will check for other error conditions.
282: */
283: return (this .findFactor(distanceToHead, maxDistanceArray,
284: maxFactorArray));
285: }
286:
287: /*
288: * Error checking
289: */
290: if (maxDistanceArray == null || maxFactorArray == null) {
291: if (debugFlag)
292: debugPrint(" findFactor: arrays null");
293: return -1.0f;
294: }
295: // Assuming length > 1 already tested in set attenuation arrays methods
296: int arrayLength = maxDistanceArray.length;
297: if (arrayLength < 2) {
298: if (debugFlag)
299: debugPrint(" findFactor: arrays length < 2");
300: return -1.0f;
301: }
302: int largestIndex = arrayLength - 1;
303: /*
304: * Calculate distanceGain scale factor
305: */
306: /*
307: * distanceToHead is larger than greatest distance in maxDistanceArray
308: * so head is beyond the outer-most ellipse.
309: */
310: if (distanceToHead >= maxDistanceArray[largestIndex]) {
311: if (debugFlag)
312: debugPrint(" findFactor: distance > "
313: + maxDistanceArray[largestIndex]);
314: if (debugFlag)
315: debugPrint(" maxDistanceArray length = "
316: + maxDistanceArray.length);
317: if (debugFlag)
318: debugPrint(" findFactor returns ****** "
319: + maxFactorArray[largestIndex] + " ******");
320: return maxFactorArray[largestIndex];
321: }
322:
323: /*
324: * distanceToHead is smaller than least distance in minDistanceArray
325: * so head is inside the inner-most ellipse.
326: */
327: if (distanceToHead <= minDistanceArray[0]) {
328: if (debugFlag)
329: debugPrint(" findFactor: distance < "
330: + maxDistanceArray[0]);
331: if (debugFlag)
332: debugPrint(" findFactor returns ****** "
333: + minFactorArray[0] + " ******");
334: return minFactorArray[0];
335: }
336:
337: /*
338: * distanceToHead is between points within attenuation arrays.
339: * Use binary halfing of distance attenuation arrays.
340: */
341: {
342: double[] distanceArray = new double[arrayLength];
343: float[] factorArray = new float[arrayLength];
344: boolean[] intersectionCalculated = new boolean[arrayLength];
345: // initialize intersection calculated array flags to false
346: for (int i = 0; i < arrayLength; i++)
347: intersectionCalculated[i] = false;
348: boolean intersectionOnEllipse = false;
349: int factorIndex = -1;
350:
351: /*
352: * Using binary halving to find the two index values in the
353: * front and back distance arrays that the distanceToHead
354: * parameter (from sound source position to head) fails between.
355: * Changing the the current low and high index values
356: * calculate the intesection of ellipses (defined by this
357: * min/max distance values) with the ray (sound source to
358: * head). Put the resulting value into the distanceArray.
359: */
360: /*
361: * initialize the lowIndex to first index of distance arrays.
362: * initialize the highIndex to last index of distance arrays.
363: */
364: lowIndex = 0;
365: highIndex = largestIndex;
366:
367: if (debugFlag)
368: debugPrint(" while loop to find index that's closest: ");
369: while (lowIndex < (highIndex - 1)) {
370: if (debugFlag)
371: debugPrint(" lowIndex " + lowIndex
372: + ", highIndex " + highIndex);
373: /*
374: * Calculate the Intersection of Ellipses (defined by this
375: * min/max values) with the ray from the sound source to the
376: * head. Put the resulting value into the distanceArray.
377: */
378: if (!intersectionCalculated[lowIndex]) {
379: distanceArray[lowIndex] = this .intersectEllipse(
380: maxDistanceArray[lowIndex],
381: minDistanceArray[lowIndex]);
382: // If return intersection distance is < 0 an error occurred.
383: if (distanceArray[lowIndex] >= 0.0)
384: intersectionCalculated[lowIndex] = true;
385: else {
386: /*
387: * Error in ellipse intersection calculation. Use
388: * average of max/min difference for intersection value.
389: */
390: distanceArray[lowIndex] = (minDistanceArray[lowIndex] + maxDistanceArray[lowIndex]) * 0.5;
391: if (internalErrors)
392: debugPrint("Internal Error in intersectEllipse; use "
393: + distanceArray[lowIndex]
394: + " for intersection value ");
395: // Rather than aborting, just use average and go on...
396: intersectionCalculated[lowIndex] = true;
397: }
398: } // end of if intersection w/ lowIndex not already calculated
399:
400: if (!intersectionCalculated[highIndex]) {
401: distanceArray[highIndex] = this .intersectEllipse(
402: maxDistanceArray[highIndex],
403: minDistanceArray[highIndex]);
404: // If return intersection distance is < 0 an error occurred.
405: if (distanceArray[highIndex] >= 0.0f)
406: intersectionCalculated[highIndex] = true;
407: else {
408: /*
409: * Error in ellipse intersection calculation. Use
410: * average of max/min difference for intersection value.
411: */
412: distanceArray[highIndex] = (minDistanceArray[highIndex] + maxDistanceArray[highIndex]) * 0.5f;
413: if (internalErrors)
414: debugPrint("Internal Error in intersectEllipse; use "
415: + distanceArray[highIndex]
416: + " for intersection value ");
417: // Rather than aborting, just use average and go on...
418: intersectionCalculated[highIndex] = true;
419: }
420: } // end of if intersection w/ highIndex not already calculated
421:
422: /*
423: * Test for intersection points being the same as head position
424: * distanceArray[lowIndex] and distanceArray[highIndex], if so
425: * return factor value directly from array
426: */
427: if (distanceArray[lowIndex] >= distanceToHead) {
428: if ((lowIndex != 0)
429: && (distanceToHead < distanceArray[lowIndex])) {
430: if (internalErrors)
431: debugPrint("Internal Error: binary halving in "
432: + "findFactor failed; distance < low "
433: + "index value");
434: }
435: if (debugFlag) {
436: debugPrint(" distanceArray[lowIndex] >= "
437: + "distanceToHead");
438: debugPrint(" factorIndex = " + lowIndex);
439: }
440: intersectionOnEllipse = true;
441: factorIndex = lowIndex;
442: break;
443: } else if (distanceArray[highIndex] <= distanceToHead) {
444: if ((highIndex != largestIndex)
445: && (distanceToHead > distanceArray[highIndex])) {
446: if (internalErrors)
447: debugPrint("Internal Error: binary halving in "
448: + "findFactor failed; distance > high "
449: + "index value");
450: }
451: if (debugFlag) {
452: debugPrint(" distanceArray[highIndex] >= "
453: + "distanceToHead");
454: debugPrint(" factorIndex = " + highIndex);
455: }
456: intersectionOnEllipse = true;
457: factorIndex = highIndex;
458: break;
459: }
460:
461: if (distanceToHead > distanceArray[lowIndex]
462: && distanceToHead < distanceArray[highIndex]) {
463: indexMid = lowIndex + ((highIndex - lowIndex) / 2);
464: if (distanceToHead <= distanceArray[indexMid])
465: // value of distance in lower "half" of list
466: highIndex = indexMid;
467: else
468: // value if distance in upper "half" of list
469: lowIndex = indexMid;
470: }
471: } /* of while */
472:
473: /*
474: * First check to see if distanceToHead is beyond min or max
475: * ellipses, or on an ellipse.
476: * If so, factor is calculated using the distance Ratio
477: * (distanceToHead - min) / (max-min)
478: * where max = maxDistanceArray[factorIndex], and
479: * min = minDistanceArray[factorIndex]
480: */
481: if (intersectionOnEllipse && factorIndex >= 0) {
482: if (debugFlag) {
483: debugPrint(" ratio calculated using factorIndex "
484: + factorIndex);
485: debugPrint(" d.A. max pair for factorIndex "
486: + maxDistanceArray[factorIndex] + ", "
487: + maxFactorArray[factorIndex]);
488: debugPrint(" d.A. min pair for lowIndex "
489: + minDistanceArray[factorIndex] + ", "
490: + minFactorArray[factorIndex]);
491: }
492: returnValue = (((distanceArray[factorIndex] - minDistanceArray[factorIndex]) / (maxDistanceArray[factorIndex] - minDistanceArray[factorIndex])) * (maxFactorArray[factorIndex] - minFactorArray[factorIndex]))
493: + minFactorArray[factorIndex];
494: if (debugFlag)
495: debugPrint(" findFactor returns ****** "
496: + returnValue + " ******");
497: return (float) returnValue;
498: }
499:
500: /* Otherwise, for distanceToHead between distance intersection
501: * values, we need to calculate two factors - one for the
502: * ellipse defined by lowIndex min/max factor arrays, and
503: * the other by highIndex min/max factor arrays. Then the
504: * distance Ratio (defined above) is applied, using these
505: * two factor values, to get the final return value.
506: */
507: double highFactorValue = 1.0;
508: double lowFactorValue = 0.0;
509: highFactorValue = (((distanceArray[highIndex] - minDistanceArray[highIndex]) / (maxDistanceArray[highIndex] - minDistanceArray[highIndex])) * (maxFactorArray[highIndex] - minFactorArray[highIndex]))
510: + minFactorArray[highIndex];
511: if (debugFlag) {
512: debugPrint(" highFactorValue calculated w/ highIndex "
513: + highIndex);
514: debugPrint(" d.A. max pair for highIndex "
515: + maxDistanceArray[highIndex] + ", "
516: + maxFactorArray[highIndex]);
517: debugPrint(" d.A. min pair for lowIndex "
518: + minDistanceArray[highIndex] + ", "
519: + minFactorArray[highIndex]);
520: debugPrint(" highFactorValue " + highFactorValue);
521: }
522: lowFactorValue = (((distanceArray[lowIndex] - minDistanceArray[lowIndex]) / (maxDistanceArray[lowIndex] - minDistanceArray[lowIndex])) * (maxFactorArray[lowIndex] - minFactorArray[lowIndex]))
523: + minFactorArray[lowIndex];
524: if (debugFlag) {
525: debugPrint(" lowFactorValue calculated w/ lowIndex "
526: + lowIndex);
527: debugPrint(" d.A. max pair for lowIndex "
528: + maxDistanceArray[lowIndex] + ", "
529: + maxFactorArray[lowIndex]);
530: debugPrint(" d.A. min pair for lowIndex "
531: + minDistanceArray[lowIndex] + ", "
532: + minFactorArray[lowIndex]);
533: debugPrint(" lowFactorValue " + lowFactorValue);
534: }
535: /*
536: * calculate gain scale factor based on the ratio distance
537: * between ellipses the distanceToHead lies between.
538: */
539: /*
540: * ratio: distance from listener to sound source
541: * between lowIndex and highIndex times
542: * attenuation value between lowIndex and highIndex
543: * gives linearly interpolationed attenuation value
544: */
545: if (debugFlag) {
546: debugPrint(" ratio calculated using distanceArray"
547: + lowIndex + ", highIndex " + highIndex);
548: debugPrint(" calculated pair for lowIndex "
549: + distanceArray[lowIndex] + ", "
550: + lowFactorValue);
551: debugPrint(" calculated pair for highIndex "
552: + distanceArray[highIndex] + ", "
553: + highFactorValue);
554: }
555:
556: returnValue = (((distanceToHead - distanceArray[lowIndex]) / (distanceArray[highIndex] - distanceArray[lowIndex])) * (highFactorValue - lowFactorValue))
557: + factorArray[lowIndex];
558: if (debugFlag)
559: debugPrint(" findFactor returns ******"
560: + returnValue + " ******");
561: return (float) returnValue;
562: }
563:
564: }
565:
566: /**
567: * CalculateDistanceAttenuation
568: *
569: * Simply calls ConeSound specific 'findFactor()' with
570: * both front and back attenuation linear distance and gain scale factor
571: * arrays.
572: */
573: float calculateDistanceAttenuation(float distance) {
574: float factor = findFactor(distance, this .attenuationDistance,
575: this .attenuationGain, this .backAttenuationDistance,
576: this .backAttenuationGain);
577: if (factor < 0.0f)
578: return 1.0f;
579: else
580: return factor;
581: }
582:
583: /**
584: * CalculateAngularGain
585: *
586: * Simply calls generic (for PointSound) 'findFactor()' with
587: * a single set of angular attenuation distance and gain scalefactor arrays.
588: */
589: float calculateAngularGain() {
590: float angle = findAngularOffset();
591: float factor = findFactor(angle, this .angularDistance,
592: this .angularGain);
593: if (factor < 0.0f)
594: return 1.0f;
595: else
596: return factor;
597: }
598:
599: /* *****************
600: *
601: * Find Angular Offset
602: *
603: * *****************/
604: /*
605: * Calculates the angle from the sound's direction axis and the ray from
606: * the sound origin to the listener'center ear.
607: * For Cone Sounds this value is the arc cosine of dot-product between
608: * the sound direction vector and the vector (sound position,centerEar)
609: * all in Virtual World coordinates space.
610: * Center ear position is in Virtual World coordinates.
611: * Assumes that calculation done in VWorld Space...
612: * Assumes that xformPosition is already calculated...
613: */
614: float findAngularOffset() {
615: Vector3f unitToEar = new Vector3f();
616: Vector3f unitDirection = new Vector3f();
617: Point3f xformPosition = positions[currentIndex];
618: Point3f xformCenterEar = centerEars[currentIndex];
619: float dotProduct;
620: float angle;
621: /*
622: * TODO: (Question) is assumption that xformed values available O.K.
623: * TODO: (Performance) save this angular offset and only recalculate
624: * if centerEar or sound position have changed.
625: */
626: unitToEar.x = xformCenterEar.x - xformPosition.x;
627: unitToEar.y = xformCenterEar.y - xformPosition.y;
628: unitToEar.z = xformCenterEar.z - xformPosition.z;
629: unitToEar.normalize();
630: unitDirection.normalize(this .direction);
631: dotProduct = unitToEar.dot(unitDirection);
632: angle = (float) (Math.acos((double) dotProduct));
633: if (debugFlag)
634: debugPrint(" angle from cone direction = "
635: + angle);
636: return (angle);
637: }
638:
639: /************
640: *
641: * Calculate Filter
642: *
643: * *****************/
644: /*
645: * Calculates the low-pass cutoff frequency filter value applied to the
646: * a sound based on both:
647: * Distance Filter (from Aural Attributes) based on distance
648: * between the sound and the listeners position
649: * Angular Filter (for Directional Sounds) based on the angle
650: * between a sound's projected direction and the
651: * vector between the sounds position and center ear.
652: * The lowest of these two filter is used.
653: * This filter value is stored into the sample's filterFreq field.
654: */
655: void calculateFilter(float distance, AuralParameters attribs) {
656: // setting filter cutoff freq to 44.1kHz which, in this
657: // implementation, is the same as not performing filtering
658: float distanceFilter = 44100.0f;
659: float angularFilter = 44100.0f;
660: int arrayLength = attribs.getDistanceFilterLength();
661: int filterType = attribs.getDistanceFilterType();
662:
663: boolean distanceFilterFound = false;
664: boolean angularFilterFound = false;
665: if ((filterType == AuralParameters.NO_FILTERING)
666: && arrayLength > 0) {
667: double[] distanceArray = new double[arrayLength];
668: float[] cutoffArray = new float[arrayLength];
669: attribs.getDistanceFilter(distanceArray, cutoffArray);
670:
671: if (debugFlag) {
672: debugPrint("distanceArray cutoffArray");
673: for (int i = 0; i < arrayLength; i++)
674: debugPrint((float) distanceArray[i] + ", "
675: + cutoffArray[i]);
676: }
677:
678: // Calculate angle from direction axis towards listener
679: float angle = findAngularOffset();
680: distanceFilter = findFactor((double) angle,
681: angularDistance, angularFilterCutoff);
682: if (distanceFilter < 0.0f)
683: distanceFilterFound = false;
684: else
685: distanceFilterFound = true;
686: } else {
687: distanceFilterFound = false;
688: distanceFilter = -1.0f;
689: }
690:
691: if (debugFlag)
692: debugPrint(" calculateFilter arrayLength = "
693: + arrayLength);
694:
695: // Angular filter of directional sound sources.
696: arrayLength = angularDistance.length;
697: filterType = angularFilterType;
698: if ((filterType != AuralParameters.NO_FILTERING)
699: && arrayLength > 0) {
700: angularFilter = findFactor((double) distance,
701: angularDistance, angularFilterCutoff);
702: if (angularFilter < 0.0f)
703: angularFilterFound = false;
704: else
705: angularFilterFound = true;
706: } else {
707: angularFilterFound = false;
708: angularFilter = -1.0f;
709: }
710:
711: filterFlag = distanceFilterFound || angularFilterFound;
712: if (distanceFilter < 0.0f)
713: filterFreq = angularFilter;
714: else if (angularFilter < 0.0f)
715: filterFreq = distanceFilter;
716: else
717: // both filter frequencies are > 0
718: filterFreq = Math.min(distanceFilter, angularFilter);
719:
720: if (debugFlag)
721: debugPrint(" calculateFilter flag,freq = " + filterFlag
722: + "," + filterFreq);
723: }
724:
725: }
|