All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jme3.audio.AudioNode Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2009-2021 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.audio.AudioData.DataType;
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 com.jme3.util.clone.Cloner;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * An AudioNode is a scene Node which can play audio assets.
 *
 * An AudioNode is either positional or ambient, with positional being the
 * default. Once a positional node is attached to the scene, its location and
 * velocity relative to the {@link Listener} affect how it sounds when played.
 * Positional nodes can only play monaural (single-channel) assets, not stereo
 * ones.
 *
 * An ambient AudioNode plays in "headspace", meaning that the node's location
 * and velocity do not affect how it sounds when played. Ambient audio nodes can
 * play stereo assets.
 *
 * The "positional" property of an AudioNode can be set via
 * {@link AudioNode#setPositional(boolean)}.
 *
 * @author normenhansen
 * @author Kirill Vainer
 */
public class AudioNode extends Node implements AudioSource {

    // Version #1 : AudioKey is now stored into "audio_key" instead of "key"
    public static final int SAVABLE_VERSION = 1;
    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 AudioSource.Status status = AudioSource.Status.Stopped;
    protected transient volatile int channel = -1;
    protected Vector3f previousWorldTranslation = Vector3f.NAN.clone();
    protected Vector3f velocity = new Vector3f();
    protected boolean reverbEnabled = false;
    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;
    protected boolean velocityFromTranslation = false;
    protected float lastTpf;

    /**
     * Status indicates the current status of the audio node.
     * @deprecated - use AudioSource.Status instead
     */
    @Deprecated
    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.
     */
    public AudioNode() {
    }

    /**
     * Creates a new AudioNode 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 AudioNode 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 type The type. If {@link com.jme3.audio.AudioData.DataType}.Stream,
     *     the audio will be streamed gradually from disk,
     *     otherwise it will be buffered ({@link com.jme3.audio.AudioData.DataType}.Buffer)
     */
    public AudioNode(AssetManager assetManager, String name, DataType type) {
        this(assetManager, name, type == DataType.Stream, true);
    }

    /**
     * Creates a new AudioNode 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.
     *
     * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String,
     *     com.jme3.audio.AudioData.DataType)} instead
     */
    @Deprecated
    public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) {
        this.audioKey = new AudioKey(name, stream, streamCache);
        this.data = assetManager.loadAsset(audioKey);
    }

    /**
     * Creates a new AudioNode 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.
     *
     * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String,
     *     com.jme3.audio.AudioData.DataType)} instead
     */
    @Deprecated
    public AudioNode(AssetManager assetManager, String name, boolean stream) {
        this(assetManager, name, stream, true); // Always streamCached
    }

    /**
     * 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
     *
     * @deprecated AudioRenderer parameter is ignored.
     */
    @Deprecated
    public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) {
        this(assetManager, name, DataType.Buffer);
    }

    /**
     * Creates a new AudioNode 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
     * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String,
     *     com.jme3.audio.AudioData.DataType) } instead
     */
    @Deprecated
    public AudioNode(AssetManager assetManager, String name) {
        this(assetManager, name, DataType.Buffer);
    }

    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() {
        if (positional && data.getChannels() > 1) {
            throw new IllegalStateException("Only mono audio is supported for positional audio nodes");
        }
        getRenderer().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 affect the
     * instances already playing.
     */
    public void playInstance() {
        if (positional && data.getChannels() > 1) {
            throw new IllegalStateException("Only mono audio is supported for positional audio nodes");
        }
        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.
     */
    @Override
    public final void setChannel(int channel) {
        if (status != AudioSource.Status.Stopped) {
            throw new IllegalStateException("Can only set source id when stopped");
        }

        this.channel = channel;
    }

    /**
     * Do not use.
     */
    @Override
    public int getChannel() {
        return channel;
    }

    /**
     * @return The {#link Filter dry filter} that is set.
     * @see AudioNode#setDryFilter(com.jme3.audio.Filter)
     */
    @Override
    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 relevant 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 it's 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.
     */
    @Override
    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.
     */
    @Override
    public AudioSource.Status getStatus() {
        return status;
    }

    /**
     * Do not use.
     *
     * @param status the desired status
     */
    @Override
    public final void setStatus(AudioSource.Status status) {
        this.status = status;
    }

    /**
     * Get the Type of the underlying AudioData to see if it's streamed or buffered.
     * This is a shortcut to getAudioData().getType()
     * Warning: Can return null!
     * @return The {@link com.jme3.audio.AudioData.DataType} of the audio node.
     */
    public DataType getType() {
        if (data == null)
            return null;
        else
            return data.getDataType();
    }

    /**
     * @return True if the audio will keep looping after it is done playing,
     * otherwise, false.
     * @see AudioNode#setLooping(boolean)
     */
    @Override
    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)
     */
    @Override
    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)
     */
    @Override
    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.
     */
    @Override
    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) {
            ((AudioStream) data).setTime(timeOffset);
        } else if (status == AudioSource.Status.Playing) {
            stop();
            play();
        }
    }

    @Override
    public float getPlaybackTime() {
        if (channel >= 0)
            return getRenderer().getSourcePlaybackTime(this);
        else
            return 0;
    }

    @Override
    public Vector3f getPosition() {
        return getWorldTranslation();
    }

    /**
     * @return The velocity of the audio node.
     *
     * @see AudioNode#setVelocity(com.jme3.math.Vector3f)
     */
    @Override
    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)
     */
    @Override
    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) { getRenderer().updateSourceParam(this, AudioParam.ReverbEnabled); } } /** * @return Filter for the reverberations of this audio node. * * @see AudioNode#setReverbFilter(com.jme3.audio.Filter) */ @Override 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) { this.reverbFilter = reverbFilter; if (channel >= 0) getRenderer().updateSourceParam(this, AudioParam.ReverbFilter); } /** * @return Maximum distance for this audio node. * * @see AudioNode#setMaxDistance(float) */ @Override 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. *
