/* * Copyright 1997-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.awt.AWTEvent; import java.awt.event.WindowEvent; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import javax.vecmath.Point2f; import javax.vecmath.Point3d; import javax.vecmath.Point3f; import javax.vecmath.Vector3d; import javax.vecmath.Vector3f; /** * This structure parallels the RenderBin structure and * is used for sounds */ class SoundScheduler extends J3dStructure { /** * The View that owns this SoundScheduler */ View view = null; /** * This boolean tells the thread to suspend itself. * This is true ONLY when everythings ready to render using run loop */ boolean ready = false; /** * The ViewPlatform that is associated with this SoundScheduler */ ViewPlatformRetained viewPlatform = null; /** * The GraphicContext3D that we are currently unning in. */ GraphicsContext3D graphicsCtx = null; /** * Maintain what reference to the last AuralAttributes found active * was so that only if there was a change do we need to reset these * parameters in the AudioDevice3D. */ AuralAttributesRetained lastAA = null; /** * Since AuralAttribute gain scale factor is multipled with sound's * initialGain scale factor, any change in AuralAttrib gain scale * factor should force an update of all active sounds' gains * Also, change in AuralAttributes should force a sound update * even if no other sound field changes occurred. */ boolean resetAA = true; /** * Audio Device */ AudioDevice audioDevice = null; AudioDevice3D audioDevice3D = null; AudioDevice3DL2 audioDevice3DL2 = null; int totalChannels = 0; /** * Array of SoundScapeRetained nodes that intersect the viewPlatform * This list is a subset of the soundscapes array, and is used when * selecting the closest Soundscape. * Maintained as an expandable array. */ SoundscapeRetained[] intersectedSoundscapes = new SoundscapeRetained[32]; /** * Array of Bounds nodes for the corresponding intersectedSoundscapes * array. This array is used when selecting the closest Soundscape. * This list is used when selecting the closest Soundscape. * Maintained as an expandable array. */ Bounds[] intersectedRegions = new Bounds[32]; /** * Reference to last processed region within run(). * Maintained to avoid re-transforming this bounds. */ Bounds region = null; /** * An array of prioritized sounds currently playing "live" sounds. * This prioritized sound list is NO longer re-create instead sounds * are insert, shuffled or removed as messages are processed. */ // XXXX: (Enhancement) should have a seperate list for // background sound and a list for positional sounds ArrayList prioritizedSounds = new ArrayList(); /** * Current number of scene graph sound nodes in the universe */ int nRetainedSounds = -1; // none calculated yet /** * Current number of immediate mode sound nodes in the universe */ int nImmedSounds = -1; // none calculated yet /** * Current active (selected) attribute node in the sceneGraph */ AuralAttributesRetained aaRetained = null; // variables for processing transform messages boolean transformMsg = false; UpdateTargets targets = null; /** * Current active (selected) attribute node in the sceneGraph */ AuralAttributesRetained aaImmed = null; // Dirty flags for fields and parameters that are unique to the // Sound Scheduler or the Audio Device // Any listener (body) and/or view transform changes processed in // CanvasViewCache force one of these flags to be set. static final int EAR_POSITIONS_CHANGED = 0x0001; static final int EYE_POSITIONS_CHANGED = 0x0002; static final int IMAGE_PLATE_TO_VWORLD_CHANGED = 0x0004; static final int HEAD_TO_VWORLD_CHANGED = 0x0008; static final int LISTENER_CHANGED = 0x000F;// all of the above private int listenerUpdated = LISTENER_CHANGED; /** * Temporary flag that's denotes that a positional sound was processed * in the current loop of renderChange(). */ private boolean positionalSoundUpdated = false; /** * Temporary flag that's denotes that some field auralAttribute was changed */ private boolean auralAttribsChanged = true; // force processing 1st x private boolean stallThread = false; int lastEventReceived = WindowEvent.WINDOW_CLOSED; /** * Constructs a new SoundScheduler */ SoundScheduler(VirtualUniverse u, View v) { super(u, J3dThread.SOUND_SCHEDULER); // Assertion check view & universe if (v == null) { System.err.println("WARNING: SoundScheduler constructed with null view"); } if (u == null) { System.err.println("WARNING: SoundScheduler constructed with null universe"); } universe = u; view = v; reset(); } // NOTE: processMessage only called with updatethread.active true @Override void processMessages(long referenceTime) { J3dMessage[] messages = getMessages(referenceTime); int nMsg = getNumMessage(); J3dMessage m; int nSounds; if (nMsg > 0) { for (int i=0; i < nMsg; i++) { m = messages[i]; switch (m.type) { case J3dMessage.INSERT_NODES: insertNodes(m); break; case J3dMessage.REMOVE_NODES: removeNodes(m); break; case J3dMessage.SOUND_ATTRIB_CHANGED: changeNodeAttrib(m); break; case J3dMessage.SOUND_STATE_CHANGED: changeNodeState(m); break; case J3dMessage.BOUNDINGLEAF_CHANGED: processBoundingLeafChanged(m); break; case J3dMessage.SOUNDSCAPE_CHANGED: SoundscapeRetained ss = (SoundscapeRetained)m.args[0]; if (universe.soundStructure.isSoundscapeScopedToView(ss, view)) { auralAttribsChanged = true; changeNodeAttrib(m); } break; case J3dMessage.AURALATTRIBUTES_CHANGED: auralAttribsChanged = true; changeNodeAttrib(m); break; case J3dMessage.MEDIA_CONTAINER_CHANGED: changeNodeAttrib(m); break; case J3dMessage.TRANSFORM_CHANGED: transformMsg = true; auralAttribsChanged = true; break; case J3dMessage.RENDER_IMMEDIATE: processImmediateNodes(m.args, referenceTime); break; case J3dMessage.VIEWSPECIFICGROUP_CHANGED: processViewSpecificGroupChanged(m); break; case J3dMessage.UPDATE_VIEW: if (debugFlag) debugPrint(".processMessage() UPDATE_VIEW"); // NOTE: can only rely on seeing UPDATE_VIEW when canvas [re]Created // AND when view deactivated... // NOTE: // temp work-around // calling prioritizeSounds() wipes out old atom fields // QUESTION: prioritizedSound is NEVER empty - why if size is 0 can // .isEmpty return anything but TRUE??? // if (prioritizedSounds.isEmpty()) { nSounds = prioritizeSounds(); } break; case J3dMessage.SWITCH_CHANGED: if (debugFlag) debugPrint(".processMessage() " + "SWITCH_CHANGED ignored"); break; } // switch m.decRefcount(); } // for if (transformMsg) { targets = universe.transformStructure.getTargetList(); updateTransformChange(targets, referenceTime); transformMsg = false; targets = null; } Arrays.fill(messages, 0, nMsg, null); } // Call renderChanges within try/catch so errors won't kill // the SoundScheduler. try { renderChanges(); } catch (RuntimeException e) { System.err.println("Exception occurred " + "during Sound rendering:"); e.printStackTrace(); } catch (Error e) { // Issue 264 - catch Error System.err.println("Error occurred " + "during Sound rendering:"); e.printStackTrace(); } // what if the user/app makes no change to scenegraph? // must still re-render after retest for sound complete // calculate which sound will finished first and set a // wait time to this shortest time so that scheduler is // re-entered to process sound complete. long waitTime = shortestTimeToFinish(); if (waitTime == 0L) { // come right back if (debugFlag) debugPrint(".processMessage calls sendRunMessage " + "for immediate processing"); VirtualUniverse.mc.sendRunMessage(universe, J3dThread.SOUND_SCHEDULER); } else if (waitTime > 0L) { // Use TimerThread to send message with sounds complete. // This uses waitForElapse time to sleep for at least the duration // returned by shortestTimeToFinish method. if (debugFlag) debugPrint(".processMessage calls sendRunMessage " + "with wait time = " + waitTime ); // QUESTION (ISSUE): even when this is set to a large time // processMessage is reentered immediately. // Why is timer thread not waiting?? VirtualUniverse.mc.sendRunMessage(waitTime, view, J3dThread.SOUND_SCHEDULER); } } void insertNodes(J3dMessage m) { Object[] nodes = (Object[])m.args[0]; ArrayList viewScopedNodes = (ArrayList)m.args[3]; ArrayList> scopedNodesViewList = (ArrayList>)m.args[4]; for (int i=0; i vl = scopedNodesViewList.get(i); // If the node object is scoped to this view, then .. if (vl.contains(view)) { if (node instanceof SoundRetained) { nRetainedSounds++; // insert sound node into sound scheduler's prioritized list addSound((SoundRetained) node); } else if (node instanceof SoundscapeRetained) { auralAttribsChanged = true; } } } } } /** * Add sound to sounds list. */ void addSound(SoundRetained sound) { if (sound == null) return; if (debugFlag) debugPrint(".addSound()"); synchronized (prioritizedSounds) { addPrioritizedSound(sound); } } // end addSound /** * Node removed from tree */ @Override void removeNodes(J3dMessage m) { Object[] nodes = (Object[])m.args[0]; ArrayList viewScopedNodes = (ArrayList)m.args[3]; ArrayList> scopedNodesViewList = (ArrayList>)m.args[4]; for (int i=0; i vl = scopedNodesViewList.get(i); // If the node object is scoped to this view, then .. if (vl.contains(view)) { if (node instanceof SoundRetained) { SoundSchedulerAtom soundAtom = null; for (int arrIndx=1; ;arrIndx++) { soundAtom = findSoundAtom((SoundRetained)node, arrIndx); if (soundAtom == null) break; stopSound(soundAtom, false); } } else if (node instanceof SoundscapeRetained) { auralAttribsChanged = true; } } } } } // deletes all instances of the sound nodes from the priority list void deleteSound(SoundRetained sound) { if (sound != null) return; if (debugFlag) debugPrint(".deleteSound()"); synchronized (prioritizedSounds) { if (!prioritizedSounds.isEmpty()) { // find sound in list and remove it int arrSize = prioritizedSounds.size(); for (int index=0; index 0) { if (debugFlag) debugPrint(" MuteDirtyBit is on"); muteSound((SoundRetained) node); } if ((attribDirty & SoundRetained.PAUSE_DIRTY_BIT) > 0) { if (debugFlag) debugPrint(" PauseDirtyBit is on"); pauseSound((SoundRetained) node); } } else if (node instanceof SoundscapeRetained && universe.soundStructure.isSoundscapeScopedToView(node, view)) { auralAttribsChanged = true; } else if (node instanceof AuralAttributesRetained) { auralAttribsChanged = true; } else if (node instanceof MediaContainerRetained) { int listSize = ((Integer)m.args[2]).intValue(); ArrayList userList = (ArrayList)m.args[3]; for (int i = 0; i < listSize; i++) { SoundRetained sound = (SoundRetained)userList.get(i); if (sound != null) { loadSound(sound, true); if (debugFlag) debugPrint(".changeNodeAttrib " + "MEDIA_CONTAINER_CHANGE calls loadSound"); } } } } void changeNodeState(J3dMessage m) { Object node = m.args[0]; Object value = m.args[1]; if (debugFlag) debugPrint(".changeNodeState:"); if (node instanceof SoundRetained && universe.soundStructure.isSoundScopedToView(node, view)) { int stateDirty = ((Integer)value).intValue(); setStateDirtyFlag((SoundRetained)node, stateDirty); if (debugFlag) debugPrint(" Sound node dirty bit = "+stateDirty); if ((stateDirty & SoundRetained.LIVE_DIRTY_BIT) > 0) { if (debugFlag) debugPrint(".changeNodeState LIVE_DIRTY_BIT " + "calls loadSound"); loadSound((SoundRetained) node, false); } if ((stateDirty & SoundRetained.ENABLE_DIRTY_BIT) > 0) { if (debugFlag) debugPrint(" EnableDirtyBit is on"); if (((Boolean) m.args[4]).booleanValue()) { enableSound((SoundRetained) node); } else { SoundSchedulerAtom soundAtom; SoundRetained soundRetained = (SoundRetained) node; for (int i=prioritizedSounds.size()-1; i >=0; i--) { soundAtom = prioritizedSounds.get(i); if (soundAtom.sound.sgSound == soundRetained) { // ignore soundRetained.release // flag which is not implement turnOff(soundAtom); // Fix to Issue 431. soundAtom.enable(soundRetained.enable); } } } } } } void shuffleSound(SoundRetained sound) { // Find sound atom that references this sound node and // reinsert it into prioritized sound list by removing atom for // this sound from priority list, then re-add it. // Assumes priority has really changed since a message is not sent // to the scheduler if the 'new' priority value isn't different. deleteSound(sound); // remove atom for this sound addSound(sound); // then re-insert it back into list in new position } void loadSound(SoundRetained sound, boolean forceReload) { // find sound atom that references this sound node // QUESTION: "node" probably not mirror node? SoundSchedulerAtom soundAtom = null; for (int i=1; ;i++) { soundAtom = findSoundAtom(sound, i); if (soundAtom == null) break; MediaContainer mediaContainer = sound.getSoundData(); if (forceReload || soundAtom.loadStatus != SoundRetained.LOAD_COMPLETE) { if (debugFlag) debugPrint(": not LOAD_COMPLETE - try attaching"); attachSoundData(soundAtom, mediaContainer, forceReload); } } } void enableSound(SoundRetained sound) { if (debugFlag) debugPrint(".enableSound " + sound ); // find sound atom that references this sound node SoundSchedulerAtom soundAtom = null; for (int i=1; ;i++) { soundAtom = findSoundAtom(sound, i); if (soundAtom == null) break; // Set atom enabled field based on current Sound node // enable boolean flag soundAtom.enable(sound.enable); } } void muteSound(SoundRetained sound) { // make mute pending // mute -> MAKE-SILENT // unmute -> MAKE-AUDIBLE if (debugFlag) debugPrint(".muteSound " + sound ); // find sound atom that references this sound node SoundSchedulerAtom soundAtom = null; for (int i=1; ;i++) { soundAtom = findSoundAtom(sound, i); if (soundAtom == null) break; // Set atom mute field based on node current // mute boolean flag soundAtom.mute(sound.mute); } } void pauseSound(SoundRetained sound) { // make pause pending // Pause is a separate action // When resumed it has to reset its state // PAUSE_AUDIBLE // PAUSE_SILENT // RESUME_AUDIBLE // RESUME_SILENT // to whatever it was before if (debugFlag) debugPrint(".pauseSound " + sound ); // find sound atom that references this sound node SoundSchedulerAtom soundAtom = null; for (int i=1; ;i++) { soundAtom = findSoundAtom(sound, i); if (soundAtom == null) break; // Set atom pause field based on node's current // pause boolean flag soundAtom.pause(sound.pause); } } void processImmediateNodes(Object[] args, long referenceTime) { Object command = args[0]; Object newNode = args[1]; Object oldNode = args[2]; Sound oldSound = (Sound)oldNode; Sound newSound = (Sound)newNode; int action = ((Integer)command).intValue(); if (debugFlag) debugPrint(".processImmediateNodes() - action = " + action); switch (action) { case GraphicsContext3D.ADD_SOUND : case GraphicsContext3D.INSERT_SOUND : addSound((SoundRetained)newSound.retained); nImmedSounds++; break; case GraphicsContext3D.REMOVE_SOUND : deleteSound((SoundRetained)oldSound.retained); nImmedSounds--; break; case GraphicsContext3D.SET_SOUND : deleteSound((SoundRetained)oldSound.retained); addSound((SoundRetained)newSound.retained); break; } } void updateTransformChange(UpdateTargets targets, long referenceTime) { // node.updateTransformChange() called immediately rather than // waiting for updateObject to be called and process xformChangeList // which apprears to only happen when sound started... UnorderList arrList = targets.targetList[Targets.SND_TARGETS]; if (arrList != null) { int j,i; Object nodes[], nodesArr[]; int size = arrList.size(); nodesArr = arrList.toArray(false); for (j = 0; j 0) { calcSchedulingAction(); muteSilentSounds(); // short term flag set within performActions->update() positionalSoundUpdated = false; // if listener parameters changed re-set View parameters if (testListenerFlag()) { if (debugFlag) debugPrint(" audioDevice3D.setView"); audioDevice3D.setView(view); } numActiveSounds = performActions(); if (positionalSoundUpdated) { // if performActions updated at least one positional sound // was processed so the listener/view changes were processed, // thus we can clear the SoundScheduler dirtyFlag, otherwise // leave the flag dirty until a positional sound is updated clearListenerFlag(); // clears listenerUpdated flag } } /* } */ } /** * Prioritize all sounds associated with SoundScheduler (view) * This only need be done once when scheduler is initialized since * the priority list is updated when: * a) PRIORITY_DIRTY_BIT in soundDirty field set; or * b) sound added or removed from live array list */ int prioritizeSounds() { int size; synchronized (prioritizedSounds) { if (!prioritizedSounds.isEmpty()) { prioritizedSounds.clear(); } // XXXX: sync soundStructure sound list UnorderList retainedSounds = universe.soundStructure.getSoundList(view); // QUESTION: what is in this sound list?? // mirror node or actual node??? nRetainedSounds = 0; nImmedSounds = 0; if (debugFlag) debugPrint(" prioritizeSound , num retained sounds" + retainedSounds.size()); for (int i=0; i canvases = view.getAllCanvas3Ds(); while (canvases.hasMoreElements()) { Canvas3D canvas = canvases.nextElement(); GraphicsContext3D graphicsContext = canvas.getGraphicsContext3D(); Enumeration nonretainedSounds = graphicsContext.getAllSounds(); while (nonretainedSounds.hasMoreElements()) { if (debugFlag) debugPrint(" prioritizeSound , get non-retained sound"); Sound sound = (Sound)nonretainedSounds.nextElement(); if (sound == null) { if (debugFlag) debugPrint(" prioritizeSound , sound element is null"); // QUESTION: why should I have to do this? continue; } addPrioritizedSound((SoundRetained)sound.retained); nImmedSounds++; } } if (debugFlag) debugPrint(" prioritizeSound , num of processed retained sounds" + nRetainedSounds); debugPrint(" prioritizeSound , num of processed non-retained sounds" + nImmedSounds); size = prioritizedSounds.size(); } // sync return size; } // methods that call this should synchronize prioritizedSounds void addPrioritizedSound(SoundRetained mirSound) { SoundRetained sound = mirSound.sgSound; if (sound == null) { // this mirSound is a nonretained sound // pad the "child" sg sound pointer with itself mirSound.sgSound = mirSound; sound = mirSound; if (debugFlag) debugPrint(":addPritorizedSound() sound NULL"); } boolean addAtom = false; // see if this atom is in the list already // covers the case where the node was detached or unswitched but NOT // deleted (so sample already loaded // QUESTION: is above logic correct??? SoundSchedulerAtom atom = null; atom = findSoundAtom(mirSound, 1); // look thru list for 1st instance if (atom == null) { atom = new SoundSchedulerAtom(); atom.soundScheduler = this; // save scheduler atom is associated with addAtom = true; } // update fields in atom based on sound nodes state atom.sound = mirSound; // new mirror sound updateTransformedFields(mirSound); if ( !addAtom ) { return; } // if this atom being added then set the enable state atom.enable(sound.enable); if (prioritizedSounds.isEmpty()) { // List is currently empty, so just add it // insert into empty list of prioritizedSounds prioritizedSounds.add(atom); if (debugFlag) debugPrint(":addPritorizedSound() inset sound " + mirSound + " into empty priority list"); } else { // something's in the proirity list already // Since list is not empty insert sound into list. // // Order is highest to lowest priority values, and // for sounds with equal priority values, sound // inserted first get in list given higher priority. SoundRetained jSound; SoundSchedulerAtom jAtom; int j; int jsounds = (prioritizedSounds.size() - 1); float soundPriority = sound.priority; for (j=jsounds; j>=0; j--) { jAtom = prioritizedSounds.get(j); jSound = jAtom.sound; if (debugFlag) debugPrint(": priority of sound " + jSound.sgSound + " element " + (j+1) + " of prioritized list"); if (soundPriority <= jSound.sgSound.priority) { if (j==jsounds) { // last element's priority is larger than // current sound's priority, so add this // sound to the end of the list prioritizedSounds.add(atom); if (debugFlag) debugPrint(": insert sound at list bottom"); break; } else { if (debugFlag) debugPrint( ": insert sound as list element " + (j+1)); prioritizedSounds.add(j+1, atom); break; } } } // for loop if (j < 0) { // insert at the top of the list if (debugFlag) debugPrint(": insert sound at top of priority list"); prioritizedSounds.add(0, atom); } } // else list not empty } /** * Process active Soundscapes (if there are any) and intersect these * soundscapes with the viewPlatform. * * Returns the number of soundscapes that intesect with * view volume. */ int findActiveSoundscapes() { int nSscapes = 0; int nSelectedSScapes = 0; SoundscapeRetained ss = null; SoundscapeRetained lss = null; boolean intersected = false; int nUnivSscapes = 0; UnorderList soundScapes = null; // Make a copy of references to the soundscapes in the universe // that are both switch on and have non-null (transformed) regions, // don't bother testing for intersection with view. if (universe == null) { if (debugFlag) debugPrint(".findActiveSoundscapes() univ=null"); return 0; } soundScapes = universe.soundStructure.getSoundscapeList(view); if (soundScapes == null) { if (debugFlag) debugPrint(".findActiveSoundscapes() soundScapes null"); return 0; } synchronized (soundScapes) { nUnivSscapes = soundScapes.size; if (nUnivSscapes == 0) { if (debugFlag) debugPrint( ".findActiveSoundscapes() soundScapes size=0"); return 0; } // increase arrays lengths by increments of 32 elements if (intersectedRegions.length < nSscapes) { intersectedRegions = new Bounds[nSscapes + 32]; } if (intersectedSoundscapes.length < nSscapes) { intersectedSoundscapes = new SoundscapeRetained[nSscapes + 32]; } // nSscapes is incremented for every Soundscape found if (debugFlag) debugPrint(".findActiveSoundscapes() nUnivSscapes="+ nUnivSscapes); nSelectedSScapes = 0; for (int k=0; k 1) { Bounds closestRegions; closestRegions = viewPlatform.schedSphere.closestIntersection( intersectedRegions); for (int j=0; j < intersectedRegions.length; j++) { if (debugFlag) debugPrint(" element " + j + " in intersectedSoundsscapes is " + intersectedRegions[j]); if (intersectedRegions[j] == closestRegions) { ss = intersectedSoundscapes[j]; if (debugFlag) debugPrint(" element " + j + " is closest"); break; } } } if (ss != null) { if (debugFlag) debugPrint(" closest SoundScape found is " + ss); aa = ss.getAuralAttributes(); if (aa != null) { if (debugFlag) debugPrint(": AuralAttribute for " + "soundscape is NOT null"); } else { if (debugFlag) debugPrint(": AuralAttribute for " + "soundscape " + ss + " is NULL"); } } else { if (debugFlag) debugPrint(": AuralAttribute is null " + "since soundscape is NULL"); } if (debugFlag) debugPrint( " auralAttrib for closest SoundScape found is " + aa); return ((AuralAttributesRetained)aa.retained); } /** * Send current aural attributes to audio device * * Note that a AA's dirtyFlag is clear only after parameters are sent to * audio device. */ void updateAuralAttribs(AuralAttributesRetained attribs) { if (auralAttribsChanged) { if (attribs != null) { synchronized (attribs) { /* // XXXX: remove use of aaDirty from AuralAttrib node if ((attribs != lastAA) || attribs.aaDirty) */ if (debugFlag) { debugPrint(" set real updateAuralAttribs because"); } // Send current aural attributes to audio device // Assumes that aural attribute parameter is NOT null. audioDevice3D.setRolloff(attribs.rolloff); if (debugFlag) debugPrint(" rolloff " + attribs.rolloff); // Distance filter parameters int arraySize = attribs.getDistanceFilterLength(); if ((attribs.filterType == AuralAttributesRetained.NO_FILTERING) || arraySize == 0 ) { audioDevice3D.setDistanceFilter( attribs.NO_FILTERING, null, null); if (debugFlag) debugPrint(" no filtering"); } else { Point2f[] attenuation = new Point2f[arraySize]; for (int i=0; i< arraySize; i++) attenuation[i] = new Point2f(); attribs.getDistanceFilter(attenuation); double[] distance = new double[arraySize]; float[] cutoff = new float[arraySize]; for (int i=0; i< arraySize; i++) { distance[i] = attenuation[i].x; cutoff[i] = attenuation[i].y; } audioDevice3D.setDistanceFilter(attribs.filterType, distance, cutoff); if (debugFlag) { debugPrint(" filtering parameters: " + " distance, cutoff arrays"); for (int jj=0; jj0 && soundAtom.endTime<=currentTime) { // sound's completed playing, force action soundAtom.schedulingAction = SoundSchedulerAtom.COMPLETE; if (debugFlag) debugPrint(": sample complete;"+ " endTime = " + soundAtom.endTime + ", currentTime = " + currentTime + " so turned off"); soundAtom.status = SoundSchedulerAtom.SOUND_COMPLETE; turnOff(soundAtom); // Stop sound in device that are complete if (debugFlag) debugPrint(": sound "+soundAtom.sampleId+ " action COMPLETE results in call to stop"); } break; case SoundSchedulerAtom.RESTART_AUDIBLE: case SoundSchedulerAtom.START_AUDIBLE: case SoundSchedulerAtom.RESTART_SILENT: case SoundSchedulerAtom.START_SILENT: break; default: // includes COMPLETE, DO_NOTHING soundAtom.schedulingAction = SoundSchedulerAtom.DO_NOTHING; break; } // switch if (debugFlag) debugPrint(": final scheduling action " + "set to " + soundAtom.schedulingAction); } /** * Determine scheduling action for each live sound */ int calcSchedulingAction() { // Temp variables SoundRetained sound; SoundRetained mirSound; SoundSchedulerAtom soundAtom; SoundRetained jSound; int nSounds = 0; boolean processSound; // number of sounds to process including scene graph and immediate nodes int numSoundsToProcess = 0; if (universe == null) { if (debugFlag) debugPrint( ": calcSchedulingAction: univ NULL"); return 0; } if (universe.soundStructure == null) { if (debugFlag) debugPrint( ": calcSchedulingAction: soundStructure NULL"); return 0; } // List of prioritized "live" sounds taken from universe list of sounds. // Maintained as an expandable array - start out with a small number of // elements for this array then grow the list larger if necessary... synchronized (prioritizedSounds) { nSounds = prioritizedSounds.size(); if (debugFlag) debugPrint( ": calcSchedulingAction: soundsList size = " + nSounds); // (Large) Loop over all switched on sounds and conditionally put // these into a order prioritized list of sound. // Try throw out as many sounds as we can: // Sounds finished playing (reached end before stopped) // Sounds still yet to be loaded // Positional sounds whose regions don't intersect view // Sound to be stopped // Those sounds remaining are inserted into a prioritized list for (int i=0; i>>>>>sound using sgSound at " + sound); printAtomState(soundAtom); } processSoundAtom(soundAtom); } // end of process sound else { soundAtom.schedulingAction = SoundSchedulerAtom.DO_NOTHING; } // end of not process sound } // end loop over all sound in soundList } // sync if (debugFlag) { if (numSoundsToProcess > 0) debugPrint(": number of liveSounds = " + numSoundsToProcess); else debugPrint(": number of liveSounds <= 0"); } return numSoundsToProcess; } /** * Mute sounds that are to be played silently. * * Not all the sound in the prioritized enabled sound list * may be able to be played. Due to low priority, some sounds * must be muted/silenced (if such an action frees up channel * resources) to make way for sounds with higher priority. * For each sound in priority list: * For sounds whose actions are X_SILENT: * Mute sounds to be silenced * Add the number of channels used by this muted sound to * current total number of channels used * For all remaining sounds (with actions other than above) * The number of channels that 'would be used' to play * potentially audible sounds is compared with * the number left on the device: * If this sound would use more channels than available * Change it's X_AUDIBLE action to X_SILENT * Mute sounds to be silenced * Add the number of channels used by this sound, muted * or not, to current total number of channels used * * NOTE: requests for sounds to play beyond channel capability of * the audio device do NOT throw an exception when more sounds are * started than can be played. Rather the unplayable sounds are * muted. It is up to the AudioDevice3D implementation to determine * how muted/silent sounds are implememted (playing with gain zero * and thus using up channel resources, or stop and restarted with * correct offset when inactivated then re-actived. */ void muteSilentSounds() { // Temp variables SoundRetained sound; SoundRetained mirSound; int totalChannelsUsed = 0; SoundSchedulerAtom soundAtom; int nAtoms; synchronized (prioritizedSounds) { nAtoms = prioritizedSounds.size(); if (debugFlag) debugPrint(".muteSilentSounds(): Loop over prioritizedSounds list, " + "size = " + nAtoms); for (int i=0; itotalChannels) { if ((soundAtom.schedulingAction == SoundSchedulerAtom.MAKE_AUDIBLE) || (soundAtom.schedulingAction == SoundSchedulerAtom.LEAVE_AUDIBLE)) { soundAtom.schedulingAction = SoundSchedulerAtom.MAKE_SILENT; } else if (soundAtom.schedulingAction == SoundSchedulerAtom.RESTART_AUDIBLE) soundAtom.schedulingAction = SoundSchedulerAtom.RESTART_SILENT; else if (soundAtom.schedulingAction == SoundSchedulerAtom.START_AUDIBLE) soundAtom.schedulingAction = SoundSchedulerAtom.START_SILENT; else if (soundAtom.schedulingAction == SoundSchedulerAtom.PAUSE_AUDIBLE) soundAtom.schedulingAction = SoundSchedulerAtom.PAUSE_SILENT; else if (soundAtom.schedulingAction == SoundSchedulerAtom.RESUME_AUDIBLE) soundAtom.schedulingAction = SoundSchedulerAtom.RESUME_SILENT; audioDevice3D.muteSample(sampleId); if (debugFlag) { debugPrint(": sound " + sampleId + "number of channels needed is " + numberChannels); debugPrint(": sound " + sampleId + " action is x_AUDIBLE but " + "not enough channels free (" + (totalChannels - totalChannelsUsed) + ") so, sound muted"); } } // sound has enough channels to play else if (status != SoundSchedulerAtom.SOUND_AUDIBLE) { // old status is not already unmuted/audible audioDevice3D.unmuteSample(sampleId); if (debugFlag) debugPrint(": sound " + sampleId + " action is x_AUDIBLE and channels free so, " + "sound unmuted"); } // now that the exact muting state is known (re-)get actual // number of channels used by this sound and add to total numberChannels = audioDevice3D.getNumberOfChannelsUsed(sampleId); soundAtom.numberChannels = numberChannels; // used in audio device totalChannelsUsed += numberChannels; } // otherwise, scheduling is for potentally audible sound // No sound in list should have action TURN_ or LEAVE_OFF } // of for loop over sounds in list } } void muteSilentSound(SoundSchedulerAtom soundAtom) { // Temp variables SoundRetained sound; SoundRetained mirSound; mirSound = (SoundRetained)soundAtom.sound; sound = mirSound.sgSound; int sampleId = soundAtom.sampleId; int status = soundAtom.status; if (status == SoundSchedulerAtom.SOUND_COMPLETE) { return; } if (sampleId == SoundRetained.NULL_SOUND) { return; } if (debugFlag) { debugPrint(": contents of current sound " + soundAtom.sampleId + " before switch on sAction" ); printAtomState(soundAtom); } if ( (soundAtom.schedulingAction == SoundSchedulerAtom.MAKE_SILENT) || (soundAtom.schedulingAction == SoundSchedulerAtom.RESTART_SILENT) || (soundAtom.schedulingAction == SoundSchedulerAtom.LEAVE_SILENT) || (soundAtom.schedulingAction == SoundSchedulerAtom.START_SILENT) ) { // Mute sounds that are not already silent if (status != SoundSchedulerAtom.SOUND_SILENT) { // old status is not already muted/silent audioDevice3D.muteSample(sampleId); if (debugFlag) debugPrint(": sound " + sampleId + " action is x_SILENT, sound muted"); } } // scheduling is for silent sound } /** * Determine amount of time before next playing sound will be * is complete. * * find the atom that has the least amount of time before is * finished playing and return this time * @return length of time in millisecond until the next active sound * will be complete. Returns -1 if no sounds are playing (or all are * complete). */ long shortestTimeToFinish() { long currentTime = J3dClock.currentTimeMillis(); long shortestTime = -1L; SoundSchedulerAtom soundAtom; synchronized (prioritizedSounds) { int nAtoms = prioritizedSounds.size(); for (int i=0; i= 0) { if (debugFlag) debugPrint(".start: " + index ); soundAtom.playing = true; soundAtom.startTime = audioDevice3D.getStartTime(index); soundAtom.calculateEndTime(); if (debugFlag) debugPrint(".start: begintime = " + soundAtom.startTime + ", endtime " + soundAtom.endTime); } else { // error returned by audio device when trying to start soundAtom.startTime = 0; soundAtom.endTime = 0; soundAtom.playing = false; if (debugFlag) { debugPrint(".start: error " + startStatus + " returned by audioDevice3D.startSample(" + index + ")" ); debugPrint( " start/endTime set to zero"); } } } /** * Exlicitly update the sound parameters associated with a sample */ void update(SoundSchedulerAtom soundAtom) { int index = soundAtom.sampleId; if (index == SoundRetained.NULL_SOUND) { return; } SoundRetained sound = soundAtom.sound; audioDevice3D.updateSample(index); if (debugFlag) { debugPrint(".update: " + index ); } soundAtom.calculateEndTime(); if (sound instanceof PointSoundRetained || sound instanceof ConeSoundRetained) { positionalSoundUpdated = true; } } /** * stop playing one specific sound node * * If setPending flag true, sound is stopped but enable state * is set to pending-on so that it is restarted. */ void stopSound(SoundSchedulerAtom soundAtom, boolean setPending) { if (audioDevice3D == null) return; if (debugFlag) debugPrint(":stopSound(" + soundAtom + "), enabled = " + soundAtom.enabled); switch (soundAtom.enabled) { case SoundSchedulerAtom.ON: if (setPending) soundAtom.setEnableState(SoundSchedulerAtom.PENDING_ON); else soundAtom.setEnableState(SoundSchedulerAtom.SOUND_OFF); break; case SoundSchedulerAtom.PENDING_OFF: soundAtom.setEnableState(SoundSchedulerAtom.SOUND_OFF); break; case SoundSchedulerAtom.PENDING_ON: if (!setPending) // Pending sounds to be stop from playing later soundAtom.setEnableState(SoundSchedulerAtom.SOUND_OFF); break; default: break; } soundAtom.status = SoundSchedulerAtom.SOUND_OFF; turnOff(soundAtom); } /** * Deactive all playing sounds * If the sound is continuous thendSilence it but leave it playing * otherwise stop sound */ synchronized void deactivateAllSounds() { SoundRetained sound; SoundRetained mirSound; SoundSchedulerAtom soundAtom; if (audioDevice3D == null) return; if (debugFlag) debugPrint(".deactivateAllSounds"); // sync this method from interrupting run() while loop synchronized (prioritizedSounds) { if (prioritizedSounds != null) { int nAtoms = prioritizedSounds.size(); if (debugFlag) debugPrint("silenceAll " + nAtoms + " Sounds"); for (int i=0; i ~/Current/MoveAppBoundingLeaf.outted, // instead transformed position and direction // points/vectors will be passed to AudioDevice directly. // vvvvvvvvvvvvvvvvvvvvvvvvvvv if (updateAll || soundAtom.testDirtyFlag(SoundRetained.XFORM_DIRTY_BIT){ Transform3D xform = new Transform3D(); ps.trans.getWithLock(xform); if (debugFlag) { debugPrint(".updateXformedParams " + "setVworldXfrm for ps @ " + ps + ":"); debugPrint(" xformPosition " + ps.xformPosition.x + ", " + ps.xformPosition.y + ", " + ps.xformPosition.z ); debugPrint(" column-major transform "); debugPrint(" " + xform.mat[0]+", " + xform.mat[1]+", "+ xform.mat[2]+", " + xform.mat[3]); debugPrint(" " + xform.mat[4]+", " + xform.mat[5]+", "+ xform.mat[6]+", " + xform.mat[7]); debugPrint(" " + xform.mat[8]+", " + xform.mat[9]+", "+ xform.mat[10]+", " + xform.mat[11]); debugPrint(" " + xform.mat[12]+", " + xform.mat[13]+", "+ xform.mat[14]+", " + xform.mat[15]); } audioDevice3D.setVworldXfrm(index, xform); soundAtom.clearStateDirtyFlag( SoundRetained.XFORM_DIRTY_BIT); // XXXX: make sure position and direction are already transformed and stored // into xformXxxxxxx fields. } // ^^^^^^^^^^^^^^^^^^^^^ */ // Set Position if (updateAll || testListenerFlag() || soundAtom.testDirtyFlag(soundAtom.attribsDirty, SoundRetained.POSITION_DIRTY_BIT) || soundAtom.testDirtyFlag(soundAtom.stateDirty, SoundRetained.XFORM_DIRTY_BIT) ) { Point3f xformLocation = new Point3f(); mirrorPtSound.getXformPosition(xformLocation); Point3d positionD = new Point3d(xformLocation); if (debugFlag) debugPrint("xform'd Position: ("+positionD.x+", "+ positionD.y+", "+ positionD.z+")" ); audioDevice3D.setPosition(index, positionD); } // Set Direction if (mirrorPtSound instanceof ConeSoundRetained) { ConeSoundRetained cn = (ConeSoundRetained)mirrorPtSound; ConeSoundRetained cnSound = (ConeSoundRetained)mirrorPtSound.sgSound; if (updateAll || // XXXX: test for XFORM_DIRTY only in for 1.2 soundAtom.testDirtyFlag(soundAtom.attribsDirty, (SoundRetained.DIRECTION_DIRTY_BIT | SoundRetained.XFORM_DIRTY_BIT) ) ) { Vector3f xformDirection = new Vector3f(); cn.getXformDirection(xformDirection); Vector3d directionD = new Vector3d(xformDirection); audioDevice3D.setDirection(index, directionD); } } } void updateSoundParams(boolean updateAll, SoundSchedulerAtom soundAtom, AuralAttributesRetained attribs) { SoundRetained mirrorSound = soundAtom.sound; SoundRetained sound = mirrorSound.sgSound; int index = soundAtom.sampleId; int arraySize; if (index == SoundRetained.NULL_SOUND) return; if (debugFlag) debugPrint(".updateSoundParams(dirytFlags=" + soundAtom.attribsDirty + ", " + soundAtom.stateDirty + ")"); // since the sound is audible, make sure that the parameter for // this sound are up-to-date. if (updateAll || soundAtom.testDirtyFlag( soundAtom.attribsDirty, SoundRetained.INITIAL_GAIN_DIRTY_BIT)) { if (attribs != null) { audioDevice3D.setSampleGain(index, (sound.initialGain * attribs.attributeGain)); } else { audioDevice3D.setSampleGain(index, sound.initialGain); } } if (updateAll || soundAtom.testDirtyFlag( soundAtom.attribsDirty, SoundRetained.LOOP_COUNT_DIRTY_BIT)) { if (debugFlag) debugPrint(" audioDevice.setLoop(" + sound.loopCount + ") called"); audioDevice3D.setLoop(index, sound.loopCount); } if (updateAll || soundAtom.testDirtyFlag( soundAtom.attribsDirty, SoundRetained.RATE_DIRTY_BIT)) { if (audioDevice3DL2 != null) { if (debugFlag) debugPrint(" audioDevice.setRateScaleFactor(" + sound.rate + ") called"); audioDevice3DL2.setRateScaleFactor(index, sound.rate); } } if (updateAll || soundAtom.testDirtyFlag( soundAtom.attribsDirty, SoundRetained.DISTANCE_GAIN_DIRTY_BIT)){ if (sound instanceof ConeSoundRetained) { ConeSoundRetained cnSound = (ConeSoundRetained)sound; // set distance attenuation arraySize = cnSound.getDistanceGainLength(); if (arraySize == 0) { // send default audioDevice3D.setDistanceGain(index, null, null, null, null); } else { Point2f[] attenuation = new Point2f[arraySize]; Point2f[] backAttenuation = new Point2f[arraySize]; for (int i=0; i< arraySize; i++) { attenuation[i] = new Point2f(); backAttenuation[i] = new Point2f(); } cnSound.getDistanceGain(attenuation, backAttenuation); double[] frontDistance = new double[arraySize]; float[] frontGain = new float[arraySize]; double[] backDistance = new double[arraySize]; float[] backGain = new float[arraySize]; for (int i=0; i< arraySize; i++) { frontDistance[i] = attenuation[i].x; frontGain[i] = attenuation[i].y; backDistance[i] = backAttenuation[i].x; backGain[i] = backAttenuation[i].y; } audioDevice3D.setDistanceGain(index, frontDistance, frontGain, backDistance, backGain); } } // ConeSound distanceGain else if (sound instanceof PointSoundRetained) { PointSoundRetained ptSound = (PointSoundRetained)sound; // set distance attenuation arraySize = ptSound.getDistanceGainLength(); if (arraySize == 0) { // send default audioDevice3D.setDistanceGain(index, null, null, null, null); } else { Point2f[] attenuation = new Point2f[arraySize]; for (int i=0; i< arraySize; i++) attenuation[i] = new Point2f(); ptSound.getDistanceGain(attenuation); double[] frontDistance = new double[arraySize]; float[] frontGain = new float[arraySize]; for (int i=0; i< arraySize; i++) { frontDistance[i] = attenuation[i].x; frontGain[i] = attenuation[i].y; } audioDevice3D.setDistanceGain(index, frontDistance, frontGain, null, null); } } // PointSound distanceGain } if ((sound instanceof ConeSoundRetained) && (updateAll || soundAtom.testDirtyFlag(soundAtom.attribsDirty, SoundRetained.ANGULAR_ATTENUATION_DIRTY_BIT)) ) { // set angular attenuation ConeSoundRetained cnSound = (ConeSoundRetained)sound; arraySize = cnSound.getAngularAttenuationLength(); if (arraySize == 0) { // send default double[] angle = new double[2]; float[] scaleFactor = new float[2]; angle[0] = 0.0; angle[1] = (Math.PI)/2.0; scaleFactor[0] = 1.0f; scaleFactor[1] = 0.0f; audioDevice3D.setAngularAttenuation(index, cnSound.NO_FILTERING, angle, scaleFactor, null); } else { Point3f[] attenuation = new Point3f[arraySize]; for (int i=0; i< arraySize; i++) { attenuation[i] = new Point3f(); } cnSound.getAngularAttenuation(attenuation); double[] angle = new double[arraySize]; float[] scaleFactor = new float[arraySize]; float[] cutoff = new float[arraySize]; for (int i=0; i< arraySize; i++) { angle[i] = attenuation[i].x; scaleFactor[i] = attenuation[i].y; cutoff[i] = attenuation[i].z; } audioDevice3D.setAngularAttenuation(index, cnSound.filterType, angle, scaleFactor, cutoff); } } } /** * Check (and set if necessary) AudioDevice3D field */ boolean checkAudioDevice3D() { if (universe != null) { if (universe.currentView != null) if (universe.currentView.physicalEnvironment != null) { audioDevice = universe.currentView.physicalEnvironment.audioDevice; if (audioDevice != null) { if (audioDevice instanceof AudioDevice3DL2) { audioDevice3DL2 = (AudioDevice3DL2)audioDevice; } if (audioDevice instanceof AudioDevice3D) { audioDevice3D = (AudioDevice3D)audioDevice; } else { // audioDevice is only an instance of AudioDevice if (internalErrors) debugPrint("AudioDevice implementation not supported"); // audioDevice3D should already be null } } else { // if audioDevice is null, clear extended class fields audioDevice3DL2 = null; audioDevice3D = null; } } } if (audioDevice3D == null) return false; if (audioDevice3D.getTotalChannels() == 0) return false; // can not render sounds on AudioEngine that has no channels return true; } /** * Clears the fields associated with sample data for this sound. * Assumes soundAtom is non-null, and that non-null atom * would have non-null sound field. */ void clearSoundData(SoundSchedulerAtom soundAtom) { if (checkAudioDevice3D() && soundAtom.sampleId != SoundRetained.NULL_SOUND) { stopSound(soundAtom, false); // force stop of playing sound // Unload sound data from AudioDevice audioDevice3D.clearSound(soundAtom.sampleId); } soundAtom.sampleId = SoundRetained.NULL_SOUND; // set load state into atom soundAtom.loadStatus = SoundRetained.LOAD_NULL; // NOTE: setting node load status not 1-to-1 w/actual load; // this is incorrect SoundRetained sound = soundAtom.sound; soundAtom.loadStatus = SoundRetained.LOAD_NULL; soundAtom.soundData = null; sound.changeAtomList(soundAtom, SoundRetained.LOAD_NULL); } /** * Attempts to load sound data for a particular sound source onto * the chosen/initialized audio device * If this called, it is assumed that SoundRetained.audioDevice is * NOT null. * If an error in loading occurs (an exception is caught,...) * an error is printed out to stderr - an exception is not thrown. * @param soundData descrition of sound source data */ // QUESTION: should this method be synchronized? void attachSoundData(SoundSchedulerAtom soundAtom, MediaContainer soundData, boolean forceReload) { if (!forceReload && (soundAtom.soundData == soundData)) { return; } SoundRetained sound = soundAtom.sound.sgSound; if (!checkAudioDevice3D()) { if (debugFlag) debugPrint(".attachSoundData audioDevice3D null"); soundAtom.loadStatus = SoundRetained.LOAD_PENDING; sound.changeAtomList(soundAtom, SoundRetained.LOAD_PENDING); return; } if (soundAtom.soundData != null) { // clear sound data field for view specific atom NOT sound node clearSoundData(soundAtom); if (soundData == null) { if (debugFlag) debugPrint(".attachSoundData with null soundData"); return; } } URL url = ((MediaContainerRetained)sound.soundData.retained).url; String path = ((MediaContainerRetained)sound.soundData.retained).urlString; InputStream stream = ((MediaContainerRetained)sound.soundData.retained).inputStream; if (url == null && path == null && stream == null) { if (debugFlag) debugPrint(".attachSoundData with null soundData"); // clear non-null sample associated with this soundData if (soundAtom.sampleId != SoundRetained.NULL_SOUND) { clearSoundData(soundAtom); } return; } int id; if (sound instanceof ConeSoundRetained) sound.soundType = AudioDevice3D.CONE_SOUND; else if (sound instanceof PointSoundRetained) sound.soundType = AudioDevice3D.POINT_SOUND; else sound.soundType = AudioDevice3D.BACKGROUND_SOUND; if (debugFlag) { debugPrint(".attachSoundData soundType = " + sound.soundType); debugPrint(".attachSoundData this is = " + sound); } // Clone the MediaContainer associated with this node and // set the capability bits for this clone to allow access to // all fields; this copy is passed to the audioDevice. // As the fields of the MediaContainer expands, this code must // be appended. MediaContainer cloneMediaContainer = new MediaContainer(); cloneMediaContainer.duplicateAttributes(soundData, true); cloneMediaContainer.setCapability(MediaContainer.ALLOW_CACHE_READ); cloneMediaContainer.setCapability(MediaContainer.ALLOW_URL_READ); id = audioDevice3D.prepareSound(sound.soundType, cloneMediaContainer); if (debugFlag) debugPrint(".attachSoundData prepareSound returned " + id); if (id == SoundRetained.NULL_SOUND) { soundAtom.loadStatus = SoundRetained.LOAD_FAILED; // NOTE: setting node load status not 1-to-1 with actual load; // this is incorrect sound.changeAtomList(soundAtom, SoundRetained.LOAD_FAILED); //System.err.println(path + ": "+ J3dI18N.getString("SoundRetained1")); } else { if (debugFlag) debugPrint(".attachSoundData - sampleId set"); soundAtom.sampleId = id; // For now loopLength=sampleLength, loop points not supported long duration = audioDevice3D.getSampleDuration(id); soundAtom.sampleLength = duration; soundAtom.loopLength = soundAtom.sampleLength; // XXXX: for most this will be 0 but not all soundAtom.loopStartOffset = 0; soundAtom.attackLength = 0; // portion of sample before loop section soundAtom.releaseLength = 0; // portion of sample after loop section soundAtom.loadStatus = SoundRetained.LOAD_COMPLETE; soundAtom.soundData = soundData; sound.changeAtomList(soundAtom, SoundRetained.LOAD_COMPLETE); if (debugFlag) debugPrint(" attachSoundData; index = "+soundAtom.sampleId); } } SoundSchedulerAtom findSoundAtom(SoundRetained node, int nthInstance) { // find nth sound atom in the list of prioritized sounds that // references this sound node // nthInstance=1 would look for first instance if (node == null) return null; SoundSchedulerAtom returnAtom = null; synchronized (prioritizedSounds) { if (!prioritizedSounds.isEmpty()) { SoundSchedulerAtom soundAtom = null; int atomFound = 0; // find sound in list and remove it int arrSize = prioritizedSounds.size(); for (int index=0; index 0) return true; else return false; } /** * set dirty flags associated with SoundSchedulerAtom */ void setAttribsDirtyFlag(SoundRetained node, int dirtyFlag) { if (debugFlag) debugPrint(".setAttribsDirtyFlag " + node ); // find sound atom that references this sound node SoundSchedulerAtom soundAtom = null; for (int i=1; ;i++) { soundAtom = findSoundAtom(node, i); if (soundAtom == null) break; soundAtom.setAttribsDirtyFlag(dirtyFlag); } } void setStateDirtyFlag(SoundRetained node, int dirtyFlag) { if (debugFlag) debugPrint(".setStateDirtyFlag " + node ); // find sound atom that references this sound node SoundSchedulerAtom soundAtom = null; for (int i=1; ;i++) { soundAtom = findSoundAtom(node, i); if (soundAtom == null) break; soundAtom.setStateDirtyFlag(dirtyFlag); } } void printAtomState(SoundSchedulerAtom atom) { SoundRetained sound = atom.sound.sgSound; debugPrint(" this atom = " + atom + " "); debugPrint(" references sound = " + sound + " "); debugPrint(" enabled " + atom.enabled); debugPrint(" status " + atom.status); debugPrint(" activated " + atom.activated); debugPrint(" released " + sound.release); debugPrint(" continuous " + sound.continuous); debugPrint(" scheduling " + atom.schedulingAction); } // Debug print mechanism for Sound nodes static final boolean debugFlag = false; static final boolean internalErrors = false; void debugPrint(String message) { if (debugFlag) System.err.println("SS."+message); } void processViewSpecificGroupChanged(J3dMessage m) { int component = ((Integer)m.args[0]).intValue(); Object[] objAry = (Object[])m.args[1]; if (((component & ViewSpecificGroupRetained.ADD_VIEW) != 0) || ((component & ViewSpecificGroupRetained.SET_VIEW) != 0)) { int i; Object obj; View v = (View)objAry[0]; ArrayList leafList = (ArrayList)objAry[2]; // View being added is this view if (v == view) { int size = leafList.size(); for (i = 0; i < size; i++) { obj = leafList.get(i); if (obj instanceof SoundRetained) { nRetainedSounds++; addSound((SoundRetained) obj); } else if (obj instanceof SoundscapeRetained) { auralAttribsChanged = true; } } } } if (((component & ViewSpecificGroupRetained.REMOVE_VIEW) != 0)|| ((component & ViewSpecificGroupRetained.SET_VIEW) != 0)) { int i; Object obj; ArrayList leafList; View v; if ((component & ViewSpecificGroupRetained.REMOVE_VIEW) != 0) { v = (View)objAry[0]; leafList = (ArrayList)objAry[2]; } else { v = (View)objAry[4]; leafList = (ArrayList)objAry[6]; } if (v == view) { int size = leafList.size(); for (i = 0; i < size; i++) { obj = leafList.get(i); if (obj instanceof SoundRetained) { SoundSchedulerAtom soundAtom = null; for (int arrIndx=1; ;arrIndx++) { soundAtom = findSoundAtom((SoundRetained)obj, arrIndx); if (soundAtom == null) break; stopSound(soundAtom, false); } } else if (obj instanceof SoundscapeRetained) { auralAttribsChanged = true; } } } } } void processBoundingLeafChanged(J3dMessage m) { // Notify all users of this bounding leaf, it may // result in the re-evaluation of the lights/fogs/backgrounds Object[] users = (Object[])(m.args[3]); int i; for (i = 0; i < users.length; i++) { LeafRetained leaf = (LeafRetained)users[i]; if (leaf instanceof SoundRetained && universe.soundStructure.isSoundScopedToView(leaf, view)) { auralAttribsChanged = true; } else if (leaf instanceof SoundscapeRetained && universe.soundStructure.isSoundscapeScopedToView(leaf, view)){ auralAttribsChanged = true; } } } @Override void cleanup() { // clean up any messages that are queued up, since they are // irrelevant // clearMessages(); } }