Feature Files/VRSound.c

//////////
//
//  File:       VRSound.c
//
//  Contains:   Sound support for QuickTime VR movies.
//
//  Written by: Tim Monroe
//
//  Copyright:  © 1997 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <11>     03/13/98    rtm     removed support for chunk-based files (AIFF and WAVE)
//     <10>     09/18/97    rtm     added parameter to VRSound_CheckForCompletedSounds
//     <9>      08/30/97    rtm     begun adding support for chunk-based files (AIFF and WAVE)
//     <8>      08/11/97    rtm     added support for sound files (of type 'sfil')
//     <7>      06/19/97    rtm     added support for scene-wide sounds
//     <6>      04/30/97    rtm     added distance attenuation to VRSound_SetBalanceAndVolume
//     <5>      04/25/97    rtm     added VRSound_SetBalanceAndVolume to substitute for SoundSprocket
//                                  (and to support 68K Macs)
//     <4>      04/24/97    rtm     merged VRSound.c with previous VR3DSound.c
//     <3>      04/21/97    rtm     implemented fade-out option when stopping sounds
//     <2>      04/18/97    rtm     implemented callback procedure
//     <1>      04/17/97    rtm     first file
//   
//
// This file provides functions to play both ambient and localized sounds in QTVR
// panoramas and objects.
//
// Some of this code is straight out of Inside Macintosh: Sound and the SoundSprocket
// documentation (by yours truly!) or the SoundSprocket sample source (by Dan Venolia).
//
//////////
 
// TODO:
 
//////////
//
// header files
//
//////////
 
#include "VRSound.h"
#include "QTVRUtilities.h"
 
 
//////////
//
// global variables
//
//////////
 
SndCallBackUPP          gSndCallBackProc = NULL;    // a routine descriptor for our sound callback
Boolean                 gHasSoundManager30;         // is Sound Manager version 3.0 (or greater) available?
 
extern Boolean          gHasSoundSprockets;         // is SoundSprockets available?
 
 
//////////
//
// constants
//
//////////
 
#define kBlockIsLocked  0x80                        // the block-is-locked bit in the SignedByte returned by HGetState
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Sound utility functions.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// VRSound_CheckVersionNumber
// Returns true if the given version number is compatible with
// (that is, not older than) version theMajor.theMinor.theBug.
//
//////////
 
Boolean VRSound_CheckVersionNumber (
    const NumVersion        *theVersion,
    UInt8                   theMajor,
    UInt8                   theMinor,
    UInt8                   theBug)
{
    if (theVersion->majorRev != theMajor)
        return(theVersion->majorRev > theMajor);
    else
        return(theVersion->minorAndBugRev >= theMinor << 4 | theBug);
}
 
 
//////////
//
// VRSound_GetSoundHeader
// Returns a pointer to the sound header in a sampled sound resource.
//
//////////
 
SoundHeaderPtr VRSound_GetSoundHeader (Handle theSndHandle)
{
    SoundHeaderPtr      mySndHeader = NULL;
    long                myOffset = 0;
    OSErr               myErr = noErr;
    
    myErr = GetSoundHeaderOffset((SndListHandle)theSndHandle, &myOffset);
    if (myErr == noErr)
        mySndHeader = (SoundHeaderPtr)(*theSndHandle + myOffset);
 
    return(mySndHeader);
}
 
 
//////////
//
// VRSound_GetSndBaseFrequency
// Returns the base frequency of a sampled sound.
// We need this when installing the sound as a voice, in VRSound_PlayResource.
//
//////////
 
long VRSound_GetSndBaseFrequency (Handle theSndHandle)
{
    SoundHeaderPtr      mySndHeader;
    long                myBaseFreq = kMiddleC;      // a reasonable default
    
    mySndHeader = VRSound_GetSoundHeader(theSndHandle);
    if (mySndHeader != NULL)
        myBaseFreq = mySndHeader->baseFrequency;
 
    return(myBaseFreq);
}
 
 
//////////
//
// VRSound_GetVolume
// Get the current left and right volumes of a sound channel.
//
//////////
 
