| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754 |
- /*
- * Copyright (c) 2009-2010 jMonkeyEngine
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- package com.jme3.audio;
- import com.jme3.asset.AssetManager;
- import com.jme3.asset.AssetNotFoundException;
- import com.jme3.export.InputCapsule;
- import com.jme3.export.JmeExporter;
- import com.jme3.export.JmeImporter;
- import com.jme3.export.OutputCapsule;
- import com.jme3.math.Vector3f;
- import com.jme3.scene.Node;
- import com.jme3.util.PlaceholderAssets;
- import java.io.IOException;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- /**
- * An <code>AudioNode</code> is used in jME3 for playing audio files.
- * <br/>
- * 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}.
- * <br/>
- * 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 {
- protected boolean loop = false;
- protected float volume = 1;
- protected float pitch = 1;
- protected float timeOffset = 0;
- protected Filter dryFilter;
- protected AudioKey audioKey;
- protected transient AudioData data = null;
- protected transient volatile Status status = Status.Stopped;
- protected transient volatile int channel = -1;
- protected Vector3f velocity = new Vector3f();
- protected boolean reverbEnabled = true;
- protected float maxDistance = 200; // 200 meters
- protected float refDistance = 10; // 10 meters
- protected Filter reverbFilter;
- private boolean directional = false;
- protected Vector3f direction = new Vector3f(0, 0, 1);
- protected float innerAngle = 360;
- protected float outerAngle = 360;
- protected boolean positional = true;
- /**
- * <code>Status</code> 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 <code>AudioNode</code> without any audio data set.
- */
- public AudioNode() {
- }
- /**
- * Creates a new <code>AudioNode</code> with the given data and key.
- *
- * @param audioData The audio data contains the audio track to play.
- * @param audioKey The audio key that was used to load the AudioData
- */
- public AudioNode(AudioData audioData, AudioKey audioKey) {
- setAudioData(audioData, audioKey);
- }
- /**
- * Creates a new <code>AudioNode</code> with the given audio file.
- *
- * @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.
- * @param streamCache If stream is also true, then this specifies if
- * the stream cache is used. When enabled, the audio stream will
- * be read entirely but not decoded, allowing features such as
- * seeking, looping and determining duration.
- */
- public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) {
- this.audioKey = new AudioKey(name, stream, streamCache);
- this.data = (AudioData) assetManager.loadAsset(audioKey);
- }
- /**
- * Creates a new <code>AudioNode</code> with the given audio file.
- *
- * @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(AssetManager assetManager, String name, boolean stream) {
- this(assetManager, name, stream, false);
- }
- /**
- * Creates a new <code>AudioNode</code> 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
- *
- * @deprecated AudioRenderer parameter is ignored.
- */
- public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) {
- this(assetManager, name, false);
- }
-
- /**
- * Creates a new <code>AudioNode</code> with the given audio file.
- *
- * @param assetManager The asset manager to use to load the audio file
- * @param name The filename of the audio file
- */
- public AudioNode(AssetManager assetManager, String name) {
- this(assetManager, name, false);
- }
-
- protected AudioRenderer getRenderer() {
- AudioRenderer result = AudioContext.getAudioRenderer();
- if( result == null )
- throw new IllegalStateException( "No audio renderer available, make sure call is being performed on render thread." );
- return result;
- }
-
- /**
- * Start playing the audio.
- */
- public void play(){
- getRenderer().playSource(this);
- }
- /**
- * Start playing an instance of this audio. This method can be used
- * to play the same <code>AudioNode</code> multiple times. Note
- * that changes to the parameters of this AudioNode will not effect the
- * instances already playing.
- */
- public void playInstance(){
- getRenderer().playSourceInstance(this);
- }
-
- /**
- * Stop playing the audio that was started with {@link AudioNode#play() }.
- */
- public void stop(){
- getRenderer().stopSource(this);
- }
-
- /**
- * Pause the audio that was started with {@link AudioNode#play() }.
- */
- public void pause(){
- getRenderer().pauseSource(this);
- }
-
- /**
- * Do not use.
- */
- public final void setChannel(int channel) {
- if (status != Status.Stopped) {
- throw new IllegalStateException("Can only set source id when stopped");
- }
- 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) {
- this.dryFilter = dryFilter;
- if (channel >= 0)
- getRenderer().updateSourceParam(this, AudioParam.DryFilter);
- }
- /**
- * 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 audioKey The audio key that was used to load the AudioData
- */
- public void setAudioData(AudioData audioData, AudioKey audioKey) {
- if (data != null) {
- throw new IllegalStateException("Cannot change data once its set");
- }
- data = audioData;
- this.audioKey = audioKey;
- }
- /**
- * @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 final 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)
- getRenderer().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");
- }
- this.pitch = pitch;
- if (channel >= 0)
- getRenderer().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");
- }
- this.volume = volume;
- if (channel >= 0)
- getRenderer().updateSourceParam(this, AudioParam.Volume);
- }
- /**
- * @return the time offset in the sound sample when to start playing.
- */
- public float getTimeOffset() {
- return timeOffset;
- }
- /**
- * Set the time offset in the sound sample when to 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");
- }
- this.timeOffset = timeOffset;
- if (data instanceof AudioStream) {
- System.out.println("request setTime");
- ((AudioStream) data).setTime(timeOffset);
- }else if(status == Status.Playing){
- stop();
- play();
- }
- }
- /**
- * @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)
- getRenderer().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.
- * <br/>
- * 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) {
- getRenderer().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.
- * <br/>
- * 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) {
- this.reverbFilter = reverbFilter;
- if (channel >= 0)
- getRenderer().updateSourceParam(this, AudioParam.ReverbFilter);
- }
- /**
- * @return Max distance for this audio node.
- *
- * @see AudioNode#setMaxDistance(float)
- */
- public float getMaxDistance() {
- return maxDistance;
- }
- /**
- * Set the maximum distance for the attenuation of the audio node.
- * Does nothing if the audio node is not positional.
- * <br/>
- * The maximum distance is the distance beyond which the audio
- * node will no longer be attenuated. Normal attenuation is logarithmic
- * from refDistance (it reduces by half when the distance doubles).
- * Max distance sets where this fall-off stops and the sound will never
- * get any quieter than at that distance. If you want a sound to fall-off
- * very quickly then set ref distance very short and leave this distance
- * very long.
- *
- * @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");
- }
- this.maxDistance = maxDistance;
- if (channel >= 0)
- getRenderer().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.
- * <br/>
- * 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");
- }
- this.refDistance = refDistance;
- if (channel >= 0)
- getRenderer().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.
- * <br/>
- * 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)
- getRenderer().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)
- getRenderer().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)
- getRenderer().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)
- getRenderer().updateSourceParam(this, AudioParam.OuterAngle);
- }
- /**
- * @return True if the audio node is positional.
- *
- * @see AudioNode#setPositional(boolean)
- */
- public boolean isPositional() {
- return positional;
- }
- /**
- * 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) {
- getRenderer().updateSourceParam(this, AudioParam.IsPositional);
- }
- }
- @Override
- public void updateGeometricState(){
- boolean updatePos = false;
- if ((refreshFlags & RF_TRANSFORM) != 0){
- updatePos = true;
- }
-
- super.updateGeometricState();
- if (updatePos && channel >= 0)
- getRenderer().updateSourceParam(this, AudioParam.Position);
- }
- @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 {
- super.write(ex);
- OutputCapsule oc = ex.getCapsule(this);
- oc.write(audioKey, "audio_key", null);
- oc.write(loop, "looping", false);
- oc.write(volume, "volume", 1);
- oc.write(pitch, "pitch", 1);
- oc.write(timeOffset, "time_offset", 0);
- oc.write(dryFilter, "dry_filter", null);
- oc.write(velocity, "velocity", null);
- oc.write(reverbEnabled, "reverb_enabled", false);
- oc.write(reverbFilter, "reverb_filter", null);
- oc.write(maxDistance, "max_distance", 20);
- oc.write(refDistance, "ref_distance", 10);
- oc.write(directional, "directional", false);
- oc.write(direction, "direction", null);
- oc.write(innerAngle, "inner_angle", 360);
- oc.write(outerAngle, "outer_angle", 360);
-
- oc.write(positional, "positional", false);
- }
- @Override
- public void read(JmeImporter im) throws IOException {
- super.read(im);
- InputCapsule ic = im.getCapsule(this);
-
- // NOTE: In previous versions of jME3, audioKey was actually
- // written with the name "key". This has been changed
- // to "audio_key" in case Spatial's key will be written as "key".
- if (ic.getSavableVersion(AudioNode.class) == 0){
- audioKey = (AudioKey) ic.readSavable("key", null);
- }else{
- audioKey = (AudioKey) ic.readSavable("audio_key", null);
- }
-
- loop = ic.readBoolean("looping", false);
- volume = ic.readFloat("volume", 1);
- pitch = ic.readFloat("pitch", 1);
- timeOffset = ic.readFloat("time_offset", 0);
- dryFilter = (Filter) ic.readSavable("dry_filter", null);
- velocity = (Vector3f) ic.readSavable("velocity", null);
- reverbEnabled = ic.readBoolean("reverb_enabled", false);
- reverbFilter = (Filter) ic.readSavable("reverb_filter", null);
- maxDistance = ic.readFloat("max_distance", 20);
- refDistance = ic.readFloat("ref_distance", 10);
- directional = ic.readBoolean("directional", false);
- direction = (Vector3f) ic.readSavable("direction", null);
- innerAngle = ic.readFloat("inner_angle", 360);
- outerAngle = ic.readFloat("outer_angle", 360);
-
- positional = ic.readBoolean("positional", false);
-
- if (audioKey != null) {
- try {
- data = im.getAssetManager().loadAsset(audioKey);
- } catch (AssetNotFoundException ex){
- Logger.getLogger(AudioNode.class.getName()).log(Level.FINE, "Cannot locate {0} for audio node {1}", new Object[]{audioKey, key});
- data = PlaceholderAssets.getPlaceholderAudio();
- }
- }
- }
- @Override
- public String toString() {
- String ret = getClass().getSimpleName()
- + "[status=" + status;
- if (volume != 1f) {
- ret += ", vol=" + volume;
- }
- if (pitch != 1f) {
- ret += ", pitch=" + pitch;
- }
- return ret + "]";
- }
- }
|