aboutsummaryrefslogtreecommitdiffstats
path: root/src/javax/media/j3d/AuralAttributesRetained.java
blob: f5e3d6a2ff054e68eccde19bfca4b956e8831160 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
/*
 * Copyright 1997-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 *
 */

package javax.media.j3d;

import javax.vecmath.Point2f;

/**
 * The AuralAttributesRetained object defines all rendering state that can
 * be set as a component object of a retained Soundscape node.
 */
class AuralAttributesRetained extends NodeComponentRetained {

     /**
      *  Gain Scale Factor applied to source with this attribute
      */
     float      attributeGain = 1.0f;      // Valid values are >= 0.0.

     /**
      * Atmospheric Rolloff - speed of sound - coeff
      *    Normal gain attenuation based on distance of sound from
      *    listener is scaled by a rolloff factor, which can increase
      *    or decrease the usual inverse-distance-square value.
      */
     float      rolloff = 1.0f;             // Valid values are >= 0.0
     static final float  SPEED_OF_SOUND  = 0.344f;  // in meters/milliseconds

     /*
      * Reverberation
      *
      *   Within Java 3D's model for auralization, the components to
      *   reverberation for a particular space are:
      *     Reflection and Reverb Coefficients -
      *         attenuation of sound (uniform for all frequencies) due to
      *         absorption of reflected sound off materials within the
      *         listening space.
      *     Reflection and Reverb Delay -
      *         approximating time from the start of the direct sound that
      *         initial early and late reflection waves take to reach listener.
      *     Reverb Decay -
      *         approximating time from the start of the direct sound that
      *         reverberation is audible.
      */

     /**
      *   Coefficients for reverberation
      *     The (early) Reflection and Reverberation coefficient scale factors
      *     are used to approximate the reflective/absorptive characteristics
      *     of the surfaces in this bounded Auralizaton environment.
      *     Theses scale factors is applied to sound's amplitude regardless
      *     of sound's position.
      *     Value of 1.0 represents complete (unattenuated) sound reflection.
      *     Value of 0.0 represents full absorption; reverberation is disabled.
      */
     float      reflectionCoefficient = 0.0f; // Range of values 0.0 to 1.0
     float      reverbCoefficient = 1.0f; // Range of values 0.0 to 1.0

     /**
       *  Time Delays in milliseconds
       *    Set with either explicitly with time, or impliciticly by supplying
       *    bounds volume and having the delay time calculated.
       *    Bounds of reverberation space does not have to be the same as
       *    Attribute bounds.
       */
     float      reflectionDelay = 20.0f;    // in milliseconds
     float      reverbDelay = 40.0f;        // in milliseconds
     Bounds     reverbBounds = null;

     /**
      *   Decay parameters
      *     Length and timbre of reverb decay tail
      */
     float      decayTime = 1000.0f;        // in milliseconds
     float      decayFilter = 5000.0f;      // low-pass cutoff frequency

     /**
      *   Reverb Diffusion and Density ratios (0=min, 1=max)
      */
     float      diffusion = 1.0f;
     float      density = 1.0f;

     /**
      *   Reverberation order
      *     This limits the number of Reverberation iterations executed while
      *     sound is being reverberated.  As long as reflection coefficient is
      *     small enough, the reverberated sound decreases (as it would naturally)
      *     each successive iteration.
      *     Value of > zero defines the greatest reflection order to be used by
      *         the reverberator.
      *     All positive values are used as the number of loop iteration.
      *     Value of <= zero signifies that reverberation is to loop until reverb
      *         gain reaches zero (-60dB or 1/1000 of sound amplitude).
      */
     int      reverbOrder = 0;

     /**
      *   Distance Filter
      *   Each sound source is attenuated by a filter based on it's distance
      *   from the listener.
      *   For now the only supported filterType will be LOW_PASS frequency cutoff.
      *   At some time full FIR filtering will be supported.
      */
     static final int  NO_FILTERING  = -1;
     static final int  LOW_PASS      =  1;

     int         filterType = NO_FILTERING;
     float[]     distance = null;
     float[]     frequencyCutoff = null;