OSErr VRSound_GetVolume (SndChannelPtr theChannel, unsigned short *theLeftVol, unsigned short *theRightVol)
{
    SndCommand          mySndCommand;
    unsigned long       myVolume;
    OSErr               myErr = noErr;
 
    if (!gHasSoundManager30)
        return(paramErr);
        
    mySndCommand.cmd = getVolumeCmd;
    mySndCommand.param1 = 0;
    mySndCommand.param2 = (long)&myVolume;
    myErr = SndDoImmediate(theChannel, &mySndCommand);
    
    if (myErr == noErr) {
        *theLeftVol = myVolume & 0x0000ffff;
        *theRightVol = myVolume >> 16;
    }
    
    return(myErr);
}
 
 
//////////
//
// VRSound_SetVolume
// Set the left and right volumes of a sound channel.
//
//////////
 
OSErr VRSound_SetVolume (SndChannelPtr theChannel, unsigned short theLeftVol, unsigned short theRightVol)
{
    SndCommand          mySndCommand;
    OSErr               myErr = noErr;
 
    if (!gHasSoundManager30)
        return(paramErr);
        
    mySndCommand.cmd = volumeCmd;
    mySndCommand.param1 = 0;
    mySndCommand.param2 = (theRightVol << 16) | theLeftVol;
    
    myErr = SndDoImmediate(theChannel, &mySndCommand);
    
    return(myErr);
}
 
 
//////////
//
// VRSound_CreateSoundChannel
// Create a sound channel.
//
//////////
 
SndChannelPtr VRSound_CreateSoundChannel (Boolean theSoundIsLocalized)
{
    SndChannelPtr       mySndChannel = NULL;
    SoundComponentLink  myLink;
    OSStatus            myErr;
    
    // create storage for a new sound channel
    mySndChannel = (SndChannelPtr)NewPtrClear(sizeof(SndChannel));
    if (mySndChannel == NULL)
        return(mySndChannel);
    
    // set the number of commands in the sound channel queue
    mySndChannel->qLength = kVRSound_NumCmdsInQueue;
    
    // create the sound channel
    myErr = SndNewChannel(&mySndChannel, sampledSynth, initMono, gSndCallBackProc);
    if (myErr != noErr) {
        DisposePtr((Ptr)mySndChannel);
        return(NULL);
    }
    
    if (theSoundIsLocalized) {
        if (gHasSoundSprockets) {
            // install the 3D sound filters
            myLink.description.componentType            = kSoundEffectsType;
            myLink.description.componentSubType         = kSSpLocalizationSubType;
            myLink.description.componentManufacturer    = 0;
            myLink.description.componentFlags           = 0;
            myLink.description.componentFlagsMask       = 0;
            myLink.mixerID                              = NULL;
            myLink.linkID                               = NULL;
            
            myErr = SndSetInfo(mySndChannel, siPreMixerSoundComponent, &myLink);
        }
    }
    
    if (myErr != noErr) {
        DisposePtr((Ptr)mySndChannel);
        mySndChannel = NULL;
    }
    
    return(mySndChannel);
}
 
 
//////////
//
// VRSound_CreateLocalizedSource
// Create a localized sound source.
//
//////////
 
SSpSourceReference VRSound_CreateLocalizedSource (void)
{
    SSpSourceReference      mySource = 0;
    
    // create a new sound source
#if SOUNDSPROCKET_AVAIL
    if (gHasSoundSprockets)
        SSpSource_New(&mySource);
#endif
 
    return(mySource);
}
 
 
//////////
//
// VRSound_SetLocation
// Set the location of a localized sound.
//
//////////
 
