home *** CD-ROM | disk | FTP | other *** search
- /*
- File: SoundUtils.c
- Written by Hiep Dam.
-
- Based on code written by Brigham Stevens, develop 17 (GameSounds.c)
- Herein lies the "core" sound routines generic enuf to be usable by anyone
- and any application.
-
- Last Update: Oct 1995
-
- Version 1.1 (Oct 26 95) [HTD]: Compiled with PowerPC, updated CreateSndChannel
- to be compatible with PPC via call to NewSndCallBackProc & addition of
- global UPP sSndCallBackProc.
-
- */
-
- #include <Sound.h>
- #include "SoundUtils.h"
-
-
- enum {
- kSndIdle = 0, // No sound is playing, and sound has been purged
- kSndDone = -1 // Sound done playing, and needs to be unlocked & purged
- };
-
- // Our private data structure, used to hold the sound channel,
- // as well as other info pertaining to the channel
- typedef struct {
- SndChannelPtr chan;
- Handle snd;
- short priority; // Either kSndIdle, kSndDone, or a number > 0
- short padding;
- } SndChanInfo, *SndChanInfoPtr;
-
- // Our global storage of channel data (4 max)
- static SndChanInfo gSndChans[kMaxChans] =
- { nil, nil, kSndIdle, 0,
- nil, nil, kSndIdle, 0,
- nil, nil, kSndIdle, 0,
- nil, nil, kSndIdle, 0 };
-
- // Sound Manager Version miscellany
- #define kEnhancedSoundMgr 3
- static unsigned char gSoundMgrVersion;
- static char padding_char;
- static short padding_short;
- static SndCallBackUPP sSndCallBackProc = NULL;
-
- //#define REINIT_CHANNEL
-
- // ---------------------------------------------------------------------------
-
- // Our callback procedure.
- pascal void SndDoneProc(SndChannelPtr chan, SndCommand *cmd);
-
- // ---------------------------------------------------------------------------
-
- void InitSoundUtils() {
- /*
- This sets up some internal variables; you should call this
- before using any other calls in SoundUtils.c, else some
- strange things might happen...
- */
- //gSoundMgrVersion = SndSoundManagerVersion().majorRev;
- gSoundMgrVersion = SndSoundManagerVersion();
-
- sSndCallBackProc = NewSndCallBackProc(SndDoneProc);
- } // END InitSoundUtils
-
- // ---------------------------------------------------------------------------
-
- OSErr CreateSndChannel(short whichChan) {
- /*
- Create a sound channel. If the channel whichChan is currently
- occupied, it disposes it first before allocating a new channel.
- */
- OSErr err;
- if (gSndChans[whichChan].chan != nil)
- err = DisposeSndChannel(whichChan);
-
- // Ah, for the love of PPC!
- // If the snd callback proc hasn't been assigned yet, do it now
- if (sSndCallBackProc == NULL)
- sSndCallBackProc = NewSndCallBackProc(SndDoneProc);
-
- err = SndNewChannel(&gSndChans[whichChan].chan, sampledSynth,
- initMono + initNoDrop + initNoInterp, sSndCallBackProc);
-
- return(err);
- } // END CreateSndChannel
-
- // ---------------------------------------------------------------------------
-
- OSErr DisposeSndChannel(short whichChan) {
- /*
- Dispose a sound channel and resets some internal data.
- If the channel is already disposed, it just exits.
- */
- OSErr err;
-
- if (gSndChans[whichChan].chan == nil)
- return(noErr);
-
- err = SndDisposeChannel(gSndChans[whichChan].chan, true);
- // Reset our internal data, flags...
- if (err == noErr) {
- gSndChans[whichChan].chan = nil;
- gSndChans[whichChan].snd = nil;
- gSndChans[whichChan].priority = kSndIdle;
- }
-
- return(err);
- } // END DisposeSndChannel
-
- // ---------------------------------------------------------------------------
-
- void PlayAsynch(Handle sndHdl, short whichChan) {
- /*
- Play a sound asynchronously, using SndPlay.
-
- NOTE: The channel specified in whichChan must be
- already allocated via CreateSndChannel(). No checking
- is done.
- */
- OSErr err;
-
- #ifdef REINIT_CHANNEL
- if (gSoundMgrVersion < kEnhancedSoundMgr) {
- err = DisposeSndChannel(whichChan);
- if (err != noErr)
- return;
- err = CreateSndChannel(whichChan);
- if (err != noErr)
- return;
- }
- #endif
-
- HLock(sndHdl);
- err = SndPlay(gSndChans[whichChan].chan, (SndListHandle)sndHdl, true); // Simple, no?
- } // END PlayAsynch
-
- // ---------------------------------------------------------------------------
-
- void PlayAsynchBuffer(Handle sndHdl, short whichChan) {
- /*
- This routine plays a sound asynchronously, using
- bufferCmds and SndDoImmediate, rather than SndPlay.
- */
- SoundHeader *sndDataOffset;
- long offset;
- SndCommand sndCmd;
- OSErr err;
-
- // First, lock the sucker.
- if (sndHdl == nil) return;
- HLockHi(sndHdl);
-
- #ifdef REINIT_CHANNEL
- if (gSoundMgrVersion < kEnhancedSoundMgr) {
- err = DisposeSndChannel(whichChan);
- if (err != noErr) {
- HUnlock(sndHdl); return;
- }
- err = CreateSndChannel(whichChan);
- if (err != noErr) {
- HUnlock(sndHdl); return;
- }
- }
- #endif
-
- // Get the offset into the sound resource, wherein the
- // sound header lies...
- err = RetrieveSndHeaderOffset(sndHdl, &offset);
- if (err != noErr) {
- HUnlock(sndHdl);
- return;
- }
-
- // The bufferCmd requires we give it a pointer to a sound
- // header, which lies somewhere in the sound resource.
- // We retrieve this location by determining the offset into
- // the resource via RetrieveSndHeaderOffset, add it to
- // the beginning address of the sound, and voila!
- sndDataOffset = (SoundHeader*)(StripAddress(*sndHdl) + offset);
-
- // Stuff in the parameters so bufferCmd can do its thing...
- sndCmd.cmd = bufferCmd;
- sndCmd.param1 = 0;
- sndCmd.param2 = (long)sndDataOffset;
-
- // Push it into the sound channel queue...
- err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
- } // END PlayAsycnchBuffer
-
- // ---------------------------------------------------------------------------
-
- void SndChanStop(SndChannelPtr chan) {
- /*
- Use this to stop any sound currently playing.
- It does this by first telling the sound channel to
- stop (quiet) and removes any other sounds that
- may have been queued in the channel (flush), else
- those might start playing...
- */
- SndCommand sndCmd;
- OSErr err;
-
- sndCmd.cmd = quietCmd;
- sndCmd.param1 = sndCmd.param2 = 0; // Unused
- err = SndDoImmediate(chan, &sndCmd);
- if (err == noErr) {
- sndCmd.cmd = flushCmd;
- err = SndDoImmediate(chan, &sndCmd);
- }
- } // END SndChanStop
-
- void SndStop(short whichChan) {
- SndChanStop(gSndChans[whichChan].chan);
- } // END SndStop
-
- // ---------------------------------------------------------------------------
-
- void SndStopSoftly(short whichChan) {
- if (!SndDone(whichChan)) {
- short saveAmp, i;
- long dummy;
-
- saveAmp = SndGetAmplitude(whichChan);
-
- i = saveAmp;
- while (i > 0) {
- SndSetAmplitude(whichChan, i);
- i -= 10;
- Delay(1, &dummy);
- if (i < 0) break;
- }
-
- // Stop it now.
- SndStop(whichChan);
- // Restore amplitude, since setting amplitude with no
- // sound playing will apply it to next sound played...
- SndSetAmplitude(whichChan, saveAmp);
- }
- } // END SndStopSoftly
-
- // ---------------------------------------------------------------------------
-
- void WaitTillSndDone(short whichChan) {
- while (!SndDone(whichChan)) {
- // Do nothing
- }
- } // END WaitTillSndDone
-
- // ---------------------------------------------------------------------------
-
- Boolean SndDone(short whichChan) {
- /*
- Tells you whether the sound is done playing
- (i.e. whether the sound channel is idle or not).
- Specify which channel...
- */
- return(SndChanDone(gSndChans[whichChan].chan));
- } // END SndDone
-
- Boolean SndChanDone(SndChannelPtr chan) {
- /*
- Same as SndDone, but you can pass any 'ol sound channel,
- not just our private array of snd channels...
- */
- OSErr err;
- SCStatus status;
-
- // Poll the channel
- err = SndChannelStatus(chan, sizeof(SCStatus), &status);
- if (err == noErr)
- // If the channel is busy, then the sound is NOT done,
- // else the sound is done (and the channel is idle)...
- return(!status.scChannelBusy);
- else
- return(false); // Hmm. Error!
- } // END SndChanDone
-
- // ---------------------------------------------------------------------------
-
- short SndChanGetAmplitude(SndChannelPtr chan) {
- /*
- Get the amplitude of the sound currently being played
- in chan. If an error occurred, will return -1, else
- a value in the range 0..255
- */
- SndCommand sndCmd;
- OSErr err;
- short amp;
-
- sndCmd.cmd = getAmpCmd;
- sndCmd.param1 = 0; // Unused
- sndCmd.param2 = (long)&
-
- err = SndDoImmediate(chan, &sndCmd);
- if (err != noErr)
- return(-1);
- else
- return(amp);
- } // END SndChanGetAmplitude
-
- short SndGetAmplitude(short whichChan) {
- return(SndChanGetAmplitude(gSndChans[whichChan].chan));
- } // END SndGetAmplitude
-
- // ---------------------------------------------------------------------------
-
- void SndChanSetAmplitude(SndChannelPtr chan, short amp) {
- /*
- Use this to change the amplitude (loudness) of the sound being
- currently played in the sound channel. Note, if no sound
- is playing this sets the amplitude of the next sound to
- be played.
- The amplitude range in "amp" should be in the range 0..255
- */
- SndCommand sndCmd;
- OSErr err;
-
- if (chan != nil) {
- sndCmd.cmd = ampCmd;
- sndCmd.param1 = amp;
- sndCmd.param2 = 0;
- }
-
- err = SndDoImmediate(chan, &sndCmd);
- } // END SndChanSetAmplitude
-
- void SndSetAmplitude(short whichChan, short amp) {
- SndChanSetAmplitude(gSndChans[whichChan].chan, amp);
- } // END SndSetAmplitude
-
- // ---------------------------------------------------------------------------
-
- // Format 1 of 'snd ' rsrc header
- typedef struct {
- short format, numSynths;
- } Snd1Header, *Snd1HeaderPtr;
-
- // Format 2 of 'snd ' rsrc header
- typedef struct {
- short format, refCount;
- } Snd2Header, *Snd2HeaderPtr;
-
- OSErr RetrieveSndHeaderOffset(Handle sndHdl, long *offset) {
- /*
- This routine is used to find the offset of a handle to a sound
- into the sound's header. This routine can be used either
- with the older Sound Manager or the newer 3.0 version
- (it calls GetSoundHeaderOffset() if using 3.0, else it
- steps through the muck itself under older versions).
-
- NOTE: Make sure you lock 'sndHdl' before passing it to
- RetrieveSndHeaderOffset...
- */
- Ptr myPtr; // To navigate resource
- long myOffset; // Offset into resource
- short numSynths; // Info about resource
- short numCmds; // Ditto.
- Boolean isDone; // Are we done yet?
- OSErr myErr; // Oooh...
-
- if (gSoundMgrVersion >= kEnhancedSoundMgr)
- // Using new sound manager, our work is done for us...
- #ifdef __MWERKS__
- return(GetSoundHeaderOffset((SndListHandle)sndHdl, offset));
- #else
- // I'm assuming if this is not MetroWerks, it is an older
- // Symantec C++ (non-Universal headers) so here is the
- // workaround. Delete this if you have the Universal
- // headers [yeah I have an older version of SymC++; who
- // wants to pay $200+ for a friggin' upgrade?!?]
- return(20);
- #endif
- else {
- // Hmm. Have to do this crap ourselves...
- // Initialize variables
- myOffset = 0; // Return 0 if no snd header found
- myPtr = (Ptr)*sndHdl; // Point to start of rsrc data
- isDone = false; // Haven't yet found snd header
- myErr = noErr;
-
- // This thing doesn't work; so I'll just assume it's a
- // Type 1 sound (system) and set the offset as 20. If
- // it's type 2 (hypercard) it won't work, since the offset
- // is 14...
- *offset = 20;
- return(myErr);
-
- // Skip everything before sound commands
- switch(((Snd1HeaderPtr)myPtr)->format) {
-
- // Format 1 'snd ' resource
- case firstSoundFormat:
- // Skip header start, synth id, etc.
- numSynths = ((Snd1HeaderPtr)myPtr)->numSynths;
- myPtr += sizeof(Snd1Header);
- myPtr += numSynths + sizeof(short) + sizeof(long);
- break;
-
- // Format 2 'snd ' resource
- case secondSoundFormat:
- myPtr += sizeof(Snd2Header);
- break;
-
- // Unrecognized format
- default:
- myErr = badFormat;
- isDone = true;
- break;
- } // END switch
-
- // Find number of commands and move to start of first cmd
- numCmds = *(short*)myPtr;
- myPtr += sizeof(short);
-
- // Search for bufferCmd or soundCmd to obtain sound header
- while ((numCmds >= 1) && !isDone) {
- if ((*(short*)myPtr == bufferCmd + dataOffsetFlag ||
- *(short*)myPtr == soundCmd + dataOffsetFlag)) {
- myOffset = ((SndCommand*)myPtr)->param2;
- isDone = true;
- }
- else {
- myPtr += sizeof(SndCommand);
- numCmds--;
- }
- } // END while
-
- *offset = myOffset;
- return(myErr);
- }
- } // END RetrieveSndHeaderOffset
-
- // ---------------------------------------------------------------------------
-
- SoundHeader *GetSoundHeader(Handle sndHdl) {
- /*
- Obtain a pointer to the sound header of a sound resource. Duh!
-
- NOTE: It is up to you to lock the handle to the sound.
- Unlock it ONLY after you a done with the pointer returned
- by GetSoundHeader (if the sound not locked & becomes relocated,
- then your sound header pointer may point to trash!)
- */
- long offset;
- OSErr err;
-
- if (sndHdl == nil) return(nil);
- err = RetrieveSndHeaderOffset(sndHdl, &offset);
-
- if (err != noErr)
- return(nil);
- else
- return((SoundHeader*)(StripAddress(*sndHdl) + offset));
- } // END GetSoundHeader
-
- // ==========================================================================
-
- /*
- All the following routines use the callback mechansim:
-
- You play a sound, install a callback, and call a routine
- to check the callback status during your idle loop.
- (use PlayAsynchCallback and CheckSounds).
-
- Before playing, the sound is locked down. Thus it must be
- unlocked eventually. But how do you know when the sound is done?
- There are two ways. One is the callback method. Install a
- callback procedure, and it's called once the sound is done.
- The callback sets a flag, and exits. Your periodically-invoked
- idle procedure then checks this flag, and if set, interprets this
- to mean the sound is done playing; it unlocks and disposes
- of the sound.
- Why can't we dispose of the sound in the callback, instead of
- having to call our checking routine periodically? Because
- callbacks have to stay within the rules of a routine that
- is called during interrupt time: you cannot use any routines
- that may move memory. Thus this semi-roundabout method...
-
- There is one limitation with this method, however. Normally,
- you can queue sounds to be played in a sound channel by calling
- several bufferCmds (or SndPlays for that matter) one after
- another, consecutively, with the same or different sounds.
- You cannot do this using this mechanism! Because when you
- call it, it stores whatever handle to that sound into a private
- data structure, along with the sound channel. Later when the
- sound is done, this sound handle is purged. But if you queue
- another sound to be played (and the current sound isn't done)
- then the new sound handle is stuffed into the private data
- structure, losing our reference to the old sound, the one
- being played. So you have to wait until the sound is done
- (and it's been purged via CheckSounds before playing another
- sound.
- The solution: Try PlayAsynchCallbackPriority(), which takes
- a priority. If the new priority is higher than the priority
- of the sound being played (if any) then this routine takes
- the time to stop the sound and purge the old sound handle
- before playing the new sound...
- */
-
- OSErr PlayAsynchCallback(Handle sndHdl, short whichChan) {
- SoundHeader *sndDataOffset;
- SndCommand sndCmd;
- long offset;
- OSErr err;
-
- if (sndHdl == nil) return(-1972);
- HLockHi(sndHdl);
-
- #ifdef REINIT_CHANNEL
- if (gSoundMgrVersion < kEnhancedSoundMgr) {
- err = DisposeSndChannel(whichChan);
- if (err != noErr) {
- HUnlock(sndHdl); return(err);
- }
- err = CreateSndChannel(whichChan);
- if (err != noErr) {
- HUnlock(sndHdl); return(err);
- }
- }
- #endif
-
- err = RetrieveSndHeaderOffset(sndHdl, &offset);
- if (err != noErr) {
- HUnlock(sndHdl);
- return(err);
- }
- sndDataOffset = (SoundHeader*)(StripAddress(*sndHdl) + offset);
-
- sndCmd.cmd = bufferCmd;
- sndCmd.param1 = 0;
- sndCmd.param2 = (long)sndDataOffset;
- err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
- if (err == noErr) {
- // Install the callback
- gSndChans[whichChan].snd = sndHdl;
- gSndChans[whichChan].priority = 1;
- sndCmd.cmd = callBackCmd;
- sndCmd.param1 = 0;
- sndCmd.param2 = (long)&gSndChans[whichChan];
- err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
- }
-
- return(err);
- } // END PlayAsynchCallback
-
- // ---------------------------------------------------------------------------
-
- OSErr PlayAsynchCallbackPriority(Handle sndHdl, short whichChan, short priority) {
- SoundHeader *sndDataOffset;
- SndCommand sndCmd;
- long offset;
- OSErr err;
-
- if (sndHdl == nil) return(-1972);
-
- HLockHi(sndHdl);
-
- // First, check priority.
- if (gSndChans[whichChan].priority == kSndIdle ||
- (gSndChans[whichChan].priority) != kSndIdle &&
- (priority >= gSndChans[whichChan].priority)) {
- // OK, new sound has same or higher priority, and a sound
- // is currently playing. Stop the sound...
- if (gSndChans[whichChan].priority != kSndIdle)
- SndChanStop(gSndChans[whichChan].chan);
-
- #ifdef REINIT_CHANNEL
- // If we're using the old sound manager (bummer!) we
- // have to allocate a new channel [and destroy the old one]
- // before playing a new sound. Yuck!
- if (gSoundMgrVersion < kEnhancedSoundMgr) {
- err = DisposeSndChannel(whichChan);
- if (err != noErr) {
- HUnlock(sndHdl); return(err);
- }
- err = CreateSndChannel(whichChan);
- if (err != noErr) {
- HUnlock(sndHdl); return(err);
- }
- }
- #endif
-
- // Time to play the sound...
- err = RetrieveSndHeaderOffset(sndHdl, &offset);
- if (err != noErr) {
- HUnlock(sndHdl);
- return(err);
- }
- sndDataOffset = (SoundHeader*)(StripAddress(*sndHdl) + offset);
-
- sndCmd.cmd = bufferCmd;
- sndCmd.param1 = 0;
- sndCmd.param2 = (long)sndDataOffset;
- // Play the sound
- err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
-
- // Install the callback
- if (err == noErr) {
- // Install the callback
- gSndChans[whichChan].snd = sndHdl;
- gSndChans[whichChan].priority = priority;
- sndCmd.cmd = callBackCmd;
- sndCmd.param1 = 0;
- sndCmd.param2 = (long)&gSndChans[whichChan];
- err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
- }
- }
-
- return(err);
- } // END PlayAsynchCallbackPriority
-
- // ---------------------------------------------------------------------------
-
- pascal void SndDoneProc(SndChannelPtr chan, SndCommand *cmd) {
- /*
- This is the callback routine. It just sets the
- priority to -1, meaning a sound is done playing.
- */
- SndChanInfo *sndChan;
-
- sndChan = (SndChanInfo*)cmd->param2;
- sndChan->priority = kSndDone;
- } // END SndDoneProc
-
- // ---------------------------------------------------------------------------
-
- void CheckSounds(short whichChan, SndCleanupProc cleaner) {
- /*
- Call this within your idle loop. It checks the sound channels,
- and disposes of sounds that have been already played, freeing
- up memory.
-
- Pass -1 in argument whichChan to check all sound channels...
-
- You can pass to CheckSounds() the address of a cleanup routine,
- which should do whatever you need it to do after you're done
- with a sound (such as unlocking it and disposing of the sound).
- You can use DefaultSndCleaner(), which unlocks and purges the
- sound. Or pass nil and CheckSounds() will not do anything with
- your sounds (perhaps you want to keep them locked, for example).
- */
- short i, max;
-
- if (whichChan == -1) {
- i = 0;
- max = kMaxChans;
- }
- else {
- i = whichChan;
- max = whichChan + 1;
- }
-
- for (i; i < max; i++) {
- if (gSndChans[i].chan != nil && gSndChans[i].priority == kSndDone) {
- // Sound is done playing. Invoke cleaning routine, if any.
- if (cleaner)
- (*cleaner)(gSndChans[i].snd);
- gSndChans[i].priority = kSndIdle;
- }
- }
- } // END CheckSounds
-
- // ---------------------------------------------------------------------------
-
- void DefaultSndCleaner(Handle snd) {
- HUnlock(snd);
- HPurge(snd);
- } // END DefaultSndCleaner