     /**
      *   Doppler Effect parameters
      *     Between two snapshots of the head and sound source positions some
      *     delta time apart, the distance between head and source is compared.
      *     If there has been no change in the distance between head and sound
      *     source over this delta time:
      *         f' = f
      *
      *     If there has been a change in the distance between head and sound:
      *         f' = f * Af * v
      *
      *     When head and sound are moving towards each other then
      *                |  (S * Ar)  +  (deltaV(h,t) * Av) |
      *         v  =   | -------------------------------- |
      *                |  (S * Ar)  -  (deltaV(s,t) * Av)  |
      *
      *     When head and sound are moving away from each other then
      *                |  (S * Ar)  -  (deltaV(h,t) * Av) |
      *         v  =   | -------------------------------- |
      *                |  (S * Ar)  +  (deltaV(s,t) * Av) |
      *
      *
      *     Af = AuralAttribute frequency scalefactor
      *     Ar = AuralAttribute rolloff scalefactor
      *     Av = AuralAttribute velocity scalefactor
      *     deltaV = delta velocity
      *     f = frequency of sound
      *     h = Listeners head position
      *     v = Ratio of delta velocities
      *     Vh = Vector from center ear to sound source
      *     S = Speed of sound
      *     s = Sound source position
      *     t = time
      *
      *     If adjusted velocity of head or adjusted velocity of sound is
      *     greater than adjusted speed of sound, f' is undefined.
      */
     /**
      *   Frequency Scale Factor
      *     used to increase or reduce the change of frequency associated
      *     with normal rate of playback.
      *     Value of zero causes sounds to be paused.
      */
     float      frequencyScaleFactor = 1.0f;
     /**
      *   Velocity Scale Factor
      *     Float value applied to the Change of distance between Sound Source
      *     and Listener over some delta time.  Non-zero if listener moving
      *     even if sound is not.  Value of zero implies no Doppler applied.
      */
     float      velocityScaleFactor = 0.0f;

     /**
      * This boolean is set when something changes in the attributes
      */
     boolean         aaDirty = true;

     /**
      * The mirror copy of this AuralAttributes.
      */
     AuralAttributesRetained mirrorAa = null;

    /**
     ** Debug print mechanism for Sound nodes
     **/
    static final // 'static final' so compiler doesn't include debugPrint calls
    boolean  debugFlag = false;

    static final  // 'static final' so internal error message are not compiled
    boolean internalErrors = false;

    void debugPrint(String message) {
        if (debugFlag) // leave test in in case debugFlag made non-static final
            System.err.println(message);
    }


    // ****************************************
    //
    // Set and Get individual attribute values
    //
    // ****************************************

    /**
     * Set Attribute Gain (amplitude)
     * @param gain scale factor applied to amplitude
     */
    void setAttributeGain(float gain) {
        this.attributeGain = gain;
	this.aaDirty = true;
	notifyUsers();
    }
    /**
     * Retrieve Attribute Gain (amplitude)
     * @return gain amplitude scale factor
     */
    float getAttributeGain() {
        return this.attributeGain;
    }

    /**
     * Set Attribute Gain Rolloff
     * @param rolloff atmospheric gain scale factor (changing speed of sound)
     */
    void setRolloff(float rolloff) {
        this.rolloff = rolloff;
	this.aaDirty = true;
	notifyUsers();
    }
    /**
     * Retrieve Attribute Gain Rolloff
     * @return rolloff atmospheric gain scale factor (changing speed of sound)
     */
    float getRolloff() {
        return this.rolloff;
    }

    /**
     * Set Reflective Coefficient
     * @param reflectionCoefficient reflection/absorption factor applied to
     * early reflections.
     */
    void setReflectionCoefficient(float reflectionCoefficient) {
        this.reflectionCoefficient = reflectionCoefficient;
	this.aaDirty = true;
	notifyUsers();
    }
    /**
     * Retrieve Reflective Coefficient
     * @return reflection coeff reflection/absorption factor applied to
     * early reflections.
     */
    float getReflectionCoefficient() {
        return this.reflectionCoefficient;
    }

