G4Sound.c

/*
    File:       G4Sound.c
 
    Contains:   xxx put contents here xxx
 
    Version:    xxx put version here xxx
 
    Copyright:  © 1998 by Apple Computer, Inc., all rights reserved.
 
    File Ownership:
 
        DRI:                xxx put dri here xxx
 
        Other Contact:      xxx put other contact here xxx
 
        Technology:         xxx put technology here xxx
 
    Writers:
 
        (sjb)   Steve Bollinger
 
    Change History (most recent first):
 
         <2>      7/1/98    sjb     Update to CWPro 2
*/
 
 
//============================================================================
//----------------------------------------------------------------------------
//                                  Sound.c
//----------------------------------------------------------------------------
//============================================================================
 
// This file handles all sound routines.  It handles 2 concurrent soundÉ
// channels allowing 2 sounds to be played simultaneously.  It also handlesÉ
// a system of priorites whereby you can ensure that "important" sounds don'tÉ
// get cut off by "lesser" sounds.  In that there are 2 channels however,É
// "lesser" sounds are not discounted outright - both channels are consideredÉ
// to determine if one of the channels is not playing at all (priority = 0) orÉ
// playing a sound of an even lesser priority.  Make sense?
 
 
#include <Sound.h>
#include "G4Externs.h"
#include <Resources.h>
 
#define kMaxSounds              17          // Number of sounds to load.
#define kBaseBufferSoundID      1000        // ID of first sound (assumed sequential).
#define kSoundDone              913         // Just a number I chose.
#define kSoundDone2             749         // Just a number I chose.
 
 
void PlaySound1 (short, short);
void PlaySound2 (short, short);
pascal void ExternalCallBack (SndChannelPtr, SndCommand *);
pascal void ExternalCallBack2 (SndChannelPtr, SndCommand *);
void LoadAllSounds (void);
OSErr LoadBufferSounds (void);
OSErr DumpBufferSounds (void);
OSErr OpenSoundChannel (void);
OSErr CloseSoundChannel (void);
 
 
SndCallBackUPP      externalCallBackUPP, externalCallBackUPP2;
SndChannelPtr       externalChannel, externalChannel2;
Ptr                 theSoundData[kMaxSounds];
short               externalPriority, externalPriority2;
Boolean             channelOpen, soundOn;
 
 
//==============================================================  Functions
//--------------------------------------------------------------  PlaySound1
// This function takes a sound ID and a priority, and forces that sound to 
// play through channel 1 - and saves the priority globally.  As well, a 
// callback command is queues up in channel 1.
 
void PlaySound1 (short soundID, short priority)
{
    SndCommand  theCommand;
    OSErr       theErr;
    
    theCommand.cmd = flushCmd;          // Send 1st a flushCmd to clear the sound queue.
    theCommand.param1 = 0;
    theCommand.param2 = 0L;
    theErr = SndDoImmediate(externalChannel, &theCommand);
    
    theCommand.cmd = quietCmd;          // Send quietCmd to stop any current sound.
    theCommand.param1 = 0;
    theCommand.param2 = 0L;
    theErr = SndDoImmediate(externalChannel, &theCommand);
    
    externalPriority = priority;        // Copy priority to global variable.
    
    theCommand.cmd = bufferCmd;         // Then, send a bufferCmd to channel 1.
    theCommand.param1 = 0;              // The sound played will be soundID.
    theCommand.param2 = (long)(theSoundData[soundID]);
    theErr = SndDoImmediate(externalChannel, &theCommand);
    
    theCommand.cmd = callBackCmd;       // Lastly, queue up a callBackCmd to notify usÉ
    theCommand.param1 = kSoundDone;     // when the sound has finished playing.
    theCommand.param2 = SetCurrentA5();
    theErr = SndDoCommand(externalChannel, &theCommand, TRUE);
}
 
//--------------------------------------------------------------  PlaySound2
// This function is identical to the above function except that it handlesÉ
// playing sounds through channel 2.
 
