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

javax.media.j3d.SoundRetained Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 1996-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 *
 */

package javax.media.j3d;

import java.util.ArrayList;



/**
 * SoundRetained is an abstract class that contains instance varables common
 * to all retained sounds.
 */

abstract class SoundRetained extends LeafRetained
{

   /**
    * Null Sound identifier denotes sound is not created or initialized
    */
    static final int NULL_SOUND = -1;

    /**
     *  sound data associated with sound source
     */
    MediaContainer  soundData = null;

    /**
     *  Overall Scale Factor applied to sound.
     */
    float     initialGain = 1.0f;  // Valid values are >= 0.0.

    /**
     *  Number of times sound is looped/repeated during play
     */
    int       loopCount = 0;  //  Range from 0 to POSITIVE_INFINITY(-1)

    /**
     *  Switch for turning sound on or off while the sound is "active"
     */
    boolean   enable = false;

    /**
     * Type of release when sound is disabled.
     *     If true, sound plays thru to end of sample before disabled
     *     Otherwise, sound is disabled immediately.
     */
    boolean   release = false;

    /**
     * Flag denoting if sound silently continues playing when it's deactivated.
     */
    boolean   continuous = false;

    /**
     * Flag denoting if sound is explicitly muted, so that if begins playing
     * it will be played silently.
     */
    boolean   mute = false;

    /**
     * Flag denoting if sound is paused from playing - waiting to be resumed
     */
    boolean   pause = false;

    /**
     * Sound priority ranking value.
     * Valid values are 0.0 to 1.0
     */
    float     priority = 1.0f;

    /**
     * Rate Scale Factor applied to sounds playback sample rate in Hertz.
     * Valid values are 0.0 to 1.0
     */
    float     rate = 1.0f;

    /**
     * The Boundary object defining the sound's scheduling region.
     */
    Bounds    schedulingRegion = null;

    /**
     * The bounding leaf reference
     */
    BoundingLeafRetained boundingLeaf = null;

    /**
     * The transformed bounds from either schedulingRegion or boundingLeaf
     */
    Bounds transformedRegion = null;

    // Dirty bit flags used to pass change as part of message, and are
    // acclummuated/stored in SoundSchedulerAtoms.
    // These flags are grouped into two catagories:
    // attribsDirty for sound node fields
    // stateDirty for changes to sound state not reflected by sound fields.

    // Attributes Dirty bit flags
    // This bitmask is set when sound node attribute is changed by the user.
    static final int SOUND_DATA_DIRTY_BIT          = 0x0001;
    static final int INITIAL_GAIN_DIRTY_BIT        = 0x0002;
    static final int LOOP_COUNT_DIRTY_BIT          = 0x0004;
    static final int BOUNDS_DIRTY_BIT              = 0x0008;
    static final int BOUNDING_LEAF_DIRTY_BIT       = 0x0010;
    static final int PRIORITY_DIRTY_BIT            = 0x0020;
    static final int POSITION_DIRTY_BIT            = 0x0040;
    static final int DISTANCE_GAIN_DIRTY_BIT       = 0x0080;
    static final int BACK_DISTANCE_GAIN_DIRTY_BIT  = 0x0100;
    static final int DIRECTION_DIRTY_BIT           = 0x0200;
    static final int ANGULAR_ATTENUATION_DIRTY_BIT = 0x0400;
    static final int RATE_DIRTY_BIT                = 0x0800;

    static final int BOUNDS_CHANGED                =
                         BOUNDS_DIRTY_BIT      | BOUNDING_LEAF_DIRTY_BIT;

    static final int ATTRIBUTE_DIRTY_BITS          =
                         SOUND_DATA_DIRTY_BIT  | INITIAL_GAIN_DIRTY_BIT  |
                         LOOP_COUNT_DIRTY_BIT  | PRIORITY_DIRTY_BIT    |
                         RATE_DIRTY_BIT;

    static final int POSITIONAL_DIRTY_BITS         =
                         ATTRIBUTE_DIRTY_BITS  |
                         POSITION_DIRTY_BIT    | DISTANCE_GAIN_DIRTY_BIT;

    static final int DIRECTIONAL_DIRTY_BITS        =
                         POSITIONAL_DIRTY_BITS | BACK_DISTANCE_GAIN_DIRTY_BIT |
                         DIRECTION_DIRTY_BIT   | ANGULAR_ATTENUATION_DIRTY_BIT;