    /**
     * Set Reflection Delay Time
     * @param reflectionDelay time before the start of early (first order)
     * reflections.
     */
    void setReflectionDelay(float reflectionDelay) {
        this.reflectionDelay = reflectionDelay;
	this.aaDirty = true;
	notifyUsers();
    }
    /**
     * Retrieve Reflection Delay Time
     * @return reflection delay time
     */
    float getReflectionDelay() {
        return this.reflectionDelay;
    }

    /**
     * Set Reverb Coefficient
     * @param reverbCoefficient reflection/absorption factor applied to
     * late reflections.
     */
    void setReverbCoefficient(float reverbCoefficient) {
        this.reverbCoefficient = reverbCoefficient;
	this.aaDirty = true;
	notifyUsers();
    }
    /**
     * Retrieve Reverb Coefficient
     * @return reverb coeff reflection/absorption factor applied to late
     * reflections.
     */
    float getReverbCoefficient() {
        return this.reverbCoefficient;
    }

    /**
     * Set Revereration Delay Time
     * @param reverbDelay time between each order of reflection
     */
    void setReverbDelay(float reverbDelay) {
        this.reverbDelay = reverbDelay;
	this.aaDirty = true;
	notifyUsers();
    }
    /**
     * Retrieve Revereration Delay Time
     * @return reverb delay time between each order of reflection
     */
    float getReverbDelay() {
        return this.reverbDelay;
    }
    /**
     * Set Decay Time
     * @param decayTime length of time reverb takes to decay
     */
    void setDecayTime(float decayTime) {
        this.decayTime = decayTime;
	this.aaDirty = true;
	notifyUsers();
    }
    /**
     * Retrieve Revereration Decay Time
     * @return reverb delay time
     */
    float getDecayTime() {
        return this.decayTime;
    }

    /**
     * Set Decay Filter
     * @param decayFilter frequency referenced used in low-pass filtering
     */
    void setDecayFilter(float decayFilter) {
        this.decayFilter = decayFilter;
	this.aaDirty = true;
	notifyUsers();
    }

    /**
     * Retrieve Revereration Decay Filter
     * @return reverb delay Filter
     */
    float getDecayFilter() {
        return this.decayFilter;
    }

    /**
     * Set Reverb Diffusion
     * @param diffusion ratio between min and max device diffusion settings
     */
    void setDiffusion(float diffusion) {
        this.diffusion = diffusion;
	this.aaDirty = true;
	notifyUsers();
    }

    /**
     * Retrieve Revereration Decay Diffusion
     * @return reverb diffusion
     */
    float getDiffusion() {
        return this.diffusion;
    }

    /**
     * Set Reverb Density
     * @param density ratio between min and max device density settings
     */
    void setDensity(float density) {
        this.density = density;
	this.aaDirty = true;
	notifyUsers();
    }

    /**
     * Retrieve Revereration Density
     * @return reverb density
     */
    float getDensity() {
        return this.density;
    }


    /**
     * Set Revereration Bounds
     * @param reverbVolume bounds used to approximate reverb time.
     */
    synchronized void setReverbBounds(Bounds reverbVolume) {
        this.reverbBounds = reverbVolume;
	this.aaDirty = true;
	notifyUsers();
    }
    /**
     * Retrieve Revereration Delay Bounds volume
     * @return reverb bounds volume that defines the Reverberation space and
     * indirectly the delay
     */
    Bounds getReverbBounds() {
        return this.reverbBounds;
    }

    /**
     * Set Reverberation Order of Reflections
     * @param reverbOrder number of times reflections added to reverb signal
     */
    void setReverbOrder(int reverbOrder) {
        this.reverbOrder = reverbOrder;
	this.aaDirty = true;
	notifyUsers();
    }
    /**
     * Retrieve Reverberation Order of Reflections
     * @return reverb order number of times reflections added to reverb signal
     */
    int getReverbOrder() {
        return this.reverbOrder;
    }

