• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2009-2010 jMonkeyEngine
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17  *   may be used to endorse or promote products derived from this software
18  *   without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 package com.jme3.audio;
34 
35 import com.jme3.asset.AssetManager;
36 import com.jme3.asset.AssetNotFoundException;
37 import com.jme3.export.InputCapsule;
38 import com.jme3.export.JmeExporter;
39 import com.jme3.export.JmeImporter;
40 import com.jme3.export.OutputCapsule;
41 import com.jme3.math.Vector3f;
42 import com.jme3.scene.Node;
43 import com.jme3.util.PlaceholderAssets;
44 import java.io.IOException;
45 import java.util.logging.Level;
46 import java.util.logging.Logger;
47 
48 /**
49  * An <code>AudioNode</code> is used in jME3 for playing audio files.
50  * <br/>
51  * First, an {@link AudioNode} is loaded from file, and then assigned
52  * to an audio node for playback. Once the audio node is attached to the
53  * scene, its location will influence the position it is playing from relative
54  * to the {@link Listener}.
55  * <br/>
56  * An audio node can also play in "headspace", meaning its location
57  * or velocity does not influence how it is played.
58  * The "positional" property of an AudioNode can be set via
59  * {@link AudioNode#setPositional(boolean) }.
60  *
61  * @author normenhansen
62  * @author Kirill Vainer
63  */
64 public class AudioNode extends Node {
65 
66     protected boolean loop = false;
67     protected float volume = 1;
68     protected float pitch = 1;
69     protected float timeOffset = 0;
70     protected Filter dryFilter;
71     protected AudioKey audioKey;
72     protected transient AudioData data = null;
73     protected transient volatile Status status = Status.Stopped;
74     protected transient volatile int channel = -1;
75     protected Vector3f velocity = new Vector3f();
76     protected boolean reverbEnabled = true;
77     protected float maxDistance = 200; // 200 meters
78     protected float refDistance = 10; // 10 meters
79     protected Filter reverbFilter;
80     private boolean directional = false;
81     protected Vector3f direction = new Vector3f(0, 0, 1);
82     protected float innerAngle = 360;
83     protected float outerAngle = 360;
84     protected boolean positional = true;
85 
86     /**
87      * <code>Status</code> indicates the current status of the audio node.
88      */
89     public enum Status {
90         /**
91          * The audio node is currently playing. This will be set if
92          * {@link AudioNode#play() } is called.
93          */
94         Playing,
95 
96         /**
97          * The audio node is currently paused.
98          */
99         Paused,
100 
101         /**
102          * The audio node is currently stopped.
103          * This will be set if {@link AudioNode#stop() } is called
104          * or the audio has reached the end of the file.
105          */
106         Stopped,
107     }
108 
109     /**
110      * Creates a new <code>AudioNode</code> without any audio data set.
111      */
AudioNode()112     public AudioNode() {
113     }
114 
115     /**
116      * Creates a new <code>AudioNode</code> without any audio data set.
117      *
118      * @param audioRenderer The audio renderer to use for playing. Cannot be null.
119      *
120      * @deprecated AudioRenderer parameter is ignored.
121      */
AudioNode(AudioRenderer audioRenderer)122     public AudioNode(AudioRenderer audioRenderer) {
123     }
124 
125     /**
126      * Creates a new <code>AudioNode</code> with the given data and key.
127      *
128      * @param audioRenderer The audio renderer to use for playing. Cannot be null.
129      * @param audioData The audio data contains the audio track to play.
130      * @param audioKey The audio key that was used to load the AudioData
131      *
132      * @deprecated AudioRenderer parameter is ignored.
133      */
AudioNode(AudioRenderer audioRenderer, AudioData audioData, AudioKey audioKey)134     public AudioNode(AudioRenderer audioRenderer, AudioData audioData, AudioKey audioKey) {
135         setAudioData(audioData, audioKey);
136     }
137 
138     /**
139      * Creates a new <code>AudioNode</code> with the given data and key.
140      *
141      * @param audioData The audio data contains the audio track to play.
142      * @param audioKey The audio key that was used to load the AudioData
143      */
AudioNode(AudioData audioData, AudioKey audioKey)144     public AudioNode(AudioData audioData, AudioKey audioKey) {
145         setAudioData(audioData, audioKey);
146     }
147 
148     /**
149      * Creates a new <code>AudioNode</code> with the given audio file.
150      *
151      * @param audioRenderer The audio renderer to use for playing. Cannot be null.
152      * @param assetManager The asset manager to use to load the audio file
153      * @param name The filename of the audio file
154      * @param stream If true, the audio will be streamed gradually from disk,
155      *               otherwise, it will be buffered.
156      * @param streamCache If stream is also true, then this specifies if
157      * the stream cache is used. When enabled, the audio stream will
158      * be read entirely but not decoded, allowing features such as
159      * seeking, looping and determining duration.
160      *
161      * @deprecated AudioRenderer parameter is ignored.
162      */
AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name, boolean stream, boolean streamCache)163     public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name, boolean stream, boolean streamCache) {
164         this.audioKey = new AudioKey(name, stream, streamCache);
165         this.data = (AudioData) assetManager.loadAsset(audioKey);
166     }
167 
168     /**
169      * Creates a new <code>AudioNode</code> with the given audio file.
170      *
171      * @param assetManager The asset manager to use to load the audio file
172      * @param name The filename of the audio file
173      * @param stream If true, the audio will be streamed gradually from disk,
174      *               otherwise, it will be buffered.
175      * @param streamCache If stream is also true, then this specifies if
176      * the stream cache is used. When enabled, the audio stream will
177      * be read entirely but not decoded, allowing features such as
178      * seeking, looping and determining duration.
179      */
AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache)180     public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) {
181         this.audioKey = new AudioKey(name, stream, streamCache);
182         this.data = (AudioData) assetManager.loadAsset(audioKey);
183     }
184 
185     /**
186      * Creates a new <code>AudioNode</code> with the given audio file.
187      *
188      * @param audioRenderer The audio renderer to use for playing. Cannot be null.
189      * @param assetManager The asset manager to use to load the audio file
190      * @param name The filename of the audio file
191      * @param stream If true, the audio will be streamed gradually from disk,
192      *               otherwise, it will be buffered.
193      *
194      * @deprecated AudioRenderer parameter is ignored.
195      */
AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name, boolean stream)196     public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name, boolean stream) {
197         this(audioRenderer, assetManager, name, stream, false);
198     }
199 
200     /**
201      * Creates a new <code>AudioNode</code> with the given audio file.
202      *
203      * @param assetManager The asset manager to use to load the audio file
204      * @param name The filename of the audio file
205      * @param stream If true, the audio will be streamed gradually from disk,
206      *               otherwise, it will be buffered.
207      */
AudioNode(AssetManager assetManager, String name, boolean stream)208     public AudioNode(AssetManager assetManager, String name, boolean stream) {
209         this(assetManager, name, stream, false);
210     }
211 
212     /**
213      * Creates a new <code>AudioNode</code> with the given audio file.
214      *
215      * @param audioRenderer The audio renderer to use for playing. Cannot be null.
216      * @param assetManager The asset manager to use to load the audio file
217      * @param name The filename of the audio file
218      *
219      * @deprecated AudioRenderer parameter is ignored.
220      */
AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name)221     public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) {
222         this(assetManager, name, false);
223     }
224 
225     /**
226      * Creates a new <code>AudioNode</code> with the given audio file.
227      *
228      * @param assetManager The asset manager to use to load the audio file
229      * @param name The filename of the audio file
230      */
AudioNode(AssetManager assetManager, String name)231     public AudioNode(AssetManager assetManager, String name) {
232         this(assetManager, name, false);
233     }
234 
getRenderer()235     protected AudioRenderer getRenderer() {
236         AudioRenderer result = AudioContext.getAudioRenderer();
237         if( result == null )
238             throw new IllegalStateException( "No audio renderer available, make sure call is being performed on render thread." );
239         return result;
240     }
241 
242     /**
243      * Start playing the audio.
244      */
play()245     public void play(){
246         getRenderer().playSource(this);
247     }
248 
249     /**
250      * Start playing an instance of this audio. This method can be used
251      * to play the same <code>AudioNode</code> multiple times. Note
252      * that changes to the parameters of this AudioNode will not effect the
253      * instances already playing.
254      */
playInstance()255     public void playInstance(){
256         getRenderer().playSourceInstance(this);
257     }
258 
259     /**
260      * Stop playing the audio that was started with {@link AudioNode#play() }.
261      */
stop()262     public void stop(){
263         getRenderer().stopSource(this);
264     }
265 
266     /**
267      * Pause the audio that was started with {@link AudioNode#play() }.
268      */
pause()269     public void pause(){
270         getRenderer().pauseSource(this);
271     }
272 
273     /**
274      * Do not use.
275      */
setChannel(int channel)276     public final void setChannel(int channel) {
277         if (status != Status.Stopped) {
278             throw new IllegalStateException("Can only set source id when stopped");
279         }
280 
281         this.channel = channel;
282     }
283 
284     /**
285      * Do not use.
286      */
getChannel()287     public int getChannel() {
288         return channel;
289     }
290 
291     /**
292      * @return The {#link Filter dry filter} that is set.
293      * @see AudioNode#setDryFilter(com.jme3.audio.Filter)
294      */
getDryFilter()295     public Filter getDryFilter() {
296         return dryFilter;
297     }
298 
299     /**
300      * Set the dry filter to use for this audio node.
301      *
302      * When {@link AudioNode#setReverbEnabled(boolean) reverb} is used,
303      * the dry filter will only influence the "dry" portion of the audio,
304      * e.g. not the reverberated parts of the AudioNode playing.
305      *
306      * See the relevent documentation for the {@link Filter} to determine
307      * the effect.
308      *
309      * @param dryFilter The filter to set, or null to disable dry filter.
310      */
setDryFilter(Filter dryFilter)311     public void setDryFilter(Filter dryFilter) {
312         this.dryFilter = dryFilter;
313         if (channel >= 0)
314             getRenderer().updateSourceParam(this, AudioParam.DryFilter);
315     }
316 
317     /**
318      * Set the audio data to use for the audio. Note that this method
319      * can only be called once, if for example the audio node was initialized
320      * without an {@link AudioData}.
321      *
322      * @param audioData The audio data contains the audio track to play.
323      * @param audioKey The audio key that was used to load the AudioData
324      */
setAudioData(AudioData audioData, AudioKey audioKey)325     public void setAudioData(AudioData audioData, AudioKey audioKey) {
326         if (data != null) {
327             throw new IllegalStateException("Cannot change data once its set");
328         }
329 
330         data = audioData;
331         this.audioKey = audioKey;
332     }
333 
334     /**
335      * @return The {@link AudioData} set previously with
336      * {@link AudioNode#setAudioData(com.jme3.audio.AudioData, com.jme3.audio.AudioKey) }
337      * or any of the constructors that initialize the audio data.
338      */
getAudioData()339     public AudioData getAudioData() {
340         return data;
341     }
342 
343     /**
344      * @return The {@link Status} of the audio node.
345      * The status will be changed when either the {@link AudioNode#play() }
346      * or {@link AudioNode#stop() } methods are called.
347      */
getStatus()348     public Status getStatus() {
349         return status;
350     }
351 
352     /**
353      * Do not use.
354      */
setStatus(Status status)355     public final void setStatus(Status status) {
356         this.status = status;
357     }
358 
359     /**
360      * @return True if the audio will keep looping after it is done playing,
361      * otherwise, false.
362      * @see AudioNode#setLooping(boolean)
363      */
isLooping()364     public boolean isLooping() {
365         return loop;
366     }
367 
368     /**
369      * Set the looping mode for the audio node. The default is false.
370      *
371      * @param loop True if the audio should keep looping after it is done playing.
372      */
setLooping(boolean loop)373     public void setLooping(boolean loop) {
374         this.loop = loop;
375         if (channel >= 0)
376             getRenderer().updateSourceParam(this, AudioParam.Looping);
377     }
378 
379     /**
380      * @return The pitch of the audio, also the speed of playback.
381      *
382      * @see AudioNode#setPitch(float)
383      */
getPitch()384     public float getPitch() {
385         return pitch;
386     }
387 
388     /**
389      * Set the pitch of the audio, also the speed of playback.
390      * The value must be between 0.5 and 2.0.
391      *
392      * @param pitch The pitch to set.
393      * @throws IllegalArgumentException If pitch is not between 0.5 and 2.0.
394      */
setPitch(float pitch)395     public void setPitch(float pitch) {
396         if (pitch < 0.5f || pitch > 2.0f) {
397             throw new IllegalArgumentException("Pitch must be between 0.5 and 2.0");
398         }
399 
400         this.pitch = pitch;
401         if (channel >= 0)
402             getRenderer().updateSourceParam(this, AudioParam.Pitch);
403     }
404 
405     /**
406      * @return The volume of this audio node.
407      *
408      * @see AudioNode#setVolume(float)
409      */
getVolume()410     public float getVolume() {
411         return volume;
412     }
413 
414     /**
415      * Set the volume of this audio node.
416      *
417      * The volume is specified as gain. 1.0 is the default.
418      *
419      * @param volume The volume to set.
420      * @throws IllegalArgumentException If volume is negative
421      */
setVolume(float volume)422     public void setVolume(float volume) {
423         if (volume < 0f) {
424             throw new IllegalArgumentException("Volume cannot be negative");
425         }
426 
427         this.volume = volume;
428         if (channel >= 0)
429             getRenderer().updateSourceParam(this, AudioParam.Volume);
430     }
431 
432     /**
433      * @return The time offset in seconds when the sound will start playing.
434      */
getTimeOffset()435     public float getTimeOffset() {
436         return timeOffset;
437     }
438 
439     /**
440      * Set the time offset in seconds when the sound will start playing.
441      *
442      * @param timeOffset The time offset
443      * @throws IllegalArgumentException If timeOffset is negative
444      */
setTimeOffset(float timeOffset)445     public void setTimeOffset(float timeOffset) {
446         if (timeOffset < 0f) {
447             throw new IllegalArgumentException("Time offset cannot be negative");
448         }
449 
450         this.timeOffset = timeOffset;
451         if (data instanceof AudioStream) {
452             System.out.println("request setTime");
453             ((AudioStream) data).setTime(timeOffset);
454         }else if(status == Status.Playing){
455             stop();
456             play();
457         }
458     }
459 
460     /**
461      * @return The velocity of the audio node.
462      *
463      * @see AudioNode#setVelocity(com.jme3.math.Vector3f)
464      */
getVelocity()465     public Vector3f getVelocity() {
466         return velocity;
467     }
468 
469     /**
470      * Set the velocity of the audio node. The velocity is expected
471      * to be in meters. Does nothing if the audio node is not positional.
472      *
473      * @param velocity The velocity to set.
474      * @see AudioNode#setPositional(boolean)
475      */
setVelocity(Vector3f velocity)476     public void setVelocity(Vector3f velocity) {
477         this.velocity.set(velocity);
478         if (channel >= 0)
479             getRenderer().updateSourceParam(this, AudioParam.Velocity);
480     }
481 
482     /**
483      * @return True if reverb is enabled, otherwise false.
484      *
485      * @see AudioNode#setReverbEnabled(boolean)
486      */
isReverbEnabled()487     public boolean isReverbEnabled() {
488         return reverbEnabled;
489     }
490 
491     /**
492      * Set to true to enable reverberation effects for this audio node.
493      * Does nothing if the audio node is not positional.
494      * <br/>
495      * When enabled, the audio environment set with
496      * {@link AudioRenderer#setEnvironment(com.jme3.audio.Environment) }
497      * will apply a reverb effect to the audio playing from this audio node.
498      *
499      * @param reverbEnabled True to enable reverb.
500      */
setReverbEnabled(boolean reverbEnabled)501     public void setReverbEnabled(boolean reverbEnabled) {
502         this.reverbEnabled = reverbEnabled;
503         if (channel >= 0)
504             getRenderer().updateSourceParam(this, AudioParam.ReverbEnabled);
505     }
506 
507     /**
508      * @return Filter for the reverberations of this audio node.
509      *
510      * @see AudioNode#setReverbFilter(com.jme3.audio.Filter)
511      */
getReverbFilter()512     public Filter getReverbFilter() {
513         return reverbFilter;
514     }
515 
516     /**
517      * Set the reverb filter for this audio node.
518      * <br/>
519      * The reverb filter will influence the reverberations
520      * of the audio node playing. This only has an effect if
521      * reverb is enabled.
522      *
523      * @param reverbFilter The reverb filter to set.
524      * @see AudioNode#setDryFilter(com.jme3.audio.Filter)
525      */
setReverbFilter(Filter reverbFilter)526     public void setReverbFilter(Filter reverbFilter) {
527         this.reverbFilter = reverbFilter;
528         if (channel >= 0)
529             getRenderer().updateSourceParam(this, AudioParam.ReverbFilter);
530     }
531 
532     /**
533      * @return Max distance for this audio node.
534      *
535      * @see AudioNode#setMaxDistance(float)
536      */
getMaxDistance()537     public float getMaxDistance() {
538         return maxDistance;
539     }
540 
541     /**
542      * Set the maximum distance for the attenuation of the audio node.
543      * Does nothing if the audio node is not positional.
544      * <br/>
545      * The maximum distance is the distance beyond which the audio
546      * node will no longer be attenuated.  Normal attenuation is logarithmic
547      * from refDistance (it reduces by half when the distance doubles).
548      * Max distance sets where this fall-off stops and the sound will never
549      * get any quieter than at that distance.  If you want a sound to fall-off
550      * very quickly then set ref distance very short and leave this distance
551      * very long.
552      *
553      * @param maxDistance The maximum playing distance.
554      * @throws IllegalArgumentException If maxDistance is negative
555      */
setMaxDistance(float maxDistance)556     public void setMaxDistance(float maxDistance) {
557         if (maxDistance < 0) {
558             throw new IllegalArgumentException("Max distance cannot be negative");
559         }
560 
561         this.maxDistance = maxDistance;
562         if (channel >= 0)
563             getRenderer().updateSourceParam(this, AudioParam.MaxDistance);
564     }
565 
566     /**
567      * @return The reference playing distance for the audio node.
568      *
569      * @see AudioNode#setRefDistance(float)
570      */
getRefDistance()571     public float getRefDistance() {
572         return refDistance;
573     }
574 
575     /**
576      * Set the reference playing distance for the audio node.
577      * Does nothing if the audio node is not positional.
578      * <br/>
579      * The reference playing distance is the distance at which the
580      * audio node will be exactly half of its volume.
581      *
582      * @param refDistance The reference playing distance.
583      * @throws  IllegalArgumentException If refDistance is negative
584      */
setRefDistance(float refDistance)585     public void setRefDistance(float refDistance) {
586         if (refDistance < 0) {
587             throw new IllegalArgumentException("Reference distance cannot be negative");
588         }
589 
590         this.refDistance = refDistance;
591         if (channel >= 0)
592             getRenderer().updateSourceParam(this, AudioParam.RefDistance);
593     }
594 
595     /**
596      * @return True if the audio node is directional
597      *
598      * @see AudioNode#setDirectional(boolean)
599      */
isDirectional()600     public boolean isDirectional() {
601         return directional;
602     }
603 
604     /**
605      * Set the audio node to be directional.
606      * Does nothing if the audio node is not positional.
607      * <br/>
608      * After setting directional, you should call
609      * {@link AudioNode#setDirection(com.jme3.math.Vector3f) }
610      * to set the audio node's direction.
611      *
612      * @param directional If the audio node is directional
613      */
setDirectional(boolean directional)614     public void setDirectional(boolean directional) {
615         this.directional = directional;
616         if (channel >= 0)
617             getRenderer().updateSourceParam(this, AudioParam.IsDirectional);
618     }
619 
620     /**
621      * @return The direction of this audio node.
622      *
623      * @see AudioNode#setDirection(com.jme3.math.Vector3f)
624      */
getDirection()625     public Vector3f getDirection() {
626         return direction;
627     }
628 
629     /**
630      * Set the direction of this audio node.
631      * Does nothing if the audio node is not directional.
632      *
633      * @param direction
634      * @see AudioNode#setDirectional(boolean)
635      */
setDirection(Vector3f direction)636     public void setDirection(Vector3f direction) {
637         this.direction = direction;
638         if (channel >= 0)
639             getRenderer().updateSourceParam(this, AudioParam.Direction);
640     }
641 
642     /**
643      * @return The directional audio node, cone inner angle.
644      *
645      * @see AudioNode#setInnerAngle(float)
646      */
getInnerAngle()647     public float getInnerAngle() {
648         return innerAngle;
649     }
650 
651     /**
652      * Set the directional audio node cone inner angle.
653      * Does nothing if the audio node is not directional.
654      *
655      * @param innerAngle The cone inner angle.
656      */
setInnerAngle(float innerAngle)657     public void setInnerAngle(float innerAngle) {
658         this.innerAngle = innerAngle;
659         if (channel >= 0)
660             getRenderer().updateSourceParam(this, AudioParam.InnerAngle);
661     }
662 
663     /**
664      * @return The directional audio node, cone outer angle.
665      *
666      * @see AudioNode#setOuterAngle(float)
667      */
getOuterAngle()668     public float getOuterAngle() {
669         return outerAngle;
670     }
671 
672     /**
673      * Set the directional audio node cone outer angle.
674      * Does nothing if the audio node is not directional.
675      *
676      * @param outerAngle The cone outer angle.
677      */
setOuterAngle(float outerAngle)678     public void setOuterAngle(float outerAngle) {
679         this.outerAngle = outerAngle;
680         if (channel >= 0)
681             getRenderer().updateSourceParam(this, AudioParam.OuterAngle);
682     }
683 
684     /**
685      * @return True if the audio node is positional.
686      *
687      * @see AudioNode#setPositional(boolean)
688      */
isPositional()689     public boolean isPositional() {
690         return positional;
691     }
692 
693     /**
694      * Set the audio node as positional.
695      * The position, velocity, and distance parameters effect positional
696      * audio nodes. Set to false if the audio node should play in "headspace".
697      *
698      * @param positional True if the audio node should be positional, otherwise
699      * false if it should be headspace.
700      */
setPositional(boolean positional)701     public void setPositional(boolean positional) {
702         this.positional = positional;
703         if (channel >= 0)
704             getRenderer().updateSourceParam(this, AudioParam.IsPositional);
705     }
706 
707     @Override
updateGeometricState()708     public void updateGeometricState(){
709         boolean updatePos = false;
710         if ((refreshFlags & RF_TRANSFORM) != 0){
711             updatePos = true;
712         }
713 
714         super.updateGeometricState();
715 
716         if (updatePos && channel >= 0)
717             getRenderer().updateSourceParam(this, AudioParam.Position);
718     }
719 
720     @Override
clone()721     public AudioNode clone(){
722         AudioNode clone = (AudioNode) super.clone();
723 
724         clone.direction = direction.clone();
725         clone.velocity  = velocity.clone();
726 
727         return clone;
728     }
729 
730     @Override
write(JmeExporter ex)731     public void write(JmeExporter ex) throws IOException {
732         super.write(ex);
733         OutputCapsule oc = ex.getCapsule(this);
734         oc.write(audioKey, "audio_key", null);
735         oc.write(loop, "looping", false);
736         oc.write(volume, "volume", 1);
737         oc.write(pitch, "pitch", 1);
738         oc.write(timeOffset, "time_offset", 0);
739         oc.write(dryFilter, "dry_filter", null);
740 
741         oc.write(velocity, "velocity", null);
742         oc.write(reverbEnabled, "reverb_enabled", false);
743         oc.write(reverbFilter, "reverb_filter", null);
744         oc.write(maxDistance, "max_distance", 20);
745         oc.write(refDistance, "ref_distance", 10);
746 
747         oc.write(directional, "directional", false);
748         oc.write(direction, "direction", null);
749         oc.write(innerAngle, "inner_angle", 360);
750         oc.write(outerAngle, "outer_angle", 360);
751 
752         oc.write(positional, "positional", false);
753     }
754 
755     @Override
read(JmeImporter im)756     public void read(JmeImporter im) throws IOException {
757         super.read(im);
758         InputCapsule ic = im.getCapsule(this);
759 
760         // NOTE: In previous versions of jME3, audioKey was actually
761         // written with the name "key". This has been changed
762         // to "audio_key" in case Spatial's key will be written as "key".
763         if (ic.getSavableVersion(AudioNode.class) == 0){
764             audioKey = (AudioKey) ic.readSavable("key", null);
765         }else{
766             audioKey = (AudioKey) ic.readSavable("audio_key", null);
767         }
768 
769         loop = ic.readBoolean("looping", false);
770         volume = ic.readFloat("volume", 1);
771         pitch = ic.readFloat("pitch", 1);
772         timeOffset = ic.readFloat("time_offset", 0);
773         dryFilter = (Filter) ic.readSavable("dry_filter", null);
774 
775         velocity = (Vector3f) ic.readSavable("velocity", null);
776         reverbEnabled = ic.readBoolean("reverb_enabled", false);
777         reverbFilter = (Filter) ic.readSavable("reverb_filter", null);
778         maxDistance = ic.readFloat("max_distance", 20);
779         refDistance = ic.readFloat("ref_distance", 10);
780 
781         directional = ic.readBoolean("directional", false);
782         direction = (Vector3f) ic.readSavable("direction", null);
783         innerAngle = ic.readFloat("inner_angle", 360);
784         outerAngle = ic.readFloat("outer_angle", 360);
785 
786         positional = ic.readBoolean("positional", false);
787 
788         if (audioKey != null) {
789             try {
790                 data = im.getAssetManager().loadAudio(audioKey);
791             } catch (AssetNotFoundException ex){
792                 Logger.getLogger(AudioNode.class.getName()).log(Level.FINE, "Cannot locate {0} for audio node {1}", new Object[]{audioKey, key});
793                 data = PlaceholderAssets.getPlaceholderAudio();
794             }
795         }
796     }
797 
798     @Override
toString()799     public String toString() {
800         String ret = getClass().getSimpleName()
801                 + "[status=" + status;
802         if (volume != 1f) {
803             ret += ", vol=" + volume;
804         }
805         if (pitch != 1f) {
806             ret += ", pitch=" + pitch;
807         }
808         return ret + "]";
809     }
810 }
811