001: /*
002: * $RCSfile: AuralAttributesRetained.java,v $
003: *
004: * Copyright 1997-2008 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
006: *
007: * This code is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU General Public License version 2 only, as
009: * published by the Free Software Foundation. Sun designates this
010: * particular file as subject to the "Classpath" exception as provided
011: * by Sun in the LICENSE file that accompanied this code.
012: *
013: * This code is distributed in the hope that it will be useful, but WITHOUT
014: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
015: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
016: * version 2 for more details (a copy is included in the LICENSE file that
017: * accompanied this code).
018: *
019: * You should have received a copy of the GNU General Public License version
020: * 2 along with this work; if not, write to the Free Software Foundation,
021: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
022: *
023: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
024: * CA 95054 USA or visit www.sun.com if you need additional information or
025: * have any questions.
026: *
027: * $Revision: 1.7 $
028: * $Date: 2008/02/28 20:17:19 $
029: * $State: Exp $
030: */
031:
032: package javax.media.j3d;
033:
034: import java.util.Hashtable;
035: import javax.vecmath.Point2f;
036:
037: /**
038: * The AuralAttributesRetained object defines all rendering state that can
039: * be set as a component object of a retained Soundscape node.
040: */
041: class AuralAttributesRetained extends NodeComponentRetained {
042:
043: /**
044: * Gain Scale Factor applied to source with this attribute
045: */
046: float attributeGain = 1.0f; // Valid values are >= 0.0.
047:
048: /**
049: * Atmospheric Rolloff - speed of sound - coeff
050: * Normal gain attenuation based on distance of sound from
051: * listener is scaled by a rolloff factor, which can increase
052: * or decrease the usual inverse-distance-square value.
053: */
054: float rolloff = 1.0f; // Valid values are >= 0.0
055: static final float SPEED_OF_SOUND = 0.344f; // in meters/milliseconds
056:
057: /*
058: * Reverberation
059: *
060: * Within Java 3D's model for auralization, the components to
061: * reverberation for a particular space are:
062: * Reflection and Reverb Coefficients -
063: * attenuation of sound (uniform for all frequencies) due to
064: * absorption of reflected sound off materials within the
065: * listening space.
066: * Reflection and Reverb Delay -
067: * approximating time from the start of the direct sound that
068: * initial early and late reflection waves take to reach listener.
069: * Reverb Decay -
070: * approximating time from the start of the direct sound that
071: * reverberation is audible.
072: */
073:
074: /**
075: * Coefficients for reverberation
076: * The (early) Reflection and Reverberation coefficient scale factors
077: * are used to approximate the reflective/absorptive characteristics
078: * of the surfaces in this bounded Auralizaton environment.
079: * Theses scale factors is applied to sound's amplitude regardless
080: * of sound's position.
081: * Value of 1.0 represents complete (unattenuated) sound reflection.
082: * Value of 0.0 represents full absorption; reverberation is disabled.
083: */
084: float reflectionCoefficient = 0.0f; // Range of values 0.0 to 1.0
085: float reverbCoefficient = 1.0f; // Range of values 0.0 to 1.0
086:
087: /**
088: * Time Delays in milliseconds
089: * Set with either explicitly with time, or impliciticly by supplying
090: * bounds volume and having the delay time calculated.
091: * Bounds of reverberation space does not have to be the same as
092: * Attribute bounds.
093: */
094: float reflectionDelay = 20.0f; // in milliseconds
095: float reverbDelay = 40.0f; // in milliseconds
096: Bounds reverbBounds = null;
097:
098: /**
099: * 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:
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: // Set and Get individual attribute values
218: //
219: // ****************************************
220:
221: /**
222: * Set Attribute Gain (amplitude)
223: * @param gain scale factor applied to amplitude
224: */
225: void setAttributeGain(float gain) {
226: this .attributeGain = gain;
227: this .aaDirty = true;
228: notifyUsers();
229: }
230:
231: /**
232: * Retrieve Attribute Gain (amplitude)
233: * @return gain amplitude scale factor
234: */
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: */
243: void setRolloff(float rolloff) {
244: this .rolloff = rolloff;
245: this .aaDirty = true;
246: notifyUsers();
247: }
248:
249: /**
250: * Retrieve Attribute Gain Rolloff
251: * @return rolloff atmospheric gain scale factor (changing speed of sound)
252: */
253: float getRolloff() {
254: return this .rolloff;
255: }
256:
257: /**
258: * Set Reflective Coefficient
259: * @param reflectionCoefficient reflection/absorption factor applied to
260: * early reflections.
261: */
262: void setReflectionCoefficient(float reflectionCoefficient) {
263: this .reflectionCoefficient = reflectionCoefficient;
264: this .aaDirty = true;
265: notifyUsers();
266: }
267:
268: /**
269: * Retrieve Reflective Coefficient
270: * @return reflection coeff reflection/absorption factor applied to
271: * early reflections.
272: */
273: float getReflectionCoefficient() {
274: return this .reflectionCoefficient;
275: }
276:
277: /**
278: * Set Reflection Delay Time
279: * @param reflectionDelay time before the start of early (first order)
280: * reflections.
281: */
282: void setReflectionDelay(float reflectionDelay) {
283: this .reflectionDelay = reflectionDelay;
284: this .aaDirty = true;
285: notifyUsers();
286: }
287:
288: /**
289: * Retrieve Reflection Delay Time
290: * @return reflection delay time
291: */
292: float getReflectionDelay() {
293: return this .reflectionDelay;
294: }
295:
296: /**
297: * Set Reverb Coefficient
298: * @param reverbCoefficient reflection/absorption factor applied to
299: * late reflections.
300: */
301: void setReverbCoefficient(float reverbCoefficient) {
302: this .reverbCoefficient = reverbCoefficient;
303: this .aaDirty = true;
304: notifyUsers();
305: }
306:
307: /**
308: * Retrieve Reverb Coefficient
309: * @return reverb coeff reflection/absorption factor applied to late
310: * reflections.
311: */
312: float getReverbCoefficient() {
313: return this .reverbCoefficient;
314: }
315:
316: /**
317: * Set Revereration Delay Time
318: * @param reverbDelay time between each order of reflection
319: */
320: void setReverbDelay(float reverbDelay) {
321: this .reverbDelay = reverbDelay;
322: this .aaDirty = true;
323: notifyUsers();
324: }
325:
326: /**
327: * Retrieve Revereration Delay Time
328: * @return reverb delay time between each order of reflection
329: */
330: float getReverbDelay() {
331: return this .reverbDelay;
332: }
333:
334: /**
335: * Set Decay Time
336: * @param decayTime length of time reverb takes to decay
337: */
338: void setDecayTime(float decayTime) {
339: this .decayTime = decayTime;
340: this .aaDirty = true;
341: notifyUsers();
342: }
343:
344: /**
345: * Retrieve Revereration Decay Time
346: * @return reverb delay time
347: */
348: float getDecayTime() {
349: return this .decayTime;
350: }
351:
352: /**
353: * Set Decay Filter
354: * @param decayFilter frequency referenced used in low-pass filtering
355: */
356: void setDecayFilter(float decayFilter) {
357: this .decayFilter = decayFilter;
358: this .aaDirty = true;
359: notifyUsers();
360: }
361:
362: /**
363: * Retrieve Revereration Decay Filter
364: * @return reverb delay Filter
365: */
366: float getDecayFilter() {
367: return this .decayFilter;
368: }
369:
370: /**
371: * Set Reverb Diffusion
372: * @param diffusion ratio between min and max device diffusion settings
373: */
374: void setDiffusion(float diffusion) {
375: this .diffusion = diffusion;
376: this .aaDirty = true;
377: notifyUsers();
378: }
379:
380: /**
381: * Retrieve Revereration Decay Diffusion
382: * @return reverb diffusion
383: */
384: float getDiffusion() {
385: return this .diffusion;
386: }
387:
388: /**
389: * Set Reverb Density
390: * @param density ratio between min and max device density settings
391: */
392: void setDensity(float density) {
393: this .density = density;
394: this .aaDirty = true;
395: notifyUsers();
396: }
397:
398: /**
399: * Retrieve Revereration Density
400: * @return reverb density
401: */
402: float getDensity() {
403: return this .density;
404: }
405:
406: /**
407: * Set Revereration Bounds
408: * @param reverbVolume bounds used to approximate reverb time.
409: */
410: synchronized void setReverbBounds(Bounds reverbVolume) {
411: this .reverbBounds = reverbVolume;
412: this .aaDirty = true;
413: notifyUsers();
414: }
415:
416: /**
417: * Retrieve Revereration Delay Bounds volume
418: * @return reverb bounds volume that defines the Reverberation space and
419: * indirectly the delay
420: */
421: Bounds getReverbBounds() {
422: return this .reverbBounds;
423: }
424:
425: /**
426: * Set Reverberation Order of Reflections
427: * @param reverbOrder number of times reflections added to reverb signal
428: */
429: void setReverbOrder(int reverbOrder) {
430: this .reverbOrder = reverbOrder;
431: this .aaDirty = true;
432: notifyUsers();
433: }
434:
435: /**
436: * Retrieve Reverberation Order of Reflections
437: * @return reverb order number of times reflections added to reverb signal
438: */
439: int getReverbOrder() {
440: return this .reverbOrder;
441: }
442:
443: /**
444: * Set Distance Filter (based on distances and frequency cutoff)
445: * @param attenuation array of pairs defining distance frequency cutoff
446: */
447: synchronized void setDistanceFilter(Point2f[] attenuation) {
448: if (attenuation == null) {
449: this .filterType = NO_FILTERING;
450: return;
451: }
452: int attenuationLength = attenuation.length;
453: if (attenuationLength == 0) {
454: this .filterType = NO_FILTERING;
455: return;
456: }
457: this .filterType = LOW_PASS;
458: // Reallocate every time unless size of new array equal old array
459: if (distance == null
460: || (distance != null && (distance.length != attenuationLength))) {
461: this .distance = new float[attenuationLength];
462: this .frequencyCutoff = new float[attenuationLength];
463: }
464: for (int i = 0; i < attenuationLength; i++) {
465: this .distance[i] = attenuation[i].x;
466: this .frequencyCutoff[i] = attenuation[i].y;
467: }
468: this .aaDirty = true;
469: notifyUsers();
470: }
471:
472: /**
473: * Set Distance Filter (based on distances and frequency cutoff) using
474: * separate arrays
475: * @param distance array containing distance values
476: * @param filter array containing low-pass frequency cutoff values
477: */
478: synchronized void setDistanceFilter(float[] distance, float[] filter) {
479: if (distance == null || filter == null) {
480: this .filterType = NO_FILTERING;
481: return;
482: }
483: int distanceLength = distance.length;
484: int filterLength = filter.length;
485: if (distanceLength == 0 || filterLength == 0) {
486: this .filterType = NO_FILTERING;
487: return;
488: }
489: // Reallocate every time unless size of new array equal old array
490: if (this .distance == null
491: || (this .distance != null && (this .distance.length != filterLength))) {
492: this .distance = new float[distanceLength];
493: this .frequencyCutoff = new float[distanceLength];
494: }
495: this .filterType = LOW_PASS;
496: // Copy the distance array into nodes field
497: System.arraycopy(distance, 0, this .distance, 0, distanceLength);
498: // Copy the filter array an array of same length as the distance array
499: if (distanceLength <= filterLength) {
500: System.arraycopy(filter, 0, this .frequencyCutoff, 0,
501: distanceLength);
502: } else {
503: System.arraycopy(filter, 0, this .frequencyCutoff, 0,
504: filterLength);
505: // Extend filter array to length of distance array by
506: // replicate last filter values.
507: for (int i = filterLength; i < distanceLength; i++) {
508: this .frequencyCutoff[i] = filter[filterLength - 1];
509: }
510: }
511: if (debugFlag) {
512: debugPrint("AAR setDistanceFilter(D,F)");
513: for (int jj = 0; jj < distanceLength; jj++) {
514: debugPrint(" from distance, freq = " + distance[jj]
515: + ", " + filter[jj]);
516: debugPrint(" into distance, freq = "
517: + this .distance[jj] + ", "
518: + this .frequencyCutoff[jj]);
519: }
520: }
521: this .aaDirty = true;
522: notifyUsers();
523: }
524:
525: /**
526: * Retrieve Distance Filter array length
527: * @return attenuation array length
528: */
529: int getDistanceFilterLength() {
530: if (distance == null)
531: return 0;
532: else
533: return this .distance.length;
534: }
535:
536: /**
537: * Retrieve Distance Filter (distances and frequency cutoff)
538: * @return attenaution pairs of distance and frequency cutoff filter
539: */
540: void getDistanceFilter(Point2f[] attenuation) {
541: // Write into existing param array already allocated
542: if (attenuation == null)
543: return;
544: if (this .distance == null || this .frequencyCutoff == null)
545: return;
546: // The two filter attenuation arrays length should be the same
547: // We can assume that distance and filter lengths are the same
548: // and are non-zero.
549: int distanceLength = this .distance.length;
550: // check that attenuation array large enough to contain
551: // auralAttribute arrays
552: if (distanceLength > attenuation.length)
553: distanceLength = attenuation.length;
554: for (int i = 0; i < distanceLength; i++) {
555: attenuation[i].x = this .distance[i];
556: if (filterType == NO_FILTERING)
557: attenuation[i].y = Sound.NO_FILTER;
558: else if (filterType == LOW_PASS)
559: attenuation[i].y = this .frequencyCutoff[i];
560: if (debugFlag)
561: debugPrint("AAR: getDistF: " + attenuation[i].x + ", "
562: + attenuation[i].y);
563: }
564: }
565:
566: /**
567: * Retrieve Distance Filter as arrays distances and frequency cutoff array
568: * @param distance array of float values
569: * @param frequencyCutoff array of float cutoff filter values in Hertz
570: */
571: void getDistanceFilter(float[] distance, float[] filter) {
572: // Write into existing param arrays already allocated
573: if (distance == null || filter == null)
574: return;
575: if (this .distance == null || this .frequencyCutoff == null)
576: return;
577: int distanceLength = this .distance.length;
578: // check that distance parameter large enough to contain auralAttribute
579: // distance array
580: // We can assume that distance and filter lengths are the same
581: // and are non-zero.
582: if (distance.length < distanceLength)
583: // parameter array not large enough to hold all this.distance data
584: distanceLength = distance.length;
585: System.arraycopy(this .distance, 0, distance, 0, distanceLength);
586: if (debugFlag)
587: debugPrint("AAR getDistanceFilter(D,F) " + this .distance[0]);
588: int filterLength = this .frequencyCutoff.length;
589: if (filter.length < filterLength)
590: // parameter array not large enough to hold all this.filter data
591: filterLength = filter.length;
592: if (filterType == NO_FILTERING) {
593: for (int i = 0; i < filterLength; i++)
594: filter[i] = Sound.NO_FILTER;
595: }
596: if (filterType == LOW_PASS) {
597: System.arraycopy(this .frequencyCutoff, 0, filter, 0,
598: filterLength);
599: }
600: if (debugFlag)
601: debugPrint(", " + this .frequencyCutoff[0]);
602: }
603:
604: /**
605: * Set Frequency Scale Factor
606: * @param frequencyScaleFactor factor applied to sound's base frequency
607: */
608: void setFrequencyScaleFactor(float frequencyScaleFactor) {
609: this .frequencyScaleFactor = frequencyScaleFactor;
610: this .aaDirty = true;
611: notifyUsers();
612: }
613:
614: /**
615: * Retrieve Frequency Scale Factor
616: * @return frequency scale factor applied to sound's base frequency
617: */
618: float getFrequencyScaleFactor() {
619: return this .frequencyScaleFactor;
620: }
621:
622: /**
623: * Set Velocity ScaleFactor used in calculating Doppler Effect
624: * @param velocityScaleFactor applied to velocity of sound in relation to listener
625: */
626: void setVelocityScaleFactor(float velocityScaleFactor) {
627: this .velocityScaleFactor = velocityScaleFactor;
628: this .aaDirty = true;
629: notifyUsers();
630: }
631:
632: /**
633: * Retrieve Velocity ScaleFactor used in calculating Doppler Effect
634: * @return velocity scale factor
635: */
636: float getVelocityScaleFactor() {
637: return this .velocityScaleFactor;
638: }
639:
640: synchronized void reset(AuralAttributesRetained aa) {
641: int i;
642:
643: this .attributeGain = aa.attributeGain;
644: this .rolloff = aa.rolloff;
645: this .reflectionCoefficient = aa.reflectionCoefficient;
646: this .reverbCoefficient = aa.reverbCoefficient;
647: this .reflectionDelay = aa.reflectionDelay;
648: this .reverbDelay = aa.reverbDelay;
649: this .reverbBounds = aa.reverbBounds;
650: this .reverbOrder = aa.reverbOrder;
651: this .decayTime = aa.decayTime;
652: this .decayFilter = aa.decayFilter;
653: this .diffusion = aa.diffusion;
654: this .density = aa.density;
655: this .frequencyScaleFactor = aa.frequencyScaleFactor;
656: this .velocityScaleFactor = aa.velocityScaleFactor;
657:
658: if (aa.distance != null) {
659: this .distance = new float[aa.distance.length];
660: if (debugFlag)
661: debugPrint("reset aa; aa.distance.length = "
662: + this .distance.length);
663: System.arraycopy(aa.distance, 0, this .distance, 0,
664: this .distance.length);
665: } else if (debugFlag)
666: debugPrint("reset aa; aa.distance = null");
667: if (aa.frequencyCutoff != null) {
668: this .frequencyCutoff = new float[aa.frequencyCutoff.length];
669: if (debugFlag)
670: debugPrint("reset aa; aa.frequencyCutoff.length = "
671: + this .frequencyCutoff.length);
672: System.arraycopy(aa.frequencyCutoff, 0,
673: this .frequencyCutoff, 0,
674: this .frequencyCutoff.length);
675: } else if (debugFlag)
676: debugPrint("reset aa; aa.frequencyCutoff = null");
677: // XXXX: (Enhancement) Why are these dirtyFlag cleared rather than aa->this
678: this .aaDirty = false;
679: aa.aaDirty = false;
680: }
681:
682: void update(AuralAttributesRetained aa) {
683: this.reset(aa);
684: }
685:
686: }
|