/* * 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