void VRSound_SetLocation (WindowObject theWindowObject, UInt32 theEntryID, float theX, float theY, float theZ, UInt32 theOptions)
{
    VRScriptSoundPtr    myPointer;
 
    myPointer = (VRScriptSoundPtr)VRScript_GetObjectByEntryID(theWindowObject, kVREntry_Sound, theEntryID);
    if (myPointer != NULL) {
        if (theOptions == kVRValue_Relative) {
            myPointer->fLocation.x += theX;
            myPointer->fLocation.y += theY;
            myPointer->fLocation.z += theZ;
        } else {
            myPointer->fLocation.x = theX;
            myPointer->fLocation.y = theY;
            myPointer->fLocation.z = theZ;
        }
        
#if SOUNDSPROCKET_AVAIL
        if (gHasSoundSprockets)
            SSpSource_SetPosition(myPointer->fSource, &(myPointer->fLocation));
#endif
    }
}
 
 
//////////
//
// VRSound_GetSndResourceID
// Get the resource ID of the first 'snd ' resource in the specified resource file.
//
//////////
 
short VRSound_GetSndResourceID (short theRefNum)
{
    Handle      myResource;
    short       myResID = 0;
    short       myCurRefNum;
    ResType     myResType;
    Str255      myResName;
    
    // make sure the specified file is the current resource file
    myCurRefNum = CurResFile();
    if (theRefNum != myCurRefNum)
        UseResFile(theRefNum);
    
    if (theRefNum > 0) {
        // find the resource ID of the single 'snd ' resource in the resource file;
        // we do this by loading the resource and then getting its info.
        myResource = Get1IndResource(soundListRsrc, 1);
        if (myResource != NULL)
            GetResInfo(myResource, &myResID, &myResType, myResName);
    }
    
    // restore the current resource file
    if (theRefNum != myCurRefNum)
        UseResFile(myCurRefNum);
    
    return(myResID);
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Sound list utility functions.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// VRSound_Update3DSoundEnv
// Update the virtual audio environment.
//
//////////
 
void VRSound_Update3DSoundEnv (WindowObject theWindowObject)
{
#if SOUNDSPROCKET_AVAIL
    SSpLocalizationData     myData;
#endif
    ApplicationDataHdl      myAppData;
    VRScriptSoundPtr        myPointer;
 
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData == NULL)
        return;
    
    // walk our linked list and update this node's virtual audio environment
    myPointer = (VRScriptSoundPtr)(**myAppData).fListArray[kVREntry_Sound];
    if (gHasSoundSprockets) {
#if SOUNDSPROCKET_AVAIL
        while (myPointer != NULL) {
            if (myPointer->fSoundIsLocalized) {
                SSpSource_CalcLocalization(myPointer->fSource, (**myAppData).fListener, &myData);
                SndSetInfo(myPointer->fChannel, siSSpLocalization, &myData);
            }
            myPointer = myPointer->fNextEntry;
        }
#endif
    } else {
        // VRSound_SetBalanceAndVolume is triggered by the prescreen routine, so do an update
        (**myAppData).fSoundHasChanged = true;
        QTVRUpdate((**theWindowObject).fInstance, kQTVRCurrentMode);
    }
}
 
 
//////////
//
// VRSound_SetBalanceAndVolume
// Set the balance and volume attenuation of any localized sounds.
//
// This is a low-budget SoundSprocket replacement: we use the Sound Manager's volumeCmd 
// to adjust the volume and balance of a sound channel according to the given pan angle.
//
//////////
 
