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