void PlaySound2 (short soundID, short priority)
{
    SndCommand  theCommand;
    OSErr       theErr;
    
    theCommand.cmd = flushCmd;          // Send 1st a flushCmd to clear the sound queue.
    theCommand.param1 = 0;
    theCommand.param2 = 0L;
    theErr = SndDoImmediate(externalChannel2, &theCommand);
    
    theCommand.cmd = quietCmd;          // Send quietCmd to stop any current sound.
    theCommand.param1 = 0;
    theCommand.param2 = 0L;
    theErr = SndDoImmediate(externalChannel2, &theCommand);
    
    externalPriority2 = priority;       // Copy priority to global variable.
    
    theCommand.cmd = bufferCmd;         // Then, send a bufferCmd to channel 1.
    theCommand.param1 = 0;              // The sound played will be soundID.
    theCommand.param2 = (long)(theSoundData[soundID]);
    theErr = SndDoImmediate(externalChannel2, &theCommand);
    
    theCommand.cmd = callBackCmd;       // Lastly, queue up a callBackCmd to notify usÉ
    theCommand.param1 = kSoundDone2;    // when the sound has finished playing.
#if !GENERATINGCFM
    theCommand.param2 = SetCurrentA5();
#endif
    theErr = SndDoCommand(externalChannel2, &theCommand, TRUE);
}
 
//--------------------------------------------------------  PlayExternalSound
// This function is probably poorly named for this application.  I lifted thisÉ
// whole library from one of my games and chopped it down for purposes of Glypha.
// The original game treated "external" and "cockpit" sounds as seperate channelsÉ
// (such that cockpit sounds could only "override" other cockpit sounds andÉ
// external sounds could only override other external sounds.
// In any event, this is the primary function called from throughout Glypha.
// This function is called with a sound ID and a priority (just some number) andÉ
// the function then determines if one of the two sound channels is free to playÉ
// the sound.  It determines this by way of priorities.  If a sound channel isÉ
// idle and playing no sound, its channel priority is 0.  Since the priority ofÉ
// the sound you want to play is assumed to be greater than 0, it will, withoutÉ
// a doubt, be allowed to play on an idle channel.  If however there is alreadyÉ
// a sound playing (the channel's priority is not equal to 0), the sound with theÉ
// largest priority wins.  Mind you though that there are two channels to chooseÉ
// between.  Therefore, the function compares the priority passed in with theÉ
// sound channel with the lowest priority.
 
void PlayExternalSound (short soundID, short priority)
{                           // A little error-checking.
    if ((soundID >= 0) && (soundID < kMaxSounds))
    {
        if (soundOn)        // More error-checking.
        {                   // Find channel with lowest priority.
            if (externalPriority < externalPriority2)
            {               // Compare priority with that of channel 1.
                if (priority >= externalPriority)
                    PlaySound1(soundID, priority);
            }
            else
            {               // Compare priority with that of channel 2.
                if (priority >= externalPriority2)
                    PlaySound2(soundID, priority);
            }
        }
    }
}
 
//--------------------------------------------------------  ExternalCallBack
// Callback routine.  If this looks ugly, blame Apple's Universal Headers.
// The callback routine is called after a sound finishes playing.  TheÉ
// callback routine is extremely useful in that it enables us to know whenÉ
// to set the sound channels priority back to 0 (meaning no sound playing).
// Keep in mind (by the way) that this funciton is called at interrupt timeÉ
// and thus may not cause memory to be moved.  Also, note that also becauseÉ
// of the interupt situation, we need to handle setting A5 to point to ourÉ
// app's A5 and then set it back again.
 
#if GENERATINGCFM
RoutineDescriptor ExternalCallBackRD = 
        BUILD_ROUTINE_DESCRIPTOR(uppSndCallBackProcInfo, ExternalCallBack);
#endif
 