void VRSound_SetBalanceAndVolume (WindowObject theWindowObject, float thePan, float theTilt)
{
#pragma unused(theTilt)
 
    ApplicationDataHdl      myAppData;
    VRScriptSoundPtr        myPointer;
    
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData == NULL)
        return;
 
    // walk our linked list and set the balance and volume of any localized sounds
    myPointer = (VRScriptSoundPtr)(**myAppData).fListArray[kVREntry_Sound];
    while (myPointer != NULL) {
        if (myPointer->fSoundIsLocalized) {
            float           myPan;
            float           myPanDelta;
            float           myCosLimit;
            float           myCosDeltaPlus, myCosDeltaMinus;
            unsigned short  myLeftVol, myRightVol;
            float           myDistance;
            
            // get the pan angle of the localized sound
            myPan = QTVRUtils_Point3DToPanAngle(myPointer->fLocation.x, myPointer->fLocation.y, myPointer->fLocation.z);
                
            myPanDelta = thePan - myPan;
            myCosLimit = cos(myPointer->fProjAngle);
            myCosDeltaPlus = cos(myPanDelta + QTVRUtils_DegreesToRadians(kVRSound_SpeakerAngle));
            myCosDeltaMinus = cos(myPanDelta - QTVRUtils_DegreesToRadians(kVRSound_SpeakerAngle));
            
            // inside cone of attenuation, volume scales from 1.0 (at center) to 0.0 (at cone edge)
            // outside cone of attenuation, volume is 0.0
            myLeftVol = (myCosDeltaPlus >= myCosLimit) ? (kQTMaxSoundVolume * ((myCosDeltaPlus - myCosLimit) / (1 - myCosLimit))) : kNoVolume;
            myRightVol = (myCosDeltaMinus >= myCosLimit) ? (kQTMaxSoundVolume * ((myCosDeltaMinus - myCosLimit) / (1 - myCosLimit))) : kNoVolume;
 
            // now adjust the volume for the distance of the object from the listener;
            // we could use many other algorithms here....
            myDistance = QTVRUtils_GetDistance(myPointer->fLocation);
            if (myDistance > 1.0) {
                myLeftVol /= myDistance;
                myRightVol /= myDistance;
            }
            
            VRSound_SetVolume(myPointer->fChannel, myLeftVol, myRightVol);
        }
        
        myPointer = myPointer->fNextEntry;
    }
}
    
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Application initialization and shutdown.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// VRSound_Init
// Initialize for sound.
//
//////////
 
void VRSound_Init (void)
{
    NumVersion          myVersion;
 
    // check the Sound Manager version
    myVersion = SndSoundManagerVersion();
    
    // for doing any sound, we require version 3.0 or later
    gHasSoundManager30 = VRSound_CheckVersionNumber(&myVersion, 3, 0, 0);
 
    // for doing localized sound using SoundSprockets, we require version 3.2.1 or later
    if (!VRSound_CheckVersionNumber(&myVersion, 3, 2, 1))
        gHasSoundSprockets = false;
    
    // now see whether SoundSprockets is available;
    // there is no Gestalt selector for this package, so we use an alternate strategy:
#if SOUNDSPROCKET_AVAIL
    gHasSoundSprockets = ((short)SSpListener_New != (short)kUnresolvedCFragSymbolAddress);
#else
    gHasSoundSprockets = false;
#endif
 
    // allocate a routine descriptor for our sound callback routine
    if (gSndCallBackProc == NULL)
        gSndCallBackProc = NewSndCallBackUPP(VRSound_CallbackProc);
}
 
 
//////////
//
// VRSound_Stop
// Close down for sound.
//
//////////
 
void VRSound_Stop (void)
{
    // deallocate routine descriptor
    if (gSndCallBackProc != NULL)
        DisposeSndCallBackUPP(gSndCallBackProc);
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Window-specific initialization and clean-up.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// VRSound_InitWindowData
// Initialize window-specific data for sounds.
//
//////////
 
void VRSound_InitWindowData (WindowObject theWindowObject)
{
    ApplicationDataHdl  myAppData;
 
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData == NULL)
        return;
 
#if SOUNDSPROCKET_AVAIL
    if (gHasSoundSprockets) {
        OSStatus        myErr;
    
        // create a listener and set listener units to feet
        myErr = SSpListener_New(&(**myAppData).fListener);
        if (myErr == noErr)
            SSpListener_SetMetersPerUnit((**myAppData).fListener, 0.3048);
    }
#endif
}
 
 
//////////
//
// VRSound_DumpWindowData
// Get rid of window-specific data for sounds.
//
//////////
 
