mirror of
https://github.com/nickshanks/ResKnife.git
synced 2024-12-22 17:30:15 +00:00
1711 lines
60 KiB
C++
1711 lines
60 KiB
C++
#include "Asynchronous.h"
|
|
|
|
//=======================================================================================
|
|
// Statics
|
|
//=======================================================================================
|
|
static Boolean gsSHInited = false; // Flags whether Helper has been inited
|
|
static Boolean *gsSHNeedsTime; // Pointer to app "SH needs time" flag
|
|
|
|
static SHOutputVars gsSHOutVars; // Sound output variables
|
|
static SHInputVars gsSHInVars; // Sound input variables
|
|
|
|
static SndCallBackUPP gsSHPlayCompletionUPP; // UPP for SoundCallBackProc (BDM)
|
|
static SICompletionUPP gsSHRecordCompletionUPP; // UPP for SIRecordCompletion (BDM)
|
|
|
|
//=======================================================================================
|
|
// Static prototypes
|
|
//=======================================================================================
|
|
static long SHNewRefNum( void );
|
|
static OSErr SHNewOutRec( SHOutPtr *outRec );
|
|
static pascal void SHPlayCompletion( SndChannelPtr channel, SndCommand *command );
|
|
static pascal void SHRecordCompletion( SPBPtr inParams );
|
|
static OSErr SHInitOutRec( SHOutPtr outRec, long refNum, Handle sound, char handleState );
|
|
static void SHReleaseOutRec( SHOutPtr outRec );
|
|
static OSErr SHQueueCallback( SndChannel *channel );
|
|
static OSErr SHBeginPlayback( SHOutPtr outRec );
|
|
static char SHGetState( Handle snd );
|
|
static SHOutPtr SHOutRecFromRefNum( long refNum );
|
|
static void SHPlayStopByRec( SHOutPtr outRec );
|
|
static OSErr SHGetDeviceSettings( long inRefNum, short *numChannels, Fixed *sampleRate, short *sampleSize, OSType *compType );
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHInitSoundHelper( Boolean *attnFlag, short numChannels )
|
|
//
|
|
// Summary:
|
|
// This routine initializes the Asynchronous Sound Helper.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// attnFlag Pointer to an application Boolean. This Boolean will be set to
|
|
// true when the Helper needs a call to SHIdle. For example, the
|
|
// application will have a global Boolean with a name like
|
|
// "gCallHelper", and will pass the address of that global to
|
|
// SHInitSoundHelper. Then, in the application's main event loop,
|
|
// will simply check gCallHelper, and call SHIdle if it is set.
|
|
// numChannels Tells the Helper how many output records to allocate. The number
|
|
// of simultaneous sounds that can be produced by the Helper is
|
|
// limited by a) the number of simultaneous channels the Sound
|
|
// Manager allows, and b) the number of output records specified by
|
|
// this parameter. If you specify zero, a reasonable default (4) is
|
|
// used.
|
|
//
|
|
// Returns:
|
|
// MemError() If there is one.
|
|
// memFullErr If the output array was nil but MemError returned noErr.
|
|
// noErr Otherwise.
|
|
//
|
|
// Operation:
|
|
// This routine simply allocates the output records, initializes some statics, and
|
|
// sets a flag that tells whether the Helper has been initialized.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHInitSoundHelper( Boolean *attnFlag, short numChannels )
|
|
{
|
|
OSErr error = noErr;
|
|
|
|
// Use default number of channels if zero was specified
|
|
if( numChannels == 0 )
|
|
numChannels = kSHDefChannels;
|
|
|
|
// Remember the address of the application's "attention" flag
|
|
gsSHNeedsTime = attnFlag;
|
|
|
|
// Allocate the channels
|
|
gsSHOutVars.numOutRecs = numChannels;
|
|
gsSHOutVars.outArray = (SHOutPtr) NewPtrClear( numChannels * sizeof(SHOutRec) );
|
|
|
|
// Set up UPPs for play completion and input completion
|
|
gsSHPlayCompletionUPP = NewSndCallBackUPP( SHPlayCompletion );
|
|
gsSHRecordCompletionUPP = NewSICompletionUPP( SHRecordCompletion );
|
|
|
|
// If successful, flag that we're initialized and exit
|
|
if( gsSHOutVars.outArray != nil )
|
|
{
|
|
gsSHInited = true;
|
|
return error;
|
|
}
|
|
else
|
|
{
|
|
// Return some kind of error (MemError if there is one, otherwise make one up )
|
|
error = MemError();
|
|
if( error == noErr )
|
|
error = memFullErr;
|
|
return error;
|
|
}
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal void SHIdle( void )
|
|
//
|
|
// Summary:
|
|
// This routine performs various cleanup operations when sounds have finished
|
|
// playing or recording.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// None.
|
|
//
|
|
// Returns:
|
|
// Nothing.
|
|
//
|
|
// Operation:
|
|
// First, SHIdle clears the flag that indicates an SHIdle call is needed. Next,
|
|
// SHIdle performs playback cleanup. It iterates through the output records
|
|
// looking for records that are both in use and complete. Such records are disposed
|
|
// with SHReleaseOutRec. This frees the record for use later, and closes the sound
|
|
// channel with the Sound Manager. Next, SHIdle performs recording cleanup. It
|
|
// checks if recording is underway and is flagged as complete. If so, it unlocks
|
|
// the (previously locked) input handle, and checks for errors. If errors occurred,
|
|
// the input handle is disposed and the recordErr field of the input variable record
|
|
// is filled with the error. This allows the error to later be reported to the
|
|
// caller when he calls SHGetRecordedSound. If no error occured, then the sound
|
|
// header is re-written into the input sound handle, this time with the correct
|
|
// length, and the handle is sized down to match the actual number of samples that
|
|
// were recorded. Then the sound input device is closed and the application is
|
|
// notified that recording is complete through his Boolean completion flag.
|
|
//
|
|
//=======================================================================================
|
|
pascal void SHIdle( void )
|
|
{
|
|
OSErr error = noErr;
|
|
long realSize;
|
|
|
|
// Immediately turn off the application's "Helper needs time" flag
|
|
*gsSHNeedsTime = false;
|
|
|
|
// Do playback cleanup
|
|
for( short i = 0; i < gsSHOutVars.numOutRecs; i++ )
|
|
if( gsSHOutVars.outArray[i].inUse &&
|
|
gsSHOutVars.outArray[i].channel.userInfo == kSHComplete )
|
|
// We've found a channel that needs closing...
|
|
SHReleaseOutRec( &gsSHOutVars.outArray[i] );
|
|
|
|
// Do recording cleaunp
|
|
if( gsSHInVars.recording && gsSHInVars.recordComplete )
|
|
{
|
|
HUnlock( gsSHInVars.inHandle );
|
|
|
|
if( gsSHInVars.inPB.error && gsSHInVars.inPB.error != abortErr )
|
|
{
|
|
// An error (other than a manual stop) occurred during recording. Kill the
|
|
// handle and save the error code.
|
|
gsSHInVars.recordErr = gsSHInVars.inPB.error;
|
|
DisposeHandle( gsSHInVars.inHandle );
|
|
gsSHInVars.inHandle = nil;
|
|
}
|
|
else
|
|
{
|
|
// Recording completed normally (which includes abortErr, the "error" that
|
|
// occurs when recording is manually stopped). We re-write the header (to
|
|
// slam the correct size in there), and size the handle to fit the actual
|
|
// recorded size (which either shortens the handle, or doesn't change its
|
|
// size -- that's why we don't bother checking the error).
|
|
gsSHInVars.recordErr = noErr;
|
|
realSize = gsSHInVars.inPB.count + gsSHInVars.headerLength;
|
|
error = SetupSndHeader( (SndListHandle) gsSHInVars.inHandle, gsSHInVars.numChannels,
|
|
gsSHInVars.sampleRate, gsSHInVars.sampleSize, gsSHInVars.compType,
|
|
kSHBaseNote, realSize, &gsSHInVars.headerLength );
|
|
SetHandleSize( gsSHInVars.inHandle, realSize ); // Shorten the handle
|
|
}
|
|
|
|
// Error or not, close the recording device, and notify the application that
|
|
// recording is complete, through the recording-completed flag that the caller
|
|
// originally passed into SHRecordStart.
|
|
SPBCloseDevice( gsSHInVars.inRefNum );
|
|
gsSHInVars.recording = false;
|
|
gsSHInVars.inRefNum = 0;
|
|
if( gsSHInVars.appComplete != nil )
|
|
*gsSHInVars.appComplete = true;
|
|
}
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal void SHKillSoundHelper( void )
|
|
//
|
|
// Summary:
|
|
// This routine terminates the Asynchronous Sound Helper.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// None.
|
|
//
|
|
// Returns:
|
|
// Nothing.
|
|
//
|
|
// Operation:
|
|
// This routine, after checking that the Helper was previously initialized, stops
|
|
// all current playback and recording, waits 1/60th of a second (to allow the Sound
|
|
// Manager to call our callback routines, SHPlayCompletion and SHRecordCompletion),
|
|
// then calls SHIdle to force cleanup (releasing sound channels and closing the
|
|
// sound input device if appropriate). Then, SHKillSoundHelper disposes of the
|
|
// output records.
|
|
//
|
|
//=======================================================================================
|
|
pascal void SHKillSoundHelper( void )
|
|
{
|
|
unsigned long timeout;
|
|
Boolean outputClean, inputClean;
|
|
|
|
if( !gsSHInited )
|
|
return;
|
|
|
|
SHPlayStopAll(); // Kill all playback
|
|
SHRecordStop(); // Kill recording
|
|
|
|
// Now sync-wait for everything to clean itself up
|
|
timeout = TickCount() + kSHSyncWaitTimeout;
|
|
do {
|
|
if( *gsSHNeedsTime )
|
|
SHIdle(); // Clean up when required
|
|
|
|
// Check if all our output channels are cleaned up
|
|
outputClean = true;
|
|
for( short i = 0; i < gsSHOutVars.numOutRecs && outputClean; i++ )
|
|
if( gsSHOutVars.outArray[i].inUse )
|
|
outputClean = false;
|
|
|
|
// Check whether our recording is cleaned up
|
|
inputClean = !gsSHInVars.recording;
|
|
|
|
if( inputClean && outputClean )
|
|
break;
|
|
} while (TickCount() < timeout );
|
|
|
|
// Lose our preallocated sound channels
|
|
DisposePtr( (Ptr) gsSHOutVars.outArray );
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// long SHNewRefNum( void )
|
|
//
|
|
// Summary:
|
|
// This routine returns the next available output reference number.
|
|
//
|
|
// Scope:
|
|
// Private.
|
|
//
|
|
// Parameters:
|
|
// None.
|
|
//
|
|
// Returns:
|
|
// The next available output reference number.
|
|
//
|
|
// Operation:
|
|
// The output variable nextRef contains the next available reference number. This
|
|
// function returns the value of nextRef then increments it. This way, output ref-
|
|
// erence numbers are unique throughout a session (modulo 2,147,483,647).
|
|
//
|
|
//=======================================================================================
|
|
long SHNewRefNum( void )
|
|
{
|
|
return gsSHOutVars.nextRef++;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// OSErr SHNewOutRec( SHOutPtr *outRec )
|
|
//
|
|
// Summary:
|
|
// This routine attempts to return the first available output record.
|
|
//
|
|
// Scope:
|
|
// Private.
|
|
//
|
|
// Parameters:
|
|
// outRec A pointer to an SHOutRecPtr. This is where SHNewOutRec puts
|
|
// the pointer to the output record.
|
|
//
|
|
// Returns:
|
|
// kSHErrOutaChannels If no free output record was found.
|
|
// noErr Otherwise.
|
|
//
|
|
// Operation:
|
|
// SHNewOutRec simply iterates through the output record array looking for a record
|
|
// that is not in use. If all the records are in use, SHNewOutRec returns the
|
|
// Helper error code kSHErrOutaChannels. If a record is found, then the address of
|
|
// the record is stored via the VAR parameter, and noErr is returned.
|
|
//
|
|
//=======================================================================================
|
|
OSErr SHNewOutRec( SHOutPtr *outRec )
|
|
{
|
|
// First look for a free channel among our preallocated output records
|
|
for( short i = 0; i < gsSHOutVars.numOutRecs; i++ )
|
|
if( !gsSHOutVars.outArray[i].inUse )
|
|
{
|
|
*outRec = &gsSHOutVars.outArray[i];
|
|
return noErr;
|
|
}
|
|
|
|
return kSHErrOutaChannels;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal void SHPlayCompletion( SndChannelPtr channel, SndCommand *command )
|
|
//
|
|
// Summary:
|
|
// This routine is the playback callback routine we provide to the Sound Manager.
|
|
//
|
|
// Scope:
|
|
// Private.
|
|
//
|
|
// Parameters:
|
|
// channel A pointer to the sound channel that is calling back. It is calling
|
|
// back because we queued up a callbackCmd.
|
|
// command A pointer to the actual command that caused us to be called back.
|
|
// This command happens to have important information (like the app's
|
|
// A5, and a constant we can use to verify that this is a "real" call-
|
|
// back, as opposed to one that has been erroroneously generated by the
|
|
// Sound Manager).
|
|
//
|
|
// Returns:
|
|
// Nothing.
|
|
//
|
|
// Operation:
|
|
// This routine first looks for our "completion signature." This is how we know
|
|
// that the callback really means the sound has completed. There is a bug in the
|
|
// Sound Manager that may cause callbacks that weren't specifically requested, and
|
|
// this constant allows us to distinguish our "real" callback from one that is a
|
|
// result of that bug. If the callback is hip, then we set up A5 (so we can ref-
|
|
// erence our globals), and set the application's attention flag. Also, we set
|
|
// the channel's userInfo field to a constant that SHIdle will recognize, so SHIdle
|
|
// will know the sound on that channel has completed, and that the channel can be
|
|
// freed.
|
|
//
|
|
//=======================================================================================
|
|
pascal void SHPlayCompletion( SndChannelPtr channel, SndCommand *command )
|
|
{
|
|
// Look for our "callback signature" in the sound command.
|
|
if( command->param1 == kSHCompleteSig )
|
|
{
|
|
channel->userInfo = kSHComplete;
|
|
*gsSHNeedsTime = true; // Tell the app to give us an SHIdle call
|
|
}
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal void SHRecordCompletion( SPBPtr inParams )
|
|
//
|
|
// Summary:
|
|
// This routine is the recording callback routine we provide to the Sound Manager.
|
|
//
|
|
// Scope:
|
|
// Private.
|
|
//
|
|
// Parameters:
|
|
// inParams This points to the input sound parameter block that has completed
|
|
// recording.
|
|
//
|
|
// Returns:
|
|
// Nothing.
|
|
//
|
|
// Operation:
|
|
// When recording completes for any reason (error, consumed all the memory that was
|
|
// provided, abort, etc.) then the Sound Manager calls our callback routine. This
|
|
// routine first grabs A5 from the SPB's userLong field and sets us up to use our
|
|
// globals. Then, it sets the application's "Helper needs time" Boolean, and sets
|
|
// our internal flag that recording has completed (so SHIdle will know to close the
|
|
// recording device, etc. )
|
|
//
|
|
//=======================================================================================
|
|
pascal void SHRecordCompletion( SPBPtr inParams )
|
|
{
|
|
#pragma unused( inParams )
|
|
*gsSHNeedsTime = true; // Notify the app to give us time
|
|
gsSHInVars.recordComplete = true; // Make a note to ourselves, too
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// OSErr SHInitOutRec( SHOutPtr outRec, long refNum, Handle sound, char handleState )
|
|
//
|
|
// Summary:
|
|
// This routine is used to fill out an SHOutRec and call SndNewChannel.
|
|
//
|
|
// Scope:
|
|
// Private.
|
|
//
|
|
// Parameters:
|
|
// outRec A pointer to the SHOutRec we're filling out.
|
|
// refNum The output reference number we'll give back to the caller.
|
|
// sound A locked, non-purgeable handle to a (hopefully) valid sound.
|
|
// handleState The original handle state, before the HLock and HNoPurge. This
|
|
// allows SHReleaseOutRec to properly reset the handle's flags when
|
|
// playback is complete.
|
|
//
|
|
// Returns:
|
|
// OSErr Error results of SndNewChannel call, if an error occurred.
|
|
// noErr Otherwise.
|
|
//
|
|
// Operation:
|
|
// This routine is called to fill out a SHOutRec. First, it clears the SndChannel
|
|
// within the SHOutRec to all zeros. It then installs the default queue size, and
|
|
// calls the Sound Manager routine SndNewChannel. If an error occurs, we return
|
|
// it right away. If not, then we fill out the rest of the fields in the SHOutRec.
|
|
// Note that in the call to SndNewChannel, we specify NO SYNTHESIZER, and NO INIT-
|
|
// IALIZATION. This is because the Helper is a "sound service," and has no freakin'
|
|
// idea what kind of sound the caller is going to try to play using this channel.
|
|
// So, we assume nothing. Also note that we provide our completion routine,
|
|
// SHPlayCompletion, to SndNewChannel.
|
|
//
|
|
//=======================================================================================
|
|
OSErr SHInitOutRec( SHOutPtr outRec, long refNum, Handle sound, char handleState )
|
|
{
|
|
OSErr error;
|
|
SndChannelPtr channel;
|
|
|
|
// Initialize the sound channel inside outRec. We'll clear the bytes to zero,
|
|
// install the proper queue size, then call SndNewChannel.
|
|
for( unsigned short i = 0; i < sizeof(SndChannel); i++ )
|
|
((char *) &outRec->channel)[i] = 0;
|
|
outRec->channel.qLength = stdQLength;
|
|
channel = &outRec->channel;
|
|
error = SndNewChannel( &channel, kSHNoSynth, kSHNoInit, gsSHPlayCompletionUPP );
|
|
if( error != noErr )
|
|
return error;
|
|
|
|
// Initialize the rest of the record and return noErr. Note that we only set the
|
|
// record's inUse flag if the SndNewChannel call was successful.
|
|
outRec->refNum = refNum;
|
|
outRec->sound = sound;
|
|
outRec->rate = 0;
|
|
outRec->handleState = handleState;
|
|
outRec->inUse = true;
|
|
outRec->paused = false;
|
|
return error;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// void SHReleaseOutRec( SHOutPtr outRec )
|
|
//
|
|
// Summary:
|
|
// This routine "releases," or frees up an output record.
|
|
//
|
|
// Scope:
|
|
// Private.
|
|
//
|
|
// Parameters:
|
|
// outRec A pointer to the output record we want to release.
|
|
//
|
|
// Returns:
|
|
// Nothing.
|
|
//
|
|
// Operation:
|
|
// If the output record's inUse flag is set, that means that SndNewChannel has been
|
|
// called for it. In that case, we call SndDisposeChannel to allow the Sound Man-
|
|
// ager to dispose it's internal data structures for this channel. Either way, if
|
|
// there's an associated sound, we check whether the sound is playing on any other
|
|
// channel, and if not, we reset it's handle flags. Finally, we clear the record's
|
|
// inUse flag, thereby allowing it to be reused.
|
|
//
|
|
//=======================================================================================
|
|
void SHReleaseOutRec( SHOutPtr outRec )
|
|
{
|
|
Boolean found = false;
|
|
|
|
// An SHOutRec's inUse flag only gets set if SndNewChannel has been called on the
|
|
// record's sound channel. So if it is in use, we try a call to SndDisposeChannel,
|
|
// and ignore the error (what else can we do? )
|
|
if( outRec->inUse )
|
|
SndDisposeChannel( &outRec->channel, kSHQuietNow );
|
|
|
|
// If this sound handle isn't being used by some other output record, kindly restore
|
|
// the original handle state.
|
|
if( outRec->sound != nil )
|
|
{
|
|
for( short i = 0; i < gsSHOutVars.numOutRecs && !found; i++ )
|
|
if( &gsSHOutVars.outArray[i] != outRec && gsSHOutVars.outArray[i].inUse &&
|
|
gsSHOutVars.outArray[i].sound == outRec->sound )
|
|
found = true;
|
|
|
|
if( !found )
|
|
HSetState( outRec->sound,outRec->handleState );
|
|
}
|
|
|
|
outRec->inUse = false;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// OSErr SHQueueCallback( SndChannel *channel )
|
|
//
|
|
// Summary:
|
|
// This routine queues up a verifyable callback in the given sound channel.
|
|
//
|
|
// Scope:
|
|
// Private:
|
|
//
|
|
// Parameters:
|
|
// channel The sound channel we want a callback from.
|
|
//
|
|
// Returns:
|
|
// OSErr Results of the SndDoCommand call.
|
|
//
|
|
// Operation:
|
|
// This routine queues up a sound command in the given channel that will cause a
|
|
// callback. This is how we'll know the sound completed. In order to make the
|
|
// callback verifyable, we stuff kSHCompleteSig into param1. We can test for this
|
|
// value within the callback routine. Also, since the poor callback is called at
|
|
// interrorupt time and can't count on its A5 world, we provide the application A5
|
|
// in param2.
|
|
//
|
|
//=======================================================================================
|
|
OSErr SHQueueCallback( SndChannel *channel )
|
|
{
|
|
SndCommand command;
|
|
|
|
command.cmd = callBackCmd;
|
|
command.param1 = kSHCompleteSig; // To make the callback verifyable.
|
|
command.param2 = 0L;
|
|
|
|
return SndDoCommand( channel, &command, kSHWait );
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// OSErr SHBeginPlayback(SHOutPtr outRec )
|
|
//
|
|
// Summary:
|
|
// This routine begins playback of the sound that's installed in the given SHOutRec.
|
|
//
|
|
// Scope:
|
|
// Private.
|
|
//
|
|
// Parameters:
|
|
// outRec The output record that we want to start playback on.
|
|
//
|
|
// Returns:
|
|
// OSErr Error results of SndPlay, if an error occurred.
|
|
// noErr Otherwise.
|
|
//
|
|
// Operation:
|
|
// This routine calls SndPlay (asynchronously) for the sound that is installed in
|
|
// the given output record. This begins playback. Immediately following that, we
|
|
// queue up a callback, so that when the sound that we just start completes, we'll
|
|
// get a callback (to SHPlayCompletion), and we'll know that the channel needs to
|
|
// be released.
|
|
//
|
|
// IMPORTANT:
|
|
// This routine is called from SHPlayByID, and SHPlayByHandle when a sound handle is
|
|
// provided. The purpose of these routines is to trigger a sound, and if you call
|
|
// SHPlayByID or SHPlayByHandle that way, DON'T use SHGetChannel to get the sound
|
|
// channel Helper is using to play the sound, then subsequently call SndPlay your-
|
|
// self to play some other sound. Why not? There is a bug in pre-7.0 Systems that
|
|
// causes a crash if more than one SndPlay call is made on the same channel. Helper
|
|
// will never do this on its own, and you shouldn't either. If you want a sound
|
|
// channel that you want to send commands to, call SHPlayByHandle with a nil handle,
|
|
// then call SHGetChannel to retreive a pointer to the channel.
|
|
//
|
|
//=======================================================================================
|
|
OSErr SHBeginPlayback( SHOutPtr outRec )
|
|
{
|
|
OSErr error = noErr;
|
|
|
|
// First, initiate playback. If an error occurs, return it immediately.
|
|
error = SndPlay( &outRec->channel, (SndListHandle)outRec->sound, kSHAsync );
|
|
if( error != noErr )
|
|
return error;
|
|
|
|
// Playback started okay. Let's queue up a callback command so we'll know when
|
|
// the sound is finished.
|
|
SHQueueCallback( &outRec->channel ); // ignore error (what can we do? )
|
|
return error;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// char SHGetState( Handle snd )
|
|
//
|
|
// Summary:
|
|
// This routine is a local replacement for HGetState which tries to find snd in an
|
|
// existing output record.
|
|
//
|
|
// Scope:
|
|
// Private:
|
|
//
|
|
// Parameters:
|
|
// snd The handle we want the handle state for
|
|
//
|
|
// Returns:
|
|
// A char representing the handle flags (either currently or from some existing
|
|
// output record).
|
|
//
|
|
// Operation:
|
|
// This routine searches the output record array for an output record that is both
|
|
// in use AND has a "sound" field equal to the parameter snd. What this means is
|
|
// that we've found an output record that is currently playing snd. If we find
|
|
// such a record, we return the "handleState" field of that output record. If no
|
|
// such record is found, then we return the results of HGetState(snd). The reason
|
|
// we need this is that you could re-trigger a sound (that is, play the same sound
|
|
// simultaneously on more than one Helper output channel). In such a case, the
|
|
// first SHPlayByID or (ByHandle) call would get the actual handle state (from
|
|
// HGetState). If another SHPlayByID call came in while the original was still
|
|
// playing, the handleState from the existing output record would be retured. This
|
|
// way, the second Play doesn't save a "locked" state and restore to a locked state
|
|
// when the sound has completed.
|
|
//
|
|
//=======================================================================================
|
|
char SHGetState( Handle snd )
|
|
{
|
|
// Look for an output record that has snd for a sound. If one is found, grab and
|
|
// return its handleState instead of the handle's current flags.
|
|
for( short i = 0; i < gsSHOutVars.numOutRecs; i++ )
|
|
if( gsSHOutVars.outArray[i].inUse && gsSHOutVars.outArray[i].sound == snd )
|
|
return gsSHOutVars.outArray[i].handleState;
|
|
|
|
return HGetState(snd);
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHPlayByID( short resID, long *refNum )
|
|
//
|
|
// Summary:
|
|
// This routine begins asynchronous playback of the 'snd ' resource with ID resID.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// resID The resource ID of the 'snd ' resource the caller wants to play back.
|
|
// refNum A pointer to where to store the output reference number. THIS IS
|
|
// OPTIONAL. If nil is specified, the refNum is not returned.
|
|
//
|
|
// Returns:
|
|
// ResError() If the GetResource failed, and ResError gave an error.
|
|
// resNotFound If the GetResource failed, but ResError was noErr.
|
|
// kSHErrOutaChannels If the SHNewOutRec call failed.
|
|
// OSErr If the SHInitOutRec call failed.
|
|
// OSErr If the SHBeginPlayback call failed.
|
|
// noErr Otherwise.
|
|
//
|
|
// Operation:
|
|
// This routine plays the 'snd ' resource referrored to by ID resID. First, we try
|
|
// to load the sound resource. If successful, we note the handle state of the
|
|
// sound handle, and set it to be nonpurgeable (because we don't want subsequent
|
|
// operations, namely the SndNewChannel call that happens in SHInitOutRec, to wipe
|
|
// out the sound we so carefully read into memory). Then we get a reference number
|
|
// and a pointer to the next free output record. If successful, we initialize the
|
|
// output record (and open the sound channel) with SHInitOutRec. If successful,
|
|
// we move the sound handle as high in the heap as we can (to help avoid fragmen-
|
|
// tation), and lock it. Then we call SHBeginPlayback to start the sound playing
|
|
// and queue up a callback so we'll know when it's done. If successful, we return
|
|
// the reference number (if the caller wants it).
|
|
//
|
|
// IMPORTANT:
|
|
// DO NOT start a sound playing with SHPlayByID, get it's channel with SHGetChannel,
|
|
// and then do another SndPlay on that channel! This will crash on pre-7.0 systems.
|
|
// If you want a channel, use SHPlayByHandle with a nil handle. See the comments
|
|
// for SHPlayByHandle and SHBeginPlayback.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHPlayByID( short resID, long *refNum )
|
|
{
|
|
Handle sound;
|
|
char oldhandleState;
|
|
short ref;
|
|
OSErr error;
|
|
SHOutPtr outRec;
|
|
|
|
// First, try to get the caller's 'snd ' resource. If we can't return ResError().
|
|
// If we DO get the sound, save it's flags, then set it to be nonpurgeable. This
|
|
// is because some of the Sound Manager stuff below may cause memory allocation,
|
|
// which could cause the sound to be purged. We don't want that, since we're
|
|
// going to start playing it real soon.
|
|
sound = GetResource( soundListRsrc, resID );
|
|
if( sound == nil )
|
|
{
|
|
error = ResError();
|
|
if( error == noErr )
|
|
error = resNotFound;
|
|
return error;
|
|
}
|
|
oldhandleState = SHGetState(sound );
|
|
HNoPurge(sound );
|
|
|
|
// Now let's get a reference number and an output record.
|
|
ref = SHNewRefNum();
|
|
error = SHNewOutRec( &outRec );
|
|
if( error != noErr )
|
|
{
|
|
HSetState( sound, oldhandleState );
|
|
return error;
|
|
}
|
|
|
|
// Now let's fill in the output record with all the pertinent information. This
|
|
// routine also initializes the sound channel and flags outRec as "in use."
|
|
error = SHInitOutRec(outRec, ref, sound, oldhandleState );
|
|
if( error != noErr )
|
|
{
|
|
HSetState( sound, oldhandleState );
|
|
SHReleaseOutRec( outRec );
|
|
return error;
|
|
}
|
|
|
|
// At this point, we're in pretty good shape. We've got a reference number, an
|
|
// initialized output record, and the sound handle. Let's party.
|
|
MoveHHi( sound );
|
|
HLock( sound );
|
|
error = SHBeginPlayback( outRec );
|
|
if( error != noErr )
|
|
{
|
|
HSetState( sound, oldhandleState );
|
|
SHReleaseOutRec( outRec );
|
|
return error;
|
|
}
|
|
else
|
|
{
|
|
if( refNum != nil ) // refNum is optional -- the caller may not want it
|
|
*refNum = ref;
|
|
return error;
|
|
}
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHPlayByHandle( Handle sound, long *refNum )
|
|
//
|
|
// Summary:
|
|
// This routine begins asynchronous playback of a sound provided in a handle.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// sound A handle to the sound the caller wants to play. This may optionally
|
|
// be nil, indicating that the sound channel should be opened, but no
|
|
// SndPlay call should be made. If a caller does this, he usually calls
|
|
// SHGetChannel to get a pointer to the sound channel, so he can send
|
|
// sound commands to the channel.
|
|
// refNum A pointer to where to store the output reference number. THIS IS
|
|
// OPTIONAL. If nil is passed, the reference number is not returned.
|
|
//
|
|
// Returns:
|
|
// kSHErrOutaChannels If the SHNewOutRec call failed.
|
|
// OSErr If the SHInitOutRec call failed.
|
|
// OSErr If the SHBeginPlayback call failed.
|
|
// noErr Otherwise.
|
|
//
|
|
// Operation:
|
|
// If a handle is provided, we set it to be nonpurgeable so that subsequent oper-
|
|
// ations don't blow it away, and we note it's current handle state. Then, we get
|
|
// a reference number and a pointer to a free output record. If successful, we
|
|
// initialize the output record, thereby opening the sound channel. Then, if a
|
|
// sound was provided, we move it high, lock it, and call SHBeginPlayback to begin
|
|
// asynchronous playback and queue up a callback. Finally, we return the reference
|
|
// number if the caller wants it. If the sound wasn't provided (i.e. nil), every-
|
|
// thing is the same except there's no SHBeginPlayback call.
|
|
//
|
|
// IMPORTANT:
|
|
// DO NOT start a sound handle playing with SHPlayByHandle, get it's channel with
|
|
// SHGetChannel, and then do another SndPlay on that channel! This will crash on
|
|
// pre-7.0 systems. If you want a channel, use SHPlayByHandle with a _NIL_ handle.
|
|
// See the comments for SHBeginPlayback.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHPlayByHandle( Handle sound, long *refNum )
|
|
{
|
|
char oldhandleState;
|
|
short ref;
|
|
OSErr error;
|
|
SHOutPtr outRec;
|
|
|
|
// Save sound handle's flags, then set it to be nonpurgeable. This is because some
|
|
// of the Sound Manager stuff below may cause memory allocation, which could cause
|
|
// the handle to be purged. We don't want that, since we're going to start playing
|
|
// it real soon. If the caller gave us nil for a sound handle, that means he's
|
|
// really just interested in having the sound channel. So, we go on our merrory way
|
|
// without a sound handle.
|
|
if( sound != nil )
|
|
{
|
|
oldhandleState = SHGetState( sound );
|
|
HNoPurge(sound );
|
|
} else oldhandleState = 0;
|
|
|
|
// Now, let's get a reference number and an output record.
|
|
ref = SHNewRefNum();
|
|
error = SHNewOutRec( &outRec );
|
|
if( error != noErr )
|
|
{
|
|
if( sound != nil )
|
|
HSetState( sound, oldhandleState );
|
|
return error;
|
|
}
|
|
|
|
// Now let's fill in the output record with all the pertinent information. This
|
|
// routine also initializes the sound channel and flags outRec as "in use."
|
|
error = SHInitOutRec( outRec, ref, sound, oldhandleState );
|
|
if( error != noErr )
|
|
{
|
|
if( sound != nil )
|
|
HSetState( sound, oldhandleState );
|
|
SHReleaseOutRec( outRec );
|
|
return error;
|
|
}
|
|
|
|
// At this point, we're in pretty good shape. We've got a reference number, an
|
|
// initialized output record, and the sound handle. Let's get whacky.
|
|
if( sound != nil )
|
|
{ // if we've got a sound, lock and begin playback
|
|
MoveHHi( sound );
|
|
HLock( sound );
|
|
error = SHBeginPlayback( outRec );
|
|
if( error != noErr )
|
|
{
|
|
HSetState( sound, oldhandleState );
|
|
SHReleaseOutRec( outRec );
|
|
return error;
|
|
}
|
|
else
|
|
{
|
|
if( refNum != nil ) // refNum is optional - the caller may not want it
|
|
*refNum = ref;
|
|
return error;
|
|
}
|
|
}
|
|
else
|
|
{ // if there's no sound, go ahead and return noErr
|
|
if( refNum != nil ) // refNum is optional - the caller may not want it
|
|
*refNum = ref;
|
|
return error;
|
|
}
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// SHOutPtr SHOutRecFromRefNum( long refNum )
|
|
//
|
|
// Summary:
|
|
// This routine finds that SHOutRec that is associated with a given refNum, if any.
|
|
//
|
|
// Scope:
|
|
// Private.
|
|
//
|
|
// Parameters:
|
|
// refNum The output reference number in question.
|
|
//
|
|
// Returns:
|
|
// A pointer to the associated SHOutRec, if any, or nil, if none was found with a
|
|
// reference number matching refNum.
|
|
//
|
|
// Operation:
|
|
// This handy routine searches the output record array looking for an output record
|
|
// that has the given reference number. If one is found, a pointer to it is
|
|
// returned. If not, then nil is returned.
|
|
//
|
|
//=======================================================================================
|
|
SHOutPtr SHOutRecFromRefNum( long refNum )
|
|
{
|
|
short i;
|
|
|
|
// Search for the specified refNum
|
|
for( i = 0; i < gsSHOutVars.numOutRecs; i++ )
|
|
if( gsSHOutVars.outArray[i].inUse && gsSHOutVars.outArray[i].refNum == refNum )
|
|
break;
|
|
|
|
// If we found it, return a pointer to that record, otherwise, nil.
|
|
if( i == gsSHOutVars.numOutRecs )
|
|
return nil;
|
|
else return &gsSHOutVars.outArray[i];
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// void SHPlayStopByRec( SHOutPtr outRec )
|
|
//
|
|
// Summary:
|
|
// This routine stops sound playback on the channel associated with the given
|
|
// output record.
|
|
//
|
|
// Scope:
|
|
// Private.
|
|
//
|
|
// Parameters:
|
|
// outRec A pointer to the output record whose sound should be stopped.
|
|
//
|
|
// Returns:
|
|
// Nothing.
|
|
//
|
|
// Operation:
|
|
// This routine sends two immediate sound commands to the channel in the given
|
|
// output record. The flushCmd gets rid of any unprocessed commands from the
|
|
// queue subsequent to the one currently being processed. The quietCmd, when sent
|
|
// with SndDoImmediate, immediately quiets the channel. Note that there might not
|
|
// be any synthesizer yet associate with this channel, and in that case, these
|
|
// commands are just eaten by the Sound Manager.
|
|
//
|
|
//=======================================================================================
|
|
void SHPlayStopByRec( SHOutPtr outRec )
|
|
{
|
|
SndCommand cmd;
|
|
|
|
// Dump the rest of the commands in the queue (including our callbackCmd).
|
|
cmd.cmd = flushCmd;
|
|
cmd.param1 = 0;
|
|
cmd.param2 = 0;
|
|
SndDoImmediate( &outRec->channel, &cmd );
|
|
|
|
// Shut up this minute! Go to your room! No dessert tonight for you, little boy.
|
|
cmd.cmd = quietCmd;
|
|
cmd.param1 = 0;
|
|
cmd.param2 = 0;
|
|
SndDoImmediate( &outRec->channel, &cmd );
|
|
|
|
// It is now safe to just manually dump our channel (we'll just skip the whole
|
|
// callback thing in this case).
|
|
SHReleaseOutRec( outRec );
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHPlayStop( long refNum )
|
|
//
|
|
// Summary:
|
|
// This routine stops playback on the output record referrored to by refNum.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// refNum The output reference number of the sound the caller wants stopped.
|
|
//
|
|
// Returns:
|
|
// kSHErrBadRefNum If the reference number does not refer to any current output
|
|
// record. (Note that this is not necessarily bad. If they
|
|
// try to stop a sound that has already stopped by its own
|
|
// accord, this error will be returned. Usually you can call
|
|
// this routine and ignore the error. )
|
|
// noErr Otherwise.
|
|
//
|
|
// Operation:
|
|
// This routine calls SHOutRecFromRefNum to try to find the output record that is
|
|
// associated with refNum. If one is found, we call SHPlayStopByRec to stop
|
|
// playback for that output record.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHPlayStop( long refNum )
|
|
{
|
|
SHOutPtr outRec;
|
|
|
|
// Look for the associated output record.
|
|
outRec = SHOutRecFromRefNum( refNum );
|
|
|
|
// If we found it, call SHPlayStopByRec to stop playback.
|
|
if( outRec != nil )
|
|
{
|
|
SHPlayStopByRec( outRec );
|
|
return noErr;
|
|
}
|
|
else return kSHErrBadRefNum;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHPlayStopAll( void )
|
|
//
|
|
// Summary:
|
|
// This routine stops all sound that the Helper initiated.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// None.
|
|
//
|
|
// Returns:
|
|
// noErr This may return something more interesting in the future.
|
|
//
|
|
// Operation:
|
|
// This routine iterates through all the output records looking for records that
|
|
// are in use. When an in-use record is found, playback on that record is stopped
|
|
// by calling SHPlayStopByRec. Errors are ignored.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHPlayStopAll( void )
|
|
{
|
|
// Look for output records that are in use and stop their playback with
|
|
// SHPlayStopByRec.
|
|
for( short i = 0; i < gsSHOutVars.numOutRecs; i++ )
|
|
if( gsSHOutVars.outArray[i].inUse )
|
|
SHPlayStopByRec( &gsSHOutVars.outArray[i] );
|
|
|
|
return noErr;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHPlayPause( long refNum )
|
|
//
|
|
// Summary:
|
|
// This routine pauses playback of sound associated with refNum.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// refNum The output reference number of the sound the caller wants paused.
|
|
//
|
|
// Returns:
|
|
// OSErr If a SndDoImmediate fails.
|
|
// kSHErrBadRefNum If the given reference number is not associated with any
|
|
// current output record.
|
|
// kSHErrAlreadyPaused If the sound is already paused.
|
|
//
|
|
// Operation:
|
|
// For a sound like "Simple beep," which is a long sequence of sound commands,
|
|
// pausing a sound means "pausing sound command queue processing," and is performed
|
|
// with the pauseCmd. Sampled sounds, like "Wild Eep," usually consist of a single
|
|
// bufferCmd to play back the sampled sound. The pauseCmd is ineffective with
|
|
// sampled sounds because the sound is paused after the current command is processed
|
|
// (the bufferCmd), so the entire sound would be played. This is rarely what the
|
|
// caller wants. So, we've got a little trick in here to pause sampled sounds. If
|
|
// you set a sampled sound's sample playback rate to zero, it effectively pauses the
|
|
// sampled sound in its tracks, mid-bufferCmd (which is what the caller probably
|
|
// wants).
|
|
//
|
|
// There is really no officially sanctioned way to know whether a sound is command-
|
|
// type or sampled without parsing the sound. However, any synthesizer that returns
|
|
// a non-zero rate from a getRateCmd call will be able to understand a rateCmd. So
|
|
// we try a getRateCmd, and if we get a non-zero rate, send a rateCmd to set the
|
|
// playback rate to zero. If we get zero from the getRateCmd, we assume that the
|
|
// synthesizer cannot understand the getRateCmd, and instead use a pauseCmd to pause
|
|
// the sound. This is the only offically sanctioned universal method of pausing any
|
|
// sound.
|
|
//
|
|
// If the sound was successfully paused, the output record's paused flag is set.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHPlayPause( long refNum )
|
|
{
|
|
SHOutPtr outRec;
|
|
SndCommand cmd;
|
|
OSErr error;
|
|
|
|
outRec = SHOutRecFromRefNum(refNum );
|
|
if( outRec != nil )
|
|
{
|
|
// Don't bother with this if we're already paused.
|
|
if( outRec->paused )
|
|
return kSHErrAlreadyPaused;
|
|
|
|
// Get the current playback rate for this sound.
|
|
/* cmd.cmd = getRateCmd;
|
|
cmd.param1 = 0;
|
|
cmd.param2 = (long) &outRec->rate;
|
|
error = SndDoImmediate( &outRec->channel, &cmd );
|
|
if( error != noErr )
|
|
return error;
|
|
*/
|
|
// Now pause with either a rateCmd or a pauseCmd, as appropriate
|
|
cmd.param1 = 0;
|
|
cmd.param2 = 0;
|
|
/* if( outRec->rate != 0 )
|
|
{
|
|
// If we get something non-zero, it's safe to assume that whatever
|
|
// synthesizer we're talking to will be able to understand a rateCmd to
|
|
// restore the rate (probably the sampled synthesizer). To pause the
|
|
// sound, we'll set the rate to zero.
|
|
cmd.cmd = rateCmd;
|
|
error = SndDoImmediate( &outRec->channel, &cmd );
|
|
if( error != noErr )
|
|
return error;
|
|
}
|
|
else
|
|
{
|
|
*/ // This synthesizer doesn't understand rateCmds. So instead we'll just
|
|
// pause command queue processing with a pauseCmd. This is how command-type
|
|
// sounds (e.g. Simple Beep) are paused.
|
|
cmd.cmd = pauseCmd;
|
|
error = SndDoImmediate( &outRec->channel, &cmd );
|
|
if( error != noErr )
|
|
return error;
|
|
/* }
|
|
*/
|
|
outRec->paused = true;
|
|
return error;
|
|
}
|
|
else return kSHErrBadRefNum;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHPlayContinue( long refNum )
|
|
//
|
|
// Summary:
|
|
// This routine continues playback of a previously paused sound.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// refNum The refNum of the sound the caller wants playback continued on. This
|
|
// should be the refNum of a sound that was previously paused with
|
|
// SHPlayPause.
|
|
//
|
|
// Returns:
|
|
// OSErr If the SndDoImmediate fails.
|
|
// kSHErrBadRefNum If the refNum doesn't refer to any current output record.
|
|
// kSHErrAlreadyContinued If the sound is not paused.
|
|
//
|
|
// Operation:
|
|
// First SHPlayContinue gets a pointer to the output record (if any) that refNum
|
|
// refers to. If found, and that sound is paused, we check the output record's
|
|
// rate field. If non-zero, then the sound was paused with rateCmd, so we send
|
|
// another rateCmd to restore its playback rate. Otherwise, we send a resumeCmd
|
|
// (to resume command-queue processing). (See the comments for SHPlayPause for
|
|
// details on the two methods of pausing sound.) If the resumeCmd is successful,
|
|
// we clear the output record's paused flag.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHPlayContinue( long refNum )
|
|
{
|
|
SHOutPtr outRec;
|
|
SndCommand cmd;
|
|
OSErr error;
|
|
|
|
outRec = SHOutRecFromRefNum(refNum );
|
|
if( outRec != nil )
|
|
{
|
|
// Don't even bother with this stuff if the channel isn't paused.
|
|
if( !outRec->paused )
|
|
return kSHErrAlreadyContinued;
|
|
|
|
// Now continue playback with a rateCmd or a resumeCmd, as appropriate.
|
|
cmd.param1 = 0;
|
|
/* if( outRec->rate != 0 )
|
|
{
|
|
// Resume sampled sound playback by restoring the synthesizer's playback
|
|
// rate with a rateCmd.
|
|
cmd.cmd = rateCmd;
|
|
cmd.param2 = outRec->rate;
|
|
error = SndDoImmediate( &outRec->channel, &cmd );
|
|
if( error != noErr )
|
|
return error;
|
|
}
|
|
else
|
|
{
|
|
*/ // Resume sound queue processing with a resumeCmd.
|
|
cmd.cmd = resumeCmd;
|
|
cmd.param2 = 0;
|
|
error = SndDoImmediate( &outRec->channel, &cmd );
|
|
if( error != noErr )
|
|
return error;
|
|
/* }
|
|
*/
|
|
outRec->paused = false;
|
|
return error;
|
|
}
|
|
else return kSHErrBadRefNum;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal SHPlayStat SHPlayStatus( long refNum )
|
|
//
|
|
// Summary:
|
|
// This routine returns a status value for the sound associated with refNum.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// refNum The sound for which the caller wishes status information.
|
|
//
|
|
// Returns:
|
|
// shpError = -1 If refNum has never been used (and is therefore invalid).
|
|
// shpFinished = 0 If the sound associated with refNum has completed.
|
|
// shpPaused = 1 If the sound associated with refNum is currently paused.
|
|
// shpPlaying = 2 If the sound associated with refNum is currently playing.
|
|
//
|
|
// Operation:
|
|
// First we check to see if refNum is greater than or equal to our next output
|
|
// reference number. If it is, then this reference number is definitely invalid,
|
|
// so we return shpError. Otherwise, we look refNum up with SHOutRecFromRefNum.
|
|
// If no record is found (but we know that refNum has been used in the past), we
|
|
// can assume that the sound has completed, and return shpFinished. Otherwise,
|
|
// the sound is currently playing or is paused, so we return either shpPaused or
|
|
// shpPlaying based on the value of the output record's paused flag.
|
|
//
|
|
//=======================================================================================
|
|
pascal SHPlayStat SHPlayStatus( long refNum )
|
|
{
|
|
SHOutPtr outRec;
|
|
|
|
if( refNum >= gsSHOutVars.nextRef )
|
|
return shpError;
|
|
else
|
|
{
|
|
outRec = SHOutRecFromRefNum( refNum );
|
|
|
|
if( outRec != nil )
|
|
{
|
|
// We found an SHOutRec for the guy's ref num, (so it's in use).
|
|
return outRec->paused? shpPaused:shpPlaying;
|
|
}
|
|
else
|
|
{
|
|
// Although we've used the reference number in the past, it's not in use, so
|
|
// we can assume whatever sound it was associated has since stopped. So,
|
|
// we'll return shpFinished in this case.
|
|
return shpFinished;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHGetChannel( long refNum, SndChannelPtr *channel )
|
|
//
|
|
// Summary:
|
|
// This routine allows the caller to retrieve a pointer to the sound channel that
|
|
// is associated with the given refNum.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// refNum The sound for which the caller wants to retrieve a sound channel
|
|
// pointer.
|
|
// channel A pointer to a SndChannelPtr. This VAR parameter is where the sound
|
|
// channel address is stored if it is found.
|
|
//
|
|
// Returns:
|
|
// kSHErrBadRefNum If refNum doesn't refer to any current output record.
|
|
// noErr Otherwise.
|
|
//
|
|
// Operation:
|
|
// This routine is provided to allow more advanced callers to have access to the
|
|
// sound channel associated with a reference number. This could be useful, for
|
|
// instance, if the caller wanted to send sound commands to the channel, and only
|
|
// use the Helper to manage the channel (but not the sound). A good example of
|
|
// this is continuous background music. You make a sound with a soundCmd and a
|
|
// loop. Then you open a channel by doing a SHPlayByHandle(nil, &ref), then get
|
|
// the channel pointer by calling SHGetChannel, manually PlaySnd the background
|
|
// music sound (which should contain a soundCmd to install the music as a voice),
|
|
// then send a freqCmd to start the music playing. It'll keep looping until a
|
|
// quietCmd comes along. (SEE NOTE BELOW. )
|
|
//
|
|
// IMPORTANT:
|
|
// If you use the above-described technique to provide looped background sound, it
|
|
// is important to note that when you change the background music (e.g. from one
|
|
// song to the next), you should SHPlayStop the channel, and allocate a new channel
|
|
// with new calls to SHPlayByHandle(nil, &ref)/SHGetChannel. DO NOT make another
|
|
// SndPlay call on the same channel to change the sound, because this will crash on
|
|
// pre-7.0 Systems.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHGetChannel( long refNum, SndChannelPtr *channel )
|
|
{
|
|
SHOutPtr outRec;
|
|
|
|
// Look for the output record associated with refNum.
|
|
outRec = SHOutRecFromRefNum( refNum );
|
|
|
|
// If we found one, return a pointer to the sound channel.
|
|
if( outRec != nil )
|
|
{
|
|
*channel = &outRec->channel;
|
|
return noErr;
|
|
}
|
|
else return kSHErrBadRefNum;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// OSErr SHGetDeviceSettings( long inRefNum, short *numChannels, Fixed *sampleRate, short *sampleSize, OSType *compType )
|
|
//
|
|
// Summary:
|
|
// This routine gets several parameters from an open sound input device.
|
|
//
|
|
// Scope:
|
|
// Private.
|
|
//
|
|
// Parameters:
|
|
// inRefNum The sound input device's input reference number.
|
|
// numChannels A VAR parameter in which the number of channels is returned.
|
|
// sampleRate A VAR parameter in which the sample rate (in Hz) is returned.
|
|
// sampleSize A VAR parameter in which the number of bits/sample is returned.
|
|
// compType A VAR parameter in which the compression type is returned.
|
|
//
|
|
// Returns:
|
|
// OSErr If any of the SPBGetDeviceInfo calls fail.
|
|
// noErr Otherwise.
|
|
//
|
|
// Operation:
|
|
// This routine does four SPBGetDeviceInfo calls to retrieve the number of channels
|
|
// the sample rate, the sample size, and the compression type for the input device
|
|
// referrored to by inRefNum. This routine is almost verbatim out of Inside Macintosh
|
|
// Volume 6.
|
|
//
|
|
//=======================================================================================
|
|
OSErr SHGetDeviceSettings( long inRefNum, short *numChannels, Fixed *sampleRate, short *sampleSize, OSType *compType )
|
|
{
|
|
OSErr error = noErr;
|
|
|
|
// Hit on that sound input device.
|
|
error = SPBGetDeviceInfo( inRefNum, siNumberChannels, (Ptr) numChannels );
|
|
if( error != noErr ) return error;
|
|
error = SPBGetDeviceInfo( inRefNum, siSampleRate, (Ptr) sampleRate );
|
|
if( error != noErr ) return error;
|
|
error = SPBGetDeviceInfo(inRefNum, siSampleSize, (Ptr) sampleSize );
|
|
if( error != noErr ) return error;
|
|
error = SPBGetDeviceInfo(inRefNum, siCompressionType, (Ptr) compType );
|
|
return error;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHRecordStart( short maxK, OSType quality, Boolean *doneFlag )
|
|
//
|
|
// Summary:
|
|
// This routine initiates asynchronous sound recording.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// maxK The amount of memory (in 1024-byte chunks) the caller wishes to
|
|
// preallocate for the user to record into.
|
|
// quality One of the standard Macintosh Sound Input Manager qualities: 'good',
|
|
// 'betr', or 'best'.
|
|
// doneFlag A pointer to a Boolean by which the Helper will inform the caller
|
|
// that recording has finished and an SHGetRecordedSound call is in
|
|
// order. If nil, then the caller will not be directly informed when
|
|
// recording is complete, but will instead have to call SHRecordStatus
|
|
// to find out.
|
|
//
|
|
// Returns:
|
|
// OSErr If any of the stages fail.
|
|
// noErr Otherwise.
|
|
//
|
|
// Operation:
|
|
// This routine initiates asynchronous recording. There are eight stages, each
|
|
// of which could fail. So, each state checks that the previous stage was success-
|
|
// ful, so errors fall through the bottom. Along the way, the local bools
|
|
// deviceOpened and allocated are set when the sound input device is opened and
|
|
// the sound input buffer is allocated, respectively. If one or more of these flags
|
|
// is set at the end of the routine AND there's been an error, then device closure/
|
|
// deallocation is performed as required. The stages are as follows:
|
|
//
|
|
// 1. Open the sound input device.
|
|
// 2. Ask the device if it can do asynchronous recording.
|
|
// 3. Allocate the sound input buffer, as specified by the maxK parameter.
|
|
// 4. Turn on metering and set the recording quality as specified by the
|
|
// quality parameter.
|
|
// 5. The fifth stage is to grab (and save inside the gsInVars structure) the
|
|
// default number of channels, sample rate, sample size, and compression
|
|
// type. We'll use the saved values later when we go to install a more
|
|
// accurate header into the sound, at completion time.
|
|
// 6. Create a sound header in the sound input buffer.
|
|
// 7. Fill out the sound input parameter block inPB. Note that we put the
|
|
// application A5 into the userLong field of the parameter block, so that
|
|
// SHRecordCompletion can access our globals. Also note that
|
|
// SHRecordCompletion is installed directly as the callback routine. This
|
|
// stage cannot fail, and sets error to noErr.
|
|
// 8. The eighth and final stage is to set various flags such that we know
|
|
// we're recording, and calls SPBRecord to initiate the asynchronous
|
|
// recording process. The reason we set our flags before we make the
|
|
// SPBRecord call is to avoid a "race" condition, where the recording could
|
|
// (theoretically) complete virtually immediately and our callback routine
|
|
// would get called before the flags were set up, thus confusing it. To
|
|
// avoid this, set the flags first, then the error handler code can reset
|
|
// them if the recording failed.
|
|
//
|
|
// If an error occurred along the way, the sound input device may be closed, and/or
|
|
// the sound input buffer may be deallocated.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHRecordStart( short maxK, OSType quality, Boolean *doneFlag )
|
|
{
|
|
Boolean deviceOpened = false;
|
|
Boolean allocated = false;
|
|
|
|
OSErr error = noErr;
|
|
short canDoAsync;
|
|
short metering;
|
|
long allocSize;
|
|
|
|
// 1. Try to open the current sound input device
|
|
error = SPBOpenDevice( nil, siWritePermission, &gsSHInVars.inRefNum );
|
|
if( error == noErr )
|
|
deviceOpened = true;
|
|
|
|
// 2. Now let's see if this device can even handle asynchronous recording.
|
|
if( error == noErr )
|
|
{
|
|
error = SPBGetDeviceInfo( gsSHInVars.inRefNum, siAsync, (Ptr) &canDoAsync );
|
|
if( error == noErr && !canDoAsync )
|
|
error = kSHErrNonAsychDevice;
|
|
}
|
|
|
|
// 3. Try to allocate memory for the guy's sound.
|
|
if( error == noErr )
|
|
{
|
|
allocSize = (maxK * 1024) + kSHHeaderSlop;
|
|
gsSHInVars.inHandle = NewHandle(allocSize );
|
|
if( gsSHInVars.inHandle == nil )
|
|
{
|
|
error = MemError();
|
|
if( error == noErr )
|
|
error = memFullErr;
|
|
}
|
|
if( error == noErr )
|
|
allocated = true;
|
|
}
|
|
|
|
// 4. Set up various recording parameters (metering and quality )
|
|
if( error == noErr )
|
|
{
|
|
metering = 1;
|
|
SPBSetDeviceInfo( gsSHInVars.inRefNum, siLevelMeterOnOff, (Ptr) &metering );
|
|
error = SPBSetDeviceInfo( gsSHInVars.inRefNum, siRecordingQuality, (Ptr) &quality );
|
|
}
|
|
|
|
// 5. Call SHGetDeviceSettings to determine a bunch of information we'll need to
|
|
// make a header for this sound.
|
|
if( error == noErr )
|
|
{
|
|
error = SHGetDeviceSettings(gsSHInVars.inRefNum, &gsSHInVars.numChannels,
|
|
&gsSHInVars.sampleRate, &gsSHInVars.sampleSize, &gsSHInVars.compType );
|
|
}
|
|
|
|
// 6. Create a header for this sound.
|
|
if( error == noErr )
|
|
{
|
|
error = SetupSndHeader((SndListHandle)gsSHInVars.inHandle, gsSHInVars.numChannels, gsSHInVars.sampleRate, gsSHInVars.sampleSize,
|
|
gsSHInVars.compType, kSHBaseNote, allocSize, &gsSHInVars.headerLength );
|
|
}
|
|
|
|
// 7. Lock the input sound handle and set up the input parameter block.
|
|
if( error == noErr )
|
|
{
|
|
MoveHHi( gsSHInVars.inHandle );
|
|
HLock( gsSHInVars.inHandle );
|
|
|
|
allocSize -= gsSHInVars.headerLength;
|
|
gsSHInVars.inPB.inRefNum = gsSHInVars.inRefNum;
|
|
gsSHInVars.inPB.count = allocSize;
|
|
gsSHInVars.inPB.milliseconds = 0;
|
|
gsSHInVars.inPB.bufferLength = allocSize;
|
|
gsSHInVars.inPB.bufferPtr = *gsSHInVars.inHandle + gsSHInVars.headerLength;
|
|
gsSHInVars.inPB.completionRoutine = gsSHRecordCompletionUPP;
|
|
gsSHInVars.inPB.interruptRoutine = nil;
|
|
gsSHInVars.inPB.userLong = 0L;
|
|
gsSHInVars.inPB.error = noErr;
|
|
gsSHInVars.inPB.unused1 = 0;
|
|
|
|
error = noErr;
|
|
}
|
|
|
|
// 8. Finally, if all went well, set our recording flag, make sure our record
|
|
// completion flag is clear, and initiate asychronous recording.
|
|
if( error == noErr )
|
|
{
|
|
gsSHInVars.recording = true;
|
|
gsSHInVars.recordComplete = false;
|
|
gsSHInVars.appComplete = doneFlag;
|
|
gsSHInVars.paused = false;
|
|
if( gsSHInVars.appComplete != nil )
|
|
*gsSHInVars.appComplete = false;
|
|
|
|
error = SPBRecord(&gsSHInVars.inPB, kSHAsync );
|
|
}
|
|
|
|
// Now clean up any errors that might have occurred.
|
|
if( error != noErr )
|
|
{
|
|
gsSHInVars.recording = false;
|
|
if( deviceOpened )
|
|
SPBCloseDevice( gsSHInVars.inRefNum );
|
|
if( allocated )
|
|
{
|
|
DisposeHandle( gsSHInVars.inHandle );
|
|
gsSHInVars.inHandle = nil;
|
|
}
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHGetRecordedSound( Handle *theSound )
|
|
//
|
|
// Summary:
|
|
// This routine returns the sound handle from the last sound the Helper recorded.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// theSound A pointer to a Handle by which to return the sound handle.
|
|
//
|
|
// Returns:
|
|
// kSHErrNoRecording If there IS NO "last recorded sound"
|
|
// OSErr If recording stopped because of an error
|
|
// noErr Otherwise.
|
|
//
|
|
// Operation:
|
|
// This routine returns the sound handle (stored in the inHandle field of the
|
|
// gsSHInVars struct) from the last sound that was recorded by the Helper. If
|
|
// the recording died by an error (other than abortErr), then the error is returned
|
|
// and *theSound is set to nil. SHGetRecordedSound is the method by which you
|
|
// retrieve a sound handle, once you know (by whatever means) that recording is
|
|
// complete.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHGetRecordedSound( Handle *theSound )
|
|
{
|
|
if( gsSHInVars.recordComplete )
|
|
{
|
|
if( gsSHInVars.recordErr != noErr )
|
|
{
|
|
*theSound = nil;
|
|
return gsSHInVars.recordErr;
|
|
}
|
|
else
|
|
{
|
|
*theSound = gsSHInVars.inHandle;
|
|
return noErr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*theSound = nil;
|
|
return kSHErrNoRecording;
|
|
}
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHRecordStop( void )
|
|
//
|
|
// Summary:
|
|
// This routine immediately stops sound recording.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// None.
|
|
//
|
|
// Returns:
|
|
// OSErr Whatever SPBStopRecording returns.
|
|
//
|
|
// Operation:
|
|
// This routine simply calls SPBStopRecording to immediately stop sound recording.
|
|
// It sets the parameter block's error code to abortErr, and calls the callback
|
|
// routine. Use this routine to implement a "stop" button.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHRecordStop( void )
|
|
{
|
|
if( gsSHInVars.recording )
|
|
return SPBStopRecording(gsSHInVars.inRefNum);
|
|
return noErr;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHRecordPause( void )
|
|
//
|
|
// Summary:
|
|
// This routine pauses sound recording.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// None.
|
|
//
|
|
// Returns:
|
|
// kSHErrNotRecording If we're not recording right now.
|
|
// kSHErrAlreadyPaused If recording is already paused.
|
|
// OSErr Whatever SPBPauseRecording returns.
|
|
//
|
|
// Operation:
|
|
// This routine pauses sound recording if we ARE recording and we're not already
|
|
// paused. Use this routine to implement a "pause" button.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHRecordPause( void )
|
|
{
|
|
OSErr error = noErr;
|
|
if( gsSHInVars.recording )
|
|
{
|
|
if( !gsSHInVars.paused )
|
|
{
|
|
error = SPBPauseRecording( gsSHInVars.inRefNum );
|
|
gsSHInVars.paused = (error == noErr);
|
|
return error;
|
|
}
|
|
else return kSHErrAlreadyPaused;
|
|
}
|
|
else return kSHErrNotRecording;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHRecordContinue( void )
|
|
//
|
|
// Summary:
|
|
// This routine resumes recording when recording has previously been paused.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// None.
|
|
//
|
|
// Returns:
|
|
// kSHErrNotRecording If we're not recording right now.
|
|
// kSHErrAlreadyContinued If we're not paused.
|
|
// OSErr Whatever SPBResumeRecording returns.
|
|
//
|
|
// Operation:
|
|
// This routine continues sound recording if we ARE recording and we've been
|
|
// previously paused. Use this routine to implement a "unpause" button.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHRecordContinue( void )
|
|
{
|
|
OSErr error = noErr;
|
|
|
|
if( gsSHInVars.recording )
|
|
{
|
|
if( gsSHInVars.paused )
|
|
{
|
|
error = SPBResumeRecording( gsSHInVars.inRefNum );
|
|
gsSHInVars.paused = !(error == noErr);
|
|
return error;
|
|
}
|
|
else return kSHErrAlreadyContinued;
|
|
}
|
|
else return kSHErrNotRecording;
|
|
}
|
|
|
|
//=======================================================================================
|
|
//
|
|
// pascal OSErr SHRecordStatus( SHRecordStatusRec *recordStatus )
|
|
//
|
|
// Summary:
|
|
// This routine returns status information on sound that is being recorded.
|
|
//
|
|
// Scope:
|
|
// Public.
|
|
//
|
|
// Parameters:
|
|
// recordStatus A pointer to a SHRecordStatusRec into which to store the
|
|
// recording status.
|
|
//
|
|
// Returns:
|
|
// kSHErrNotRecording If we're not recording right now.
|
|
// OSErr Whatever SPBGetRecordingStatus returns.
|
|
// noErr Otherwise.
|
|
//
|
|
// Operation:
|
|
// If we're currently recording, we call SPBGetRecordingStatus. It tells us lots
|
|
// of handy things, most of which we return via recordStatus, then we give a status
|
|
// of either shrPaused or shrRecording, depending upon the state of the
|
|
// gsSHInVars.paused flag. If recording is complete, we return shrFinished. If an
|
|
// error occurs, we give a status of shrError.
|
|
//
|
|
//=======================================================================================
|
|
pascal OSErr SHRecordStatus( SHRecordStatusRec *recordStatus )
|
|
{
|
|
short recStatus;
|
|
OSErr error = noErr;
|
|
unsigned long totalSamplesToRecord,numberOfSamplesRecorded;
|
|
|
|
if( gsSHInVars.recording )
|
|
{
|
|
error = SPBGetRecordingStatus( gsSHInVars.inRefNum, &recStatus,
|
|
&recordStatus->meterLevel, &totalSamplesToRecord, &numberOfSamplesRecorded,
|
|
&recordStatus->totalRecordTime, &recordStatus->currentRecordTime );
|
|
if( error == noErr )
|
|
recordStatus->recordStatus = ( gsSHInVars.paused? shrPaused:shrRecording );
|
|
else recordStatus->recordStatus = shrError;
|
|
return error;
|
|
}
|
|
else if( gsSHInVars.recordComplete )
|
|
{
|
|
recordStatus->recordStatus = shrFinished;
|
|
recordStatus->meterLevel = 0;
|
|
// Don't know about the other fields -- just leave 'em
|
|
return error;
|
|
}
|
|
else return kSHErrNotRecording;
|
|
}
|