pascal void ExternalCallBack (SndChannelPtr theChannel, SndCommand *theCommand)
{
#if !GENERATINGCFM
    long        thisA5, gameA5;
#endif
    
    if (theCommand->param1 == kSoundDone)   // See if it's OUR callback.
    {
#if !GENERATINGCFM
        gameA5 = theCommand->param2;            // Extract our A5 from sound command.
        thisA5 = SetA5(gameA5);             // Point A5 to our app (save off current A5).
#endif
        
        externalPriority = 0;               // Set global to reflect no sound playing.
        
#if !GENERATINGCFM
        thisA5 = SetA5(thisA5);             // Restire A5.
#endif
    }
}
 
//--------------------------------------------------------  ExternalCallBack2
// This function is identical to the above function but handles sound channel 2.
 
#if GENERATINGCFM
RoutineDescriptor ExternalCallBackRD2 = 
        BUILD_ROUTINE_DESCRIPTOR(uppSndCallBackProcInfo, ExternalCallBack2);
#endif
 
pascal void ExternalCallBack2 (SndChannelPtr theChannel, SndCommand *theCommand)
{
#if !GENERATINGCFM
    long        thisA5, gameA5;
#endif
    
    if (theCommand->param1 == kSoundDone2)  // See if it's OUR callback.
    {
#if !GENERATINGCFM
        gameA5 = theCommand->param2;            // Extract our A5 from sound command.
        thisA5 = SetA5(gameA5);             // Point A5 to our app (save off current A5).
#endif
        
        externalPriority2 = 0;              // Set global to reflect no sound playing.
        
#if !GENERATINGCFM
        thisA5 = SetA5(thisA5);             // Restire A5.
#endif
    }
}
 
//--------------------------------------------------------  LoadBufferSounds
// This function loads up all the sounds we'll need in the game and thenÉ
// strips off their header so that we can pass them as buffer commands.
// Sounds are stored in our resource fork as 'snd ' resources.  There is aÉ
// 20 byte header that we need to remove in order to use bufferCmd's.
// This function is called only once, when the game loads up.
 
OSErr LoadBufferSounds (void)
{
    Handle      theSound;
    long        soundDataSize;
    OSErr       theErr;
    short       i;
    
    theErr = noErr;                     // Assume no errors.
    
    for (i = 0; i < kMaxSounds; i++)    // Walk through all sounds.
    {                                   // Load 'snd ' from resource.
        theSound = GetResource('snd ', i + kBaseBufferSoundID);
        if (theSound == 0L)             // Make sure it loaded okay.
            return (ResError());        // Return reason it failed (if it did).
        
        HLock(theSound);                // If we got this far, lock sound down.
                                        // Calculate size of sound minus header.
        soundDataSize = GetHandleSize(theSound) - 20L;
        HUnlock(theSound);              // Okay, unlock.
                                        // Create pointer the size calculated above.
        theSoundData[i] = NewPtr(soundDataSize);
        if (theSoundData[i] == 0L)      // See if we created it okay.
            return (MemError());        // If failed, return the reason why.
        HLock(theSound);                // Okay, lock the sound handle again.
                                        // Copy sound data (minus header) to our pointer.
        BlockMove((Ptr)(*theSound + 20L), theSoundData[i], soundDataSize);
        HUnlock(theSound);              // Unlock sound handle again.
        ReleaseResource(theSound);      // And toss it from memory.
    }
    
    return (theErr);
}
 
//--------------------------------------------------------  DumpBufferSounds
// This function is called when Glypha exits (quits).  All those nasty pointersÉ
// we created in the above function are reclaimed.
 
OSErr DumpBufferSounds (void)
{
    OSErr       theErr;
    short       i;
    
    theErr = noErr;
    
    for (i = 0; i < kMaxSounds; i++)        // Go through all sound pointers.
    {
        if (theSoundData[i] != 0L)          // Make sure it exists.
            DisposePtr(theSoundData[i]);        // Dispose of it.
        theSoundData[i] = 0L;               // Make sure it reflects its "nonexistence".
    }
    
    return (theErr);
}
 