void VRSound_DumpWindowData (WindowObject theWindowObject)
{
    ApplicationDataHdl  myAppData;
 
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData == NULL)
        return;
        
    VRSound_DumpNodeSounds(theWindowObject);
    VRSound_DumpSceneSounds(theWindowObject);
 
#if SOUNDSPROCKET_AVAIL
    // shut down SoundSprockets, if enabled
    if (gHasSoundSprockets && ((**myAppData).fListener != NULL)) 
        SSpListener_Dispose((**myAppData).fListener);
#endif
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Manipulating multiple sound channels.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// VRSound_DumpNodeSounds
// Stop playing all sounds and release all sound resources enlisted for the current node.
//
//////////
 
void VRSound_DumpNodeSounds (WindowObject theWindowObject)
{
    VRSound_DumpSelectedSounds(theWindowObject, kVRSelect_Node);
}
 
 
//////////
//
// VRSound_DumpSceneSounds
// Stop playing all sounds and release all sound resources enlisted for the current scene.
//
//////////
 
void VRSound_DumpSceneSounds (WindowObject theWindowObject)
{
    VRSound_DumpSelectedSounds(theWindowObject, kVRSelect_Scene);
}
 
 
//////////
//
// VRSound_DumpSelectedSounds
// Stop playing selected sounds and release selected sound resources.
//
//////////
 
void VRSound_DumpSelectedSounds (WindowObject theWindowObject, UInt32 theOptions)
{
    ApplicationDataHdl  myAppData;
    VRScriptSoundPtr    myPointer;
    VRScriptSoundPtr    myNext;
    
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData == NULL)
        return;
 
    // stop playing and delist any sounds associated with this node
    myPointer = (VRScriptSoundPtr)(**myAppData).fListArray[kVREntry_Sound];
    while (myPointer != NULL) {     
        myNext = myPointer->fNextEntry;
        if (((myPointer->fNodeID != kVRAnyNode) && (theOptions == kVRSelect_Node)) ||
            ((myPointer->fNodeID == kVRAnyNode) && (theOptions == kVRSelect_Scene))) {
            if (myPointer->fFadeWhenStopping)
                VRSound_FadeSilence(theWindowObject, myPointer->fChannel);
            else
                VRSound_PlaySilence(theWindowObject, myPointer->fChannel);
                
            VRScript_DelistEntry(theWindowObject, (VRScriptGenericPtr)myPointer);
        }
        myPointer = myNext;
    }
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Manipulating a single sound channel.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// VRSound_FadeSilence
// Fade out the sound playing on a particular sound channel.
//
//////////
 
void VRSound_FadeSilence (WindowObject theWindowObject, SndChannelPtr theChannel)
{
#pragma unused(theWindowObject)
 
    unsigned short      myLeftVol;
    unsigned short      myRightVol;
    short               myCount;
    unsigned long       myTicks;
    float               myFactor;
    OSErr               myErr = noErr;
 
    if (theChannel == NULL)
        return;
    
    // get the current right and left volumes
    myErr = VRSound_GetVolume(theChannel, &myLeftVol, &myRightVol);
    
    if (myErr == noErr) {
        // now gradually reduce the volume of each channel to silence
        for (myCount = 1; myCount < kVRSound_NumFadeSteps; myCount++) {
            Delay(kVRSound_FadeStepWait, &myTicks);         // wait a small bit, to make fade perceptible
            myFactor = (float)(kVRSound_NumFadeSteps - myCount) / (float)kVRSound_NumFadeSteps;
            myLeftVol *= myFactor;
            myRightVol *= myFactor;
            VRSound_SetVolume(theChannel, myLeftVol, myRightVol);
        }
    }   
}
 
 
//////////
//
// VRSound_PlaySilence
// Stop the sound playing on a particular sound channel.
//
//////////
 