    // All attribute bits that are specifically set or cleared for any node */
    static final int ALL_ATTIBS_DIRTY_BITS         = 0x0FFF;

    // State Dirty bit flags
    // This bitmask is set when scene graph state is changed.
    static final int LIVE_DIRTY_BIT                = 0x0001;
    static final int IMMEDIATE_MODE_DIRTY_BIT      = 0x0002;
    static final int LOAD_SOUND_DIRTY_BIT          = 0x0004;
    static final int RELEASE_DIRTY_BIT             = 0x0008;
    static final int CONTINUOUS_DIRTY_BIT          = 0x0010;
    static final int ENABLE_DIRTY_BIT              = 0x0020;
    static final int MUTE_DIRTY_BIT                = 0x0040;
    static final int PAUSE_DIRTY_BIT               = 0x0080;
    static final int XFORM_DIRTY_BIT               = 0x8000;

    // All attribute bits that are specifically set or cleared for any node */
    static final int ALL_STATE_DIRTY_BITS          = 0x80FF;

    // The type of sound node: Background, Point, Cone
    int soundType = NULL_SOUND;

    // A back reference to the scene graph sound, when this is a mirror sound
    SoundRetained sgSound = null;

    // A HashKey for sounds in a shared group
    HashKey key = null;

    // An array of mirror sounds, one for each instance of this sound in a
    // shared group.  Entry 0 is the only one valid if we are not in a shared
    // group.
    SoundRetained[] mirrorSounds = new SoundRetained[1];

    // The number of valid sounds in mirrorSounds
    int numMirrorSounds = 0;

    /**
     * Array of references to sound scheduler atoms associated with this node.
     * For each view that a sound node is associated with a sound scheduler
     * atom is created and maintained
     */
    // for a particular view that are playing either audibly or silently.
    private SoundSchedulerAtom[] loadedAtoms = new SoundSchedulerAtom[1];
    private int                  atomCount    = 0;

    /**
     * This is true when this sound is referenced in an immediate mode context
     */
    boolean   inImmCtx = false;

    /**
     * Load Sound Data Status
     */
    static final int LOAD_COMPLETE = 2;
    // load requested but could not be performed due because sound not live
    static final int LOAD_PENDING = 1;
    static final int LOAD_NULL = 0;
    static final int LOAD_FAILED = -1;
    int       loadStatus = LOAD_NULL;
    long      duration = Sound.DURATION_UNKNOWN;

    // Static initializer for SoundRetained class
    static {
	VirtualUniverse.loadLibraries();
    }

    // Target threads to be notified when sound changes
    static final int targetThreads = J3dThread.UPDATE_SOUND |
                                     J3dThread.SOUND_SCHEDULER;

    // Is true, if the mirror light is viewScoped
    boolean isViewScoped = false;


    /**
     * Dispatch a message about a sound attribute change
     */
    void dispatchAttribChange(int dirtyBit, Object argument) {
        // Send message including a integer argument
        J3dMessage createMessage = new J3dMessage();
        createMessage.threads = J3dThread.UPDATE_SOUND |
                                J3dThread.SOUND_SCHEDULER;
        createMessage.type = J3dMessage.SOUND_ATTRIB_CHANGED;
        createMessage.universe = universe;
        createMessage.args[0] = this;
        createMessage.args[1]= new Integer(dirtyBit);
        if (inSharedGroup)
                createMessage.args[2] = new Integer(numMirrorSounds);
        else
                createMessage.args[2] = new Integer(1);
        createMessage.args[3] = mirrorSounds.clone();
        createMessage.args[4] = argument;
        if (debugFlag)
	    debugPrint("dispatchAttribChange with " + dirtyBit);
        VirtualUniverse.mc.processMessage(createMessage);
    }