    /**
     * Set Distance Filter (based on distances and frequency cutoff)
     * @param attenuation array of pairs defining distance frequency cutoff
     */
    synchronized void setDistanceFilter(Point2f[] attenuation) {
        if (attenuation == null) {
            this.filterType = NO_FILTERING;
            return;
        }
        int attenuationLength = attenuation.length;
        if (attenuationLength == 0) {
            this.filterType = NO_FILTERING;
            return;
        }
        this.filterType = LOW_PASS;
        // Reallocate every time unless size of new array equal old array
        if ( distance == null ||
            (distance != null && (distance.length != attenuationLength) ) ) {
            this.distance = new float[attenuationLength];
            this.frequencyCutoff = new float[attenuationLength];
        }
        for (int i = 0; i< attenuationLength; i++) {
            this.distance[i] = attenuation[i].x;
            this.frequencyCutoff[i] = attenuation[i].y;
        }
	this.aaDirty = true;
	notifyUsers();
    }
    /**
     * Set Distance Filter (based on distances and frequency cutoff) using
     * separate arrays
     * @param distance array containing distance values
     * @param filter array containing low-pass frequency cutoff values
     */
    synchronized void setDistanceFilter(float[] distance, float[] filter) {
        if (distance == null || filter == null) {
            this.filterType = NO_FILTERING;
            return;
        }
        int distanceLength = distance.length;
        int filterLength = filter.length;
        if (distanceLength == 0 || filterLength == 0) {
            this.filterType = NO_FILTERING;
            return;
        }
        // Reallocate every time unless size of new array equal old array
        if ( this.distance == null ||
            ( this.distance != null &&
                (this.distance.length != filterLength) ) ) {
            this.distance = new float[distanceLength];
            this.frequencyCutoff = new float[distanceLength];
        }
        this.filterType = LOW_PASS;
        // Copy the distance array into nodes field
        System.arraycopy(distance, 0, this.distance, 0, distanceLength);
        // Copy the filter array an array of same length as the distance array
        if (distanceLength <= filterLength) {
            System.arraycopy(filter, 0, this.frequencyCutoff,0, distanceLength);
        }
        else {
            System.arraycopy(filter, 0, this.frequencyCutoff, 0, filterLength);
	    // Extend filter array to length of distance array by
	    // replicate last filter values.
            for (int i=filterLength; i< distanceLength; i++) {
                this.frequencyCutoff[i] = filter[filterLength - 1];
            }
        }
        if (debugFlag) {
            debugPrint("AAR setDistanceFilter(D,F)");
            for (int jj=0;jj<distanceLength;jj++) {
                debugPrint(" from distance, freq = " + distance[jj] + ", " +
                         filter[jj]);
                debugPrint(" into distance, freq = " + this.distance[jj] + ", " +
                         this.frequencyCutoff[jj]);
            }
        }
	this.aaDirty = true;
	notifyUsers();
    }

    /**
     * Retrieve Distance Filter array length
     * @return attenuation array length
     */
    int getDistanceFilterLength() {
        if (distance == null)
            return 0;
        else
            return this.distance.length;
    }


    /**
     * Retrieve Distance Filter (distances and frequency cutoff)
     * @return attenaution pairs of distance and frequency cutoff filter
     */
    void getDistanceFilter(Point2f[] attenuation) {
        // Write into existing param array already allocated
        if (attenuation == null)
            return;
        if (this.distance == null || this.frequencyCutoff == null)
            return;
        // The two filter attenuation arrays length should be the same
        // We can assume that distance and filter lengths are the same
        // and are non-zero.
        int distanceLength = this.distance.length;
        // check that attenuation array large enough to contain
        // auralAttribute arrays
        if (distanceLength > attenuation.length)
            distanceLength = attenuation.length;
        for (int i=0; i< distanceLength; i++) {
            attenuation[i].x = this.distance[i];
            if (filterType == NO_FILTERING)
                attenuation[i].y = Sound.NO_FILTER;
            else if (filterType == LOW_PASS)
                attenuation[i].y = this.frequencyCutoff[i];
            if (debugFlag)
                debugPrint("AAR: getDistF: " + attenuation[i].x + ", " +
                  attenuation[i].y);
        }
    }
    /**
     * Retrieve Distance Filter as arrays distances and frequency cutoff array
     * @param distance array of float values
     * @param frequencyCutoff array of float cutoff filter values in Hertz
     */
    void getDistanceFilter(float[] distance, float[] filter) {
        // Write into existing param arrays already allocated
        if (distance == null || filter == null)
            return;
        if (this.distance == null || this.frequencyCutoff == null)
            return;
        int distanceLength = this.distance.length;
        // check that distance parameter large enough to contain auralAttribute
        // distance array
        // We can assume that distance and filter lengths are the same
        // and are non-zero.
        if (distance.length < distanceLength)
            // parameter array not large enough to hold all this.distance data
            distanceLength = distance.length;
        System.arraycopy(this.distance, 0, distance, 0, distanceLength);
        if (debugFlag)
            debugPrint("AAR getDistanceFilter(D,F) " + this.distance[0]);
        int filterLength = this.frequencyCutoff.length;
        if (filter.length < filterLength)
            // parameter array not large enough to hold all this.filter data
            filterLength = filter.length;
        if (filterType == NO_FILTERING) {
            for (int i=0; i< filterLength; i++)
                filter[i] = Sound.NO_FILTER;
        }
        if (filterType == LOW_PASS) {
            System.arraycopy(this.frequencyCutoff, 0, filter, 0, filterLength);
        }
        if (debugFlag)
            debugPrint(", " + this.frequencyCutoff[0]);
    }