void VRSound_PlaySilence (WindowObject theWindowObject, SndChannelPtr theChannel)
{
#pragma unused(theWindowObject)
    SndCommand          mySndCommand;
 
    if (theChannel == NULL)
        return;
        
    mySndCommand.cmd = quietCmd;
    mySndCommand.param1 = 0;
    mySndCommand.param2 = 0;
    SndDoImmediate(theChannel, &mySndCommand);
}
 
 
//////////
//
// VRSound_PlayResource
// Play the specified sound resource on the specified sound channel.
//
//////////
 
void VRSound_PlayResource (WindowObject theWindowObject, SndChannelPtr theChannel, SndListHandle theResHandle, UInt32 theOptions)
{
    SndCommand          mySndCommand;
    long                myOffset;
    ApplicationDataHdl  myAppData;
    SignedByte          myHState;
    OSErr               myErr = noErr;
 
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData == NULL)
        return;
        
    if (theResHandle == NULL)
        return;
 
    // silence the sound channel
    VRSound_PlaySilence(theWindowObject, theChannel);
        
    // lock the resource data down, if it isn't already locked
    myHState = HGetState((Handle)theResHandle);
    if (!(myHState & kBlockIsLocked))
        HLock((Handle)theResHandle);
    
    GetSoundHeaderOffset(theResHandle, &myOffset);
    
    switch (theOptions) {
        case kVRPlay_Loop:
            // loop the sound indefinitely
            // TO DO: need to rewirte this without freqCmd, which ain't in Carbon
#if !TARGET_API_MAC_CARBON
            mySndCommand.cmd = soundCmd;
            mySndCommand.param1 = 0;
            mySndCommand.param2 = (long)*(theResHandle) + myOffset;
            myErr = SndDoImmediate(theChannel, &mySndCommand);
 
            mySndCommand.cmd = freqCmd;
            mySndCommand.param1 = 0;
            mySndCommand.param2 = VRSound_GetSndBaseFrequency((Handle)theResHandle);
            myErr = SndDoImmediate(theChannel, &mySndCommand);
#endif
            break;
            
        case kVRPlay_Once:
            // play the sound once thru, asynchronously
            mySndCommand.cmd = bufferCmd;
            mySndCommand.param1 = 0;
            mySndCommand.param2 = (long)*(theResHandle) + myOffset;
            myErr = SndDoImmediate(theChannel, &mySndCommand);
            break;
        
        default:
            break;
    }
}
 
 
//////////
//
// VRSound_PlaySound
// Start a sound playing or stop one from playing.
//
//////////
 
