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