Package javax.media.j3d

Source Code of javax.media.j3d.SoundScheduler

/*
* 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<SoundSchedulerAtom> prioritizedSounds = new ArrayList<SoundSchedulerAtom>();

    /**
     * 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<NodeRetained> viewScopedNodes = (ArrayList<NodeRetained>)m.args[3];
  ArrayList<ArrayList<View>> scopedNodesViewList = (ArrayList<ArrayList<View>>)m.args[4];

  for (int i=0; i<nodes.length; i++) {
      Object node = nodes[i];
      if (node instanceof SoundRetained) {
    nRetainedSounds++;
    // insert sound node into sound scheduler's prioritized list
    addSound((SoundRetained) node);
      }
      else if (node instanceof SoundscapeRetained) {
    auralAttribsChanged = true;
      }
      else if (node instanceof AuralAttributesRetained) {
    auralAttribsChanged = true;
      }
      else if (node instanceof ViewPlatformRetained) {
    // XXXX: don't support multiple viewPlatforms per scheduler
    /*
    // useful for resetting VP ??
    addViewPlatform((ViewPlatformRetained) node);
    */
    if (debugFlag) {
        debugPrint(".insertNodes() viewPlatformRetained not supported yet");
    }
      }
  }

  // Handle ViewScoped Nodes
  if (viewScopedNodes != null) {
      int size = viewScopedNodes.size();
      for (int i = 0; i < size; i++) {
    NodeRetained node = viewScopedNodes.get(i);
    ArrayList<View> 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<NodeRetained> viewScopedNodes = (ArrayList<NodeRetained>)m.args[3];
  ArrayList<ArrayList<View>> scopedNodesViewList = (ArrayList<ArrayList<View>>)m.args[4];

  for (int i=0; i<nodes.length; i++) {
      Object node = nodes[i];
      if (node instanceof SoundRetained) {
    // sound is deactivated but NOT deleted
    // incase sound is reattached

// QUESTION: what's the difference in messages between really deleting
//     a node and just deactivating it.
/*
//
// can't delete atom in case it's reactivitated
//
      nRetainedSounds--;
      deleteSound((SoundRetained) node);
//
// until there's a difference in messages between detaching and deleting
// a sound node, sound is stopped and atom enable state changed but atom
// is NOT deleted from list.
*/
    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;
      }
  }
  // Handle ViewScoped Nodes
  if (viewScopedNodes != null) {
      int size = viewScopedNodes.size();
      for (int i = 0; i < size; i++) {
    NodeRetained node = viewScopedNodes.get(i);
    ArrayList<View> 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<arrSize; index++) {
        SoundSchedulerAtom soundAtom = prioritizedSounds.get(index);
        // QUESTION: which???
        if (soundAtom.sound == sound ||
      soundAtom.sound.sgSound == sound) {
      stopSound(soundAtom, false);
      prioritizedSounds.remove(index);
        }
    }
      }
  }
    }


    void changeNodeAttrib(J3dMessage m) {
  Object node = m.args[0];
  Object value = m.args[1];
  int    attribDirty = ((Integer)value).intValue();
  if (debugFlag)
      debugPrint(".changeNodeAttrib:");

  if (node instanceof SoundRetained &&
      universe.soundStructure.isSoundScopedToView(node, view)) {

      this.setAttribsDirtyFlag((SoundRetained)node, attribDirty);
      if (debugFlag)
    debugPrint("         Sound node dirty bit = " + attribDirty);
      if ((attribDirty & SoundRetained.PRIORITY_DIRTY_BIT) > 0) {
    shuffleSound((SoundRetained) node);
      }
      if ((attribDirty & SoundRetained.SOUND_DATA_DIRTY_BIT) >0) {
    if (debugFlag)
        debugPrint(".changeNodeAttrib " +
                               "SOUND_DATA_DIRTY_BIT calls loadSound");
    loadSound((SoundRetained) node, true);
      }
      if ((attribDirty & SoundRetained.MUTE_DIRTY_BIT) > 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<size; j++) {
    nodes = (Object[])nodesArr[j];


    for (i = 0; i < nodes.length; i++) {
        if (nodes[i] instanceof ConeSoundRetained && universe.soundStructure.isSoundScopedToView(nodes[i], view)) {
      ConeSoundRetained cnSndNode =
          (ConeSoundRetained)nodes[i];
      synchronized (cnSndNode) {
          cnSndNode.updateTransformChange();
      }
      // set XFORM_DIRTY_BIT in corresponding atom
      setStateDirtyFlag((SoundRetained)nodes[i],
            SoundRetained.XFORM_DIRTY_BIT);
        } else if (nodes[i] instanceof PointSoundRetained && universe.soundStructure.isSoundScopedToView(nodes[i], view)) {
      PointSoundRetained ptSndNode =
          (PointSoundRetained)nodes[i];
      synchronized (ptSndNode) {
          ptSndNode.updateTransformChange();
      }
      // set XFORM_DIRTY_BIT in corresponding atom
      setStateDirtyFlag((SoundRetained)nodes[i],
            SoundRetained.XFORM_DIRTY_BIT);
        } else if (nodes[i] instanceof SoundscapeRetained && universe.soundStructure.isSoundscapeScopedToView(nodes[i], view)) {
      SoundscapeRetained sndScapeNode =
          (SoundscapeRetained)nodes[i];
      synchronized (sndScapeNode) {
          sndScapeNode.updateTransformChange();
      }
        }
    }
      }
  }
    }


    void updateTransformedFields(SoundRetained mirSound) {
  if (mirSound instanceof ConeSoundRetained && universe.soundStructure.isSoundScopedToView(mirSound, view)) {
      ConeSoundRetained cnSndNode = (ConeSoundRetained)mirSound;
      synchronized (cnSndNode) {
    cnSndNode.updateTransformChange();
      }
  }
  else if (mirSound instanceof PointSoundRetained && universe.soundStructure.isSoundScopedToView(mirSound, view)) {
      PointSoundRetained ptSndNode = (PointSoundRetained)mirSound;
      synchronized (ptSndNode) {
    ptSndNode.updateTransformChange();
      }
  }
    }


    void activate() {
  updateThread.active = true;
  if (debugFlag)
      debugPrint(".activate(): calls sendRunMessage for immediate processing");
  VirtualUniverse.mc.sendRunMessage(universe,
            J3dThread.SOUND_SCHEDULER);
  // renderChanges() called indirectly thru processMessage now
    }


    // deactivate scheduler only if it state has such that it can not perform
    // sound processing
    void deactivate() {
  if (debugFlag)
      debugPrint(".deactivate()");
  //

  // XXXX: The following code is clearly erroneous.
  // The indendation, along with the 2nd test of
  // "if (debugFlag)" in the else clause, suggests that
  // the intent was to make the else clause apply to
  // "if (checkState())".  However, the else clause
  // actually applies to the first "if (debugFlag)".
  // This is a textbook example of why one should
  // *ALWAYS* enclose the target of an "if", "while", or
  // "else" in curly braces -- even when the target
  // consists of a single statement.
  //
  // The upshot of this, is that the else clause is
  // unconditionally executed, since "debugFlag" is a
  // static final constant that is set to false for
  // production builds.  We won't fix it now, because
  // The SoundScheduler may actually be relying on the
  // fact that all sounds are unconditionally
  // deactivated when this method is called, and we
  // don't want to introduce a new bug.
  //
  if (checkState())
      if (debugFlag)
    debugPrint("  checkState returns true");
  else {
      if (debugFlag)
    debugPrint("  checkState returns false; deactive scheduler");
      // Call deactivateAllSounds within try/catch so
      // errors won't kill the SoundScheduler.
      try {
    deactivateAllSounds();
      }
      catch (RuntimeException e) {
    System.err.println("Exception occurred " +
           "during sound deactivation:");
    e.printStackTrace();
      }
      catch (Error e) {
                // Issue 264 - catch Error
    System.err.println("Error occurred " +
           "during sound deactivation:");
    e.printStackTrace();
      }
      updateThread.active = false;
  }
    }


    // Check the ready state and return true if ready
    boolean checkState() {
  boolean runState = false;
  if (stallThread) {
      if (debugFlag)
    debugPrint(" checkState stallThread true");
      runState = false;
  }

  if (ready) {
      if (debugFlag)
    debugPrint(" checkState ready to run");
      runState = true;
  }
  else {
      // previous not ready, force reset call to see if everything
      // ready now or not
      reset();
      if (ready) {
    if (debugFlag)
        debugPrint(" checkState Now ready to run");
    runState = true;
      }
      else {
    if (debugFlag) {
        debugPrint(" checkState NOT ready to run");
    }
    runState = false;
      }
  }
  return runState;
    }

    synchronized void reset() {
  // Return quickly if universe, view, physical env, or audio
  // device are null
  if (universe == null ||
      view == null ||
      view.physicalEnvironment == null ||
      view.physicalEnvironment.audioDevice == null) {

      audioDevice = null;
      ready = false;
      return;
  }

  // Set AudioDevice
  audioDevice = view.physicalEnvironment.audioDevice;

  // Get viewPlatform; if it is null or not live, we can't render
  ViewPlatform vp = view.getViewPlatform();
  if (vp == null || vp.retained == null) {
      // System.err.println("    vp is null");
      viewPlatform = null;
      ready = false;
      return;
  }

  viewPlatform = (ViewPlatformRetained)vp.retained;
  if (!vp.isLive()) {
      ready = false;
      return;
  }

  // XXXX: Does not support multiple canvases per view, thus
  //      multiple GraphicsContext3Ds
  // QUESTION: what does that mean for sound -
  //      being applied to only ONE graphics context?
  // GET FIRST Canvas
  Canvas3D canvas = view.getFirstCanvas();
  if (canvas != null) {
      graphicsCtx = canvas.getGraphicsContext3D();
  }

  // now the render loop can be run successfully
  audioDevice3DL2 = null;
  audioDevice3D = null;
  if (audioDevice instanceof AudioDevice3DL2) {
      audioDevice3DL2 = (AudioDevice3DL2)audioDevice;
        }
  if (audioDevice instanceof AudioDevice3D) {
      audioDevice3D = (AudioDevice3D)audioDevice;
            if (debugFlag)
          debugPrint(".reset: audioDevice3D.setView");
      audioDevice3D.setView(view);
      totalChannels = audioDevice.getTotalChannels();
      if (debugFlag)
          debugPrint(" audioDevice3D.getTotalChannels returned " +
                                   totalChannels);
  }
  else {
      if (internalErrors)
          debugPrint(": AudioDevice implementation not supported");
      totalChannels = 0;
  }

        if (totalChannels == 0) {
      ready = false;
            return;
        }

        ready = true;
        // since audio device is ready; set enable flag for continuous
        // calculating userHead-to-VirtualWorld transform
        view.setUserHeadToVworldEnable(true);
  return;
    }


    void receiveAWTEvent(AWTEvent evt) {
  int eventId = evt.getID();
  if (debugFlag)
      debugPrint(".receiveAWTEvent " + eventId);
  if (ready && eventId == WindowEvent.WINDOW_ICONIFIED) {
      lastEventReceived = eventId;
  }
  else if (ready &&
     (lastEventReceived == WindowEvent.WINDOW_ICONIFIED &&
      eventId == WindowEvent.WINDOW_DEICONIFIED) ) {
      lastEventReceived = eventId;
      // used to notify
  }
    }


    /**
     * The main loop for the Sound Scheduler.
     */
    void renderChanges() {
  int nSounds = 0;
  int totalChannelsUsed = 0;
  int nPrioritizedSound = 0;
  int numActiveSounds = 0;
  if (debugFlag)
      debugPrint(" renderChanges begun");
  // XXXX: BUG?? should wait if audioDevice is NULL or nSounds = 0
  //          when a new sound is added or deleted from list, or
  //          when the audioDevice is set into PhysicalEnvironment

  if (!checkState()) {
      if (debugFlag)
    debugPrint(".workToDo()  checkState failed");
      return;
  }

  /*
  synchronized (prioritizedSounds) {
  */
      nPrioritizedSound = prioritizedSounds.size();
      if (debugFlag)
    debugPrint(" nPrioritizedSound = " + nPrioritizedSound);
      if (nPrioritizedSound == 0)
    return;

      if (auralAttribsChanged) {
    // Find closest active aural attributes in scene graph
    int nIntersected = findActiveSoundscapes();
    if (nIntersected > 0) {
        if (debugFlag)
      debugPrint("        "+ nIntersected +
           " active SoundScapes found");
        // XXXX: (Performance) calling clone everytime, even
        //     though closest AA has NOT changed, is expensive
        aaRetained = (AuralAttributesRetained)
      (findClosestAAttribs(nIntersected)).clone();
    }
    else {
        if (debugFlag) debugPrint(" NO active SoundScapes found");
    }
      }
      if (nPrioritizedSound > 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<retainedSounds.size(); i++) {
    addPrioritizedSound((SoundRetained)retainedSounds.get(i));
    nRetainedSounds++;
      }
      // XXXX: sync canvases
    Enumeration<Canvas3D> 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<nUnivSscapes; k++) {
    lss = (SoundscapeRetained)soundScapes.get(k);

    //  Find soundscapes that intersect the view platform region
    if (lss.transformedRegion == null ) {
        continue;
    }
    if ((region instanceof BoundingSphere &&
         lss.transformedRegion instanceof BoundingSphere) ||
        (region instanceof BoundingBox &&
         lss.transformedRegion instanceof BoundingBox) ||
        (region instanceof BoundingPolytope &&
         lss.transformedRegion instanceof BoundingPolytope) ) {
        lss.transformedRegion.getWithLock(region);
        if (debugFlag)
      debugPrint(" get tranformed region " + region );
    } else {
        region = (Bounds)lss.transformedRegion.clone();
        if (debugFlag)
      debugPrint(" clone tranformed region " + region );
    }
    if (region!=null && viewPlatform.schedSphere.intersect(region)){
        intersectedRegions[nSelectedSScapes] = (Bounds)region.clone();
        // test View Platform intersection(lss))
        intersectedSoundscapes[nSelectedSScapes] = lss;
        if (debugFlag)
      debugPrint(" store sscape " + lss +
           " and region " + region + " into array");
        nSelectedSScapes++;
        if (debugFlag)
      debugPrint(": region of intersection for "+
           "soundscape "+k+" found at "+J3dClock.currentTimeMillis());
    } else {
        if (debugFlag)
      debugPrint(
           ": region of intersection for soundscape "+
           k + " not found at "+ J3dClock.currentTimeMillis());
    }
      }
  }
  return(nSelectedSScapes);
    }


    AuralAttributesRetained findClosestAAttribs(int nSelectedSScapes) {
  AuralAttributes    aa = null;
  SoundscapeRetained ss = null;
  boolean            intersected = false;

  // Get auralAttributes from closest (non-null) soundscape
  ss = null;
  if (nSelectedSScapes == 1)
      ss = intersectedSoundscapes[0];
  else if (nSelectedSScapes > 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; jj<arraySize; jj++)
             debugPrint(
          "        " + distance[jj]+", "+cutoff[jj]);
      }
        }
        audioDevice3D.setFrequencyScaleFactor(
               attribs.frequencyScaleFactor);
        if (debugFlag)
      debugPrint("     freq.scale " +
            attribs.frequencyScaleFactor);
        audioDevice3D.setVelocityScaleFactor(
             attribs.velocityScaleFactor);
        if (debugFlag)
      debugPrint("     velocity scale " +
          attribs.velocityScaleFactor);
        audioDevice3D.setReflectionCoefficient(
          attribs.reflectionCoefficient);
                    if (audioDevice3DL2 != null) {
            audioDevice3DL2.setReverbCoefficient(
                                        attribs.reverbCoefficient);
            audioDevice3DL2.setDecayFilter(attribs.decayFilter);
            audioDevice3DL2.setDiffusion(attribs.diffusion);
            audioDevice3DL2.setDensity(attribs.density);
             if (debugFlag) {
          debugPrint("     reflect coeff " +
                                        attribs.reflectionCoefficient);
          debugPrint("     reverb coeff " +
                                        attribs.reverbCoefficient);
          debugPrint("     decay filter " +
                                        attribs.decayFilter);
          debugPrint("     diffusion " +
                                        attribs.diffusion);
          debugPrint("     density " +
                                        attribs.density);
                        }
                    }

                    // Precidence for determining Reverb Delay
                    // 1) If ReverbBounds set, bounds used to calculate delay.
                    //    Default ReverbBounds is null meaning it's not defined.
                    // 2) If ReverbBounds is null, ReverbDelay used
                    //    (implitic default value is defined as 40ms)

        // If Bounds null, use Reverb Delay
        // else calculate Reverb Delay from Bounds
        Bounds reverbVolume = attribs.reverbBounds;
        if (reverbVolume == null) {
      audioDevice3D.setReverbDelay(attribs.reverbDelay);
      if (debugFlag)
          debugPrint(
           "     reverbDelay " + attribs.reverbDelay);
        }
        else {
      float  reverbDelay;
      if (reverbVolume instanceof BoundingSphere) {
          // worst (slowest) case is if sound in center of
          // bounding sphere
          reverbDelay = (float)
        ((2.0*((BoundingSphere)reverbVolume).radius)/
         AuralAttributesRetained.SPEED_OF_SOUND);
      }
      else {
          // create a bounding sphere the surrounds the bounding
          // object then calcalate the worst case as above
          BoundingSphere tempSphere =
        new BoundingSphere(reverbVolume);
          reverbDelay = (float)
        ((2.0 * tempSphere.radius)/
        AuralAttributesRetained.SPEED_OF_SOUND);
      }
      audioDevice3D.setReverbDelay(reverbDelay);
      if (debugFlag)
          debugPrint("     reverbDelay " + reverbDelay);
        }

                    // Audio device makes calculations for reverb decay
                    // using API's precidence where positive reverb order
                    // is used to clamp reverb decay time.
                    // Because of that the values are just passed along.
        audioDevice3D.setReverbOrder(attribs.reverbOrder);
                    if (audioDevice3DL2 != null)
            audioDevice3DL2.setDecayTime(attribs.decayTime);
    } // sync attribs
          resetAA = true;
      // attribs not null

      else if (lastAA != null) {
          // Even when there are no active auralAttributes, still check
          // if a set of auralAttributes was passed to the AudioDevice,
          // thus is now inactive and must be cleared out (set to
          // default values).
                // Gain scale factor of 1.0 implicit by default -  this value
                // passed to AudioDevice3D as part of InitialScaleFactor value.
          if (debugFlag)
              debugPrint(": set updateDefaultAAs");
          audioDevice3D.setRolloff(1.0f);
          audioDevice3D.setReflectionCoefficient(0.0f);
          audioDevice3D.setReverbDelay(40.0f);
          audioDevice3D.setReverbOrder(0);
          // Distance filter parameters
          audioDevice3D.setDistanceFilter(
      AuralAttributesRetained.NO_FILTERING, null, null);
          audioDevice3D.setFrequencyScaleFactor(1.0f);
          audioDevice3D.setVelocityScaleFactor(0.0f);
                if (audioDevice3DL2 != null) {
              audioDevice3DL2.setReverbCoefficient(0.0f);
              audioDevice3DL2.setReflectionDelay(20.0f);
        audioDevice3DL2.setDecayTime(1000.0f);
        audioDevice3DL2.setDecayFilter(5000.0f);
        audioDevice3DL2.setDiffusion(1.0f);
        audioDevice3DL2.setDensity(1.0f);
                }

    // Any change in AuralAttrib should force an update of all
    // active sounds as passed to AudioDevice3D.
          resetAA = true;
      }
      else  {
                if (debugFlag)
          debugPrint(" updateAuralAttribs: none set or cleared");
      }