void VRSound_PlaySound (WindowObject theWindowObject, UInt32 theNodeID, UInt32 theResID, UInt32 theEntryID, float theX, float theY, float theZ, float theProjAngle, UInt32 theSourceMode, UInt32 theMode, UInt32 theFade, UInt32 theOptions)
{
    ApplicationDataHdl      myAppData;
    Handle                  myHandle;
    Boolean                 myNeedPlaySound = false;
    Boolean                 myNeedNewChannel = false;
    SndChannelPtr           mySndChannel;
    VRScriptSoundPtr        myPointer = NULL;
    SSpSourceReference      mySource = 0;
    
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData == NULL)
        return;
    
    // see if this sound is already in our list of playing sounds               
    myPointer = (VRScriptSoundPtr)VRScript_GetObjectByEntryID(theWindowObject, kVREntry_Sound, theEntryID);
    if (myPointer == NULL) {
        // this sound isn't in our list yet, so we'll need to add it to the list
        myNeedPlaySound = true;
        myNeedNewChannel = true;
    } else {
        // this sound is already in our list; theOptions determines how we handle this request:
        
        mySource = myPointer->fSource;
 
        switch (theOptions) {
        
            case kVRMedia_PlayNew:
                // start the sound playing (whether it's already playing or not)
                myNeedPlaySound = true;
                myNeedNewChannel = true;
                break;
                
            case kVRMedia_Restart:
                // stop the current sound and then restart it; use the existing sound channel
                // (PlayResource automatically stops the current sound on this channel)
                myNeedPlaySound = true;
                myNeedNewChannel = false;
                break;
                
            case kVRMedia_ToggleStop:
            case kVRMedia_TogglePause:
                // toggle the sound's current play/stop state
                myNeedNewChannel = false;
                if (myPointer->fSoundIsPlaying) {
                    // stop the sound
                    if (myPointer->fFadeWhenStopping)
                        VRSound_FadeSilence(theWindowObject, myPointer->fChannel);
                    else
                        VRSound_PlaySilence(theWindowObject, myPointer->fChannel);
                    myPointer->fSoundIsPlaying = false;
                    myNeedPlaySound = false;
                } else {
                    // start the sound; use the existing sound channel
                    myNeedPlaySound = true;
                }
                break;
                
            case kVRMedia_Continue:
                // just let the sound already in progress continue
                myNeedPlaySound = false;
                myNeedNewChannel = false;
                break;
                
            case kVRMedia_Stop:
                // stop the sound
                if (myPointer->fFadeWhenStopping)
                    VRSound_FadeSilence(theWindowObject, myPointer->fChannel);
                else
                    VRSound_PlaySilence(theWindowObject, myPointer->fChannel);
                myPointer->fSoundIsPlaying = false;
                myNeedPlaySound = false;
                myNeedNewChannel = false;
                break;
                
            default:
                // unrecognized option
                // start the specified resource playing; use the existing sound channel
                myNeedPlaySound = true;
                myNeedNewChannel = false;
                break;
        }
        
    }
    
    if (myNeedNewChannel) {
        if (theSourceMode == kSSpSourceMode_Ambient) {
            mySndChannel = VRSound_CreateSoundChannel(false);
        } else {
            mySource = VRSound_CreateLocalizedSource();
            mySndChannel = VRSound_CreateSoundChannel(true);
        }
        
        if (mySndChannel == NULL) 
            return;     // couldn't allocate a new channel, so we cannot play sound....
        
        myHandle = GetResource(soundListRsrc, theResID);
        if (myHandle != NULL) {
            HNoPurge((Handle)myHandle);
            if (theSourceMode == kSSpSourceMode_Ambient) {
                myPointer = VRScript_EnlistSound(theWindowObject, theNodeID, theResID, theEntryID, theMode, theFade, theOptions, mySndChannel, (SndListHandle)myHandle);
            } else {
                myPointer = VRScript_EnlistLocalizedSound(theWindowObject, theNodeID, theResID, theEntryID, theX, theY, theZ, theProjAngle, theMode, theFade, theOptions, mySndChannel, (SndListHandle)myHandle, mySource);
            }
        } else {
            SndDisposeChannel(mySndChannel, true);
            DisposePtr((Ptr)mySndChannel);
            return;
        }
        
    }
 
    if (myNeedPlaySound) {
        if (myPointer != NULL) {
        
            if (theSourceMode != kSSpSourceMode_Ambient) {
                if (gHasSoundSprockets) {
                    TQ3Point3D          myPoint;
                    TQ3Vector3D         myOrient;
                    
                    // install this sound using SoundSprocket's high-level interfaces
                    myPoint.x = theX;
                    myPoint.y = theY;
                    myPoint.z = theZ;
                    
                    myOrient.x = -myPoint.x;
                    myOrient.y = -myPoint.y;
                    myOrient.z = -myPoint.z;
                    
#if SOUNDSPROCKET_AVAIL
                    SSpSource_SetPosition(mySource, &myPoint);
                    SSpSource_SetOrientation(mySource, &myOrient);
                    SSpSource_SetAngularAttenuation(mySource, kVRPi/8, 20.0);
                    SSpSource_SetMode(mySource, theSourceMode);
                    SSpSource_SetReferenceDistance(mySource, 1.0);
                    SSpSource_SetSize(mySource, 0.0, 0.0, 0.0);
#endif
                }
                
                VRSound_Update3DSoundEnv(theWindowObject);
            }
            
            VRSound_PlayResource(theWindowObject, myPointer->fChannel, myPointer->fResourceData, theMode);
            myPointer->fSoundIsPlaying = true;
            if (theMode == kVRPlay_Once)
                VRSound_InstallCallbackMessage(myPointer, kVRSound_Complete);
        }   
    }
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Callback procedure functions and utilities.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// VRSound_InstallCallbackMessage
// Clean up after a sound is finished playing.
//
//////////
 