    /**
     * Dispatch a message about a sound state change
     */
    void dispatchStateChange(int dirtyBit, Object argument) {
        // Send message including a integer argument
        J3dMessage createMessage = new J3dMessage();
        createMessage.threads = J3dThread.UPDATE_SOUND |
                                J3dThread.SOUND_SCHEDULER;
        createMessage.type = J3dMessage.SOUND_STATE_CHANGED;
        createMessage.universe = universe;
        createMessage.args[0] = this;
        createMessage.args[1]= new Integer(dirtyBit);
        if (inSharedGroup)
                createMessage.args[2] = new Integer(numMirrorSounds);
        else
                createMessage.args[2] = new Integer(1);
        createMessage.args[3] = mirrorSounds.clone();
        createMessage.args[4] = argument;
        if (debugFlag)
	    debugPrint("dispatchStateChange with " + dirtyBit);
        VirtualUniverse.mc.processMessage(createMessage);
    }

    /**
     * Assign value into sound data field
     * @param soundData description of sound source data
     */
    void setSoundDataState(MediaContainer soundData) {
        this.soundData = soundData;
    }

    /**
     * Associates sound data with this sound source node
     * Attempt to load sound
     * @param soundData descrition of sound source data
     */
    void setSoundData(MediaContainer soundData) {
        // if resetting soundData to the same value don't bother doing anything
        if (this.soundData == soundData) {
            return;
        }

        if (this.soundData != null) {
            // this sound node had older sound data; clear it out
	    ((MediaContainerRetained)this.soundData.retained).removeUser(this);
        }

	if (source != null && source.isLive()) {
	    if (this.soundData != null) {
		((MediaContainerRetained)this.soundData.retained).clearLive(refCount);
	    }

	    if (soundData != null) {
		((MediaContainerRetained)soundData.retained).setLive(inBackgroundGroup, refCount);
	        ((MediaContainerRetained)soundData.retained).addUser(this);
	    }
	}

        this.soundData = soundData;
        dispatchAttribChange(SOUND_DATA_DIRTY_BIT, soundData);

	if (source != null && source.isLive()) {
	    notifySceneGraphChanged(false);
	}
    }

    /**
     * Retrieves sound data associated with this sound source node
     * @return sound source data container
     */
    MediaContainer getSoundData() {
        return ( this.soundData );
    }


    /**
     * Set the gain scale factor applied to this sound
     * @param amplitude gain scale factor
     */
    void setInitialGain(float scaleFactor) {
        if (scaleFactor < 0.0f)
            this.initialGain = 0.0f;
        else
	    this.initialGain = scaleFactor;

        dispatchAttribChange(INITIAL_GAIN_DIRTY_BIT, (new Float(scaleFactor)));
	if (source != null && source.isLive()) {
	    notifySceneGraphChanged(false);
	}
    }
    /**
     * Get the overall gain (applied to the sound data associated with source).
     * @return overall gain of sound source
     */
    float getInitialGain() {
        return (float) this.initialGain;
    }


    /**
     * Sets the sound's loop count
     * @param loopCount number of times sound is looped during play
     */
    void setLoop(int loopCount) {
        if (loopCount < -1)
            this.loopCount = -1;
        else
	    this.loopCount = (int) loopCount;
        if (debugFlag)
            debugPrint("setLoopCount called with " + this.loopCount);

        dispatchAttribChange(LOOP_COUNT_DIRTY_BIT, (new Integer(loopCount)));
	if (source != null && source.isLive()) {
	    notifySceneGraphChanged(false);
	}
    }

    /**
     * Retrieves the loop count
     * @return loop count for data associated with sound
     */
    int getLoop() {
        return (int) this.loopCount;
    }

    /**
     * Enable or disable the release flag for this sound source
     * @param state flag denoting release sound before stopping
     */
    void setReleaseEnable(boolean state) {
        this.release = state;
        dispatchAttribChange(RELEASE_DIRTY_BIT, (state ? Boolean.TRUE: Boolean.FALSE));
	if (source != null && source.isLive()) {
	    notifySceneGraphChanged(false);
	}
    }

    /**
     * Retrieves release flag for sound associated with this source node
     * @return sound's release flag
     */
    boolean getReleaseEnable() {
        return (boolean) this.release;
    }

    /**
     * Enable or disable continuous play flag
     * @param state denotes if sound continues playing silently when deactivated
     */
    void setContinuousEnable(boolean state) {
        this.continuous = state;
        dispatchAttribChange(CONTINUOUS_DIRTY_BIT, (state ? Boolean.TRUE: Boolean.FALSE));
	if (source != null && source.isLive()) {
	    notifySceneGraphChanged(false);
	}
    }