/*
            attribs.aaDirty = false;
*/
      lastAA = attribs;
      auralAttribsChanged = false;
        }
    }


    void processSoundAtom(SoundSchedulerAtom soundAtom) {
  SoundRetained mirSound = soundAtom.sound;
  SoundRetained sound = mirSound.sgSound;

  // Sounds that have finished playing are not put into list
  if ((soundAtom.status == SoundSchedulerAtom.SOUND_COMPLETE) &&
      (soundAtom.enabled != SoundSchedulerAtom.PENDING_ON )) {
      // XXXX:/QUESTION test for immediate mode (?)

      // Unless the sound has been re-started, there's no need
      // to process sound the finished playing the last time thru
      // the run() loop
      return// don't need to process unless re-started
  }

  // Sound must be loaded if it hasn't been successfully loaded already
  if (soundAtom.loadStatus != SoundRetained.LOAD_COMPLETE) {
      // should be OK for soundData to be NULL - this will unclear
      if (debugFlag)
    debugPrint(": LOAD_PENDING - try attaching");
      attachSoundData(soundAtom, sound.soundData, false);
  }

  // if the loadStatus is STILL NOT COMPLETE, wait to schedule
  if (soundAtom.loadStatus != SoundRetained.LOAD_COMPLETE) {
      if (debugFlag)
    debugPrint(": not LOAD_COMPLETE yet, so bail");
      // if sound is still not loaded, or loaded with null
      return// don't process this sound now
  }

  if (resetAA)  { // explicitly force update of gain for sound
      soundAtom.setAttribsDirtyFlag(SoundRetained.INITIAL_GAIN_DIRTY_BIT);
  }

  // Determine if sound is "active"
  //    Immediate mode sounds are always active.
  //    Sounds (both background and positional) are active only when their
  //      scheduling region intersects the viewPlatform.
  boolean intersected = false;
  if (!updateThread.active) {
      region = null;
      intersected = false; // force sound to be made inactive
      if (debugFlag)
    debugPrint(": updateThread NOT active");
  }
  else if (sound.getInImmCtx()) {
      region = null;
      intersected = true;
      if (debugFlag)
    debugPrint(": sound.getInImmCtx TRUE");
  }
  else {
      if ( sound.schedulingRegion != null &&
     mirSound.transformedRegion != null ) {
    // QUESTION: shouldn't mirror sound transformedRegion be
    //        set to null when sound node's region set null?
    if ((region instanceof BoundingSphere &&
         mirSound.transformedRegion instanceof BoundingSphere) ||
        (region instanceof BoundingBox &&
         mirSound.transformedRegion instanceof BoundingBox) ||
        (region instanceof BoundingPolytope &&
         mirSound.transformedRegion instanceof BoundingPolytope)){
        mirSound.transformedRegion.getWithLock(region);
    } else {
        region = (Bounds)mirSound.transformedRegion.clone();
    }
      } else {
    region = null;
      }

      if (region != null) {
    if (debugFlag)
        debugPrint(": region is " + region );
    intersected = viewPlatform.schedSphere.intersect(region);
    if (debugFlag)
        debugPrint("    intersection with viewPlatform is " +
             intersected );
      }
      else {
    intersected = false;
    if (debugFlag)
        debugPrint(": region is null, " +
             "so region does NOT intersect viewPlatform");
      }
  }

  // Get scheduling action based on sound state (flags, status)
  // Sound must be unmuted or pending unmuting and
  // either immediate mode node, or if retained
  // then intersecting active region and switch state must be on.
  if (debugFlag) {
      debugPrint(":      intersected = " + intersected);
      debugPrint(":      switchState = " + mirSound.switchState);
      if (mirSound.switchState != null)
    debugPrint(":      switchOn = " + mirSound.switchState.currentSwitchOn);
      debugPrint(":      soundAtom.muted = " + soundAtom.muted);
  }
  if ( (sound.getInImmCtx() ||
        (intersected &&
         mirSound.switchState != null &&
         mirSound.switchState.currentSwitchOn) ) &&
       (soundAtom.muted == soundAtom.UNMUTED ||
        soundAtom.muted == soundAtom.PENDING_UNMUTE) ) {
      if (debugFlag)
    debugPrint(": calcActiveSchedAction");
      soundAtom.schedulingAction = soundAtom.calcActiveSchedAction();
  }
  else {
      if (debugFlag)
    debugPrint(": calcInactiveSchedAction");
      soundAtom.schedulingAction = soundAtom.calcInactiveSchedAction();
  }

  if (debugFlag) {
      debugPrint(": scheduling action calculated " +
           "as "+ soundAtom.schedulingAction);
      debugPrint("    dirtyFlag test of LISTENER_CHANGED " +
           testListenerFlag() );
  }

  // If state has not changed but parameters have, set
  // action to UPDATE.
  // This test includes checking that SoundScheduler dirtyFlag
  // set when Eye, Ear or ImagePlate-to-Vworld Xform changed
  // even for non-BackgroundSounds
  if ((soundAtom.schedulingAction == SoundSchedulerAtom.LEAVE_AUDIBLE) &&
      (soundAtom.testDirtyFlags() || (testListenerFlag() &&
              !(sound instanceof BackgroundSoundRetained)))) {
      if (debugFlag) {
    if (testListenerFlag()) {
        debugPrint(" testListenerFlag = " +
             testListenerFlag());
    }
    debugPrint(": action changed from " +
         "as LEAVE_AUDIBLE to UPDATE");
      }
      soundAtom.schedulingAction = SoundSchedulerAtom.UPDATE;
  }

  // Update prioritized list of sounds whose scheduling action
  // demands processing...

  // Ensure sound are not stopped while looping thru prioritized sounds
  switch (soundAtom.schedulingAction) {
  case SoundSchedulerAtom.TURN_OFF:
      soundAtom.status = SoundSchedulerAtom.SOUND_OFF;
      turnOff(soundAtom); // Stop sound that need to be turned off
      if (debugFlag)
    debugPrint(": sound " + soundAtom.sampleId +
         " action OFF results in call to stop");
      soundAtom.schedulingAction = SoundSchedulerAtom.LEAVE_OFF;
      break;

  case SoundSchedulerAtom.MAKE_AUDIBLE:
  case SoundSchedulerAtom.MAKE_SILENT:
  case SoundSchedulerAtom.LEAVE_AUDIBLE:
  case SoundSchedulerAtom.LEAVE_SILENT:
  case SoundSchedulerAtom.PAUSE_AUDIBLE:
  case SoundSchedulerAtom.PAUSE_SILENT:
  case SoundSchedulerAtom.RESUME_AUDIBLE:
  case SoundSchedulerAtom.RESUME_SILENT:
  case SoundSchedulerAtom.UPDATE:

      // Test for sound finishing playing since the last
      // thru the run() loop.
      //
      // test current time against endTime of sound to determine
      // if sound is Completely finished playing
      long currentTime = J3dClock.currentTimeMillis();
      if (soundAtom.endTime>0 && 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<nSounds; i++) {
    soundAtom = prioritizedSounds.get(i);
    mirSound = soundAtom.sound;
    sound = mirSound.sgSound;
    if (debugFlag) {
        debugPrint(" calcSchedulingAction: sound at " + sound);
        printAtomState(soundAtom);
    }
    // First make a list of switched on live sounds
    // make sure to process turned-off sounds even if they are
    // NOT active
    processSound = false;
    if ( (!sound.source.isLive() &&
          !sound.getInImmCtx() ) ) {
        if (debugFlag) {
      debugPrint(" calcSchedulingAction sound " +
           sound + " is NOT Live");
      if (mirSound.source != null) {
          if (mirSound.source.isLive()!=sound.source.isLive())
        debugPrint(
             " !=!=!=  sound.isLive != mirSound");
      }
        }
        if (soundAtom.playing ||
      (soundAtom.enabled == SoundSchedulerAtom.ON)) {
      soundAtom.setEnableState(SoundSchedulerAtom.PENDING_OFF);
      processSound = true;
      if (debugFlag)
          debugPrint(" calcSchedulingAction !isLive: " +
               "sound playing or ON, so set PENDING_OFF");
        }
        else if (soundAtom.enabled==SoundSchedulerAtom.PENDING_OFF){
      processSound = true;
      if (debugFlag)
          debugPrint(" calcSchedulingAction !isLive: " +
               "sound == PENDING_OFF so process");
        }
        else if (soundAtom.enabled==SoundSchedulerAtom.PENDING_ON) {
      soundAtom.setEnableState(SoundSchedulerAtom.OFF);
      if (debugFlag)
          debugPrint(" calcSchedulingAction !isLive: " +
               "sound == PENDING_ON so set OFF");
        }
    }
    else // live and switched on retained node or
        // non-retained (immediate mode) node
        processSound = true;
    }

    if (processSound) {
        numSoundsToProcess++;
        if (debugFlag) {
      debugPrint(".testListenerFlag = " +
           testListenerFlag() + "....");
      debugPrint("       contents of live sound " +
           soundAtom.sampleId + " before processing," );
      debugPrint(" >>>>>>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; i<nAtoms; i++) {
    soundAtom = prioritizedSounds.get(i);
    mirSound = (SoundRetained)soundAtom.sound;
    sound = mirSound.sgSound;
    int sampleId   = soundAtom.sampleId;
    int status     = soundAtom.status;

    if (debugFlag) {
        debugPrint(":       contents of current sound " +
             soundAtom.sampleId + " before switch on sAction" );
        printAtomState(soundAtom);
    }

    if (soundAtom.status == SoundSchedulerAtom.SOUND_COMPLETE) {
        continue;
    }
    if (soundAtom.schedulingAction == SoundSchedulerAtom.DO_NOTHING) {
        continue;
    }
    if (sampleId == SoundRetained.NULL_SOUND) {
        // skip it until next time thru calcSchedulingAction
        continue;
    }
    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");
        }
        // now that the exact muting state is known get the actual
        // number of channels used by this sound and add to total
        int numberChannels =
      audioDevice3D.getNumberOfChannelsUsed(sampleId);
        soundAtom.numberChannels = numberChannels; // used in audio device
        totalChannelsUsed += numberChannels;  // could return zero
    } //  scheduling is for silent sound

    else {
        // First, test to see if the sound can play as unmuted.
        int numberChannels =
      audioDevice3D.getNumberOfChannelsUsed(sampleId, false);
        // Mute sounds that have too low priority
        if ((totalChannelsUsed+numberChannels)>totalChannels) {
      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<nAtoms; i++) {
    soundAtom = prioritizedSounds.get(i);
    if (soundAtom.status == SoundSchedulerAtom.SOUND_OFF ||
        soundAtom.status == SoundSchedulerAtom.SOUND_COMPLETE )
        continue;
    long endTime = soundAtom.endTime;
    if (endTime < 0)
        // skip sounds that are to play infinitely (until stopped)
        continue;
    if (debugFlag) {
        if (endTime == 0)
      debugPrint(".shortestTimeToFinish: " +
           "Internal Error - endTime 0 while sound playing");
    }
    // for all playing sounds (audible and silent) find how much
    // time is left before the sound completed playing
    long timeLeft = endTime - currentTime;
    if (debugFlag)
        debugPrint(
             "              shortestTimeToFinish timeLeft = " +
             timeLeft);
    if (timeLeft < 0L) {
        // normalize completed sounds; force to zero
        // so no waiting occurs before scheduler re-entered
        timeLeft = 0L;
    }
    if (shortestTime < 0L) {
        // save first atom's time as shortest
        shortestTime = timeLeft;
    }
    else if (timeLeft < shortestTime) {
        shortestTime = timeLeft;
    }
      }
  }
  if (debugFlag)
      debugPrint(".shortestTimeToFinish returns " + shortestTime );
  return shortestTime;
    }


    /**
     * Perform the scheduling action for each prioritized sound.
     *
     * Now, finally, the scheduling action value reflects what is
     * requested and what is physically posible to perform.
     * So, for each sound in list of prioritized enabled sounds,
     * start/update sounds that are REALLY supposed to be either
     * playing audibly or playing silently.
     * @return number of active (audible and silent) sounds
     */
    int  performActions()  {
  // Temp variables
  SoundRetained sound;
  SoundRetained mirSound;
  int nAtoms;
  SoundSchedulerAtom soundAtom;
  AuralAttributesRetained attribs;
  int numActiveSounds = 0;
  int sampleId;

  synchronized (prioritizedSounds) {
      nAtoms = prioritizedSounds.size();
      for (int i=0; i<nAtoms; i++) {
    // XXXX: (Enhancement) Get all sound node fields here
    //          and store locally for performance
    soundAtom = prioritizedSounds.get(i);
    mirSound = soundAtom.sound;
    sound = mirSound.sgSound;
    sampleId = soundAtom.sampleId;

    if (sampleId == SoundRetained.NULL_SOUND) {
        // skip it until next time thru calcSchedulingAction
        continue;
    }

    // Two flags denoting that AuralAttributes have be changed and thus
    // sounds have to potentially be rendered are maintained and set in
    // updateAuralAttribs().
    resetAA = false;

    // check to see if aural attributes changed and have to be updated
    // must be done before list of sound processed so that Aural Attributes
    // that affect Sound fields can be set in AudioDevice
    // XXXX: this is not effient if auralAttribs always the same
    if (sound.getInImmCtx()) {
        if (graphicsCtx !=null && graphicsCtx.auralAttributes !=null) {
      aaImmed = (AuralAttributesRetained)
          (graphicsCtx.auralAttributes.retained);
      attribs = aaImmed;
        }
        else  {
      attribs = null;
        }
    }
    else {
        attribs = aaRetained;
    }
    updateAuralAttribs(attribs);

    if (debugFlag) {
        debugPrint(":       contents of current sound " +
             sampleId + " before start/update " );
        printAtomState(soundAtom);
    }

    switch (soundAtom.schedulingAction) {
    case SoundSchedulerAtom.RESTART_AUDIBLE:
        // stop sound first then fall thru to re-start
        turnOff(soundAtom);
    case SoundSchedulerAtom.START_AUDIBLE:
        // Pause and Resume related actions are checked when sound
        // is to be started or restarted
        if (soundAtom.paused == soundAtom.PENDING_PAUSE)
      pause(soundAtom);
        if (soundAtom.paused == soundAtom.PENDING_UNPAUSE)
      unpause(soundAtom);
        if (soundAtom.paused == soundAtom.UNPAUSED) {
      // if its unpaused, start audible sound
      soundAtom.status = SoundSchedulerAtom.SOUND_AUDIBLE;
      render(true, soundAtom, attribs);
        }
        else // sound paused
      soundAtom.status = SoundSchedulerAtom.SOUND_PAUSED;
      // start it after when the sound is not paused
      soundAtom.setEnableState(SoundSchedulerAtom.PENDING_ON);
        }
        numActiveSounds++;
        break;

    case SoundSchedulerAtom.RESTART_SILENT:
        // stop sound first then fall thru to re-start
        turnOff(soundAtom);
    case SoundSchedulerAtom.START_SILENT:
        // Pause and Resume related actions are checked when sound
        // is to be started or restarted
        if (soundAtom.paused == soundAtom.PENDING_PAUSE)
      pause(soundAtom);
        if (soundAtom.paused == soundAtom.PENDING_UNPAUSE)
      unpause(soundAtom);
        if (soundAtom.paused == soundAtom.UNPAUSED) {
      // if the sound is unpaused, start silent sound
      soundAtom.status = SoundSchedulerAtom.SOUND_SILENT;
      render(true, soundAtom, attribs);
        }
        else // sound paused
      soundAtom.status = SoundSchedulerAtom.SOUND_PAUSED;
      // start it after when the sound is not paused
      soundAtom.setEnableState(SoundSchedulerAtom.PENDING_ON);
        }
        numActiveSounds++;
        break;

    case SoundSchedulerAtom.RESUME_AUDIBLE:
        // pause then fall thru set make audible
        unpause(soundAtom);
    case SoundSchedulerAtom.MAKE_AUDIBLE:
        // change status to audible then update sound
        soundAtom.status = SoundSchedulerAtom.SOUND_AUDIBLE;
        render(false, soundAtom, attribs);
        numActiveSounds++;
        break;
    case SoundSchedulerAtom.RESUME_SILENT:
        // pause then fall thru set make silent
        unpause(soundAtom);
    case SoundSchedulerAtom.MAKE_SILENT:
        // change status to silent AFTER calling render so
        // that a currently audible sound will be muted.
        // XXXX: why set status AFTER??
        render(false, soundAtom, attribs);
        soundAtom.status = SoundSchedulerAtom.SOUND_SILENT;
        numActiveSounds++;
        break;

    case SoundSchedulerAtom.PAUSE_AUDIBLE:
    case SoundSchedulerAtom.PAUSE_SILENT:
        pause(soundAtom);
        soundAtom.status = SoundSchedulerAtom.SOUND_PAUSED;
        numActiveSounds++;
        break;

    case SoundSchedulerAtom.UPDATE:
        render(false, soundAtom, attribs);
        numActiveSounds++;
        break;

    case SoundSchedulerAtom.LEAVE_AUDIBLE:
    case SoundSchedulerAtom.LEAVE_SILENT:
    case SoundSchedulerAtom.LEAVE_PAUSED:
        if (resetAA || soundAtom.testDirtyFlags())
      render(false, soundAtom, attribs);
        if (debugFlag)
      debugPrint(": LEAVE_AUDIBLE or _SILENT " +
           "seen");
        numActiveSounds++;
        break;

    case SoundSchedulerAtom.TURN_OFF:
        turnOff(soundAtom);
        break;

    case SoundSchedulerAtom.LEAVE_OFF:
    case SoundSchedulerAtom.COMPLETE:
    case SoundSchedulerAtom.DO_NOTHING:
        break;

    default:
        if (internalErrors)
      debugPrint(": Internal Error"+
           " unknown action");
        break;
    }
    // Clear atom state and attrib dirty flags
    soundAtom.clearStateDirtyFlag();
    soundAtom.clearAttribsDirtyFlag();

      } // for sounds in priority list
  }

  // Now that aural attribute change forced each processed sounds
  // to be updated, clear this special reset aural attrubute flag
  resetAA = false;

  return numActiveSounds;
    }


    /**
     * render (start or update) the oscillator associated with this sound
     */
    void render(boolean startFlag,
    SoundSchedulerAtom soundAtom,
    AuralAttributesRetained attribs) {

  SoundRetained mirrorSound  = soundAtom.sound;
  SoundRetained sound  = mirrorSound.sgSound;
  if (debugFlag)
      debugPrint(".render " + sound);

  if ( soundAtom.sampleId == SoundRetained.NULL_SOUND ||
       soundAtom.soundData == null ) {
      if (internalErrors)
    debugPrint(".render - Internal Error: " +
         "null sample data");
      return;
  }

  int index = soundAtom.sampleId;

  //  Depending on Mute and/or pause flags, set sound parameters
  if (startFlag) {
      if ( (sound instanceof PointSoundRetained) ||
     (sound instanceof ConeSoundRetained) ) {
    updateXformedParams(true, soundAtom);
      }
      updateSoundParams(true, soundAtom, attribs);
      start(soundAtom);
  }
  else {
      if (soundAtom.status == SoundSchedulerAtom.SOUND_AUDIBLE) {
    if ( (sound instanceof PointSoundRetained) ||
         (sound instanceof ConeSoundRetained) ) {
        updateXformedParams(false, soundAtom);
    }
    updateSoundParams(false, soundAtom, attribs);
    update(soundAtom);
      }
  }  // if sound Audible
    } // render


    /**
     * Start the sample associated with this sound
     * Do everything necessary to start the sound:
     *     set start time
     *     the oscillator associated with this sound
     */
    void start(SoundSchedulerAtom soundAtom) {
  SoundRetained sound = soundAtom.sound.sgSound;
  int index = soundAtom.sampleId;
  int startStatus = -1;
  if (index != SoundRetained.NULL_SOUND &&
      (startStatus = audioDevice3D.startSample(index)) >= 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<nAtoms; i++) {
        // XXXX: (Enhancement) Get all sound node fields here
        //          and store locally for performance
        soundAtom = prioritizedSounds.get(i);
        mirSound = soundAtom.sound;
        sound = mirSound.sgSound;
        if (sound.continuous) {
      // make playing sound silent
      if (debugFlag)
          debugPrint("deactivateAll atomScheduling " +
               "before calcInactiveSchedAction" +
               soundAtom.schedulingAction);
      soundAtom.schedulingAction =
          soundAtom.calcInactiveSchedAction();
      if (debugFlag)
          debugPrint("deactivateAll atomScheduling " +
               "after calcInactiveSchedAction" +
               soundAtom.schedulingAction);
      // perform muting of sound
      muteSilentSound(soundAtom)// mark sound as silence
        }
        else  {
      // stop playing sound but make pending on
      stopSound(soundAtom, true); // force pendingOn TRUE
      soundAtom.schedulingAction = soundAtom.LEAVE_OFF;
      if (debugFlag)
          debugPrint("deactivateAll atomScheduling " +
               "forced to TURN_OFF, set pending On");
        }

    } // for sounds in priority list
      }
  }
  performActions();
    }


    /**
     * Pause all activity playing sounds
     */
    synchronized void pauseAllSounds() {
  SoundRetained sound;
  SoundRetained mirSound;

  if (audioDevice3D == null)
      return;

  stallThread = true;
  if (debugFlag)
      debugPrint(".pauseAll stallThread set to true");

  // sync this method from interrupting run() while loop
  synchronized (prioritizedSounds) {
      if (prioritizedSounds != null) {
    int nAtoms = prioritizedSounds.size();
    if (debugFlag)
        debugPrint(":pauseAll " + nAtoms + " Sounds");
    for (int i=0; i<nAtoms; i++) {
        // XXXX: (Enhancement) Get all sound node fields here
        //          and store locally for performance
        SoundSchedulerAtom soundAtom = prioritizedSounds.get(i);
        mirSound = soundAtom.sound;
        sound = mirSound.sgSound;

        switch (soundAtom.enabled) {
        case SoundSchedulerAtom.ON:
        case SoundSchedulerAtom.PENDING_OFF:
      pause(soundAtom);
      if (debugFlag)
          debugPrint(
               ".pauseAllSounds PAUSE sound " + sound);
      break;
        default:
      break;
        }
    } // for sounds in priority list
      }
  }
    }


    /**
     * Resume playing all paused active sounds
     */
    synchronized void resumeAllSounds() {
  SoundRetained sound;
  SoundRetained mirSound;

  if (audioDevice3D == null)
      return;

  if (debugFlag)
      debugPrint(".resumeAll stallThread set to true");

  // sync this method from interrupting run() while loop
  synchronized (prioritizedSounds) {
      if (prioritizedSounds != null) {
    int nAtoms = prioritizedSounds.size();
    if (debugFlag)
        debugPrint(": resumeAll " + nAtoms + " Sounds ");

    for (int i=0; i<nAtoms; i++) {
        // XXXX: (Enhancement) Get all sound node fields here
        //          and store locally for performance
        SoundSchedulerAtom soundAtom  = prioritizedSounds.get(i);
        mirSound = soundAtom.sound;
        sound = mirSound.sgSound;

        switch (soundAtom.enabled) {
        case SoundSchedulerAtom.ON:
        case SoundSchedulerAtom.PENDING_OFF:
      unpause(soundAtom);
      if (debugFlag)
          debugPrint(".resumeAll - sound = " + sound);
      break;
        default:
      break;
        }
    } // for sounds in priority list
      }
  }
  stallThread = false;
    }

    /**
     * Stop all activity playing sounds
     */
    synchronized void stopAllSounds() {
  stopAllSounds(false);
    }

    synchronized void stopAllSounds(boolean setPlayingSoundsPending) {
  // QUESTION: how can I assure that all sounds on device
  //     are stopped before thread paused/shutdown
  if (debugFlag)
      debugPrint(".stopAllSounds entered");
  SoundRetained sound;
  SoundRetained mirSound;

  if (audioDevice3D == null)
      return;

  if (lastEventReceived == WindowEvent.WINDOW_ICONIFIED) {
      return// leave sounds playing
  }

  // sync this method from interrupting run() while loop
  synchronized (prioritizedSounds) {
      if (prioritizedSounds != null) {
    int nAtoms = prioritizedSounds.size();
    if (debugFlag)
        debugPrint(": stopAll " + nAtoms + " Sounds ");

    for (int i=0; i<nAtoms; i++) {
        // XXXX: (Enhancement) Get all sound node fields here
        //          and store locally for performance
        SoundSchedulerAtom soundAtom = prioritizedSounds.get(i);
        if (debugFlag)
      debugPrint("      stop(" + soundAtom + ")");
        // stop playing Sound  - optionally set pending enabled
        stopSound(soundAtom, setPlayingSoundsPending);
    } // for sounds in priority list

    // QUESTION: - removed the code that empties out prioritized
    //      sound atom list.  Are there cases when core calling
    //      StopAllSounds expects sounds to be cleared??
      }
  }
  if (debugFlag)
      debugPrint(".stopAllSounds exited");
    }

      // XXXX: Mute All Sounds, complementary to Stop All Sounds
      //     "should return from run loop - but simply WAIT until sounds
      //     are unmuted. " ???


    /**
     * pause the sample associated with this sound
     */
    void pause(SoundSchedulerAtom soundAtom) {
  if (soundAtom.sampleId == SoundRetained.NULL_SOUND)
      return;
  // Ensure sound are not modified while looping thru prioritized sounds
  if (debugFlag)
      debugPrint(".pause");
  audioDevice3D.pauseSample(soundAtom.sampleId);
  soundAtom.setPauseState(soundAtom.PAUSED);
    }

    void unpause(SoundSchedulerAtom soundAtom) {
  if (soundAtom.sampleId == SoundRetained.NULL_SOUND)
      return;
  if (debugFlag)
      debugPrint(".unpause");
  audioDevice3D.unpauseSample(soundAtom.sampleId);
  soundAtom.setPauseState(soundAtom.UNPAUSED);
    }

    /**
     * stop the sample associated with this sound
     */
    void turnOff(SoundSchedulerAtom soundAtom) {
  // Ensure sound are not stopped while looping thru prioritized sounds
  if (soundAtom.sampleId == SoundRetained.NULL_SOUND)
      return;
  if (debugFlag)
      debugPrint(".turnOff");
  if (audioDevice3D.stopSample(soundAtom.sampleId) < 0) {
      if (internalErrors) {
    debugPrint("Internal Error: stop sample error");
      }
  }
  soundAtom.playing = false;
  soundAtom.startTime = 0;
  soundAtom.endTime = 0;

    }


    /**
     * Update VirtualWorld local transform, sound position and direction.
     *
     * This is done dynamically from PointSoundRetained as these fields
     * are updated (when soundAtom.status is AUDIBLE or SILENT), or by this.
     * render() method when sound is started (sound.enabled is true).
     *
     * This method should only be called if mirror sound is a Point or
     * ConeSound.
     *
     * Important: pre-transformed position and direction sent to AudioDevice.
     */
    void updateXformedParams(boolean updateAll, SoundSchedulerAtom soundAtom) {
  PointSoundRetained mirrorPtSound  = (PointSoundRetained)soundAtom.sound;
  PointSoundRetained ptSound = (PointSoundRetained)mirrorPtSound.sgSound;
  int index = soundAtom.sampleId;
  if (index == SoundRetained.NULL_SOUND)
      return;
  PointSoundRetained ps = (PointSoundRetained)mirrorPtSound;

  // Set Transform

  /*
  // XXXX: per sound tranforms can now be passed to AudioDevice
  //     modify and execute the code below

  //     MoveAppBoundingLeaf > ~/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<arrSize; index++) {
        soundAtom = prioritizedSounds.get(index);
        if (soundAtom.sound == null)
      continue;
        // soundAtom.sound is address of mirror sound not org node
        if (soundAtom.sound.sgSound == node) {
      atomFound++;
      // orginal app node pass into method
      // QUESTION: is mirror node still correct?
      // XXXX: ensure only mirror nodes passed into method
      if (atomFound == nthInstance) {
          returnAtom = soundAtom;
          break;
      }
        }
        else if (soundAtom.sound.sgSound == node.sgSound)  {
      atomFound++;
      // store potentially new mirror sound into soundAtom
      soundAtom.sound = node;
      if (atomFound == nthInstance) {
          returnAtom = soundAtom;
          break;
      }
        }
    }
      }
  }
  return returnAtom;
    }


    /**
     * 'Dirty' flag == listenerUpdated flag
     * The ambiguous name 'dirtyFlag' is a legacy from when there was only a
     * single dirty flag set by Core yet tested and cleared in this scheduler.
     * These methods specifically set/test/clear the local listenerUpdated flag.
     */
    // Called by CanvasViewCache when listener parameter(s) changes
    void setListenerFlag(int flag) {
  listenerUpdated |= flag;
    }

    void clearListenerFlag() {
  listenerUpdated = 0x0;
    }

    boolean testListenerFlag() {
  // Test if any bits are on
  if (listenerUpdated > 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();
    }
}
TOP

Related Classes of javax.media.j3d.SoundScheduler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.