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.
GameSource/GameSounds.c
/* |
This file manages the asynchronous sounds used by the game. |
It demonstrates one of the simplest techniques of playing asynch sounds, |
using SndPlay with a callback. Notice this code does NOT depend on register a5 |
for flagging that a sound is done, instead I stuff a pointer to the flag |
in the callback parameters. I am pointing this out because I think it is a much better |
way to do things, and the same technique can apply to any interrupt time code. |
This also manages each channel by using a channel priority. When a sound play request |
is received for a specific channel, if the sound has a higher priority it is played, |
otherwise it is ignored. |
This code is pretty re-usable, I have used it in a few other programs as well. |
Call InitSounds( filename ) to start it up. The file passed is a resource file |
that contains all the snds you need. |
Then use PlaySndAsynchChannel to play a sound, specifying the sound resource ID, the channel |
number, and the priorty. Right now this handles kNumChannels of sound. |
Call SoundKeeper() from your idle routine to make sure that the sound channels are |
disposed of when needed. |
Call FreeSounds before your program quits. Thats really the minimum you need in order |
to use this code. There is more to it, read the code and comments that follow. |
A point to mention here. There are so many different Macintosh sound managers out there, |
that it is hard to write good code that runs on all of them. This code SHOULD work on all |
of them. If not, please let me know. See the feedback forms. |
This code has two forks in it. If Sound Manager 3.0 is running, then it creates the |
sound channels at the start of the program, and keeps using them until the program quits. |
Earlier versions of the Sound Manager contained bugs, which caused intermittent failures |
unless a fresh sound channel was used for each asynchronous sound. So, if a sound manager |
earlier than 3.0 is installed, then this unit will create and dispose of sound channels |
on the fly. |
UNDER Sound Manager 3.0 on an AV macintosh with a DSP, it is important to open the channel |
once and keep using it until you are done. This is because every time the channel is opened |
(or closed) the disk may be hit as the DSP Synth code is loaded. |
Creating and disposing of a sound channel is not the most efficient way to play sounds, because the channel is created and |
disposed of for each sound, requiring the memory manager to scam a block from the heap. |
It would be better to open a channel, leave it open, and keep jamming bufferCmds in to |
play each sound instead. However - this does not work on all macs, like MacClassics, plusses |
and some others. If Sound Manager 3.0 is installed, then this code will work well. |
I have made this the simplest way of playing asynchronous sounds, but it is NOT the best for |
each particular Macintosh. |
Double buffering is good because it can use less RAM, but it is bad because if you are spooling |
the sound in then you are reading the disk or custom un-compressing, or otherwise doing |
some amount of processing to fill the buffer, and this can slow the program down, not to mention |
increase the complexity of any sound playing code. |
*/ |
#include "GameSounds.h" |
#include "ZAMProtos.h" |
unsigned char gSoundMgrVersion; |
SndChanInfo gChan[kNumChan]; |
short gSndResFile; |
Boolean gSoundEnable; |
void SoundEnable(void); |
void InitSounds(Str255 sndFileName) |
/* |
open up the sound resource file, and preserve the resref num and stuff |
*/ |
{ |
short i; |
OSErr err; |
short prevResFile; |
SoundEnable(); |
/* get the version number of the sound manager */ |
gSoundMgrVersion = SndSoundManagerVersion().majorRev; |
for(i = 0; i < kNumChan; i++) { |
gChan[i].channel = nil; |
gChan[i].priority = 0; |
if(gSoundMgrVersion >= 3) { |
/* we can pre-allocate the channels and leave them open till we quit */ |
err = SndNewChannel(&gChan[i].channel, sampledSynth, |
initMono + initNoDrop + initNoInterp, SndDoneProc); |
if(err) { |
ErrMsgCode("\pError opening sound channel"); |
ExitToShell(); |
} |
} |
} |
/* now open the file with our sounds in it */ |
prevResFile = CurResFile(); |
gSndResFile = OpenResFile(sndFileName); |
if(gSndResFile == -1) { |
ErrMsg("\pError opening sound resource file"); |
} |
/* remove the sound file from chain */ |
UseResFile(prevResFile); |
} |
void SoundDisable(void) |
/* |
turn sounds off |
*/ |
{ |
gSoundEnable = false; |
} |
void SoundEnable(void) |
/* |
turn sounds on |
*/ |
{ |
gSoundEnable = true; |
} |
void StopAllSounds(void) |
/* |
Stop all sounds from playing by dumping them |
*/ |
{ |
short i; |
for(i = 0; i < kNumChan; i++) { |
if(gChan[i].priority) |
SndDisposeChannel (gChan[i].channel, true); |
} |
} |
Handle GetSound(short sndID) |
/* |
Use this to get sounds from the file |
This makes the sound res file current again |
and grabs the sound. |
Inside Mac is ambiguous about locking down sound handles. |
They MUST be locked down before passed to sound play. |
*/ |
{ |
short saveResFile; |
Handle snd; |
saveResFile = CurResFile(); |
UseResFile(gSndResFile); |
snd = Get1Resource('snd ',sndID); |
HLock(snd); |
UseResFile(saveResFile); |
return snd; |
} |
OSErr PlaySndAsynchChannel(short sndID, short chanNum, short priority) |
/* |
This is the main sampled sound playing routine. See comments above. |
*/ |
{ |
OSErr err = noErr; |
SndCommand cmd; |
Handle snd; |
if (!gSoundEnable) return; |
snd = GetSound(sndID); |
if(snd != nil) { |
if( (gChan[chanNum].priority != 0) && (priority >= gChan[chanNum].priority) ) { |
/* if we got the sound and the channel is busy, and our priority is high enough */ |
/* then we are going to play, otherwise we bail */ |
if(gChan[chanNum].priority) { |
if(gSoundMgrVersion < 0x03) { |
SndDisposeChannel (gChan[chanNum].channel, true); |
} else { |
/* since the good sound manager is around, stop playing the sound */ |
cmd.cmd = quietCmd; |
cmd.param1 = 0; |
cmd.param2 = 0; |
err = SndDoImmediate(gChan[chanNum].channel, &cmd); |
cmd.cmd = flushCmd; |
err = SndDoImmediate(gChan[chanNum].channel, &cmd); |
} |
} |
} |
if(priority >= gChan[chanNum].priority) { |
if( gSoundMgrVersion < 0x03) { |
/* old sound manager around, so create a new sound channel */ |
gChan[chanNum].channel = nil; |
err = SndNewChannel(&gChan[chanNum].channel, sampledSynth, initMono + initNoDrop + initNoInterp, SndDoneProc); |
} |
if(err == noErr) { |
err = SndPlay (gChan[chanNum].channel, snd, true); |
if (err == noErr) { |
gChan[chanNum].sndHandle = snd; |
gChan[chanNum].priority = priority; |
cmd.cmd = callBackCmd; |
cmd.param2 = (long)&gChan[chanNum]; |
err = SndDoCommand (gChan[chanNum].channel, &cmd, false); |
} |
} |
} |
} |
return err; |
} |
OSErr PlaySndAsynchChannelNow(short sndID, short chanNum, short priority) |
/* |
The same as above, except does not check if sounds are enabled. |
This was a hacky way to make sure that you could hear the sorry bub you loose sound. |
*/ |
{ |
OSErr err = noErr; |
SndCommand cmd; |
Handle snd; |
snd = GetSound(sndID); |
if(snd != nil) { |
if( (gChan[chanNum].priority != 0) && (priority >= gChan[chanNum].priority) ) { |
/* if we got the sound and the channel is busy, and our priority is high enough */ |
/* then we are going to play, otherwise we bail */ |
if(gChan[chanNum].priority) { |
if(gSoundMgrVersion < 0x03) { |
SndDisposeChannel (gChan[chanNum].channel, true); |
} else { |
/* since the good sound manager is around, stop playing the sound */ |
cmd.cmd = quietCmd; |
cmd.param1 = 0; |
cmd.param2 = 0; |
err = SndDoImmediate(gChan[chanNum].channel, &cmd); |
cmd.cmd = flushCmd; |
err = SndDoImmediate(gChan[chanNum].channel, &cmd); |
} |
} |
} |
if(priority >= gChan[chanNum].priority) { |
if( gSoundMgrVersion < 0x03) { |
/* old sound manager around, so create a new sound channel */ |
gChan[chanNum].channel = nil; |
err = SndNewChannel(&gChan[chanNum].channel, sampledSynth, initMono + initNoDrop + initNoInterp, SndDoneProc); |
} |
if(err == noErr) { |
err = SndPlay (gChan[chanNum].channel, snd, true); |
if (err == noErr) { |
gChan[chanNum].sndHandle = snd; |
gChan[chanNum].priority = priority; |
cmd.cmd = callBackCmd; |
cmd.param2 = (long)&gChan[chanNum]; |
err = SndDoCommand (gChan[chanNum].channel, &cmd, false); |
} |
} |
} |
} |
return err; |
} |
void SoundKeeper(void) |
/* |
This routine must be called from your idle loop. |
It updates the sound channels, getting rid of them and setting the flags correctly. |
*/ |
{ |
short i; |
for(i = 0; i < kNumChan; i++) { |
if(gChan[i].priority == -1) { |
gChan[i].priority = 0; |
if(gChan[i].sndHandle) |
HUnlock(gChan[i].sndHandle); /* do you really want to have a big old unlocked block?*/ |
if(gSoundMgrVersion < 0x03) { |
SndDisposeChannel (gChan[i].channel, true); |
} |
} |
} |
} |
pascal void SndDoneProc(SndChannelPtr channel, SndCommand *cmd) |
/* |
This is the magical callback routine that flags SoundKeeper to dump this channel. |
It is magic because it does not use register a5, like most people suggest. |
I think it is lame to use register a5 to access a global from routines like this. |
It is much better to just store a pointer to the data you need! |
*/ |
{ |
SndChanInfo *sndChan; |
sndChan = (SndChanInfo*)cmd->param2; |
sndChan->priority = -1; |
} |
void FreeSounds(void) |
/* |
Disposes of all the currently allocated sound channels |
and stops all sounds from playing. |
Also closes the sound file |
*/ |
{ |
short i; |
for(i = 0; i < kNumChan; i++) { |
SndDisposeChannel (gChan[i].channel, true); |
gChan[i].priority = 0; |
} |
if(gSndResFile != -1) { |
CloseResFile(gSndResFile); |
} |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14