#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; }