void VRSound_InstallCallbackMessage (VRScriptSoundPtr theEntry, short theMessage)
{
    if (theEntry != NULL) {
        SndCommand          myCommand;
        
        myCommand.cmd = callBackCmd;
        myCommand.param1 = theMessage;
        myCommand.param2 = (long)theEntry;
        SndDoCommand(theEntry->fChannel, &myCommand, false);
    }
}
 
 
//////////
//
// VRSound_CallbackProc
// Handle callback messages.
//
//////////
 
PASCAL_RTN void VRSound_CallbackProc (SndChannelPtr theChannel, SndCommand *theCommand)
{
#pragma unused(theChannel)
 
    VRScriptSoundPtr    myPointer = NULL;
    
    switch(theCommand->param1) {
        case kVRSound_Complete:
            myPointer = (VRScriptSoundPtr)(theCommand->param2);
            if (myPointer != NULL)
                myPointer->fSoundIsPlaying = false;
            break;
            
        default:
            break;
    }
 
}
 
 
//////////
//
// VRSound_GetFinishedSound
// Get the first enlisted sound that is done playing.
//
//////////
 
VRScriptSoundPtr VRSound_GetFinishedSound (WindowObject theWindowObject)
{
    ApplicationDataHdl  myAppData;
    VRScriptSoundPtr    myPointer = NULL;
 
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);    
    if (myAppData == NULL)
        return(NULL);
    
    // walk our linked list of sounds to find the target sound
    myPointer = (VRScriptSoundPtr)(**myAppData).fListArray[kVREntry_Sound];
    while (myPointer != NULL) {
        if (myPointer->fSoundIsPlaying == false)
            return(myPointer);
 
        myPointer = myPointer->fNextEntry;
    }
    
    return(NULL);
}
 
 
//////////
//
// VRSound_CheckForCompletedSounds
// Clean up any sounds that are finished playing.
//
//////////
 
void VRSound_CheckForCompletedSounds (WindowObject theWindowObject)
{
    VRScriptSoundPtr        myPointer = NULL;
 
    // delist any completed sounds for the specified movie window
    if (theWindowObject != NULL)
        while ((myPointer = VRSound_GetFinishedSound(theWindowObject)) != NULL)
            VRScript_DelistEntry(theWindowObject, (VRScriptGenericPtr)myPointer);
}
 
 
//////////
//
// VRSound_DumpEntryMem
// Release any memory associated with the specified list entry.
//
//////////
 
void VRSound_DumpEntryMem (VRScriptSoundPtr theEntry)
{
    if (theEntry == NULL)
        return;
 
    // how we release memory depends on the original container of the sound data
    if (theEntry->fSoundContainer == kVRSound_SoundResource) {
        HUnlock((Handle)(theEntry->fResourceData));
        ReleaseResource((Handle)(theEntry->fResourceData));
    }
    
#if SOUNDSPROCKET_AVAIL
    if (gHasSoundSprockets && (theEntry->fSource != NULL))
        SSpSource_Dispose(theEntry->fSource);
#endif
 
    SndDisposeChannel(theEntry->fChannel, true);
    DisposePtr((Ptr)theEntry->fChannel);            // because we allocated the storage ourselves....
}