* 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) */ @Override 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"); } this.refDistance = refDistance; if (channel >= 0) getRenderer().updateSourceParam(this, AudioParam.RefDistance); } /** * @return True if the audio node is directional * * @see AudioNode#setDirectional(boolean) */ @Override 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) getRenderer().updateSourceParam(this, AudioParam.IsDirectional); } /** * @return The direction of this audio node. * * @see AudioNode#setDirection(com.jme3.math.Vector3f) */ @Override public Vector3f getDirection() { return direction; } /** * Set the direction of this audio node. * Does nothing if the audio node is not directional. * * @param direction a direction vector (alias created) * @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) */ @Override 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) */ @Override 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) */ @Override public boolean isPositional() { return positional; } /** * Set the audio node as positional. * The position, velocity, and distance parameters affect 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); } } public boolean isVelocityFromTranslation() { return velocityFromTranslation; } public void setVelocityFromTranslation(boolean velocityFromTranslation) { this.velocityFromTranslation = velocityFromTranslation; } @Override public void updateLogicalState(float tpf) { super.updateLogicalState(tpf); lastTpf = tpf; } @Override public void updateGeometricState() { super.updateGeometricState(); if (channel < 0) return; Vector3f currentWorldTranslation = worldTransform.getTranslation(); if (!previousWorldTranslation.equals(currentWorldTranslation)) { getRenderer().updateSourceParam(this, AudioParam.Position); if (velocityFromTranslation && !Float.isNaN(previousWorldTranslation.x)) { velocity.set(currentWorldTranslation) .subtractLocal(previousWorldTranslation).multLocal(1f / lastTpf); getRenderer().updateSourceParam(this, AudioParam.Velocity); } previousWorldTranslation.set(currentWorldTranslation); } } @Override public AudioNode clone() { AudioNode clone = (AudioNode) super.clone(); return clone; } /** * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); this.direction = cloner.clone(direction); this.velocity = velocityFromTranslation ? new Vector3f() : cloner.clone(velocity); this.previousWorldTranslation = Vector3f.NAN.clone(); // Change in behavior: the filters were not cloned before meaning // that two cloned audio nodes would share the same filter instance. // While settings will only be applied when the filter is actually // set, I think it's probably surprising to callers if the values of // a filter change from one AudioNode when a different AudioNode's // filter attributes are updated. // Plus if they disable and re-enable the thing using the filter then // the settings get reapplied, and it might be surprising to have them // suddenly be strange. // ...so I'll clone them. -pspeed this.dryFilter = cloner.clone(dryFilter); this.reverbFilter = cloner.clone(reverbFilter); } @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); oc.write(velocityFromTranslation, "velocity_from_translation", 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); velocityFromTranslation = ic.readBoolean("velocity_from_translation", 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 + "]"; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy