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