diff --git a/engine/src/core/com/jme3/audio/AudioNode.java b/engine/src/core/com/jme3/audio/AudioNode.java index 3b016c696..1074f6765 100644 --- a/engine/src/core/com/jme3/audio/AudioNode.java +++ b/engine/src/core/com/jme3/audio/AudioNode.java @@ -42,8 +42,20 @@ import com.jme3.scene.Node; import java.io.IOException; /** - * + * An AudioNode is used in jME3 for playing audio files. + *
+ * First, an {@link AudioNode} is loaded from file, and then assigned + * to an audio node for playback. Once the audio node is attached to the + * scene, its location will influence the position it is playing from relative + * to the {@link Listener}. + *
+ * An audio node can also play in "headspace", meaning its location + * or velocity does not influence how it is played. + * The "positional" property of an AudioNode can be set via + * {@link AudioNode#setPositional(boolean) }. + * * @author normenhansen + * @author Kirill Vainer */ public class AudioNode extends Node { @@ -67,35 +79,108 @@ public class AudioNode extends Node { protected Vector3f direction = new Vector3f(0, 0, 1); protected float innerAngle = 360; protected float outerAngle = 360; - private boolean positional = true; + protected boolean positional = true; + /** + * Status indicates the current status of the audio node. + */ public enum Status { + /** + * The audio node is currently playing. This will be set if + * {@link AudioNode#play() } is called. + */ Playing, + + /** + * The audio node is currently paused. + */ Paused, + + /** + * The audio node is currently stopped. + * This will be set if {@link AudioNode#stop() } is called + * or the audio has reached the end of the file. + */ Stopped, } + /** + * Creates a new AudioNode without any audio data set. + * + * @param audioRenderer The audio renderer to use for playing. Cannot be null. + */ public AudioNode(AudioRenderer audioRenderer) { + if (audioRenderer == null) + throw new IllegalArgumentException("audioRenderer cannot be null"); + this.renderer = audioRenderer; } - public AudioNode(AudioRenderer audioRenderer, AudioData ad, AudioKey key) { + /** + * Creates a new AudioNode with the given data and key. + * + * @param audioRenderer The audio renderer to use for playing. Cannot be null. + * @param audioData The audio data contains the audio track to play. + * @param key The audio key that was used to load the AudioData + */ + public AudioNode(AudioRenderer audioRenderer, AudioData audioData, AudioKey key) { this(audioRenderer); - setAudioData(ad, key); - } - + setAudioData(audioData, key); + } + + /** + * Creates a new AudioNode with the given audio file. + * + * @param audioRenderer The audio renderer to use for playing. Cannot be null. + * @param assetManager The asset manager to use to load the audio file + * @param name The filename of the audio file + * @param stream If true, the audio will be streamed gradually from disk, + * otherwise, it will be buffered. + */ public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name, boolean stream) { this(audioRenderer); this.key = new AudioKey(name, stream); this.data = (AudioData) assetManager.loadAsset(key); } + /** + * Creates a new AudioNode with the given audio file. + * + * @param audioRenderer The audio renderer to use for playing. Cannot be null. + * @param assetManager The asset manager to use to load the audio file + * @param name The filename of the audio file + */ public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) { this(audioRenderer, assetManager, name, false); } - - - + + /** + * Start playing the audio. + */ + public void play(){ + renderer.playSource(this); + } + + /** + * Start playing an instance of this audio. This method can be used + * to play the same AudioNode multiple times. Note + * that changes to the parameters of this AudioNode will not effect the + * instances already playing. + */ + public void playInstance(){ + renderer.playSourceInstance(this); + } + + /** + * Stop playing the audio that was started with {@link AudioNode#play() }. + */ + public void stop(){ + renderer.stopSource(this); + } + + /** + * Do not use. + */ public void setChannel(int channel) { if (status != Status.Stopped) { throw new IllegalStateException("Can only set source id when stopped"); @@ -104,59 +189,117 @@ public class AudioNode extends Node { this.channel = channel; } + /** + * Do not use. + */ public int getChannel() { return channel; } + /** + * @return The {#link Filter dry filter} that is set. + * @see AudioNode#setDryFilter(com.jme3.audio.Filter) + */ public Filter getDryFilter() { return dryFilter; } + /** + * Set the dry filter to use for this audio node. + * + * When {@link AudioNode#setReverbEnabled(boolean) reverb} is used, + * the dry filter will only influence the "dry" portion of the audio, + * e.g. not the reverberated parts of the AudioNode playing. + * + * See the relevent documentation for the {@link Filter} to determine + * the effect. + * + * @param dryFilter The filter to set, or null to disable dry filter. + */ public void setDryFilter(Filter dryFilter) { - if (this.dryFilter != null) { - throw new IllegalStateException("Filter already set"); - } - this.dryFilter = dryFilter; if (channel >= 0) renderer.updateSourceParam(this, AudioParam.DryFilter); } - public void setAudioData(AudioData ad, AudioKey key) { + /** + * Set the audio data to use for the audio. Note that this method + * can only be called once, if for example the audio node was initialized + * without an {@link AudioData}. + * + * @param audioData The audio data contains the audio track to play. + * @param key The audio key that was used to load the AudioData + */ + public void setAudioData(AudioData audioData, AudioKey key) { if (data != null) { throw new IllegalStateException("Cannot change data once its set"); } - data = ad; + data = audioData; this.key = key; } + /** + * @return The {@link AudioData} set previously with + * {@link AudioNode#setAudioData(com.jme3.audio.AudioData, com.jme3.audio.AudioKey) } + * or any of the constructors that initialize the audio data. + */ public AudioData getAudioData() { return data; } + /** + * @return The {@link Status} of the audio node. + * The status will be changed when either the {@link AudioNode#play() } + * or {@link AudioNode#stop() } methods are called. + */ public Status getStatus() { return status; } + /** + * Do not use. + */ public void setStatus(Status status) { this.status = status; } + /** + * @return True if the audio will keep looping after it is done playing, + * otherwise, false. + * @see AudioNode#setLooping(boolean) + */ public boolean isLooping() { return loop; } + /** + * Set the looping mode for the audio node. The default is false. + * + * @param loop True if the audio should keep looping after it is done playing. + */ public void setLooping(boolean loop) { this.loop = loop; if (channel >= 0) renderer.updateSourceParam(this, AudioParam.Looping); } + /** + * @return The pitch of the audio, also the speed of playback. + * + * @see AudioNode#setPitch(float) + */ public float getPitch() { return pitch; } + /** + * Set the pitch of the audio, also the speed of playback. + * The value must be between 0.5 and 2.0. + * + * @param pitch The pitch to set. + * @throws IllegalArgumentException If pitch is not between 0.5 and 2.0. + */ public void setPitch(float pitch) { if (pitch < 0.5f || pitch > 2.0f) { throw new IllegalArgumentException("Pitch must be between 0.5 and 2.0"); @@ -167,10 +310,23 @@ public class AudioNode extends Node { renderer.updateSourceParam(this, AudioParam.Pitch); } + /** + * @return The volume of this audio node. + * + * @see AudioNode#setVolume(float) + */ public float getVolume() { return volume; } + /** + * Set the volume of this audio node. + * + * The volume is specified as gain. 1.0 is the default. + * + * @param volume The volume to set. + * @throws IllegalArgumentException If volume is negative + */ public void setVolume(float volume) { if (volume < 0f) { throw new IllegalArgumentException("Volume cannot be negative"); @@ -181,10 +337,19 @@ public class AudioNode extends Node { renderer.updateSourceParam(this, AudioParam.Volume); } + /** + * @return The time offset in seconds when the sound will start playing. + */ public float getTimeOffset() { return timeOffset; } + /** + * Set the time offset in seconds when the sound will start playing. + * + * @param timeOffset The time offset + * @throws IllegalArgumentException If timeOffset is negative + */ public void setTimeOffset(float timeOffset) { if (timeOffset < 0f) { throw new IllegalArgumentException("Time offset cannot be negative"); @@ -193,30 +358,72 @@ public class AudioNode extends Node { this.timeOffset = timeOffset; } + /** + * @return The velocity of the audio node. + * + * @see AudioNode#setVelocity(com.jme3.math.Vector3f) + */ public Vector3f getVelocity() { return velocity; } + /** + * Set the velocity of the audio node. The velocity is expected + * to be in meters. Does nothing if the audio node is not positional. + * + * @param velocity The velocity to set. + * @see AudioNode#setPositional(boolean) + */ public void setVelocity(Vector3f velocity) { this.velocity.set(velocity); if (channel >= 0) renderer.updateSourceParam(this, AudioParam.Velocity); } + /** + * @return True if reverb is enabled, otherwise false. + * + * @see AudioNode#setReverbEnabled(boolean) + */ public boolean isReverbEnabled() { return reverbEnabled; } + /** + * Set to true to enable reverberation effects for this audio node. + * Does nothing if the audio node is not positional. + *
+ * When enabled, the audio environment set with + * {@link AudioRenderer#setEnvironment(com.jme3.audio.Environment) } + * will apply a reverb effect to the audio playing from this audio node. + * + * @param reverbEnabled True to enable reverb. + */ public void setReverbEnabled(boolean reverbEnabled) { this.reverbEnabled = reverbEnabled; if (channel >= 0) renderer.updateSourceParam(this, AudioParam.ReverbEnabled); } + /** + * @return Filter for the reverberations of this audio node. + * + * @see AudioNode#setReverbFilter(com.jme3.audio.Filter) + */ public Filter getReverbFilter() { return reverbFilter; } + /** + * Set the reverb filter for this audio node. + *
+ * The reverb filter will influence the reverberations + * of the audio node playing. This only has an effect if + * reverb is enabled. + * + * @param reverbFilter The reverb filter to set. + * @see AudioNode#setDryFilter(com.jme3.audio.Filter) + */ public void setReverbFilter(Filter reverbFilter) { if (this.reverbFilter != null) { throw new IllegalStateException("Filter already set"); @@ -227,10 +434,25 @@ public class AudioNode extends Node { renderer.updateSourceParam(this, AudioParam.ReverbFilter); } + /** + * @return Max distance for this audio node. + * + * @see AudioNode#setMaxDistance(float) + */ public float getMaxDistance() { return maxDistance; } + /** + * Set the maximum playing distance for the audio node. + * Does nothing if the audio node is not positional. + *
+ * The maximum distance is the distance beyond which the audio + * node will no longer be heard. + * + * @param maxDistance The maximum playing distance. + * @throws IllegalArgumentException If maxDistance is negative + */ public void setMaxDistance(float maxDistance) { if (maxDistance < 0) { throw new IllegalArgumentException("Max distance cannot be negative"); @@ -241,10 +463,25 @@ public class AudioNode extends Node { renderer.updateSourceParam(this, AudioParam.MaxDistance); } + /** + * @return The reference playing distance for the audio node. + * + * @see AudioNode#setRefDistance(float) + */ public float getRefDistance() { return refDistance; } + /** + * Set the reference playing distance for the audio node. + * Does nothing if the audio node is not positional. + *
+ * The reference playing distance is the distance at which the + * audio node will be exactly half of its volume. + * + * @param refDistance The reference playing distance. + * @throws IllegalArgumentException If refDistance is negative + */ public void setRefDistance(float refDistance) { if (refDistance < 0) { throw new IllegalArgumentException("Reference distance cannot be negative"); @@ -255,52 +492,114 @@ public class AudioNode extends Node { renderer.updateSourceParam(this, AudioParam.RefDistance); } + /** + * @return True if the audio node is directional + * + * @see AudioNode#setDirectional(boolean) + */ public boolean isDirectional() { return directional; } + /** + * Set the audio node to be directional. + * Does nothing if the audio node is not positional. + *
+ * After setting directional, you should call + * {@link AudioNode#setDirection(com.jme3.math.Vector3f) } + * to set the audio node's direction. + * + * @param directional If the audio node is directional + */ public void setDirectional(boolean directional) { this.directional = directional; if (channel >= 0) renderer.updateSourceParam(this, AudioParam.IsDirectional); } + /** + * @return The direction of this audio node. + * + * @see AudioNode#setDirection(com.jme3.math.Vector3f) + */ public Vector3f getDirection() { return direction; } + /** + * Set the direction of this audio node. + * Does nothing if the audio node is not directional. + * + * @param direction + * @see AudioNode#setDirectional(boolean) + */ public void setDirection(Vector3f direction) { this.direction = direction; if (channel >= 0) renderer.updateSourceParam(this, AudioParam.Direction); } + /** + * @return The directional audio node, cone inner angle. + * + * @see AudioNode#setInnerAngle(float) + */ public float getInnerAngle() { return innerAngle; } + /** + * Set the directional audio node cone inner angle. + * Does nothing if the audio node is not directional. + * + * @param innerAngle The cone inner angle. + */ public void setInnerAngle(float innerAngle) { this.innerAngle = innerAngle; if (channel >= 0) renderer.updateSourceParam(this, AudioParam.InnerAngle); } + /** + * @return The directional audio node, cone outer angle. + * + * @see AudioNode#setOuterAngle(float) + */ public float getOuterAngle() { return outerAngle; } + /** + * Set the directional audio node cone outer angle. + * Does nothing if the audio node is not directional. + * + * @param outerAngle The cone outer angle. + */ public void setOuterAngle(float outerAngle) { this.outerAngle = outerAngle; if (channel >= 0) renderer.updateSourceParam(this, AudioParam.OuterAngle); } + /** + * @return True if the audio node is positional. + * + * @see AudioNode#setPositional(boolean) + */ public boolean isPositional() { return positional; } - public void setPositional(boolean inHeadspace) { - this.positional = inHeadspace; + /** + * Set the audio node as positional. + * The position, velocity, and distance parameters effect positional + * audio nodes. Set to false if the audio node should play in "headspace". + * + * @param positional True if the audio node should be positional, otherwise + * false if it should be headspace. + */ + public void setPositional(boolean positional) { + this.positional = positional; if (channel >= 0) renderer.updateSourceParam(this, AudioParam.IsPositional); } @@ -318,14 +617,15 @@ public class AudioNode extends Node { renderer.updateSourceParam(this, AudioParam.Position); } -// @Override -// public AudioNode clone(){ -// try{ -// return (AudioNode) super.clone(); -// }catch (CloneNotSupportedException ex){ -// return null; -// } -// } + @Override + public AudioNode clone(){ + AudioNode clone = (AudioNode) super.clone(); + + clone.direction = direction.clone(); + clone.velocity = velocity.clone(); + + return clone; + } @Override public void write(JmeExporter ex) throws IOException { @@ -348,6 +648,8 @@ public class AudioNode extends Node { oc.write(direction, "direction", null); oc.write(innerAngle, "inner_angle", 360); oc.write(outerAngle, "outer_angle", 360); + + oc.write(positional, "positional", false); } @Override @@ -371,6 +673,9 @@ public class AudioNode extends Node { direction = (Vector3f) ic.readSavable("direction", null); innerAngle = ic.readFloat("inner_angle", 360); outerAngle = ic.readFloat("outer_angle", 360); + + positional = ic.readBoolean("positional", false); + data = im.getAssetManager().loadAudio(key); }