    /**
     * Retrieves sound's continuous play flag
     * @return flag denoting if deactivated sound silently continues playing
     */
    boolean getContinuousEnable() {
        return (boolean) this.continuous;
    }

    /**
     * Sets the flag denotine sound enabled/disabled and sends a message
     * for the following to be done:
     *   If state is true:
     *     if sound is not playing, sound is started.
     *     if sound is playing, sound is stopped, then re-started.
     *   If state is false:
     *     if sound is playing, sound is stopped
     * @param state true or false to enable or disable the sound
     */
     void setEnable(boolean state) {
        enable = state;
	// QUESTION: Is this still valid code?
	if (source != null && source.isLive()) {
	        notifySceneGraphChanged(false);
	}
        dispatchStateChange(ENABLE_DIRTY_BIT, (new Boolean(enable)));
    }

    /**
     * Retrieves sound's enabled flag
     * @return sound enabled flag
     */
    boolean getEnable() {
        return enable;
    }

    /**
     * Set the Sound's scheduling region.
     * @param region a region that contains the Sound's new scheduling region
     */
    void setSchedulingBounds(Bounds region) {
        if (region != null) {
            schedulingRegion = (Bounds) region.clone();
	    if (staticTransform != null) {
		schedulingRegion.transform(staticTransform.transform);
	    }
            // QUESTION: Clone into transformedRegion IS required.  Why?
            transformedRegion = (Bounds) schedulingRegion.clone();
            if (debugFlag)
		debugPrint("setSchedulingBounds for a non-null region");
        }
        else {
	    schedulingRegion = null;
            // QUESTION: Is transformedRegion of node (not mirror node)
            //           even looked at???
	    transformedRegion = null;
            if (debugFlag)
		debugPrint("setSchedulingBounds for a NULL region");
        }
        // XXXX: test that this works - could not new Bounds() since
        //       Bounds is an abstract class and can't be instantiated
        dispatchAttribChange(BOUNDS_DIRTY_BIT, region);
	if (source != null && source.isLive()) {
	    notifySceneGraphChanged(false);
	}
    }

    /**
     * Get the Sound's scheduling region.
     * @return this Sound's scheduling region information
     */
    Bounds getSchedulingBounds() {
	Bounds b = null;

	if (this.schedulingRegion != null) {
            b = (Bounds) schedulingRegion.clone();
            if (staticTransform != null) {
                Transform3D invTransform = staticTransform.getInvTransform();
                b.transform(invTransform);
            }
	}
	return b;
    }

    /**
     * Set the Sound's scheduling region to the specified Leaf node.
     */
    void setSchedulingBoundingLeaf(BoundingLeaf region) {
        int i;
        int numSnds = numMirrorSounds;
        if (numMirrorSounds == 0)
            numSnds = 1;

        if ((boundingLeaf != null) &&
          (source != null && source.isLive())) {
            // Remove the mirror lights as users of the original bounding leaf
            for (i = 0; i < numSnds; i++) {
                boundingLeaf.mirrorBoundingLeaf.removeUser(mirrorSounds[i]);
            }
        }

	if (region != null) {
	    boundingLeaf = (BoundingLeafRetained)region.retained;
            // Add all mirror sounds as user of this bounding leaf
	    if (source != null && source.isLive()) {
                for (i = 0; i < numSnds; i++) {
                    boundingLeaf.mirrorBoundingLeaf.addUser(mirrorSounds[i]);
                }
            }
	} else {
	    boundingLeaf = null;
	}
        // XXXX: since BoundingLeaf constructor only takes Bounds
        //       test if region passed into dispatchAttribChange correctly.
        dispatchAttribChange(BOUNDING_LEAF_DIRTY_BIT, region);
	if (source != null && source.isLive()) {
	    notifySceneGraphChanged(false);
	}
    }

    /**
     * Get the Sound's scheduling region
     */
    BoundingLeaf getSchedulingBoundingLeaf() {
	if (boundingLeaf != null) {
	    return((BoundingLeaf)boundingLeaf.source);
	} else {
	    return null;
	}
    }