//--------------------------------------------------------  OpenSoundChannel
// This should perhaps be called OpenSoundChannels() since it opens two.
// It is called once (at initialization) to set up the two sound channelsÉ
// we will use throughout Glypha.  For purposes of speed, 8-bit sound channelsÉ
// with no interpolation and monophonic are opened.  They'll use the sampledÉ
// synthesizer (digitized sound) and be assigned their respective callbackÉ
// routines.
 
OSErr OpenSoundChannel (void)
{
    OSErr       theErr;
    
    #if GENERATINGCFM
        externalCallBackUPP = &ExternalCallBackRD;  // Handle Universal Header ugliness.
        externalCallBackUPP2 = &ExternalCallBackRD2;
    #else
        externalCallBackUPP = (SndCallBackUPP) &ExternalCallBack;
        externalCallBackUPP2 = (SndCallBackUPP) &ExternalCallBack2;
    #endif
    
    theErr = noErr;                                 // Assume no errors.
    
    if (channelOpen)                                // Error checking.
        return (theErr);
    
    externalChannel = 0L;
    theErr = SndNewChannel(&externalChannel,        // Open channel 1.
            sampledSynth, initNoInterp + initMono, 
            (SndCallBackUPP)externalCallBackUPP);
    if (theErr == noErr)                            // See if it worked.
        channelOpen = TRUE;
    
    externalChannel2 = 0L;
    theErr = SndNewChannel(&externalChannel2,       // Open channel 2.
            sampledSynth, initNoInterp + initMono, 
            (SndCallBackUPP)externalCallBackUPP2);
    if (theErr == noErr)                            // See if it worked.
        channelOpen = TRUE;
    
    return (theErr);
}
 
//--------------------------------------------------------  CloseSoundChannel
// This function is called only upon quitting Glypha.  Both sound channelsÉ
// we created above are closed down.
 
OSErr CloseSoundChannel (void)
{
    OSErr       theErr;
    
    theErr = noErr;
    
    if (!channelOpen)           // Error checking.
        return (theErr);
    
    if (externalChannel != 0L)  // Dispose of channel 1 (if open).
        theErr = SndDisposeChannel(externalChannel, TRUE);
    externalChannel = 0L;       // Flag it closed.
    
    if (externalChannel2 != 0L) // Dispose of channel 2 (if open).
        theErr = SndDisposeChannel(externalChannel2, TRUE);
    externalChannel2 = 0L;      // Flag it closed.
    
    if (theErr == noErr)
        channelOpen = FALSE;
    
    return (theErr);
}
 
//--------------------------------------------------------  InitSound
// All the above initialization routines are handled by this one function.
// This single function is the only one that needs to be called - it handlesÉ
// calling the functions that load the sounds and create the sound channels.
// It is called from main() when Glypha is loading up and going through itsÉ
// initialization phase.
 
void InitSound (void)
{
    OSErr       theErr;
    
    soundOn = TRUE;         // Note that initialization of sounds has occurredÉ
                            // (or rather is just about to this instant!).
    externalChannel = 0L;   // Flag channels as nonexistant.
    externalChannel2 = 0L;
    externalPriority = 0;   // Set priorities to 0 (no sound playing).
    externalPriority2 = 0;
                            // Load up all sounds (see above function).
    theErr = LoadBufferSounds();
    if (theErr != noErr)    // If it fails, we'll quit Glypha.
        RedAlert("\pFailed Loading Sounds");
                            // Open up the two sound channels.
    theErr = OpenSoundChannel();
    if (theErr != noErr)    // If that fails we'll quit Glypha as well.
        RedAlert("\pFailed To Open Sound Channels");
}
 
//--------------------------------------------------------  KillSound
// Complementary to the above function, this one is called only when GlyphaÉ
// quits and it handles all the "shut-down" routines.  It also is called fromÉ
// main(), but it is called last - just as Glypha is quitting.
 
void KillSound (void)
{
    OSErr       theErr;
    
    theErr = DumpBufferSounds();    // Kill all sound pointers.
    theErr = CloseSoundChannel();   // Close down the sound channels.
}