Home | History | Annotate | Download | only in audio
      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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    231     public AudioNode(AssetManager assetManager, String name) {
    232         this(assetManager, name, false);
    233     }
    234 
    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      */
    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      */
    255     public void playInstance(){
    256         getRenderer().playSourceInstance(this);
    257     }
    258 
    259     /**
    260      * Stop playing the audio that was started with {@link AudioNode#play() }.
    261      */
    262     public void stop(){
    263         getRenderer().stopSource(this);
    264     }
    265 
    266     /**
    267      * Pause the audio that was started with {@link AudioNode#play() }.
    268      */
    269     public void pause(){
    270         getRenderer().pauseSource(this);
    271     }
    272 
    273     /**
    274      * Do not use.
    275      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    348     public Status getStatus() {
    349         return status;
    350     }
    351 
    352     /**
    353      * Do not use.
    354      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    701     public void setPositional(boolean positional) {
    702         this.positional = positional;
    703         if (channel >= 0)
    704             getRenderer().updateSourceParam(this, AudioParam.IsPositional);
    705     }
    706 
    707     @Override
    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
    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
    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
    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
    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