    // The update Object function.
    @Override
    synchronized void updateMirrorObject(Object[] objs) {
        Transform3D trans = null;
        int component = ((Integer)objs[1]).intValue();
        if (component == -1) { // update everything
            // object 2 contains the mirror object that needs to be
            // updated
            initMirrorObject(((SoundRetained)objs[2]));
        }

        // call the parent's mirror object update routine
        super.updateMirrorObject(objs);

    }

    void updateBoundingLeaf(long refTime) {
        // This is necessary, if for example, the region
        // changes from sphere to box.
        if (boundingLeaf != null && boundingLeaf.switchState.currentSwitchOn) {
            transformedRegion = boundingLeaf.transformedRegion;
        } else { // evaluate schedulingRegion if not null
            if (schedulingRegion != null) {
                transformedRegion = schedulingRegion.copy(transformedRegion);
                transformedRegion.transform(schedulingRegion,
                                                getLastLocalToVworld());
            } else {
                transformedRegion = null;
            }
        }
    }


    /**
     * Set sound's proirity value.
     * @param priority value used to order sound's importance for playback.
     */
    void setPriority(float rank) {
        if (rank == this.priority)
            // changing priority is expensive in the sound scheduler(s)
            // so only dispatch a message if 'new' priority value is really
            // different
            return;

        this.priority = rank;
        dispatchAttribChange(PRIORITY_DIRTY_BIT, (new Float(rank)));
	if (source != null && source.isLive()) {
	    notifySceneGraphChanged(false);
	}
    }

    /**
     * Retrieves sound's priority value.
     * @return sound priority value
     */
    float getPriority() {
        return (this.priority);
    }


    /**
     * Retrieves sound's duration in milliseconds
     * @return sound's duration, returns DURATION_UNKNOWN if duration could
     * not be queried from the audio device
     */
    long getDuration() {
        return (duration);
    }


    /**
     * Set scale factor
     * @param scaleFactor applied to sound playback rate
     */
    void setRateScaleFactor(float scaleFactor) {
        this.rate = scaleFactor;
        dispatchAttribChange(RATE_DIRTY_BIT, (new Float(scaleFactor)));
	if (source != null && source.isLive()) {
	    notifySceneGraphChanged(false);
	}
    }

    /**
     * Retrieves sound's rate scale factor
     * @return sound rate scale factor
     */
    float getRateScaleFactor() {
        return (this.rate);
    }

    void changeAtomList(SoundSchedulerAtom atom, int loadStatus) {
        if (atom == null)
            return;
        if (loadStatus == SoundRetained.LOAD_COMPLETE) {
            // atom is successfully loaded, so add this atom to array of atoms
            // associated with this sound, if not already in list
            for (int i=0; i currentArrayLength) {
                    // expand array - replace with a larger array
                    loadedAtoms = new SoundSchedulerAtom[2*currentArrayLength];
            }
            loadedAtoms[atomCount-1] = atom;  // store reference to new atom
            // all atoms sample durations SHOULD be the same so store it in node
            this.duration = atom.sampleLength; // XXXX: refine later? in ms
        }
        else {  // atom is NOT loaded or has been unloaded; remove from list
            if (atomCount == 0)
                return;

            // remove atom from array of playing atoms if it is in list
            boolean atomFound = false;
            int i;
            for (i=0; i
     * 1) the Sound node has a non-null sound data and this data has
     *    sucessfully been loaded/opened/copied/attached;
* 2) the Sound node is live;
* 3) there is at least one active View in the Universe; and
* 4) an instance of an AudioDevice is attached to the current * PhysicalEnvironment. * * * @return true if potentially playable (audibly or silently); false otherwise */ boolean isReady() { // all the atoms in the atom list must be are ready for this // method to return true // if any non-null atoms are found NOT ready, return false. boolean atomFoundReady = true; for (int i=0; i * 1) the Sound node has a non-null sound data and this data has * sucessfully been loaded/opened/copied/attached;
* 2) the Sound node is live;
* 3) the given View is active in the Universe; and
* 4) an instance of an AudioDevice is attached to the current * PhysicalEnvironment. * * * @param viewRef view to test sound readiness for * @return true if potentially playable (audibly or silently); false otherwise */ boolean isReady(View viewRef) { // if an atom in the atom list that is associated with the // given view is found and has been loaded than return true, // otherwise return false. if (viewRef == null) return false; for (int i=0; i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy