0001: /*
0002: * $RCSfile: SoundRetained.java,v $
0003: *
0004: * Copyright 1996-2008 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0006: *
0007: * This code is free software; you can redistribute it and/or modify it
0008: * under the terms of the GNU General Public License version 2 only, as
0009: * published by the Free Software Foundation. Sun designates this
0010: * particular file as subject to the "Classpath" exception as provided
0011: * by Sun in the LICENSE file that accompanied this code.
0012: *
0013: * This code is distributed in the hope that it will be useful, but WITHOUT
0014: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0015: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0016: * version 2 for more details (a copy is included in the LICENSE file that
0017: * accompanied this code).
0018: *
0019: * You should have received a copy of the GNU General Public License version
0020: * 2 along with this work; if not, write to the Free Software Foundation,
0021: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0022: *
0023: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0024: * CA 95054 USA or visit www.sun.com if you need additional information or
0025: * have any questions.
0026: *
0027: * $Revision: 1.8 $
0028: * $Date: 2008/02/28 20:17:30 $
0029: * $State: Exp $
0030: */
0031:
0032: package javax.media.j3d;
0033:
0034: import java.util.Vector;
0035: import java.util.ArrayList;
0036:
0037: /**
0038: * SoundRetained is an abstract class that contains instance varables common
0039: * to all retained sounds.
0040: */
0041:
0042: abstract class SoundRetained extends LeafRetained {
0043:
0044: /**
0045: * Null Sound identifier denotes sound is not created or initialized
0046: */
0047: static final int NULL_SOUND = -1;
0048:
0049: /**
0050: * sound data associated with sound source
0051: */
0052: MediaContainer soundData = null;
0053:
0054: /**
0055: * Overall Scale Factor applied to sound.
0056: */
0057: float initialGain = 1.0f; // Valid values are >= 0.0.
0058:
0059: /**
0060: * Number of times sound is looped/repeated during play
0061: */
0062: int loopCount = 0; // Range from 0 to POSITIVE_INFINITY(-1)
0063:
0064: /**
0065: * Switch for turning sound on or off while the sound is "active"
0066: */
0067: boolean enable = false;
0068:
0069: /**
0070: * Type of release when sound is disabled.
0071: * If true, sound plays thru to end of sample before disabled
0072: * Otherwise, sound is disabled immediately.
0073: */
0074: boolean release = false;
0075:
0076: /**
0077: * Flag denoting if sound silently continues playing when it's deactivated.
0078: */
0079: boolean continuous = false;
0080:
0081: /**
0082: * Flag denoting if sound is explicitly muted, so that if begins playing
0083: * it will be played silently.
0084: */
0085: boolean mute = false;
0086:
0087: /**
0088: * Flag denoting if sound is paused from playing - waiting to be resumed
0089: */
0090: boolean pause = false;
0091:
0092: /**
0093: * Sound priority ranking value.
0094: * Valid values are 0.0 to 1.0
0095: */
0096: float priority = 1.0f;
0097:
0098: /**
0099: * Rate Scale Factor applied to sounds playback sample rate in Hertz.
0100: * Valid values are 0.0 to 1.0
0101: */
0102: float rate = 1.0f;
0103:
0104: /**
0105: * The Boundary object defining the sound's scheduling region.
0106: */
0107: Bounds schedulingRegion = null;
0108:
0109: /**
0110: * The bounding leaf reference
0111: */
0112: BoundingLeafRetained boundingLeaf = null;
0113:
0114: /**
0115: * The transformed bounds from either schedulingRegion or boundingLeaf
0116: */
0117: Bounds transformedRegion = null;
0118:
0119: // Dirty bit flags used to pass change as part of message, and are
0120: // acclummuated/stored in SoundSchedulerAtoms.
0121: // These flags are grouped into two catagories:
0122: // attribsDirty for sound node fields
0123: // stateDirty for changes to sound state not reflected by sound fields.
0124:
0125: // Attributes Dirty bit flags
0126: // This bitmask is set when sound node attribute is changed by the user.
0127: static final int SOUND_DATA_DIRTY_BIT = 0x0001;
0128: static final int INITIAL_GAIN_DIRTY_BIT = 0x0002;
0129: static final int LOOP_COUNT_DIRTY_BIT = 0x0004;
0130: static final int BOUNDS_DIRTY_BIT = 0x0008;
0131: static final int BOUNDING_LEAF_DIRTY_BIT = 0x0010;
0132: static final int PRIORITY_DIRTY_BIT = 0x0020;
0133: static final int POSITION_DIRTY_BIT = 0x0040;
0134: static final int DISTANCE_GAIN_DIRTY_BIT = 0x0080;
0135: static final int BACK_DISTANCE_GAIN_DIRTY_BIT = 0x0100;
0136: static final int DIRECTION_DIRTY_BIT = 0x0200;
0137: static final int ANGULAR_ATTENUATION_DIRTY_BIT = 0x0400;
0138: static final int RATE_DIRTY_BIT = 0x0800;
0139:
0140: static final int BOUNDS_CHANGED = BOUNDS_DIRTY_BIT
0141: | BOUNDING_LEAF_DIRTY_BIT;
0142:
0143: static final int ATTRIBUTE_DIRTY_BITS = SOUND_DATA_DIRTY_BIT
0144: | INITIAL_GAIN_DIRTY_BIT | LOOP_COUNT_DIRTY_BIT
0145: | PRIORITY_DIRTY_BIT | RATE_DIRTY_BIT;
0146:
0147: static final int POSITIONAL_DIRTY_BITS = ATTRIBUTE_DIRTY_BITS
0148: | POSITION_DIRTY_BIT | DISTANCE_GAIN_DIRTY_BIT;
0149:
0150: static final int DIRECTIONAL_DIRTY_BITS = POSITIONAL_DIRTY_BITS
0151: | BACK_DISTANCE_GAIN_DIRTY_BIT | DIRECTION_DIRTY_BIT
0152: | ANGULAR_ATTENUATION_DIRTY_BIT;
0153:
0154: // All attribute bits that are specifically set or cleared for any node */
0155: static final int ALL_ATTIBS_DIRTY_BITS = 0x0FFF;
0156:
0157: // State Dirty bit flags
0158: // This bitmask is set when scene graph state is changed.
0159: static final int LIVE_DIRTY_BIT = 0x0001;
0160: static final int IMMEDIATE_MODE_DIRTY_BIT = 0x0002;
0161: static final int LOAD_SOUND_DIRTY_BIT = 0x0004;
0162: static final int RELEASE_DIRTY_BIT = 0x0008;
0163: static final int CONTINUOUS_DIRTY_BIT = 0x0010;
0164: static final int ENABLE_DIRTY_BIT = 0x0020;
0165: static final int MUTE_DIRTY_BIT = 0x0040;
0166: static final int PAUSE_DIRTY_BIT = 0x0080;
0167: static final int XFORM_DIRTY_BIT = 0x8000;
0168:
0169: // All attribute bits that are specifically set or cleared for any node */
0170: static final int ALL_STATE_DIRTY_BITS = 0x80FF;
0171:
0172: // The type of sound node: Background, Point, Cone
0173: int soundType = NULL_SOUND;
0174:
0175: // A back reference to the scene graph sound, when this is a mirror sound
0176: SoundRetained sgSound = null;
0177:
0178: // A HashKey for sounds in a shared group
0179: HashKey key = null;
0180:
0181: // An array of mirror sounds, one for each instance of this sound in a
0182: // shared group. Entry 0 is the only one valid if we are not in a shared
0183: // group.
0184: SoundRetained[] mirrorSounds = new SoundRetained[1];
0185:
0186: // The number of valid sounds in mirrorSounds
0187: int numMirrorSounds = 0;
0188:
0189: /**
0190: * Array of references to sound scheduler atoms associated with this node.
0191: * For each view that a sound node is associated with a sound scheduler
0192: * atom is created and maintained
0193: */
0194: // for a particular view that are playing either audibly or silently.
0195: private SoundSchedulerAtom[] loadedAtoms = new SoundSchedulerAtom[1];
0196: private int atomCount = 0;
0197:
0198: /**
0199: * This is true when this sound is referenced in an immediate mode context
0200: */
0201: boolean inImmCtx = false;
0202:
0203: /**
0204: * Load Sound Data Status
0205: */
0206: static final int LOAD_COMPLETE = 2;
0207: // load requested but could not be performed due because sound not live
0208: static final int LOAD_PENDING = 1;
0209: static final int LOAD_NULL = 0;
0210: static final int LOAD_FAILED = -1;
0211: int loadStatus = LOAD_NULL;
0212: long duration = Sound.DURATION_UNKNOWN;
0213:
0214: // Static initializer for SoundRetained class
0215: static {
0216: VirtualUniverse.loadLibraries();
0217: }
0218:
0219: // Target threads to be notified when sound changes
0220: static final int targetThreads = J3dThread.UPDATE_SOUND
0221: | J3dThread.SOUND_SCHEDULER;
0222:
0223: // Is true, if the mirror light is viewScoped
0224: boolean isViewScoped = false;
0225:
0226: /**
0227: * Dispatch a message about a sound attribute change
0228: */
0229: void dispatchAttribChange(int dirtyBit, Object argument) {
0230: // Send message including a integer argument
0231: J3dMessage createMessage = new J3dMessage();
0232: createMessage.threads = J3dThread.UPDATE_SOUND
0233: | J3dThread.SOUND_SCHEDULER;
0234: createMessage.type = J3dMessage.SOUND_ATTRIB_CHANGED;
0235: createMessage.universe = universe;
0236: createMessage.args[0] = this ;
0237: createMessage.args[1] = new Integer(dirtyBit);
0238: if (inSharedGroup)
0239: createMessage.args[2] = new Integer(numMirrorSounds);
0240: else
0241: createMessage.args[2] = new Integer(1);
0242: createMessage.args[3] = mirrorSounds.clone();
0243: createMessage.args[4] = argument;
0244: if (debugFlag)
0245: debugPrint("dispatchAttribChange with " + dirtyBit);
0246: VirtualUniverse.mc.processMessage(createMessage);
0247: }
0248:
0249: /**
0250: * Dispatch a message about a sound state change
0251: */
0252: void dispatchStateChange(int dirtyBit, Object argument) {
0253: // Send message including a integer argument
0254: J3dMessage createMessage = new J3dMessage();
0255: createMessage.threads = J3dThread.UPDATE_SOUND
0256: | J3dThread.SOUND_SCHEDULER;
0257: createMessage.type = J3dMessage.SOUND_STATE_CHANGED;
0258: createMessage.universe = universe;
0259: createMessage.args[0] = this ;
0260: createMessage.args[1] = new Integer(dirtyBit);
0261: if (inSharedGroup)
0262: createMessage.args[2] = new Integer(numMirrorSounds);
0263: else
0264: createMessage.args[2] = new Integer(1);
0265: createMessage.args[3] = mirrorSounds.clone();
0266: createMessage.args[4] = argument;
0267: if (debugFlag)
0268: debugPrint("dispatchStateChange with " + dirtyBit);
0269: VirtualUniverse.mc.processMessage(createMessage);
0270: }
0271:
0272: /**
0273: * Assign value into sound data field
0274: * @param soundData description of sound source data
0275: */
0276: void setSoundDataState(MediaContainer soundData) {
0277: this .soundData = soundData;
0278: }
0279:
0280: /**
0281: * Associates sound data with this sound source node
0282: * Attempt to load sound
0283: * @param soundData descrition of sound source data
0284: */
0285: void setSoundData(MediaContainer soundData) {
0286: // if resetting soundData to the same value don't bother doing anything
0287: if (this .soundData == soundData) {
0288: return;
0289: }
0290:
0291: if (this .soundData != null) {
0292: // this sound node had older sound data; clear it out
0293: ((MediaContainerRetained) this .soundData.retained)
0294: .removeUser(this );
0295: }
0296:
0297: if (source != null && source.isLive()) {
0298: if (this .soundData != null) {
0299: ((MediaContainerRetained) this .soundData.retained)
0300: .clearLive(refCount);
0301: }
0302:
0303: if (soundData != null) {
0304: ((MediaContainerRetained) soundData.retained).setLive(
0305: inBackgroundGroup, refCount);
0306: ((MediaContainerRetained) soundData.retained)
0307: .addUser(this );
0308: }
0309: }
0310:
0311: this .soundData = soundData;
0312: dispatchAttribChange(SOUND_DATA_DIRTY_BIT, soundData);
0313:
0314: if (source != null && source.isLive()) {
0315: notifySceneGraphChanged(false);
0316: }
0317: }
0318:
0319: /**
0320: * Retrieves sound data associated with this sound source node
0321: * @return sound source data container
0322: */
0323: MediaContainer getSoundData() {
0324: return (this .soundData);
0325: }
0326:
0327: /**
0328: * Set the gain scale factor applied to this sound
0329: * @param amplitude gain scale factor
0330: */
0331: void setInitialGain(float scaleFactor) {
0332: if (scaleFactor < 0.0f)
0333: this .initialGain = 0.0f;
0334: else
0335: this .initialGain = scaleFactor;
0336:
0337: dispatchAttribChange(INITIAL_GAIN_DIRTY_BIT, (new Float(
0338: scaleFactor)));
0339: if (source != null && source.isLive()) {
0340: notifySceneGraphChanged(false);
0341: }
0342: }
0343:
0344: /**
0345: * Get the overall gain (applied to the sound data associated with source).
0346: * @return overall gain of sound source
0347: */
0348: float getInitialGain() {
0349: return (float) this .initialGain;
0350: }
0351:
0352: /**
0353: * Sets the sound's loop count
0354: * @param loopCount number of times sound is looped during play
0355: */
0356: void setLoop(int loopCount) {
0357: if (loopCount < -1)
0358: this .loopCount = -1;
0359: else
0360: this .loopCount = (int) loopCount;
0361: if (debugFlag)
0362: debugPrint("setLoopCount called with " + this .loopCount);
0363:
0364: dispatchAttribChange(LOOP_COUNT_DIRTY_BIT, (new Integer(
0365: loopCount)));
0366: if (source != null && source.isLive()) {
0367: notifySceneGraphChanged(false);
0368: }
0369: }
0370:
0371: /**
0372: * Retrieves the loop count
0373: * @return loop count for data associated with sound
0374: */
0375: int getLoop() {
0376: return (int) this .loopCount;
0377: }
0378:
0379: /**
0380: * Enable or disable the release flag for this sound source
0381: * @param state flag denoting release sound before stopping
0382: */
0383: void setReleaseEnable(boolean state) {
0384: this .release = state;
0385: dispatchAttribChange(RELEASE_DIRTY_BIT, (state ? Boolean.TRUE
0386: : Boolean.FALSE));
0387: if (source != null && source.isLive()) {
0388: notifySceneGraphChanged(false);
0389: }
0390: }
0391:
0392: /**
0393: * Retrieves release flag for sound associated with this source node
0394: * @return sound's release flag
0395: */
0396: boolean getReleaseEnable() {
0397: return (boolean) this .release;
0398: }
0399:
0400: /**
0401: * Enable or disable continuous play flag
0402: * @param state denotes if sound continues playing silently when deactivated
0403: */
0404: void setContinuousEnable(boolean state) {
0405: this .continuous = state;
0406: dispatchAttribChange(CONTINUOUS_DIRTY_BIT,
0407: (state ? Boolean.TRUE : Boolean.FALSE));
0408: if (source != null && source.isLive()) {
0409: notifySceneGraphChanged(false);
0410: }
0411: }
0412:
0413: /**
0414: * Retrieves sound's continuous play flag
0415: * @return flag denoting if deactivated sound silently continues playing
0416: */
0417: boolean getContinuousEnable() {
0418: return (boolean) this .continuous;
0419: }
0420:
0421: /**
0422: * Sets the flag denotine sound enabled/disabled and sends a message
0423: * for the following to be done:
0424: * If state is true:
0425: * if sound is not playing, sound is started.
0426: * if sound is playing, sound is stopped, then re-started.
0427: * If state is false:
0428: * if sound is playing, sound is stopped
0429: * @param state true or false to enable or disable the sound
0430: */
0431: void setEnable(boolean state) {
0432: enable = state;
0433: // QUESTION: Is this still valid code?
0434: if (source != null && source.isLive()) {
0435: notifySceneGraphChanged(false);
0436: }
0437: dispatchStateChange(ENABLE_DIRTY_BIT, (new Boolean(enable)));
0438: }
0439:
0440: /**
0441: * Retrieves sound's enabled flag
0442: * @return sound enabled flag
0443: */
0444: boolean getEnable() {
0445: return enable;
0446: }
0447:
0448: /**
0449: * Set the Sound's scheduling region.
0450: * @param region a region that contains the Sound's new scheduling region
0451: */
0452: void setSchedulingBounds(Bounds region) {
0453: if (region != null) {
0454: schedulingRegion = (Bounds) region.clone();
0455: if (staticTransform != null) {
0456: schedulingRegion.transform(staticTransform.transform);
0457: }
0458: // QUESTION: Clone into transformedRegion IS required. Why?
0459: transformedRegion = (Bounds) schedulingRegion.clone();
0460: if (debugFlag)
0461: debugPrint("setSchedulingBounds for a non-null region");
0462: } else {
0463: schedulingRegion = null;
0464: // QUESTION: Is transformedRegion of node (not mirror node)
0465: // even looked at???
0466: transformedRegion = null;
0467: if (debugFlag)
0468: debugPrint("setSchedulingBounds for a NULL region");
0469: }
0470: // XXXX: test that this works - could not new Bounds() since
0471: // Bounds is an abstract class and can't be instantiated
0472: dispatchAttribChange(BOUNDS_DIRTY_BIT, region);
0473: if (source != null && source.isLive()) {
0474: notifySceneGraphChanged(false);
0475: }
0476: }
0477:
0478: /**
0479: * Get the Sound's scheduling region.
0480: * @return this Sound's scheduling region information
0481: */
0482: Bounds getSchedulingBounds() {
0483: Bounds b = null;
0484:
0485: if (this .schedulingRegion != null) {
0486: b = (Bounds) schedulingRegion.clone();
0487: if (staticTransform != null) {
0488: Transform3D invTransform = staticTransform
0489: .getInvTransform();
0490: b.transform(invTransform);
0491: }
0492: }
0493: return b;
0494: }
0495:
0496: /**
0497: * Set the Sound's scheduling region to the specified Leaf node.
0498: */
0499: void setSchedulingBoundingLeaf(BoundingLeaf region) {
0500: int i;
0501: int numSnds = numMirrorSounds;
0502: if (numMirrorSounds == 0)
0503: numSnds = 1;
0504:
0505: if ((boundingLeaf != null)
0506: && (source != null && source.isLive())) {
0507: // Remove the mirror lights as users of the original bounding leaf
0508: for (i = 0; i < numSnds; i++) {
0509: boundingLeaf.mirrorBoundingLeaf
0510: .removeUser(mirrorSounds[i]);
0511: }
0512: }
0513:
0514: if (region != null) {
0515: boundingLeaf = (BoundingLeafRetained) region.retained;
0516: // Add all mirror sounds as user of this bounding leaf
0517: if (source != null && source.isLive()) {
0518: for (i = 0; i < numSnds; i++) {
0519: boundingLeaf.mirrorBoundingLeaf
0520: .addUser(mirrorSounds[i]);
0521: }
0522: }
0523: } else {
0524: boundingLeaf = null;
0525: }
0526: // XXXX: since BoundingLeaf constructor only takes Bounds
0527: // test if region passed into dispatchAttribChange correctly.
0528: dispatchAttribChange(BOUNDING_LEAF_DIRTY_BIT, region);
0529: if (source != null && source.isLive()) {
0530: notifySceneGraphChanged(false);
0531: }
0532: }
0533:
0534: /**
0535: * Get the Sound's scheduling region
0536: */
0537: BoundingLeaf getSchedulingBoundingLeaf() {
0538: if (boundingLeaf != null) {
0539: return ((BoundingLeaf) boundingLeaf.source);
0540: } else {
0541: return null;
0542: }
0543: }
0544:
0545: // The update Object function.
0546: synchronized void updateMirrorObject(Object[] objs) {
0547: Transform3D trans = null;
0548: int component = ((Integer) objs[1]).intValue();
0549: if (component == -1) { // update everything
0550: // object 2 contains the mirror object that needs to be
0551: // updated
0552: initMirrorObject(((SoundRetained) objs[2]));
0553: }
0554:
0555: // call the parent's mirror object update routine
0556: super .updateMirrorObject(objs);
0557:
0558: }
0559:
0560: void updateBoundingLeaf(long refTime) {
0561: // This is necessary, if for example, the region
0562: // changes from sphere to box.
0563: if (boundingLeaf != null
0564: && boundingLeaf.switchState.currentSwitchOn) {
0565: transformedRegion = boundingLeaf.transformedRegion;
0566: } else { // evaluate schedulingRegion if not null
0567: if (schedulingRegion != null) {
0568: transformedRegion = schedulingRegion
0569: .copy(transformedRegion);
0570: transformedRegion.transform(schedulingRegion,
0571: getLastLocalToVworld());
0572: } else {
0573: transformedRegion = null;
0574: }
0575: }
0576: }
0577:
0578: /**
0579: * Set sound's proirity value.
0580: * @param priority value used to order sound's importance for playback.
0581: */
0582: void setPriority(float rank) {
0583: if (rank == this .priority)
0584: // changing priority is expensive in the sound scheduler(s)
0585: // so only dispatch a message if 'new' priority value is really
0586: // different
0587: return;
0588:
0589: this .priority = rank;
0590: dispatchAttribChange(PRIORITY_DIRTY_BIT, (new Float(rank)));
0591: if (source != null && source.isLive()) {
0592: notifySceneGraphChanged(false);
0593: }
0594: }
0595:
0596: /**
0597: * Retrieves sound's priority value.
0598: * @return sound priority value
0599: */
0600: float getPriority() {
0601: return (this .priority);
0602: }
0603:
0604: /**
0605: * Retrieves sound's duration in milliseconds
0606: * @return sound's duration, returns DURATION_UNKNOWN if duration could
0607: * not be queried from the audio device
0608: */
0609: long getDuration() {
0610: return (duration);
0611: }
0612:
0613: /**
0614: * Set scale factor
0615: * @param scaleFactor applied to sound playback rate
0616: */
0617: void setRateScaleFactor(float scaleFactor) {
0618: this .rate = scaleFactor;
0619: dispatchAttribChange(RATE_DIRTY_BIT, (new Float(scaleFactor)));
0620: if (source != null && source.isLive()) {
0621: notifySceneGraphChanged(false);
0622: }
0623: }
0624:
0625: /**
0626: * Retrieves sound's rate scale factor
0627: * @return sound rate scale factor
0628: */
0629: float getRateScaleFactor() {
0630: return (this .rate);
0631: }
0632:
0633: void changeAtomList(SoundSchedulerAtom atom, int loadStatus) {
0634: if (atom == null)
0635: return;
0636: if (loadStatus == SoundRetained.LOAD_COMPLETE) {
0637: // atom is successfully loaded, so add this atom to array of atoms
0638: // associated with this sound, if not already in list
0639: for (int i = 0; i < atomCount; i++) {
0640: if (atom == loadedAtoms[i])
0641: return;
0642: }
0643: // add atom to list
0644: atomCount++;
0645: int currentArrayLength = loadedAtoms.length;
0646: if (atomCount > currentArrayLength) {
0647: // expand array - replace with a larger array
0648: loadedAtoms = new SoundSchedulerAtom[2 * currentArrayLength];
0649: }
0650: loadedAtoms[atomCount - 1] = atom; // store reference to new atom
0651: // all atoms sample durations SHOULD be the same so store it in node
0652: this .duration = atom.sampleLength; // XXXX: refine later? in ms
0653: } else { // atom is NOT loaded or has been unloaded; remove from list
0654: if (atomCount == 0)
0655: return;
0656:
0657: // remove atom from array of playing atoms if it is in list
0658: boolean atomFound = false;
0659: int i;
0660: for (i = 0; i < atomCount; i++) {
0661: if (atom == loadedAtoms[i]) {
0662: atomFound = true;
0663: continue;
0664: }
0665: }
0666: if (!atomFound)
0667: return;
0668:
0669: // otherwise remove atom from list by close up list
0670: for (int j = i; j < atomCount; j++) {
0671: loadedAtoms[j] = loadedAtoms[j + 1];
0672: }
0673: atomCount--;
0674: if (atomCount == 0)
0675: this .duration = Sound.DURATION_UNKNOWN; // clear sound duration
0676: }
0677: }
0678:
0679: /**
0680: * Retrieves sound's ready state for ALL active views.
0681: * For this node, the list of sound scheduler atoms associated with
0682: * each view is maintained. The 'loaded' (=is ready) state is
0683: * true only if the following are true for all views/sound schedulers:
0684: *
0685: * <ul>
0686: * 1) the Sound node has a non-null sound data and this data has
0687: * sucessfully been loaded/opened/copied/attached;<br>
0688: * 2) the Sound node is live;<br>
0689: * 3) there is at least one active View in the Universe; and<br>
0690: * 4) an instance of an AudioDevice is attached to the current
0691: * PhysicalEnvironment.
0692: * </ul>
0693: *
0694: * @return true if potentially playable (audibly or silently); false otherwise
0695: */
0696: boolean isReady() {
0697: // all the atoms in the atom list must be are ready for this
0698: // method to return true
0699: // if any non-null atoms are found NOT ready, return false.
0700: boolean atomFoundReady = true;
0701: for (int i = 0; i < atomCount; i++) {
0702: SoundSchedulerAtom atom = loadedAtoms[i];
0703: if (atom == null || atom.soundScheduler == null)
0704: continue;
0705: else if (atom.loadStatus == SoundRetained.LOAD_COMPLETE) {
0706: atomFoundReady = true;
0707: continue;
0708: } else
0709: return false;
0710: }
0711: if (atomFoundReady) // at least on atom found ready
0712: return true;
0713: else
0714: // not even one atom is associated with node so none are loaded
0715: return false;
0716: }
0717:
0718: /**
0719: * Retrieves sound's ready state for a particular view.
0720: * For this node, the list of sound scheduler atoms associated with
0721: * each view is maintained. The 'loaded' (=is ready) state is
0722: * true only if the following are true for the given view:
0723: *
0724: * <ul>
0725: * 1) the Sound node has a non-null sound data and this data has
0726: * sucessfully been loaded/opened/copied/attached;<br>
0727: * 2) the Sound node is live;<br>
0728: * 3) the given View is active in the Universe; and<br>
0729: * 4) an instance of an AudioDevice is attached to the current
0730: * PhysicalEnvironment.
0731: * </ul>
0732: *
0733: * @param viewRef view to test sound readiness for
0734: * @return true if potentially playable (audibly or silently); false otherwise
0735: */
0736: boolean isReady(View viewRef) {
0737: // if an atom in the atom list that is associated with the
0738: // given view is found and has been loaded than return true,
0739: // otherwise return false.
0740: if (viewRef == null)
0741: return false;
0742: for (int i = 0; i < atomCount; i++) {
0743: SoundSchedulerAtom atom = loadedAtoms[i];
0744: if (atom == null || atom.soundScheduler == null)
0745: continue;
0746: if (atom.soundScheduler.view == viewRef)
0747: if (atom.loadStatus != SoundRetained.LOAD_COMPLETE)
0748: return false;
0749: else
0750: return true;
0751: else
0752: // atom is not associated with given referenced view
0753: continue;
0754: }
0755: return false; // sound scheduler atom for given view not found
0756:
0757: }
0758:
0759: // *******************************
0760: // Play Status - isPlaying states
0761: // *******************************
0762:
0763: /**
0764: * Retrieves sound's playing status
0765: * true if potentially audible (enabled and active) on ANY audio device
0766: * false otherwise
0767: * @return sound playing flag
0768: */
0769: boolean isPlaying() {
0770: for (int i = 0; i < atomCount; i++) {
0771: SoundSchedulerAtom atom = loadedAtoms[i];
0772: if (atom == null || atom.soundScheduler == null)
0773: continue;
0774: if (atom.status == SoundSchedulerAtom.SOUND_AUDIBLE)
0775: return true;
0776: else
0777: continue; // look for at lease one atom that is playing
0778: }
0779: // not even one atom is associated with this node so none are playing
0780: return false;
0781: }
0782:
0783: /**
0784: * Retrieves sound's playing status for a particular view
0785: * true if potentially audible (enabled and active) on audio device
0786: * associated with the given view
0787: * false otherwise
0788: * @param viewRef view to test sound playing state for
0789: * @return sound playing flag
0790: */
0791: boolean isPlaying(View viewRef) {
0792: if (viewRef == null)
0793: return false;
0794: for (int i = 0; i < atomCount; i++) {
0795: SoundSchedulerAtom atom = loadedAtoms[i];
0796: if (atom == null || atom.soundScheduler == null)
0797: continue;
0798: if (atom.soundScheduler.view == viewRef) {
0799: if (atom.status == SoundSchedulerAtom.SOUND_AUDIBLE)
0800: return true;
0801: else
0802: return false;
0803: } else
0804: // atom is not associated with given referenced view
0805: continue;
0806: }
0807: return false; // atom associated with this view not found in list
0808: }
0809:
0810: /**
0811: * Retrieves sound's playing silently status
0812: * true if enabled but not active (on any device)
0813: * false otherwise
0814: * @return sound playing flag
0815: */
0816: boolean isPlayingSilently() {
0817: for (int i = 0; i < atomCount; i++) {
0818: SoundSchedulerAtom atom = loadedAtoms[i];
0819: if (atom == null || atom.soundScheduler == null)
0820: continue;
0821: if (atom.status == SoundSchedulerAtom.SOUND_SILENT)
0822: return true;
0823: else
0824: return false;
0825: }
0826: return false; // atom not found in list or not playing audibilly
0827: }
0828:
0829: /**
0830: * Retrieves sound's playing silently status for a particular view
0831: * true if potentially audible (enabled and active) on audio device
0832: * associated with the given view
0833: * false otherwise
0834: * @param viewRef view to test sound playing silently state for
0835: * @return sound playing flag
0836: */
0837: boolean isPlayingSilently(View viewRef) {
0838: if (viewRef == null)
0839: return false;
0840: for (int i = 0; i < atomCount; i++) {
0841: SoundSchedulerAtom atom = loadedAtoms[i];
0842: if (atom == null || atom.soundScheduler == null)
0843: continue;
0844: if (atom.soundScheduler.view == viewRef) {
0845: if (atom.status == SoundSchedulerAtom.SOUND_SILENT)
0846: return true;
0847: else
0848: return false;
0849: } else
0850: // atom is not associated with given referenced view
0851: continue;
0852: }
0853: return false; // atom associated with this view not found in list
0854: }
0855:
0856: /**
0857: * Retrieves number of channels allocated for this sound on the primary
0858: * view's audio device.
0859: * @return number of channels used by sound across all devices
0860: */
0861: int getNumberOfChannelsUsed() {
0862: // retrieves the number of channels used by the atom that is:
0863: // loaded, and
0864: // playing either audibily or silently
0865: // on the device associated with the primary view.
0866: View primaryView = this .universe.getCurrentView();
0867: if (primaryView == null)
0868: return 0;
0869:
0870: // find atom associated with primary view (VirtualUniverse currentView)
0871: // then return the number of channels associated with that atom
0872: SoundSchedulerAtom atom;
0873: for (int i = 0; i < atomCount; i++) {
0874: atom = loadedAtoms[i];
0875: if (atom == null || atom.soundScheduler == null)
0876: continue;
0877: if (atom.soundScheduler.view == primaryView) {
0878: return atom.numberChannels;
0879: }
0880: }
0881: return 0; // atom associated with primary view not found
0882: }
0883:
0884: /**
0885: * Retrieves number of channels allocated for this sound on the audio
0886: * devices associated with a given view.
0887: * @param viewRef view to test sound playing silently state for
0888: * @return number of channels used by this sound on a particular device
0889: */
0890: int getNumberOfChannelsUsed(View viewRef) {
0891: // retrieves the number of channels used by the atom that is:
0892: // loaded, and
0893: // playing either audibily or silently
0894: // on the device associated with the given view.
0895: if (viewRef == null)
0896: return 0;
0897: SoundSchedulerAtom atom;
0898: for (int i = 0; i < atomCount; i++) {
0899: atom = loadedAtoms[i];
0900: if (atom == null || atom.soundScheduler == null)
0901: continue;
0902: if (atom.soundScheduler.view == viewRef) {
0903: return atom.numberChannels;
0904: }
0905: }
0906: return 0; // atom associated with primary view not found
0907: }
0908:
0909: /**
0910: * Set mute state flag. If the sound is playing it will be set to
0911: * play silently
0912: * @param state flag
0913: * @since Java 3D 1.3
0914: */
0915: void setMute(boolean state) {
0916: this .mute = state;
0917: dispatchAttribChange(MUTE_DIRTY_BIT, (state ? Boolean.TRUE
0918: : Boolean.FALSE));
0919: if (source != null && source.isLive()) {
0920: notifySceneGraphChanged(false);
0921: }
0922: }
0923:
0924: /**
0925: * Retrieves sound Mute state.
0926: * A return value of true does not imply that the sound has
0927: * been started playing or is still playing silently.
0928: * @return mute state flag
0929: * @since Java 3D 1.3
0930: */
0931: boolean getMute() {
0932: return (boolean) this .mute;
0933: }
0934:
0935: /**
0936: * Set pause state flag. If the sound is playing it will be paused
0937: * @param state flag
0938: * @since Java 3D 1.3
0939: */
0940: void setPause(boolean state) {
0941: this .pause = state;
0942: dispatchAttribChange(PAUSE_DIRTY_BIT, (state ? Boolean.TRUE
0943: : Boolean.FALSE));
0944: if (source != null && source.isLive()) {
0945: notifySceneGraphChanged(false);
0946: }
0947: }
0948:
0949: /**
0950: * Retrieves sound Pause state.
0951: * A return value of true does not imply that the sound has
0952: * been started playing auditibly or silently.
0953: * @return mute state flag
0954: * @since Java 3D 1.3
0955: */
0956: boolean getPause() {
0957: return (boolean) this .pause;
0958: }
0959:
0960: /**
0961: * This sets the immedate mode context flag
0962: */
0963: void setInImmCtx(boolean inCtx) {
0964: inImmCtx = inCtx;
0965: }
0966:
0967: /**
0968: * This gets the immedate mode context flag
0969: */
0970: boolean getInImmCtx() {
0971: return (inImmCtx);
0972: }
0973:
0974: /**
0975: * This gets the mirror sound for this sound given the key.
0976: */
0977: SoundRetained getMirrorSound(HashKey key) {
0978: int i;
0979: SoundRetained[] newSounds;
0980:
0981: if (inSharedGroup) {
0982: for (i = 0; i < numMirrorSounds; i++) {
0983: if (mirrorSounds[i].key.equals(key)) {
0984: return (mirrorSounds[i]);
0985: }
0986: }
0987: if (numMirrorSounds == mirrorSounds.length) {
0988: newSounds = new SoundRetained[numMirrorSounds * 2];
0989: for (i = 0; i < numMirrorSounds; i++) {
0990: newSounds[i] = mirrorSounds[i];
0991: }
0992: mirrorSounds = newSounds;
0993: }
0994: // mirrorSounds[numMirrorSounds] = (SoundRetained) this.clone();
0995: mirrorSounds[numMirrorSounds] = (SoundRetained) this
0996: .clone();
0997: //mirrorSounds[numMirrorSounds].key = new HashKey(key);
0998: mirrorSounds[numMirrorSounds].key = key;
0999: mirrorSounds[numMirrorSounds].sgSound = this ;
1000: return (mirrorSounds[numMirrorSounds++]);
1001: } else {
1002: if (mirrorSounds[0] == null) {
1003: // mirrorSounds[0] = (SoundRetained) this.clone(true);
1004: mirrorSounds[0] = (SoundRetained) this .clone();
1005: mirrorSounds[0].sgSound = this ;
1006: }
1007: return (mirrorSounds[0]);
1008: }
1009: }
1010:
1011: synchronized void initMirrorObject(SoundRetained ms) {
1012: GroupRetained group;
1013: Transform3D trans;
1014: Bounds region = null;
1015:
1016: ms.setSchedulingBounds(getSchedulingBounds());
1017: ms.setSchedulingBoundingLeaf(getSchedulingBoundingLeaf());
1018: ms.sgSound = sgSound;
1019: /*
1020: // QUESTION: these are not set in LightRetained???
1021: ms.key = null;
1022: ms.mirrorSounds = new SoundRetained[1];
1023: ms.numMirrorSounds = 0;
1024: */
1025: ms.inImmCtx = inImmCtx;
1026: ms.setSoundData(getSoundData());
1027:
1028: // XXXX: copy ms.atoms array from this.atoms
1029:
1030: ms.parent = parent;
1031: ms.inSharedGroup = false;
1032: ms.locale = locale;
1033: ms.parent = parent;
1034: ms.localBounds = (Bounds) localBounds.clone();
1035:
1036: ms.transformedRegion = null;
1037: if (boundingLeaf != null) {
1038: if (ms.boundingLeaf != null)
1039: ms.boundingLeaf.removeUser(ms);
1040: ms.boundingLeaf = boundingLeaf.mirrorBoundingLeaf;
1041: // Add this mirror object as user
1042: ms.boundingLeaf.addUser(ms);
1043: ms.transformedRegion = ms.boundingLeaf.transformedRegion;
1044: } else {
1045: ms.boundingLeaf = null;
1046: }
1047:
1048: if (schedulingRegion != null) {
1049: ms.schedulingRegion = (Bounds) schedulingRegion.clone();
1050: // Assign region only if bounding leaf is null
1051: if (ms.transformedRegion == null) {
1052: ms.transformedRegion = (Bounds) ms.schedulingRegion
1053: .clone();
1054: ms.transformedRegion.transform(ms.schedulingRegion, ms
1055: .getLastLocalToVworld());
1056: }
1057:
1058: } else {
1059: ms.schedulingRegion = null;
1060: }
1061: }
1062:
1063: void setLive(SetLiveState s) {
1064: SoundRetained ms;
1065: int i, j;
1066:
1067: if (debugFlag)
1068: debugPrint("Sound.setLive");
1069:
1070: if (inImmCtx) {
1071: throw new IllegalSharingException(J3dI18N
1072: .getString("SoundRetained2"));
1073: }
1074: super .setLive(s);
1075: if (inBackgroundGroup) {
1076: throw new IllegalSceneGraphException(J3dI18N
1077: .getString("SoundRetained3"));
1078: }
1079:
1080: if (this .loadStatus == LOAD_PENDING) {
1081: if (debugFlag)
1082: debugPrint("Sound.setLive load Sound");
1083: dispatchStateChange(LOAD_SOUND_DIRTY_BIT, soundData);
1084: }
1085:
1086: if (this .soundData != null) {
1087: ((MediaContainerRetained) this .soundData.retained).setLive(
1088: inBackgroundGroup, s.refCount);
1089: }
1090:
1091: if (s.inSharedGroup) {
1092: for (i = 0; i < s.keys.length; i++) {
1093: ms = this .getMirrorSound(s.keys[i]);
1094: ms.localToVworld = new Transform3D[1][];
1095: ms.localToVworldIndex = new int[1][];
1096:
1097: j = s.keys[i].equals(localToVworldKeys, 0,
1098: localToVworldKeys.length);
1099: if (j < 0) {
1100: System.err
1101: .println("SoundRetained : Can't find hashKey");
1102: }
1103:
1104: ms.localToVworld[0] = localToVworld[j];
1105: ms.localToVworldIndex[0] = localToVworldIndex[j];
1106: // If its view Scoped, then add this list
1107: // to be sent to Sound Structure
1108: if ((s.viewScopedNodeList != null)
1109: && (s.viewLists != null)) {
1110: s.viewScopedNodeList.add(ms);
1111: s.scopedNodesViewList.add(s.viewLists.get(i));
1112: } else {
1113: s.nodeList.add(ms);
1114: }
1115: // Initialization of the mirror object during the INSERT_NODE
1116: // message (in updateMirrorObject)
1117: if (s.switchTargets != null
1118: && s.switchTargets[i] != null) {
1119: s.switchTargets[i].addNode(ms, Targets.SND_TARGETS);
1120: }
1121: ms.switchState = (SwitchState) s.switchStates.get(j);
1122: if (s.transformTargets != null
1123: && s.transformTargets[i] != null) {
1124: s.transformTargets[i].addNode(ms,
1125: Targets.SND_TARGETS);
1126: s.notifyThreads |= J3dThread.UPDATE_TRANSFORM;
1127: }
1128: }
1129: } else {
1130: ms = this .getMirrorSound(null);
1131: ms.localToVworld = new Transform3D[1][];
1132: ms.localToVworldIndex = new int[1][];
1133: ms.localToVworld[0] = this .localToVworld[0];
1134: ms.localToVworldIndex[0] = this .localToVworldIndex[0];
1135: // If its view Scoped, then add this list
1136: // to be sent to Sound Structure
1137: if ((s.viewScopedNodeList != null) && (s.viewLists != null)) {
1138: s.viewScopedNodeList.add(ms);
1139: s.scopedNodesViewList.add(s.viewLists.get(0));
1140: } else {
1141: s.nodeList.add(ms);
1142: }
1143: // Initialization of the mirror object during the INSERT_NODE
1144: // message (in updateMirrorObject)
1145: if (s.switchTargets != null && s.switchTargets[0] != null) {
1146: s.switchTargets[0].addNode(ms, Targets.SND_TARGETS);
1147: }
1148: ms.switchState = (SwitchState) s.switchStates.get(0);
1149: if (s.transformTargets != null
1150: && s.transformTargets[0] != null) {
1151: s.transformTargets[0].addNode(ms, Targets.SND_TARGETS);
1152: s.notifyThreads |= J3dThread.UPDATE_TRANSFORM;
1153: }
1154: }
1155: dispatchStateChange(LIVE_DIRTY_BIT, soundData);
1156: s.notifyThreads |= targetThreads;
1157: }
1158:
1159: void clearLive(SetLiveState s) {
1160: SoundRetained ms;
1161:
1162: super .clearLive(s);
1163:
1164: // XXXX: if (inSharedGroup)
1165:
1166: if (s.inSharedGroup) {
1167: for (int i = 0; i < s.keys.length; i++) {
1168: ms = this .getMirrorSound(s.keys[i]);
1169: if (s.switchTargets != null
1170: && s.switchTargets[i] != null) {
1171: s.switchTargets[i].addNode(ms, Targets.SND_TARGETS);
1172: }
1173: if (s.transformTargets != null
1174: && s.transformTargets[i] != null) {
1175: s.transformTargets[i].addNode(ms,
1176: Targets.SND_TARGETS);
1177: s.notifyThreads |= J3dThread.UPDATE_TRANSFORM;
1178: }
1179: // If its view Scoped, then add this list
1180: // to be sent to Sound Structure
1181: if ((s.viewScopedNodeList != null)
1182: && (s.viewLists != null)) {
1183: s.viewScopedNodeList.add(ms);
1184: s.scopedNodesViewList.add(s.viewLists.get(i));
1185: } else {
1186: s.nodeList.add(ms);
1187: }
1188: }
1189: } else {
1190: ms = this .getMirrorSound(null);
1191: if (s.switchTargets != null && s.switchTargets[0] != null) {
1192: s.switchTargets[0].addNode(ms, Targets.SND_TARGETS);
1193: }
1194: if (s.transformTargets != null
1195: && s.transformTargets[0] != null) {
1196: s.transformTargets[0].addNode(ms, Targets.SND_TARGETS);
1197: s.notifyThreads |= J3dThread.UPDATE_TRANSFORM;
1198: }
1199: // If its view Scoped, then add this list
1200: // to be sent to Sound Structure
1201: if ((s.viewScopedNodeList != null) && (s.viewLists != null)) {
1202: s.viewScopedNodeList.add(ms);
1203: s.scopedNodesViewList.add(s.viewLists.get(0));
1204: } else {
1205: s.nodeList.add(ms);
1206: }
1207: }
1208: s.notifyThreads |= targetThreads;
1209:
1210: if (this .soundData != null) {
1211: ((MediaContainerRetained) this .soundData.retained)
1212: .clearLive(s.refCount);
1213: }
1214: }
1215:
1216: void mergeTransform(TransformGroupRetained xform) {
1217: super .mergeTransform(xform);
1218: if (schedulingRegion != null) {
1219: schedulingRegion.transform(xform.transform);
1220: }
1221: }
1222:
1223: /*
1224: // This makes passed in sound look just like this sound
1225: // QUESTION: DOesn't appread to be called
1226: // XXXX: ...if so, remove...
1227: synchronized void update(SoundRetained sound) {
1228: if (debugFlag)
1229: debugPrint("Sound.update ******** entered ***** this = " + this +
1230: ", and sound param = " + sound);
1231:
1232: sound.soundData = soundData;
1233: sound.initialGain = initialGain;
1234: sound.loopCount = loopCount;
1235: sound.release = release;
1236: sound.continuous = continuous;
1237: sound.enable = enable; // used to be 'on'
1238: sound.inImmCtx = inImmCtx;
1239:
1240: // QUESTION:
1241: // This line removed from 1.1.1 version; why ???
1242: sound.currentSwitchOn = currentSwitchOn;
1243:
1244: // NEW:
1245: sound.priority = priority;
1246:
1247: // QUESTION: With code below, no sound schedulingRegion found
1248: // sound.schedulingRegion = schedulingRegion;
1249: // sound.boundingLeaf = boundingLeaf;
1250: // XXXX: clone of region used in Traverse code, why not here???
1251: // if (schedulingRegion != null)
1252: // sound.schedulingRegion = (Bounds)schedulingRegion.clone();
1253: // XXXX: BoundingLeafRetained boundingLeaf ...
1254: // WHAT ABOUT transformedRegion??
1255:
1256: // XXXX: Update ALL fields
1257: // ALL THE BELOW USED TO COMMENTED OUT vvvvvvvvvvvvvvvvvvvvvvvvvvvvv
1258: sound.sampleLength = sampleLength;
1259: sound.loopStartOffset = loopStartOffset;
1260: sound.loopLength = loopLength;
1261: sound.attackLength = attackLength;
1262: sound.releaseLength = releaseLength;
1263:
1264: sound.sgSound = sgSound;
1265: sound.key = key;
1266: sound.numMirrorSounds = numMirrorSounds;
1267: for (int index=0; index<numMirrorSounds; index++)
1268: sound.mirrorSounds = mirrorSounds;
1269: sound.universe = universe;
1270: if (universe.sounds.contains(sound) == false) {
1271: universe.sounds.addElement(sound);
1272: }
1273: if (debugFlag)
1274: debugPrint("update****************************** exited");
1275: ^^^^^^^^^^^ COMMENTED OUT
1276: }
1277: */
1278:
1279: // Called on mirror object
1280: // QUESTION: doesn't transformed region need to be saved???
1281: void updateTransformChange() {
1282: // If bounding leaf is null, tranform the bounds object
1283: if (debugFlag)
1284: debugPrint("SoundRetained.updateTransformChange()");
1285: if (boundingLeaf == null) {
1286: if (schedulingRegion != null) {
1287: transformedRegion = schedulingRegion
1288: .copy(transformedRegion);
1289: transformedRegion.transform(schedulingRegion,
1290: getLastLocalToVworld());
1291: }
1292: }
1293: dispatchStateChange(XFORM_DIRTY_BIT, null);
1294: }
1295:
1296: // QUESTION:
1297: // Clone method (from 1.1.1 version) removed!?!?!? yet LightRetained has it
1298:
1299: // Debug print mechanism for Sound nodes
1300: static final boolean debugFlag = false;
1301: static final boolean internalErrors = false;
1302:
1303: void debugPrint(String message) {
1304: if (debugFlag) {
1305: System.err.println(message);
1306: }
1307: }
1308:
1309: void getMirrorObjects(ArrayList leafList, HashKey key) {
1310: if (key == null) {
1311: leafList.add(mirrorSounds[0]);
1312: } else {
1313: for (int i = 0; i < numMirrorSounds; i++) {
1314: if (mirrorSounds[i].key.equals(key)) {
1315: leafList.add(mirrorSounds[i]);
1316: break;
1317: }
1318: }
1319:
1320: }
1321: }
1322:
1323: }
|