    /**
     * Set Frequency Scale Factor
     * @param frequencyScaleFactor factor applied to sound's base frequency
     */
    void setFrequencyScaleFactor(float frequencyScaleFactor) {
        this.frequencyScaleFactor = frequencyScaleFactor;
	this.aaDirty = true;
	notifyUsers();
    }
    /**
     * Retrieve Frequency Scale Factor
     * @return frequency scale factor applied to sound's base frequency
     */
    float getFrequencyScaleFactor() {
        return this.frequencyScaleFactor;
    }

    /**
     * Set Velocity ScaleFactor used in calculating Doppler Effect
     * @param velocityScaleFactor applied to velocity of sound in relation to listener
     */
    void setVelocityScaleFactor(float velocityScaleFactor) {
        this.velocityScaleFactor = velocityScaleFactor;
	this.aaDirty = true;
	notifyUsers();
    }
    /**
     * Retrieve Velocity ScaleFactor used in calculating Doppler Effect
     * @return velocity scale factor
     */
    float getVelocityScaleFactor() {
        return this.velocityScaleFactor;
    }

    synchronized void reset(AuralAttributesRetained aa) {
	int i;

        this.attributeGain = aa.attributeGain;
        this.rolloff = aa.rolloff;
        this.reflectionCoefficient = aa.reflectionCoefficient;
        this.reverbCoefficient = aa.reverbCoefficient;
        this.reflectionDelay = aa.reflectionDelay;
        this.reverbDelay = aa.reverbDelay;
        this.reverbBounds = aa.reverbBounds;
        this.reverbOrder = aa.reverbOrder;
        this.decayTime = aa.decayTime;
        this.decayFilter = aa.decayFilter;
        this.diffusion = aa.diffusion;
        this.density = aa.density;
        this.frequencyScaleFactor = aa.frequencyScaleFactor;
        this.velocityScaleFactor = aa.velocityScaleFactor;

	if (aa.distance != null) {
            this.distance = new float[aa.distance.length];
            if (debugFlag)
                debugPrint("reset aa; aa.distance.length = " + this.distance.length);
            System.arraycopy(aa.distance, 0, this.distance, 0, this.distance.length);
        }
        else
            if (debugFlag)
                debugPrint("reset aa; aa.distance = null");
	if (aa.frequencyCutoff != null)  {
            this.frequencyCutoff = new float[aa.frequencyCutoff.length];
            if (debugFlag)
                debugPrint("reset aa; aa.frequencyCutoff.length = " + this.frequencyCutoff.length);
            System.arraycopy(aa.frequencyCutoff, 0, this.frequencyCutoff, 0,
                     this.frequencyCutoff.length);
        }
        else
            if (debugFlag)
                debugPrint("reset aa; aa.frequencyCutoff = null");
	// XXXX: (Enhancement) Why are these dirtyFlag cleared rather than aa->this
        this.aaDirty = false;
	aa.aaDirty = false;
    }

    void update(AuralAttributesRetained aa) {
	this.reset(aa);
    }

}