Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
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. |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-10-14