home *** CD-ROM | disk | FTP | other *** search
- /*****************************************************************************
- * FILE: CMSoundSystem.c
- * AUTHOR: David Hay
- * CREATED: March 9, 1995
- * DESCRIPTION: Routines for playing sound and music.
- *
- * Copyright © 1995 David Hay
- *
- * Permission to use, copy, and distribute this software and its documentation
- * for any purpose is hereby granted without fee, provided that (i) the above
- * copyright notices and this permission notice appear in all copies of the
- * software and related documentation, and (ii) the names of David Hay and
- * Caveman Creations may not be used in any advertising or publicity relating
- * to the software without the specific, prior written permission of David Hay
- *
- * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, EXPRESS,
- * IMPLIED OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, ANY WARRANTY OF
- * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL
- * DAVID HAY OR CAVEMAN CREATIONS BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
- * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
- * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF THE
- * POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT OF OR IN
- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *****************************************************************************/
-
- #ifndef __GESTALT__
- #include <Gestalt.h>
- #endif
- #ifndef __SOUND__
- #include <Sound.h>
- #endif
- #ifndef __TRAPS__
- #include <Traps.h>
- #endif
- #ifndef __CMSOUNDSYSTEM__
- #include "CMSoundSystem.h"
- #endif
-
-
- #ifndef PUBLIC
- #define PUBLIC
- #endif
- #ifndef PRIVATE
- #define PRIVATE static
- #endif
-
-
- #define kSoundDone 99L /* a sound is done playing */
- #define kMusicDone 100L /* The music block is done playing */
- #define kSoundResourceDone 101L /* a sound resource is done playing */
-
- /*****************************************************************************
- ** ExtSndChannel -- Describes an extended sound channel.
- **
- ** channel - the Sound Manager channel data. NOTE: this field
- ** MUST be first! This makes it possible for me to
- ** simply cast the extended sound channel to a
- ** sound channel for passing to Sound Manager routines.
- **
- ** soundPlaying - The reference number of the sound currently playing
- ** on this sound channel. If no sound is playing, this
- ** is set to the value kNoSound. If a sound that is not
- ** registered with the sound system is playing on the
- ** sound channel, this has the value kSoundResource.
- **
- ** isValid - Is the sound channel valid? If not a call to the
- ** routine SndNewChannel() is needed to make the
- ** channel valid again. Otherwise, the channel has
- ** already been allocated and sound may be played on it.
- **
- ** soundData - if the sound playing is an unregistered sound
- ** resource, this is the sound resource playing on
- ** the sound channel.
- **/
- struct ExtSndChannel
- {
- SndChannel channel;
- short soundPlaying;
- Boolean isValid;
- SndListHandle soundData;
- };
-
- typedef struct ExtSndChannel ExtSndChannel;
- typedef ExtSndChannel *ExtSndChannelPtr;
-
- /*****************************************************************************
- ** SoundSystem -- Describes private data the sound system
- **
- ** numChannels - the number of sound channels open
- ** soundChannel - The list of available channels.
- ** numSounds - the number of sound that have been registered with
- ** the sound system.
- ** soundList - A list of sound handles that have been registered.
- ** soundData - Every time a sound handle is registered with the
- ** sound system, the sound header is found and placed
- ** in the corresponding slot in this list. This allows
- ** the sound system to play the sounds more efficently
- ** with bufferCmd's rather than using PlaySound().
- ** musicSequence - describes which sounds to play and in what order
- ** for a music sequence
- ** musicPosition - the current position in the music sequence
- **
- ** smVersion - the version of the sound manager used.
- ** smHasStereo - is stereo sound is available?
- ** smHasStereoMixing
- ** - can the stereo be mixed into a single sound channel?
- ** smHasDoubleBuffer
- ** - Are double buffering routines available?
- ** smHasMultiChannels
- ** - Are we allowed to have more than one channel open
- ** at a time?
- **/
- struct SoundSystem
- {
- short numChannels;
- ExtSndChannelPtr soundChannel[kMaxChannels];
-
- short numSounds;
- SndListHandle soundList[kMaxSounds];
- SoundHeaderPtr soundData[kMaxSounds];
-
- PlaySequencePtr musicSequence;
- short musicPosition;
-
- NumVersion smVersion;
- Boolean smHasStereo :1;
- Boolean smHasStereoMixing :1;
- Boolean smHasDoubleBuffer :1;
- Boolean smHasMultiChannels :1;
- };
-
- typedef struct SoundSystem SoundSystem;
-
-
- /************************ PRIVATE FUNCTION PROTOTYPES ************************/
-
- PRIVATE OSErr PlayMusicBlock( short channelNum );
- PRIVATE pascal void SoundCallBack( SndChannelPtr theChannel, SndCommand theCmd );
- PRIVATE pascal void MusicCallBack( SndChannelPtr theChannel, SndCommand theCmd );
-
-
- /************************** PRIVATE GLOBAL VARIABLES *************************/
-
- PRIVATE SndCallBackUPP soundCallBackUPP; /* callback when sounds are done */
- PRIVATE SoundSystem snd; /* sound system state information */
-
-
- /*===========================================================================*/
- /* PUBLIC FUNCTION DEFINITIONS */
- /*===========================================================================*/
-
-
- /*----------------------- INITIALIZATION/DEALLOCATION -----------------------*/
-
-
- /*****************************************************************************
- * FUNCTION: CMSInitSound
- *
- * INPUT: short numChannels -- Number of sound channels to allocate
- * RETURNS: OSErr -- Returns any errors the occur.
- *
- * DESCRIPTION: Initializes the sound system for use. It creates the requested
- * number of channels and initializes internal data. Any error
- * conditions are returned.
- *****************************************************************************/
- PUBLIC OSErr CMSInitSound( short numChannels )
- {
- ExtSndChannelPtr aChannel; /* A channel to initialize */
- short ii; /* misc counter */
- long feature; /* result value from Gestalt() */
- OSErr err;
-
- /* Get some information about the current sound manager. First we
- ** find out what version of the sound manager is available. To do
- ** this we first have to check if the _SoundDispatch trap is
- ** available. If so, then we can call SndSoundManagerVersion() to
- ** get the version number. Otherwise, the enhanced sound manager
- ** is not present so set the version number to 1.0
- **/
- if ( GetToolTrapAddress(_Unimplemented) != GetToolTrapAddress(_SoundDispatch) )
- {
- snd.smVersion = SndSoundManagerVersion();
- }
- else
- {
- snd.smVersion.majorRev = 1;
- snd.smVersion.minorAndBugRev = 0;
- snd.smVersion.stage = finalStage;
- snd.smVersion.nonRelRev = 0;
- }
-
- /* Now that we have the version number, check for some capabilities
- ** on the current system using gestalt. To check if double buffering
- ** or multiple channels are available, we check two ways. If Sound
- ** Manager 3.0 is available, we can simply look at the result
- ** returned from Gestalt. Otherwise, we see if the ASC chip is
- ** present to determine if the desired features are available.
- **/
- err = Gestalt( gestaltSoundAttr, &feature );
- if ( err == noErr )
- {
- snd.smHasStereo = ((feature & (1L << gestaltStereoCapability)) != 0);
- snd.smHasStereoMixing = ((feature & (1L << gestaltStereoMixing)) != 0);
- if ( snd.smVersion.majorRev >= 3 )
- {
- snd.smHasDoubleBuffer =
- ((feature & (1L << gestaltSndPlayDoubleBuffer)) != 0);
- snd.smHasMultiChannels =
- ((feature & (1L << gestaltMultiChannels)) != 0);
- }
- else
- {
- err = Gestalt( gestaltHardwareAttr, &feature );
- if ( err == noErr )
- {
- snd.smHasDoubleBuffer =
- ((feature & (1L << gestaltHasASC)) != 0);
- snd.smHasMultiChannels = snd.smHasDoubleBuffer;
- }
- }
- }
-
- /* Initialize the sound channel information so we know
- ** what to dispose of if an error occurs.
- **/
- snd.numChannels = 0;
- for ( ii = 0; ii < kMaxChannels; ii++ )
- snd.soundChannel[ii] = NULL;
-
-
- /* Initialize the sound data information to indicate that
- ** no sounds have been registered with the sound system
- **/
- snd.numSounds = 0;
- for ( ii = 0; ii < kMaxSounds; ii++ )
- {
- snd.soundData[ii] = NULL;
- snd.soundList[ii] = NULL;
- }
-
- /* Initialize the music information to indicate that no
- ** music has been loaded
- **/
- snd.musicPosition = kNoSound;
- snd.musicSequence = NULL;
-
-
- /* Setup the callback routine pointer for sounds. We will use the
- ** same routine to handle sounds and music by putting a different
- ** number in the callback command to determine what to do.
- **/
- soundCallBackUPP = NewSndCallBackProc( SoundCallBack );
-
-
- /* Allocate the sound channels. If more than one sound channel is
- ** requested and multiple sound channels are not available, then
- **
- **/
- for ( ii = 0; ii < numChannels; ii++ )
- {
- aChannel = (ExtSndChannelPtr) NewPtr( sizeof( ExtSndChannel ) );
- if ( !aChannel )
- {
- err = MemError();
- break;
- }
- else
- {
- aChannel->channel.qLength = stdQLength;
- aChannel->channel.userInfo = ii;
- aChannel->soundPlaying = kNoSound;
- aChannel->isValid = false;
- aChannel->soundData = NULL;
- snd.soundChannel[ii] = aChannel;
- }
- }
- if ( err == noErr )
- {
- snd.numChannels = numChannels;
- }
- else
- {
- CMSDisposeSound();
- }
-
- return err;
- }
-
- /*****************************************************************************
- * FUNCTION: CMSDisposeSound
- *
- * INPUT: None.
- * RETURNS: Nothing.
- *
- * DESCRIPTION: Stops all sounds and music and frees any memory allocated.
- *****************************************************************************/
- PUBLIC void CMSDisposeSound( void )
- {
- OSErr err;
- short ii;
-
-
- /* Destroy all of the sound channels
- **/
- for ( ii = 0; ii < snd.numChannels; ii++ )
- {
- if ( snd.soundChannel[ii] )
- {
- CMSCloseChannel( ii );
- DisposePtr( (Ptr) snd.soundChannel[ii] );
- snd.soundChannel[ii] = NULL;
- }
- }
-
- DisposeRoutineDescriptor( soundCallBackUPP );
-
- /* Dispose of the registered sound data
- **/
- for ( ii = 0; ii < snd.numSounds; ii++ )
- {
- if ( snd.soundList[ii] )
- {
- HUnlock( (Handle) snd.soundList[ii] );
- DisposeHandle( (Handle) snd.soundList[ii] );
- }
- snd.soundData[ii] = NULL;
- snd.soundList[ii] = NULL;
- }
-
- /* Dispose of the music (if any)
- **/
- if ( snd.musicSequence )
- {
- DisposePtr( (Ptr) snd.musicSequence );
- snd.musicSequence = NULL;
- }
- }
-
-
-
- /*---------------------------- CHANNEL MANAGEMENT ---------------------------*/
-
-
- /*****************************************************************************
- * FUNCTION: CMSCloseChannel
- *
- * INPUT: short channelNum -- the sound channel to close.
- * RETURNS: OSErr -- result code.
- *
- * DESCRIPTION: Stops the sound on the given channel and free's the channel.
- * Sound channels should be closed when an application receives
- * a suspend event. If the channel is already closed, nothing
- * happens and noErr is returned.
- *****************************************************************************/
- PUBLIC OSErr CMSCloseChannel( short channelNum )
- {
- OSErr err;
-
- err = noErr;
- if ( snd.soundChannel[channelNum]->isValid )
- {
- err = SndDisposeChannel( (SndChannelPtr) snd.soundChannel[channelNum],
- true );
- snd.soundChannel[channelNum]->isValid = false;
- }
-
- return err;
- }
-
-
- /*****************************************************************************
- * FUNCTION: CMSCloseAllChannels
- *
- * INPUT: None.
- * RETURNS: OSErr -- result code.
- *
- * DESCRIPTION: Closes all of the currently open channels.
- *****************************************************************************/
- PUBLIC OSErr CMSCloseAllChannels( void )
- {
- short ii;
- OSErr err;
-
- err = noErr;
- for ( ii = 0; ii < snd.numChannels; ii++ )
- err = CMSCloseChannel( ii );
-
- return err;
- }
-
-
- /*****************************************************************************
- * FUNCTION: CMSOpenChannel
- *
- * INPUT: short channelNum -- the channel to open.
- * RETURNS: OSErr -- result code.
- *
- * DESCRIPTION: Opens the channel identified by the given channel number. If
- * the channel is already open, nothing is done. Sound may not be
- * played on a channel until it has been opened.
- *****************************************************************************/
- PUBLIC OSErr CMSOpenChannel( short channelNum )
- {
- OSErr err;
-
- err = noErr;
- if ( !snd.soundChannel[channelNum]->isValid )
- {
- err = SndNewChannel( (SndChannelPtr*) &snd.soundChannel[channelNum],
- sampledSynth, initNoInterp + initMono,
- soundCallBackUPP );
- if ( err == noErr )
- snd.soundChannel[channelNum]->isValid = true;
- }
-
- return err;
- }
-
-
- /*****************************************************************************
- * FUNCTION: CMSOpenAllChannels
- *
- * INPUT: None.
- * RETURNS: OSErr -- result code
- *
- * DESCRIPTION: Opens all of the channels that have been allocated.
- *****************************************************************************/
- PUBLIC OSErr CMSOpenAllChannels( void )
- {
- short ii;
- OSErr err;
-
- err = noErr;
- for ( ii = 0; ii < snd.numChannels; ii++ )
- err = CMSOpenChannel( ii );
-
- return err;
- }
-
-
- /*****************************************************************************
- * FUNCTION: CMSStopSound
- *
- * INPUT: short channelNum -- the channel to stop sound on.
- * RETURNS: OSErr -- result code.
- *
- * DESCRIPTION: Stops any playing sound on the given sound channel.
- *****************************************************************************/
- PUBLIC OSErr CMSStopSound( short channelNum )
- {
- SndCommand theCommand;
- ExtSndChannelPtr sndChannel;
- OSErr err;
-
- sndChannel = snd.soundChannel[channelNum];
-
- /* Flush the sound channel of any other sound commands
- **/
- theCommand.cmd = flushCmd;
- theCommand.param1 = 0;
- theCommand.param2 = 0L;
- err = SndDoImmediate( (SndChannelPtr) sndChannel, &theCommand );
-
- /* Send a quiet command to stop any currently playing sounds
- **/
- if ( err == noErr )
- {
- theCommand.cmd = quietCmd;
- theCommand.param1 = 0;
- theCommand.param2 = 0L;
- err = SndDoImmediate( (SndChannelPtr) sndChannel, &theCommand );
- }
-
- sndChannel = snd.soundChannel[channelNum];
- sndChannel->soundPlaying = kNoSound;
- if ( sndChannel->soundData )
- {
- HUnlock( (Handle) sndChannel->soundData );
- sndChannel->soundData = NULL;
- }
-
- return err;
- }
-
-
- /*****************************************************************************
- * FUNCTION: CMSIdleSoundTask
- *
- * INPUT: None.
- * RETURNS: Nothing.
- *
- * DESCRIPTION: Performs any idle tasks needed when a sound resource is being
- * played. It checks each of the sound channels to determine if
- * a sound is done playing and unlocks the sound resource if so.
- * This allows sound resources to be purged after they are done
- * playing.
- *****************************************************************************/
- PUBLIC void CMSIdleSoundTask( void )
- {
- short ii;
- ExtSndChannelPtr sndChannel;
-
- /* Loop over the available channels checking to see if one has
- ** fallen silent. If so and there is a sound resource attached
- ** to the channel, then unlock the sound so that it may be
- ** purged. Also detach the sound resource from the channel so
- **/
- for ( ii = 0; ii < snd.numChannels; ii++ )
- {
- sndChannel = snd.soundChannel[ii];
- if ( sndChannel->soundPlaying == kNoSound && sndChannel->soundData )
- {
- HUnlock( (Handle) sndChannel->soundData );
- sndChannel->soundData = NULL;
- }
- }
- }
-
-
- /*****************************************************************************
- * FUNCTION: CMSGetSoundPlaying
- *
- * INPUT: short channelNum -- the channel to query.
- * OUTPUT: short* refNum -- reference to the sound playing.
- * RETURNS: OSErr -- result code.
- *
- * DESCRIPTION: Returns the sound playing on the given channel in refNum. If
- * no sound is currently playing, kNoSound is returned as the
- * reference number. Any error conditions are returned.
- *****************************************************************************/
- PUBLIC OSErr CMSGetSoundPlaying( short channelNum, short* refNum )
- {
- if ( channelNum >= 0 && channelNum < snd.numChannels )
- {
- *refNum = snd.soundChannel[channelNum]->soundPlaying;
- return noErr;
- }
-
- return badChannel;
- }
-
-
- /*****************************************************************************
- * FUNCTION: CMSWaitForSilence
- *
- * INPUT: short channelNum -- the channel to wait on
- * RETURNS: OSErr -- result code.
- *
- * DESCRIPTION: Waits for the current sound to stop playing on the indicated
- * sound channel. If no sounds are playing on the channel,
- * the function returns immediately.
- *****************************************************************************/
- PUBLIC OSErr CMSWaitForSilence( short channelNum )
- {
- if ( channelNum >= 0 && channelNum < snd.numChannels )
- {
- /* We turn off global optimizer to keep the compiler from putting
- ** putting our loop control variable into a register. Since we
- ** are waiting for an interrupt to occur, putting the LCV in a
- ** register would put us in an infinite loop. By disabling the
- ** compiler's ability to assign variables to registers, we are
- ** able to avoid this nasty problem. I don't know if this hack
- ** will work on the PPC since I don't have a PPC compiler.
- **/
- #pragma options( !global_optimizer )
-
- while( snd.soundChannel[channelNum]->soundPlaying != kNoSound )
- {} /* Do nothing, just wait for the sound to complete */
-
- return noErr;
- }
-
- return badChannel; /* invalid channel specified */
- }
-
-
-
- /*-------------------------- SOUND DATA MANAGEMENT --------------------------*/
-
-
- /*****************************************************************************
- * FUNCTION: CMSRegisterSound
- *
- * INPUT: SndListHandle theSound -- the sound to register
- * short* refNum -- sound reference for later playback
- * RETURNS: OSErr -- error code.
- *
- * DESCRIPTION: Registers the given sound handle with the sound system. A
- * reference to the sound is returned in refNum which can be
- * used to play the sound at a later time.
- *****************************************************************************/
- PUBLIC OSErr CMSRegisterSound( SndListHandle sndHandle, short* refNum )
- {
- short ii;
- SoundHeaderPtr sndHeader;
- OSErr err;
-
- err = noErr;
- *refNum = kNoSound;
- if ( sndHandle != NULL )
- {
- /* First we get the sound header from the sound handle so
- ** that the sound manager does not have to parse the sound
- ** resource each time the sound is played.
- **/
- sndHeader = CMSGetSoundHeader( sndHandle );
-
- /* Now we need to find a free slot in the list of available
- ** sounds. Once we find a slot, insert the sound data into
- ** that slot and return the slot number as the sound
- ** reference.
- **/
- for ( ii = 0; ii < snd.numSounds; ii++ )
- {
- if ( snd.soundData[ii] == NULL )
- {
- *refNum = ii;
- snd.soundData[ii] = sndHeader;
- snd.soundList[ii] = sndHandle;
- break;
- }
- }
-
- /* There aren't enough slots, so bump the number of slots
- ** if possible and insert the sound at the end of the sound
- ** list. If there simply isn't any more room, return an error.
- **/
- if ( *refNum == kNoSound && snd.numSounds < kMaxSounds )
- {
- *refNum = snd.numSounds;
- snd.soundData[snd.numSounds++] = sndHeader;
- }
- else
- {
- err = notEnoughBufferSpace;
- }
- }
-
- return err;
- }
-
- /*****************************************************************************
- * FUNCTION: CMSRemoveSound
- *
- * INPUT: short refNum -- reference to the sound to remove
- * RETURNS: OSErr -- error code.
- *
- * DESCRIPTION: Removes the referenced sound from the sound system. If the
- * sound is currently playing on a sound channel, it is stopped
- * before the sound is disposed of.
- *****************************************************************************/
- PUBLIC OSErr CMSRemoveSound( short refNum )
- {
- short ii;
- SndCommand theCommand;
- OSErr err;
-
- err = noErr;
- if ( refNum >= 0 && refNum < snd.numSounds )
- {
- /* Stop the sound if it is playing on one
- ** of the sound channels
- **/
- for ( ii = 0; ii < snd.numChannels; ii++ )
- {
- if ( snd.soundChannel[ii]->soundPlaying == refNum )
- {
- err = CMSStopSound( ii );
- }
- if ( err != noErr ) break;
- }
-
- /* Dispose of the sound handle and mark it's slot in the
- ** list of registered sounds as now available.
- **/
- if ( err == noErr )
- {
- HUnlock( (Handle) snd.soundList[refNum] );
- DisposeHandle( (Handle) snd.soundList[refNum] );
- snd.soundData[ii] = NULL;
- snd.soundList[ii] = NULL;
- }
- }
-
- return err;
- }
-
- /*****************************************************************************
- * FUNCTION: CMSLoadSound
- *
- * INPUT: short soundID -- resource ID of the sound to load.
- * short* refNum -- sound reference for later playback
- * RETURNS: OSErr -- error code.
- *
- * DESCRIPTION: Loads the indicated sound from the current resource file. A
- * reference to the sound is returned in refNum which can be used
- * to play the sound back at a later time.
- *****************************************************************************/
- PUBLIC OSErr CMSLoadSound( short soundID, short* refNum )
- {
- Handle sndHandle;
- OSErr err;
-
- err = noErr;
-
- /* Get the sound resource and detach it from the resource file.
- **/
- sndHandle = GetResource( 'snd ', soundID );
- if ( sndHandle == NULL )
- err = ResError();
-
- if ( err == noErr )
- {
- DetachResource( (Handle) sndHandle );
- err = ResError();
- }
-
- /* Now that we have the sound handle, register the sound
- ** with the sound system.
- **/
- if ( err == noErr )
- err = CMSRegisterSound( (SndListHandle) sndHandle, refNum );
-
- return err;
- }
-
- /*****************************************************************************
- * FUNCTION: CMSPlaySound
- *
- * INPUT: short refNum -- reference to the sound to play.
- * short channelNum -- the channel to play the sound on.
- * RETURNS: OSErr -- error code.
- *
- * DESCRIPTION: Plays the sound refered to by the given reference number on
- * the indicated sound channel. If the sound channel is not
- * already open, it is opened.
- *****************************************************************************/
- PUBLIC OSErr CMSPlaySound( short refNum, short channelNum )
- {
- SndCommand theCommand;
- ExtSndChannelPtr theChannel;
- OSErr err;
-
- err = noErr;
- if ( channelNum >= 0 && channelNum < snd.numChannels &&
- refNum >= 0 && refNum < snd.numSounds )
- {
- /* Open the sound channel if it is not already. Otherwise, stop
- ** any sound that may be playing on that sound channel.
- **/
- if ( !snd.soundChannel[channelNum]->isValid )
- {
- err = CMSOpenChannel( channelNum );
- }
- else
- {
- err = CMSStopSound( channelNum );
- }
- theChannel = snd.soundChannel[channelNum];
-
- /* Play the sound and install a completion callback routine
- **/
- if ( err == noErr )
- {
- theCommand.cmd = bufferCmd;
- theCommand.param1 = 0;
- theCommand.param2 = (long) snd.soundData[refNum];
- err = SndDoImmediate( (SndChannelPtr) theChannel, &theCommand );
- }
-
- if ( err == noErr )
- {
- theCommand.cmd = callBackCmd;
- theCommand.param1 = kSoundDone;
- theCommand.param2 = SetCurrentA5();
- err = SndDoCommand( (SndChannelPtr) theChannel, &theCommand, true );
- }
-
- if ( err == noErr )
- theChannel->soundPlaying = refNum;
- }
-
- return err;
- }
-
-
- /*****************************************************************************
- * FUNCTION: CMSPlaySoundResource
- *
- * INPUT: short resID -- resource ID of the sound to play.
- * short channelNum -- channel to play the sound on.
- * Boolean async -- play the sound asychronously?
- * RETURNS: OSErr -- result code.
- *
- * DESCRIPTION: Loads the resource indicated and plays it on the appropriate
- * channel. Since the sound handle cannot be freed at callback
- * time. The routine CMSWaitForQuiet() should be used to wait
- * for the sound channel to become silent before calling
- * CMSStopSound() to free the sound resource.
- *****************************************************************************/
- PUBLIC OSErr CMSPlaySoundResource( short resID, short channelNum, Boolean async )
- {
- Handle sndHandle;
- SndCommand theCommand;
- OSErr err;
-
- err = noErr;
-
- if ( channelNum >= 0 && channelNum < snd.numChannels )
- {
- /* Open the sound channel if it is not already. Otherwise, stop
- ** any sound that may be playing on that sound channel.
- **/
- if ( !snd.soundChannel[channelNum]->isValid )
- {
- err = CMSOpenChannel( channelNum );
- }
- else
- {
- err = CMSStopSound( channelNum );
- }
-
- /* Get the sound resource. Then lock it down and make it purgeable
- ** so that it can be freed after the sound is finished playing.
- **/
- sndHandle = GetResource( 'snd ', resID );
- if ( sndHandle == NULL )
- return ResError();
-
- HLockHi( sndHandle );
- HPurge( sndHandle );
-
- /* Play the sound resource and install a callback routine to
- ** handle the completion of the sound
- **/
- err = SndPlay( (SndChannelPtr) snd.soundChannel[channelNum],
- (SndListHandle) sndHandle, async );
- if ( err == noErr )
- {
- theCommand.cmd = callBackCmd;
- theCommand.param1 = kSoundResourceDone;
- theCommand.param2 = SetCurrentA5();
- err = SndDoCommand( (SndChannelPtr) snd.soundChannel[channelNum],
- &theCommand, true );
-
- snd.soundChannel[channelNum]->soundData = (SndListHandle) sndHandle;
- snd.soundChannel[channelNum]->soundPlaying = kSoundResource;
- }
- }
- return err;
- }
-
-
-
- /*----------------------------- MUSIC MANAGEMENT ----------------------------*/
-
-
- /*****************************************************************************
- * FUNCTION: CMSLoadMusic
- *
- * INPUT: short musicID -- ID of the music sequence resource
- * RETURNS: OSErr -- result code.
- *
- * DESCRIPTION: Loads the music described by a MUSL resource. It loads each
- * of the sounds listed in the play sequence and registers them
- * with the sound system. It also sets up the internal play
- * sequence so that the music can later be played with a call
- * to CMSPlayMusic().
- *****************************************************************************/
- PUBLIC OSErr CMSLoadMusic( short musicID )
- {
- PlaySequenceHandle sequenceH;
- PlaySequencePtr sequencePtr;
- Size sequenceSize;
- short ii, jj;
- short soundLoaded[kMaxSounds];
- short soundRef;
- short firstSound;
- short lastSound;
- OSErr err;
-
- /* First we get the music list resource and copy it into
- ** a newly allocated block of memory
- **/
- sequenceH = (PlaySequenceHandle) GetResource( kMusicResType, musicID );
- if ( !sequenceH ) return ResError();
-
- sequenceSize = GetHandleSize( (Handle) sequenceH );
- err = MemError();
-
- if ( err == noErr )
- {
- sequencePtr = (PlaySequencePtr) NewPtr( sequenceSize );
- if ( sequencePtr == NULL )
- err = MemError();
- }
-
- if ( err == noErr && sequencePtr )
- {
- HLock( (Handle) sequenceH );
- BlockMove( (Ptr)(*sequenceH),(Ptr)(sequencePtr), sequenceSize );
- HUnlock( (Handle) sequenceH );
- }
-
- ReleaseResource( (Handle) sequenceH );
-
- if ( err == noErr )
- {
- /* Figure out the range of sound resources we are dealing with so
- ** that we know which sounds to load in. It is assumed that the
- ** sounds in the music sequence are contiguous and no other sounds
- ** are involved.
- **/
- firstSound = sequencePtr->sequence[0];
- lastSound = sequencePtr->sequence[0];
- for ( ii = 1; ii < sequencePtr->sequenceLength; ii++ )
- {
- if ( sequencePtr->sequence[ii] < firstSound )
- firstSound = sequencePtr->sequence[ii];
-
- if ( sequencePtr->sequence[ii] > lastSound )
- lastSound = sequencePtr->sequence[ii];
- }
-
- /* Load the sound data for the music. The sequence resource lists
- ** the 'snd ' resources to play, so load each one in and update
- ** the music sequence to refer to the new sound, rather than the
- ** resource number.
- **/
- for ( ii = firstSound; ii <= lastSound; ii++ )
- {
- err = CMSLoadSound( ii, &soundRef );
- if ( err != noErr ) break;
-
- soundLoaded[ii - firstSound] = soundRef;
- for ( jj = 0; jj < sequencePtr->sequenceLength; jj++ )
- {
- if ( sequencePtr->sequence[jj] == ii )
- sequencePtr->sequence[jj] = soundRef;
- }
- }
- }
-
- /* Cleanup if there were any errors
- **/
- if ( err == noErr )
- {
- sequencePtr->loopStart--;
- snd.musicSequence = sequencePtr;
- }
- else if ( sequencePtr )
- {
- /* Dispose of the sounds we could load before the error occured.
- **/
- ii -= firstSound;
- while ( --ii >= 0 )
- CMSRemoveSound( soundLoaded[ii] );
-
- DisposePtr( (Ptr) sequencePtr );
- snd.musicSequence = NULL;
- }
-
- return err;
- }
-
-
- /*****************************************************************************
- * FUNCTION: CMSRemoveMusic
- *
- * INPUT: None.
- * RETURNS: OSErr -- result code.
- *
- * DESCRIPTION: Removes a previously loaded piece of music from the sound
- * system. All sounds associated with the music are removed as
- * is the play sequence.
- *****************************************************************************/
- PUBLIC OSErr CMSRemoveMusic( void )
- {
- PlaySequencePtr sequencePtr;
- short ii;
- OSErr err;
-
- err = noErr;
- sequencePtr = snd.musicSequence;
- if ( sequencePtr )
- {
- /* Loop through the entire play sequence and release each
- ** sound. If the sound has already been released, skip it
- ** and move on to the next one.
- **/
- for ( ii = 0; ii < sequencePtr->sequenceLength; ii++ )
- {
- if ( snd.soundData[sequencePtr->sequence[ii]] )
- CMSRemoveSound( sequencePtr->sequence[ii] );
- }
-
- DisposePtr( (Ptr) sequencePtr );
- snd.musicSequence = NULL;
- snd.musicPosition = 0;
- }
- return err;
- }
-
-
- /*****************************************************************************
- * FUNCTION: CMSPlayMusic
- *
- * INPUT: short channelNum -- the sound channel to play music on.
- * RETURNS: OSErr -- result code.
- *
- * DESCRIPTION: Plays the music previously loaded by CMSLoadMusic. The music
- * is played on the referenced sound channel.
- *****************************************************************************/
- PUBLIC OSErr CMSPlayMusic( short channelNum )
- {
- short firstSound;
- short ii;
- OSErr err;
-
- err = noErr;
- if ( snd.musicSequence != NULL )
- {
- /* If the sound channel is not already open, then open it.
- ** Otherwise, stop any sound that was previously playing on it.
- **/
- if ( !snd.soundChannel[channelNum]->isValid )
- err = CMSOpenChannel( channelNum );
- else
- err = CMSStopSound( channelNum );
-
- /* Reset the music position and then play the first
- ** block of music.
- **/
- if ( err == noErr )
- {
- snd.musicPosition = 0;
- err = PlayMusicBlock( channelNum );
- }
- }
-
- return err;
- }
-
-
-
- /*----------------------------- UTILITY ROUTINES ----------------------------*/
-
-
- /*****************************************************************************
- * FUNCTION: CMSGetSoundHeader
- *
- * INPUT: SndListHandle sndHandle -- the sound handle to extract from
- * RETURNS: SoundHeaderPtr -- the header in the sound resource.
- *
- * DESCRIPTION: Obtains a pointer to the sound header in the given sound
- * resource. Because it returns a pointer into the handle, the
- * handle is locked and should not be unlocked until the sound
- * header is no longer needed. If there is not a sound header
- * in the given resource, then NULL is returned. Otherwise a
- * valid sound header is returned. It returns a pointer to a
- * sampled sound header even if the sound header is actually an
- * extended sound header or a compressed sound header.
- *****************************************************************************/
- PUBLIC SoundHeaderPtr CMSGetSoundHeader( SndListHandle sndHandle )
- {
- long offset; /* offset to sound header */
- OSErr err;
-
- HLockHi( (Handle) sndHandle );
-
- /* compute offset to sound header and use that offset to
- ** return a pointer into the sound handle.
- **/
- err = CMSGetSoundHeaderOffset( sndHandle, &offset );
- if ( err != noErr ) /* no sound header in resource */
- return NULL;
- else /* compute address of sound header */
- return ((SoundHeaderPtr)((Ptr)(*sndHandle) + offset));
- }
-
-
- /*****************************************************************************
- * FUNCTION: CMSGetSoundHeaderOffset
- *
- * INPUT: SndListHandle sndHandle -- the sound handle to extract from
- * OUTPUT: long* theOffset -- offset to the sound header.
- * RETURNS: OSErr -- result code.
- *
- * DESCRIPTION: Traverses a sound resource until it reaches the sound data.
- * It returns, in the offset parameter, the offset in bytes from
- * the beginning of a sound resource to the sound header.
- *****************************************************************************/
- PUBLIC OSErr CMSGetSoundHeaderOffset( SndListHandle soundHandle,
- long* theOffset )
- {
- Ptr sndPtr; /* to navigate resource */
- long offset; /* offset into resource */
- short numSynths; /* info about resource */
- short numCmds; /* info about resource */
- Boolean isDone; /* are we done yet? */
- OSErr err;
-
- /* If we have Sound Manager 3.0 or greater, then let it do
- ** the work for us. Otherwise, we have to parse the sound
- ** resource ourselves.
- **/
- if ( snd.smVersion.majorRev >= 3 )
- return GetSoundHeaderOffset( soundHandle, theOffset );
-
- /* Initialize variables.
- **/
- offset = 0L; /* return 0 if no sound header found */
- sndPtr = (Ptr)*soundHandle; /* point to start of resource data */
- isDone = false; /* haven't yet found sound header */
- err = noErr;
-
- /* Skip everything before sound commands.
- **/
- switch ( ((SndListPtr) sndPtr)->format )
- {
- case firstSoundFormat: /* format 1 'snd ' resource */
- numSynths = ((SndListPtr) sndPtr)->numModifiers;
- sndPtr += 2 * sizeof( short ) + numSynths * sizeof( ModRef );
- break;
-
- secondSoundFormat: /* format 2 'snd ' resource */
- sndPtr += 2 * sizeof( short );
- break;
-
- default: /* unrecognized resource format */
- err = badFormat;
- isDone = true;
- break;
- }
-
- /* Find number of commands and move to start of first command.
- **/
- numCmds = *(short*)sndPtr;
- sndPtr += sizeof( short );
-
- /* Search for bufferCmd or soundCmd to obtain sound header.
- **/
- while ( numCmds >= 1 && !isDone )
- {
- if ( (*(short*)sndPtr) == bufferCmd + dataOffsetFlag ||
- (*(short*)sndPtr) == soundCmd + dataOffsetFlag )
- {
- /* bufferCmd or soundCmd found, copy offset
- ** from sound command
- **/
- offset = ((SndCommand*)sndPtr)->param2;
- isDone = true; /* get out of loop */
- }
- else
- {
- /* soundCmd or bufferCmd not found move to next command
- **/
- sndPtr += sizeof( SndCommand );
- --numCmds;
- }
- }
-
- *theOffset = offset; /* return offset */
- return err; /* return result code */
- }
-
-
-
- /*----------------------------- INQUIRY ROUTINES ----------------------------*/
-
-
- /*****************************************************************************
- * FUNCTION: CMSHasNewSoundManager
- *
- * INPUT: None
- * RETURNS: Boolean -- is the new sound manager present?
- *
- * DESCRIPTION: Determines if the current sound manager is the new one and
- * returns true if present.
- *****************************************************************************/
- PUBLIC Boolean CMSHasNewSoundManager( void )
- {
- NumVersion sndVersion;
-
- if ( GetToolTrapAddress(_Unimplemented) != GetToolTrapAddress(_SoundDispatch) )
- {
- sndVersion = SndSoundManagerVersion();
- return (sndVersion.majorRev >= 2);
- }
-
- return false;
- }
-
- /*****************************************************************************
- * FUNCTION: CMSHasMultipleChannels
- *
- * INPUT: None
- * RETURNS: Boolean -- Are more than one channel supported?
- *
- * DESCRIPTION: Determines if multiple channels are availble.
- *****************************************************************************/
- PUBLIC Boolean CMSHasMultipleChannels( void )
- {
- OSErr err;
- long feature;
-
- err = Gestalt( gestaltSoundAttr, &feature );
- if ( err == noErr )
- {
- if ( CMSHasNewSoundManager() && SndSoundManagerVersion().majorRev >= 3 )
- {
- return ((feature & (1L << gestaltMultiChannels)) != 0);
- }
- else
- {
- err = Gestalt( gestaltHardwareAttr, &feature );
- if ( err == noErr )
- return ((feature & (1L << gestaltHasASC)) != 0);
- }
- }
- return false;
- }
-
-
- /*===========================================================================*/
- /* PRIVATE FUNCTION DEFINITIONS */
- /*===========================================================================*/
-
-
- /*****************************************************************************
- * FUNCTION: PlayMusicBlock
- *
- * INPUT: short channelNum -- the channel to play the block on.
- * RETURNS: OSErr -- result code.
- *
- * DESCRIPTION: Plays the next music block on the given channel.
- *****************************************************************************/
- PRIVATE OSErr PlayMusicBlock( short channelNum )
- {
- SndCommand theCommand;
- short soundRef;
- OSErr err;
-
- if ( snd.musicPosition < 0 )
- return noErr;
-
- soundRef = snd.musicSequence->sequence[snd.musicPosition];
- if ( soundRef < 0 )
- return noErr;
-
- theCommand.cmd = bufferCmd;
- theCommand.param1 = 0;
- theCommand.param2 = (long) snd.soundData[soundRef];
- err = SndDoImmediate( (SndChannelPtr) snd.soundChannel[channelNum],
- &theCommand );
-
- theCommand.cmd = callBackCmd;
- theCommand.param1 = kMusicDone;
- theCommand.param2 = SetCurrentA5();
- err = SndDoCommand( (SndChannelPtr) snd.soundChannel[channelNum],
- &theCommand, false );
-
- snd.soundChannel[channelNum]->soundPlaying = soundRef;
-
- return err;
- }
-
- /*****************************************************************************
- * FUNCTION: SoundCallBack
- *
- * INPUT: SndChannelPtr theChannel -- the current sound channel.
- * SndCommand theCmd -- the command that caused this.
- * RETURNS: Nothing.
- *
- * DESCRIPTION: This is the sound callback for the sound system. It has
- * different behaivor depending on the contents of the command.
- *****************************************************************************/
- PRIVATE pascal void SoundCallBack( SndChannelPtr theChannel, SndCommand theCmd )
- {
- long myA5;
- short channelNum;
-
- myA5 = SetA5( theCmd.param2 );
-
- switch( theCmd.param1 )
- {
- case kSoundDone:
- case kSoundResourceDone:
- /* A sound has finished playing on the sound channel so
- ** mark the sound channel as no longer playing any sound
- ** and return.
- **/
- channelNum = theChannel->userInfo;
- snd.soundChannel[channelNum]->soundPlaying = kNoSound;
- break;
-
-
-
- case kMusicDone:
- /* Advance the music position counter. If we have reached
- ** the end of the music, loop back to the appropriate place
- ** in the sequence.
- **/
- if ( (++snd.musicPosition) >= snd.musicSequence->sequenceLength )
- snd.musicPosition = snd.musicSequence->loopStart;
-
- /* Extract the channel number and play the next music
- ** block on that channel.
- **/
- channelNum = theChannel->userInfo;
- PlayMusicBlock( channelNum );
- break;
- }
-
- myA5 = SetA5( myA5 );
- }
-