1 /* 2 * $RCSfile: AuralAttributesRetained.java,v $ 3 * 4 * Copyright 1997-2008 Sun Microsystems, Inc. All Rights Reserved. 5 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 6 * 7 * This code is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License version 2 only, as 9 * published by the Free Software Foundation. Sun designates this 10 * particular file as subject to the "Classpath" exception as provided 11 * by Sun in the LICENSE file that accompanied this code. 12 * 13 * This code is distributed in the hope that it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16 * version 2 for more details (a copy is included in the LICENSE file that 17 * accompanied this code). 18 * 19 * You should have received a copy of the GNU General Public License version 20 * 2 along with this work; if not, write to the Free Software Foundation, 21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 22 * 23 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 24 * CA 95054 USA or visit www.sun.com if you need additional information or 25 * have any questions. 26 * 27 * $Revision: 1.7 $ 28 * $Date: 2008/02/28 20:17:19 $ 29 * $State: Exp $ 30 */ 31 32 package javax.media.j3d; 33 34 import java.util.Hashtable; 35 import javax.vecmath.Point2f; 36 37 /** 38 * The AuralAttributesRetained object defines all rendering state that can 39 * be set as a component object of a retained Soundscape node. 40 */ 41 class AuralAttributesRetained extends NodeComponentRetained { 42 43 /** 44 * Gain Scale Factor applied to source with this attribute 45 */ 46 float attributeGain = 1.0f; // Valid values are >= 0.0. 47 48 /** 49 * Atmospheric Rolloff - speed of sound - coeff 50 * Normal gain attenuation based on distance of sound from 51 * listener is scaled by a rolloff factor, which can increase 52 * or decrease the usual inverse-distance-square value. 53 */ 54 float rolloff = 1.0f; // Valid values are >= 0.0 55 static final float SPEED_OF_SOUND = 0.344f; // in meters/milliseconds 56 57 /* 58 * Reverberation 59 * 60 * Within Java 3D's model for auralization, the components to 61 * reverberation for a particular space are: 62 * Reflection and Reverb Coefficients - 63 * attenuation of sound (uniform for all frequencies) due to 64 * absorption of reflected sound off materials within the 65 * listening space. 66 * Reflection and Reverb Delay - 67 * approximating time from the start of the direct sound that 68 * initial early and late reflection waves take to reach listener. 69 * Reverb Decay - 70 * approximating time from the start of the direct sound that 71 * reverberation is audible. 72 */ 73 74 /** 75 * Coefficients for reverberation 76 * The (early) Reflection and Reverberation coefficient scale factors 77 * are used to approximate the reflective/absorptive characteristics 78 * of the surfaces in this bounded Auralizaton environment. 79 * Theses scale factors is applied to sound's amplitude regardless 80 * of sound's position. 81 * Value of 1.0 represents complete (unattenuated) sound reflection. 82 * Value of 0.0 represents full absorption; reverberation is disabled. 83 */ 84 float reflectionCoefficient = 0.0f; // Range of values 0.0 to 1.0 85 float reverbCoefficient = 1.0f; // Range of values 0.0 to 1.0 86 87 /** 88 * Time Delays in milliseconds 89 * Set with either explicitly with time, or impliciticly by supplying 90 * bounds volume and having the delay time calculated. 91 * Bounds of reverberation space does not have to be the same as 92 * Attribute bounds. 93 */ 94 float reflectionDelay = 20.0f; // in milliseconds 95 float reverbDelay = 40.0f; // in milliseconds 96 Bounds reverbBounds = null; 97 98 /** 99 * Decay parameters 100 * Length and timbre of reverb decay tail 101 */ 102 float decayTime = 1000.0f; // in milliseconds 103 float decayFilter = 5000.0f; // low-pass cutoff frequency 104 105 /** 106 * Reverb Diffusion and Density ratios (0=min, 1=max) 107 */ 108 float diffusion = 1.0f; 109 float density = 1.0f; 110 111 /** 112 * Reverberation order 113 * This limits the number of Reverberation iterations executed while 114 * sound is being reverberated. As long as reflection coefficient is 115 * small enough, the reverberated sound decreases (as it would naturally) 116 * each successive iteration. 117 * Value of > zero defines the greatest reflection order to be used by 118 * the reverberator. 119 * All positive values are used as the number of loop iteration. 120 * Value of <= zero signifies that reverberation is to loop until reverb 121 * gain reaches zero (-60dB or 1/1000 of sound amplitude). 122 */ 123 int reverbOrder = 0; 124 125 /** 126 * Distance Filter 127 * Each sound source is attenuated by a filter based on it's distance 128 * from the listener. 129 * For now the only supported filterType will be LOW_PASS frequency cutoff. 130 * At some time full FIR filtering will be supported. 131 */ 132 static final int NO_FILTERING = -1; 133 static final int LOW_PASS = 1; 134 135 int filterType = NO_FILTERING; 136 float[] distance = null; 137 float[] frequencyCutoff = null; 138 139 /** 140 * Doppler Effect parameters 141 * Between two snapshots of the head and sound source positions some 142 * delta time apart, the distance between head and source is compared. 143 * If there has been no change in the distance between head and sound 144 * source over this delta time: 145 * f' = f 146 * 147 * If there has been a change in the distance between head and sound: 148 * f' = f * Af * v 149 * 150 * When head and sound are moving towards each other then 151 * | (S * Ar) + (deltaV(h,t) * Av) | 152 * v = | -------------------------------- | 153 * | (S * Ar) - (deltaV(s,t) * Av) | 154 * 155 * When head and sound are moving away from each other then 156 * | (S * Ar) - (deltaV(h,t) * Av) | 157 * v = | -------------------------------- | 158 * | (S * Ar) + (deltaV(s,t) * Av) | 159 * 160 * 161 * Af = AuralAttribute frequency scalefactor 162 * Ar = AuralAttribute rolloff scalefactor 163 * Av = AuralAttribute velocity scalefactor 164 * deltaV = delta velocity 165 * f = frequency of sound 166 * h = Listeners head position 167 * v = Ratio of delta velocities 168 * Vh = Vector from center ear to sound source 169 * S = Speed of sound 170 * s = Sound source position 171 * t = time 172 * 173 * If adjusted velocity of head or adjusted velocity of sound is 174 * greater than adjusted speed of sound, f' is undefined. 175 */ 176 /** 177 * Frequency Scale Factor 178 * used to increase or reduce the change of frequency associated 179 * with normal rate of playback. 180 * Value of zero causes sounds to be paused. 181 */ 182 float frequencyScaleFactor = 1.0f; 183 /** 184 * Velocity Scale Factor 185 * Float value applied to the Change of distance between Sound Source 186 * and Listener over some delta time. Non-zero if listener moving 187 * even if sound is not. Value of zero implies no Doppler applied. 188 */ 189 float velocityScaleFactor = 0.0f; 190 191 /** 192 * This boolean is set when something changes in the attributes 193 */ 194 boolean aaDirty = true; 195 196 /** 197 * The mirror copy of this AuralAttributes. 198 */ 199 AuralAttributesRetained mirrorAa = null; 200 201 /** 202 ** Debug print mechanism for Sound nodes 203 **/ 204 static final // 'static final' so compiler doesn't include debugPrint calls 205 boolean debugFlag = false; 206 207 static final // 'static final' so internal error message are not compiled 208 boolean internalErrors = false; 209 debugPrint(String message)210 void debugPrint(String message) { 211 if (debugFlag) // leave test in in case debugFlag made non-static final 212 System.err.println(message); 213 } 214 215 216 // **************************************** 217 // 218 // Set and Get individual attribute values 219 // 220 // **************************************** 221 222 /** 223 * Set Attribute Gain (amplitude) 224 * @param gain scale factor applied to amplitude 225 */ setAttributeGain(float gain)226 void setAttributeGain(float gain) { 227 this.attributeGain = gain; 228 this.aaDirty = true; 229 notifyUsers(); 230 } 231 /** 232 * Retrieve Attribute Gain (amplitude) 233 * @return gain amplitude scale factor 234 */ getAttributeGain()235 float getAttributeGain() { 236 return this.attributeGain; 237 } 238 239 /** 240 * Set Attribute Gain Rolloff 241 * @param rolloff atmospheric gain scale factor (changing speed of sound) 242 */ setRolloff(float rolloff)243 void setRolloff(float rolloff) { 244 this.rolloff = rolloff; 245 this.aaDirty = true; 246 notifyUsers(); 247 } 248 /** 249 * Retrieve Attribute Gain Rolloff 250 * @return rolloff atmospheric gain scale factor (changing speed of sound) 251 */ getRolloff()252 float getRolloff() { 253 return this.rolloff; 254 } 255 256 /** 257 * Set Reflective Coefficient 258 * @param reflectionCoefficient reflection/absorption factor applied to 259 * early reflections. 260 */ setReflectionCoefficient(float reflectionCoefficient)261 void setReflectionCoefficient(float reflectionCoefficient) { 262 this.reflectionCoefficient = reflectionCoefficient; 263 this.aaDirty = true; 264 notifyUsers(); 265 } 266 /** 267 * Retrieve Reflective Coefficient 268 * @return reflection coeff reflection/absorption factor applied to 269 * early reflections. 270 */ getReflectionCoefficient()271 float getReflectionCoefficient() { 272 return this.reflectionCoefficient; 273 } 274 275 /** 276 * Set Reflection Delay Time 277 * @param reflectionDelay time before the start of early (first order) 278 * reflections. 279 */ setReflectionDelay(float reflectionDelay)280 void setReflectionDelay(float reflectionDelay) { 281 this.reflectionDelay = reflectionDelay; 282 this.aaDirty = true; 283 notifyUsers(); 284 } 285 /** 286 * Retrieve Reflection Delay Time 287 * @return reflection delay time 288 */ getReflectionDelay()289 float getReflectionDelay() { 290 return this.reflectionDelay; 291 } 292 293 /** 294 * Set Reverb Coefficient 295 * @param reverbCoefficient reflection/absorption factor applied to 296 * late reflections. 297 */ setReverbCoefficient(float reverbCoefficient)298 void setReverbCoefficient(float reverbCoefficient) { 299 this.reverbCoefficient = reverbCoefficient; 300 this.aaDirty = true; 301 notifyUsers(); 302 } 303 /** 304 * Retrieve Reverb Coefficient 305 * @return reverb coeff reflection/absorption factor applied to late 306 * reflections. 307 */ getReverbCoefficient()308 float getReverbCoefficient() { 309 return this.reverbCoefficient; 310 } 311 312 /** 313 * Set Revereration Delay Time 314 * @param reverbDelay time between each order of reflection 315 */ setReverbDelay(float reverbDelay)316 void setReverbDelay(float reverbDelay) { 317 this.reverbDelay = reverbDelay; 318 this.aaDirty = true; 319 notifyUsers(); 320 } 321 /** 322 * Retrieve Revereration Delay Time 323 * @return reverb delay time between each order of reflection 324 */ getReverbDelay()325 float getReverbDelay() { 326 return this.reverbDelay; 327 } 328 /** 329 * Set Decay Time 330 * @param decayTime length of time reverb takes to decay 331 */ setDecayTime(float decayTime)332 void setDecayTime(float decayTime) { 333 this.decayTime = decayTime; 334 this.aaDirty = true; 335 notifyUsers(); 336 } 337 /** 338 * Retrieve Revereration Decay Time 339 * @return reverb delay time 340 */ getDecayTime()341 float getDecayTime() { 342 return this.decayTime; 343 } 344 345 /** 346 * Set Decay Filter 347 * @param decayFilter frequency referenced used in low-pass filtering 348 */ setDecayFilter(float decayFilter)349 void setDecayFilter(float decayFilter) { 350 this.decayFilter = decayFilter; 351 this.aaDirty = true; 352 notifyUsers(); 353 } 354 355 /** 356 * Retrieve Revereration Decay Filter 357 * @return reverb delay Filter 358 */ getDecayFilter()359 float getDecayFilter() { 360 return this.decayFilter; 361 } 362 363 /** 364 * Set Reverb Diffusion 365 * @param diffusion ratio between min and max device diffusion settings 366 */ setDiffusion(float diffusion)367 void setDiffusion(float diffusion) { 368 this.diffusion = diffusion; 369 this.aaDirty = true; 370 notifyUsers(); 371 } 372 373 /** 374 * Retrieve Revereration Decay Diffusion 375 * @return reverb diffusion 376 */ getDiffusion()377 float getDiffusion() { 378 return this.diffusion; 379 } 380 381 /** 382 * Set Reverb Density 383 * @param density ratio between min and max device density settings 384 */ setDensity(float density)385 void setDensity(float density) { 386 this.density = density; 387 this.aaDirty = true; 388 notifyUsers(); 389 } 390 391 /** 392 * Retrieve Revereration Density 393 * @return reverb density 394 */ getDensity()395 float getDensity() { 396 return this.density; 397 } 398 399 400 /** 401 * Set Revereration Bounds 402 * @param reverbVolume bounds used to approximate reverb time. 403 */ setReverbBounds(Bounds reverbVolume)404 synchronized void setReverbBounds(Bounds reverbVolume) { 405 this.reverbBounds = reverbVolume; 406 this.aaDirty = true; 407 notifyUsers(); 408 } 409 /** 410 * Retrieve Revereration Delay Bounds volume 411 * @return reverb bounds volume that defines the Reverberation space and 412 * indirectly the delay 413 */ getReverbBounds()414 Bounds getReverbBounds() { 415 return this.reverbBounds; 416 } 417 418 /** 419 * Set Reverberation Order of Reflections 420 * @param reverbOrder number of times reflections added to reverb signal 421 */ setReverbOrder(int reverbOrder)422 void setReverbOrder(int reverbOrder) { 423 this.reverbOrder = reverbOrder; 424 this.aaDirty = true; 425 notifyUsers(); 426 } 427 /** 428 * Retrieve Reverberation Order of Reflections 429 * @return reverb order number of times reflections added to reverb signal 430 */ getReverbOrder()431 int getReverbOrder() { 432 return this.reverbOrder; 433 } 434 435 /** 436 * Set Distance Filter (based on distances and frequency cutoff) 437 * @param attenuation array of pairs defining distance frequency cutoff 438 */ setDistanceFilter(Point2f[] attenuation)439 synchronized void setDistanceFilter(Point2f[] attenuation) { 440 if (attenuation == null) { 441 this.filterType = NO_FILTERING; 442 return; 443 } 444 int attenuationLength = attenuation.length; 445 if (attenuationLength == 0) { 446 this.filterType = NO_FILTERING; 447 return; 448 } 449 this.filterType = LOW_PASS; 450 // Reallocate every time unless size of new array equal old array 451 if ( distance == null || 452 (distance != null && (distance.length != attenuationLength) ) ) { 453 this.distance = new float[attenuationLength]; 454 this.frequencyCutoff = new float[attenuationLength]; 455 } 456 for (int i = 0; i< attenuationLength; i++) { 457 this.distance[i] = attenuation[i].x; 458 this.frequencyCutoff[i] = attenuation[i].y; 459 } 460 this.aaDirty = true; 461 notifyUsers(); 462 } 463 /** 464 * Set Distance Filter (based on distances and frequency cutoff) using 465 * separate arrays 466 * @param distance array containing distance values 467 * @param filter array containing low-pass frequency cutoff values 468 */ setDistanceFilter(float[] distance, float[] filter)469 synchronized void setDistanceFilter(float[] distance, float[] filter) { 470 if (distance == null || filter == null) { 471 this.filterType = NO_FILTERING; 472 return; 473 } 474 int distanceLength = distance.length; 475 int filterLength = filter.length; 476 if (distanceLength == 0 || filterLength == 0) { 477 this.filterType = NO_FILTERING; 478 return; 479 } 480 // Reallocate every time unless size of new array equal old array 481 if ( this.distance == null || 482 ( this.distance != null && 483 (this.distance.length != filterLength) ) ) { 484 this.distance = new float[distanceLength]; 485 this.frequencyCutoff = new float[distanceLength]; 486 } 487 this.filterType = LOW_PASS; 488 // Copy the distance array into nodes field 489 System.arraycopy(distance, 0, this.distance, 0, distanceLength); 490 // Copy the filter array an array of same length as the distance array 491 if (distanceLength <= filterLength) { 492 System.arraycopy(filter, 0, this.frequencyCutoff,0, distanceLength); 493 } 494 else { 495 System.arraycopy(filter, 0, this.frequencyCutoff, 0, filterLength); 496 // Extend filter array to length of distance array by 497 // replicate last filter values. 498 for (int i=filterLength; i< distanceLength; i++) { 499 this.frequencyCutoff[i] = filter[filterLength - 1]; 500 } 501 } 502 if (debugFlag) { 503 debugPrint("AAR setDistanceFilter(D,F)"); 504 for (int jj=0;jj<distanceLength;jj++) { 505 debugPrint(" from distance, freq = " + distance[jj] + ", " + 506 filter[jj]); 507 debugPrint(" into distance, freq = " + this.distance[jj] + ", " + 508 this.frequencyCutoff[jj]); 509 } 510 } 511 this.aaDirty = true; 512 notifyUsers(); 513 } 514 515 /** 516 * Retrieve Distance Filter array length 517 * @return attenuation array length 518 */ getDistanceFilterLength()519 int getDistanceFilterLength() { 520 if (distance == null) 521 return 0; 522 else 523 return this.distance.length; 524 } 525 526 527 /** 528 * Retrieve Distance Filter (distances and frequency cutoff) 529 * @return attenaution pairs of distance and frequency cutoff filter 530 */ getDistanceFilter(Point2f[] attenuation)531 void getDistanceFilter(Point2f[] attenuation) { 532 // Write into existing param array already allocated 533 if (attenuation == null) 534 return; 535 if (this.distance == null || this.frequencyCutoff == null) 536 return; 537 // The two filter attenuation arrays length should be the same 538 // We can assume that distance and filter lengths are the same 539 // and are non-zero. 540 int distanceLength = this.distance.length; 541 // check that attenuation array large enough to contain 542 // auralAttribute arrays 543 if (distanceLength > attenuation.length) 544 distanceLength = attenuation.length; 545 for (int i=0; i< distanceLength; i++) { 546 attenuation[i].x = this.distance[i]; 547 if (filterType == NO_FILTERING) 548 attenuation[i].y = Sound.NO_FILTER; 549 else if (filterType == LOW_PASS) 550 attenuation[i].y = this.frequencyCutoff[i]; 551 if (debugFlag) 552 debugPrint("AAR: getDistF: " + attenuation[i].x + ", " + 553 attenuation[i].y); 554 } 555 } 556 /** 557 * Retrieve Distance Filter as arrays distances and frequency cutoff array 558 * @param distance array of float values 559 * @param frequencyCutoff array of float cutoff filter values in Hertz 560 */ getDistanceFilter(float[] distance, float[] filter)561 void getDistanceFilter(float[] distance, float[] filter) { 562 // Write into existing param arrays already allocated 563 if (distance == null || filter == null) 564 return; 565 if (this.distance == null || this.frequencyCutoff == null) 566 return; 567 int distanceLength = this.distance.length; 568 // check that distance parameter large enough to contain auralAttribute 569 // distance array 570 // We can assume that distance and filter lengths are the same 571 // and are non-zero. 572 if (distance.length < distanceLength) 573 // parameter array not large enough to hold all this.distance data 574 distanceLength = distance.length; 575 System.arraycopy(this.distance, 0, distance, 0, distanceLength); 576 if (debugFlag) 577 debugPrint("AAR getDistanceFilter(D,F) " + this.distance[0]); 578 int filterLength = this.frequencyCutoff.length; 579 if (filter.length < filterLength) 580 // parameter array not large enough to hold all this.filter data 581 filterLength = filter.length; 582 if (filterType == NO_FILTERING) { 583 for (int i=0; i< filterLength; i++) 584 filter[i] = Sound.NO_FILTER; 585 } 586 if (filterType == LOW_PASS) { 587 System.arraycopy(this.frequencyCutoff, 0, filter, 0, filterLength); 588 } 589 if (debugFlag) 590 debugPrint(", " + this.frequencyCutoff[0]); 591 } 592 593 /** 594 * Set Frequency Scale Factor 595 * @param frequencyScaleFactor factor applied to sound's base frequency 596 */ setFrequencyScaleFactor(float frequencyScaleFactor)597 void setFrequencyScaleFactor(float frequencyScaleFactor) { 598 this.frequencyScaleFactor = frequencyScaleFactor; 599 this.aaDirty = true; 600 notifyUsers(); 601 } 602 /** 603 * Retrieve Frequency Scale Factor 604 * @return frequency scale factor applied to sound's base frequency 605 */ getFrequencyScaleFactor()606 float getFrequencyScaleFactor() { 607 return this.frequencyScaleFactor; 608 } 609 610 /** 611 * Set Velocity ScaleFactor used in calculating Doppler Effect 612 * @param velocityScaleFactor applied to velocity of sound in relation to listener 613 */ setVelocityScaleFactor(float velocityScaleFactor)614 void setVelocityScaleFactor(float velocityScaleFactor) { 615 this.velocityScaleFactor = velocityScaleFactor; 616 this.aaDirty = true; 617 notifyUsers(); 618 } 619 /** 620 * Retrieve Velocity ScaleFactor used in calculating Doppler Effect 621 * @return velocity scale factor 622 */ getVelocityScaleFactor()623 float getVelocityScaleFactor() { 624 return this.velocityScaleFactor; 625 } 626 reset(AuralAttributesRetained aa)627 synchronized void reset(AuralAttributesRetained aa) { 628 int i; 629 630 this.attributeGain = aa.attributeGain; 631 this.rolloff = aa.rolloff; 632 this.reflectionCoefficient = aa.reflectionCoefficient; 633 this.reverbCoefficient = aa.reverbCoefficient; 634 this.reflectionDelay = aa.reflectionDelay; 635 this.reverbDelay = aa.reverbDelay; 636 this.reverbBounds = aa.reverbBounds; 637 this.reverbOrder = aa.reverbOrder; 638 this.decayTime = aa.decayTime; 639 this.decayFilter = aa.decayFilter; 640 this.diffusion = aa.diffusion; 641 this.density = aa.density; 642 this.frequencyScaleFactor = aa.frequencyScaleFactor; 643 this.velocityScaleFactor = aa.velocityScaleFactor; 644 645 if (aa.distance != null) { 646 this.distance = new float[aa.distance.length]; 647 if (debugFlag) 648 debugPrint("reset aa; aa.distance.length = " + this.distance.length); 649 System.arraycopy(aa.distance, 0, this.distance, 0, this.distance.length); 650 } 651 else 652 if (debugFlag) 653 debugPrint("reset aa; aa.distance = null"); 654 if (aa.frequencyCutoff != null) { 655 this.frequencyCutoff = new float[aa.frequencyCutoff.length]; 656 if (debugFlag) 657 debugPrint("reset aa; aa.frequencyCutoff.length = " + this.frequencyCutoff.length); 658 System.arraycopy(aa.frequencyCutoff, 0, this.frequencyCutoff, 0, 659 this.frequencyCutoff.length); 660 } 661 else 662 if (debugFlag) 663 debugPrint("reset aa; aa.frequencyCutoff = null"); 664 // XXXX: (Enhancement) Why are these dirtyFlag cleared rather than aa->this 665 this.aaDirty = false; 666 aa.aaDirty = false; 667 } 668 update(AuralAttributesRetained aa)669 void update(AuralAttributesRetained aa) { 670 this.reset(aa); 671 } 672 673 } 674