Record_Sound.c

/*
    File:       Record_sound.c
 
    Contains:   This sample shows how to use SPBRecord to record to memory and then
                write the recorded samples to disk, asynchronously, using PBWriteAsync.
 
                This sample is useful for those developers who wish more flexibility
                than what is offered with SPBRecordToFile.
 
                This sample uses two parameter blocks because as Macs get faster, and
                drives can't quite keep up, using just one parameter block for two writes
                is just asking for trouble.  It's not that much extra memory and it will
                be a weird bug you never have to find.  The pbInUse field of each parameter
                block is supposed to keep us from reusing the parameter block in case things
                get really stopped up.
 
                These routines are designed to be easliy portable (in fact they were ripped
                from another sample), so you should be able to use them with little modification.
 
                It checks for errors returned from SPBRecord and from the PBWrites.  If there
                is a disk error returned from a PBWrite the SPBRecord completion routine will
                not start the next record, killing the recording process and the error will be
                returned in the Vars structure (in the theErr field).
 
                This sample records three seconds of sound but doesn't call WaitNextEvent, so
                don't worry, your Mac isn't locked up, just wait a few seconds.
 
                Happy recording!
 
    Written by: Mark Cookson    
 
    Copyright:  Copyright © 1996-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
                8/13/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
 
#include        <Quickdraw.h>
#include        <Windows.h>
#include        <dialogs.h>
#include        <Events.h>
#include        <Memory.h>
#include        <Packages.h>
#include        <Sound.h>
#include        <SoundInput.h>
#include        <OSUtils.h>
#include        <Files.h>
#include        <StandardFile.h>
#include        <Fonts.h>
#include        <Devices.h>
 
//So we can pass our A5 and other info to our PBWrite completion routines
typedef struct myParamBlockRec {
    ParamBlockRec           pb;
    long                    myA5;
    Boolean                 pbInUse;
}myParamBlockRec, *myParmBlkPtr;
 
//Keep track of the info needed to record
typedef struct {
        long            sanitycheck;
        SPBPtr          recordRec;
        myParmBlkPtr    pb0,
                        pb1;
        Ptr             recBuffer0,
                        recBuffer1;
        Fixed           sampleRate;
        OSType          compression;
        unsigned long   totalBytes;
        long            myA5,
                        devBuffer,
                        soundRefNum;
        short           whichBuffer,
                        fileRefNum,
                        numChannels,
                        sampleSize;
        OSErr           theErr;     //last error returned by SPBRecord or PBWrite
} Vars, *VarsPtr;
 
OSErr           PrepairToRecordToDisk   (VarsPtr myVars);
OSErr           RecordToDisk            (VarsPtr myVars);
pascal void     MyRecComp               (SPBPtr inParamPtr);
OSErr           FinishRecording         (VarsPtr myVars);
 
#if GENERATINGCFM
pascal void     MyPB0WriteComp          (myParmBlkPtr passedPB);
pascal void     MyPB1WriteComp          (myParmBlkPtr passedPB);
#else 
pascal void     MyPB0WriteComp          (myParmBlkPtr passedPB:__a0);
pascal void     MyPB1WriteComp          (myParmBlkPtr passedPB:__a0);
#endif
 
static void ToolBoxInit (void)
{
    MaxApplZone();
    InitGraf (&qd.thePort);
    InitFonts ();
    InitWindows ();
    InitMenus ();
    TEInit ();
    InitDialogs ((long)nil);
    InitCursor ();
    return;
}
 
/*
void main (void) {
    Vars        myVars;
    long        temp;
    OSErr       err;
 
    ToolBoxInit ();
 
    err = PrepairToRecordToDisk (&myVars);
    err = RecordToDisk (&myVars);
 
    Delay (180, &temp);     //record for about three seconds
 
    err = myVars.theErr;    //were any errors returned from the recording?
    if (err != noErr && err != abortErr) {
        DebugStr ("\pRecording didn't complete without error");
    }
 
    //if the error was that we ran out of disk space we can still write the
    //header because we preallocated space for it.  The file may be bad,
    //but it doesn't have to be.  At any rate we want to close the file and
    //dispose of memory.
    err = FinishRecording (&myVars);
}
*/
 
static OSErr    SetupDevice (long inputDevice,
                            short *numChannels,
                            short *sampleSize,
                            Fixed *sampleRate,
                            OSType *compression,
                            long *devBuffer) {
 
    OSErr               err;
    Fixed               gain = 0x00008000;
    short               on = 1;
 
    err = SPBSetDeviceInfo (inputDevice, siSampleRate, (Ptr) sampleRate);
    if (err != noErr)
        DebugStr("\pcouldn't set sample rate");
 
    err = SPBSetDeviceInfo (inputDevice, siSampleSize, (Ptr) sampleSize);
    if (err != noErr)
        DebugStr("\pcouldn't set sample size");
 
    err = SPBSetDeviceInfo (inputDevice, siTwosComplementOnOff, (Ptr) &on);
    if (err != noErr)
        DebugStr("\pcouldn't set twos complement");
 
    err = SPBSetDeviceInfo (inputDevice, siNumberChannels, (Ptr) numChannels);
    if (err != noErr)
        DebugStr("\pcouldn't set number of channels");
 
    err = SPBSetDeviceInfo (inputDevice, siCompressionType, (Ptr) compression);
    if (err != noErr)
        DebugStr("\pcouldn't set compression type");
 
    //turn on continuous recording to "warm up" the input device
    err = SPBSetDeviceInfo (inputDevice, siContinuous, (Ptr) &on);
    if (err != noErr)
        DebugStr("\pcouldn't turn on continuous recording");
 
    //turn on Automatic Gain Control
    err = SPBSetDeviceInfo (inputDevice, siAGCOnOff, (Ptr) &on);
    if (err != noErr) {
        //If AGC isn't available, just turn it all the way down to avoid over driving
        err = SPBSetDeviceInfo (inputDevice, siInputGain, (Ptr) &gain);
        if (err != noErr)
            DebugStr("\pcouldn't get siInputGain");
    }
 
    //check to see what we really got
    err = SPBGetDeviceInfo (inputDevice, siSampleRate, (Ptr) sampleRate);
    if (err != noErr)
        DebugStr("\pcouldn't get sample rate");
 
    err = SPBGetDeviceInfo (inputDevice, siSampleSize, (Ptr) sampleSize);
    if (err != noErr)
        DebugStr("\pcouldn't get sample size");
 
    err = SPBGetDeviceInfo (inputDevice, siNumberChannels, (Ptr) numChannels);
    if (err != noErr)
        DebugStr("\pcouldn't get number of channels");
 
    err = SPBGetDeviceInfo (inputDevice, siDeviceBufferInfo, (Ptr) devBuffer);
    if (err != noErr)
        DebugStr("\pcouldn't get number of channels");
 
    err = SPBGetDeviceInfo (inputDevice, siCompressionType, (Ptr) compression);
    if (err != noErr)
        DebugStr("\pcouldn't get compression type");
 
    return err;
}
 
OSErr   PrepairToRecordToDisk (VarsPtr myVars) {
    OSErr               err;
    short               volRefNum;
    long                buffSize, parID;
    StandardFileReply   sfReply;
    StringPtr           nameString;
 
    StandardPutFile ("\pPut recorded AIFF where...", "\pRecorded sound", &sfReply);
    if (sfReply.sfGood) {
        volRefNum = sfReply.sfFile.vRefNum;
        parID = sfReply.sfFile.parID;
        nameString = (StringPtr)&sfReply.sfFile.name;
 
        err = SPBOpenDevice (nil, siWritePermission, &myVars->soundRefNum);
        if (err != noErr) {
            DebugStr("\pcouldn't open the device");
        }
 
        myVars->numChannels = 2;
        myVars->sampleSize  = 16;
        myVars->sampleRate  = rate44khz;
        myVars->compression = 'NONE';
        err = SetupDevice (myVars->soundRefNum, &myVars->numChannels,
                                                &myVars->sampleSize,
                                                &myVars->sampleRate,
                                                &myVars->compression,
                                                &myVars->devBuffer);
 
        buffSize = myVars->devBuffer * 20;  //make our buffer a multiple of the hardware's
        myVars->recBuffer0 = NewPtrClear(buffSize);
        if (MemError() != noErr || myVars->recBuffer0 == nil) {
            DebugStr("\pcouldn't get memory for recording buffer");
            err = memFullErr;
        }
 
        myVars->recBuffer1 = NewPtrClear(buffSize);
        if (MemError() != noErr || myVars->recBuffer1 == nil) {
            DebugStr("\pcouldn't get memory for recording buffer");
            err = memFullErr;
        }
 
        if (err == noErr) {
            myVars->recordRec = (SPBPtr) NewPtrClear(sizeof (SPB));
            if (MemError() != noErr || myVars->recordRec == nil) {
                DebugStr("\pcouldn't get memory for recording record");
                err = memFullErr;
            }
        }
 
        if (err == noErr) {
            myVars->pb0 = (myParmBlkPtr) NewPtrClear(sizeof (myParamBlockRec));
            if (MemError() != noErr || myVars->pb0 == nil) {
                DebugStr("\pcouldn't get memory for param block");
                err = memFullErr;
            }
        }
 
        if (err == noErr) {
            myVars->pb1 = (myParmBlkPtr) NewPtrClear(sizeof (myParamBlockRec));
            if (MemError() != noErr || myVars->pb1 == nil) {
                DebugStr("\pcouldn't get memory for param block");
                err = memFullErr;
            }
        }
 
        if (err == noErr) {
            HParamBlockRec      Hpb;
            IOCompletionUPP     MyPB0WriteCompUPP,
                                MyPB1WriteCompUPP;
            SICompletionUPP     MyRecCompUPP;
 
            myVars->sanitycheck = 'SANE';
            myVars->myA5 = SetCurrentA5 ();
            myVars->whichBuffer = 0;
            myVars->pb0->myA5 = SetCurrentA5 ();
            myVars->pb1->myA5 = SetCurrentA5 ();
 
            //set up the record parameters
            MyRecCompUPP = NewSICompletionProc (MyRecComp);
            myVars->recordRec->inRefNum = myVars->soundRefNum;
            myVars->recordRec->count = buffSize;
            myVars->recordRec->milliseconds = 0;
            myVars->recordRec->bufferLength = buffSize;
            myVars->recordRec->bufferPtr = myVars->recBuffer0;
            myVars->recordRec->completionRoutine = MyRecCompUPP;
            myVars->recordRec->interruptRoutine = nil;
            myVars->recordRec->userLong = (long)myVars;
            myVars->recordRec->error = 0;
            myVars->recordRec->unused1 = 0;
 
            //create the file
            Hpb.ioParam.ioCompletion = nil;
            Hpb.ioParam.ioNamePtr = nameString;
            Hpb.ioParam.ioVRefNum = volRefNum;
            Hpb.fileParam.ioDirID = parID;
            err = PBHCreateSync (&Hpb);
            if (err == dupFNErr) {
                err = noErr;    //overwriting the file is not an error
            }
            if (err != noErr)
                DebugStr("\pPBHCreateSync failed");
 
            if (err == noErr) {
                //set the file type and creator
                Hpb.fileParam.ioVRefNum = volRefNum;
                Hpb.fileParam.ioDirID = parID;
                Hpb.fileParam.ioNamePtr = nameString;
                Hpb.fileParam.ioFVersNum = 0;
                Hpb.fileParam.ioFDirIndex = 0;
                err = PBHGetFInfoSync(&Hpb);
                if (err != noErr)
                    DebugStr("\pPBHGetFInfoSync failed");
            }
            if (err == noErr) {
                Hpb.fileParam.ioFlFndrInfo.fdType = 'AIFF';
                Hpb.fileParam.ioFlFndrInfo.fdCreator = 'Csdo';
                Hpb.fileParam.ioDirID = parID;
                err = PBHSetFInfoSync (&Hpb);
                if (err != noErr)
                    DebugStr("\pPBHSetFInfoSync failed");
            }
 
            if (err == noErr) {
                //open the file for writing
                Hpb.ioParam.ioCompletion = nil;
                Hpb.ioParam.ioNamePtr = nameString;
                Hpb.ioParam.ioVRefNum = volRefNum;
                Hpb.ioParam.ioPermssn = fsRdWrPerm;
                Hpb.fileParam.ioDirID = parID;
                err = PBHOpenDFSync (&Hpb);
                if (err != noErr)
                    DebugStr("\pPBHOpenDFSync failed");
            }
 
            if (err == noErr) {
                myVars->fileRefNum = Hpb.ioParam.ioRefNum;
 
                //set up the parameter blocks for the coming writes
                MyPB0WriteCompUPP = NewIOCompletionProc (MyPB0WriteComp);
                MyPB1WriteCompUPP = NewIOCompletionProc (MyPB1WriteComp);
                myVars->pb0->pb.ioParam.ioCompletion = MyPB0WriteCompUPP;
                myVars->pb0->pb.ioParam.ioVRefNum = volRefNum;
                myVars->pb0->pb.ioParam.ioRefNum = myVars->fileRefNum;
                myVars->pb0->pb.ioParam.ioPosMode = fsAtMark;
 
                myVars->pb1->pb.ioParam.ioCompletion = MyPB1WriteCompUPP;
                myVars->pb1->pb.ioParam.ioVRefNum = volRefNum;
                myVars->pb1->pb.ioParam.ioRefNum = myVars->fileRefNum;
                myVars->pb1->pb.ioParam.ioPosMode = fsAtMark;
 
                //write a temp AIFF header so file pointer for data is in the right place
                err = SetupAIFFHeader (myVars->fileRefNum, myVars->numChannels, myVars->sampleRate, myVars->sampleSize, myVars->compression, 0, 0);
                if (err != noErr)
                    DebugStr("\pSetupAIFFHeader failed");
            }
        }
    } else {
        err = userCanceledErr;
    }
 
    return err;
}
 
OSErr RecordToDisk (VarsPtr myVars) {
    OSErr           err = noErr;
 
    myVars->totalBytes = 0;
    err = SPBRecord (myVars->recordRec, true);
    if (err != noErr) {
        DebugStr("\pSPBRecord failed");
    }
 
    return err;
}
 
/*
    Stops the recording by calling SPBStopRecording and then writes the
    correct file header to the file, closes the file and then disposes of
    our pointers.
*/
OSErr   FinishRecording (VarsPtr myVars) {
    OSErr           err;
    ParamBlockRec   pb;
 
    err = SPBStopRecording (myVars->soundRefNum);
    if (err != noErr)
        DebugStr("\pSPBStopRecording failed");
 
    err = SPBCloseDevice (myVars->soundRefNum);
    if (err != noErr)
        DebugStr("\pSPBCloseDevice failed");
 
    //Put the file pointer back to the start of the file to
    //write the correct header over the temp header
    pb.ioParam.ioCompletion = nil;
    pb.ioParam.ioRefNum = myVars->fileRefNum;
    pb.ioParam.ioPosMode = fsFromStart;
    pb.ioParam.ioPosOffset = 0;
    err = PBSetFPosSync (&pb);
    if (err != noErr)
        DebugStr("\pPBSetFPosSync failed");
 
    if (err == noErr) {
        //write the header with the correct information
        err = SetupAIFFHeader (myVars->fileRefNum, myVars->numChannels, myVars->sampleRate, myVars->sampleSize, myVars->compression, myVars->totalBytes, myVars->totalBytes/(myVars->sampleSize*myVars->numChannels));
        if (err != noErr)
            DebugStr("\pSetupAIFFHeader in FinishRecording failed");
    }
 
    err = PBCloseSync(&pb);
    if (err != noErr)
        DebugStr("\pPBCloseSync failed");
 
    DisposePtr ((Ptr)myVars->recordRec);
    DisposePtr (myVars->recBuffer0);
    DisposePtr (myVars->recBuffer1);
    DisposePtr ((Ptr)myVars->pb0);
    DisposePtr ((Ptr)myVars->pb1);
 
    return err;
}
 
/*
    This gets called at the end of each record, it writes the recorded data to
    disk (asynchronously) and then starts a new record using the same SPBPtr.
 
    Make sure you have continous recording turned on otherwise the recording
    may loose data between calls to SPBRecord.  
*/
pascal void MyRecComp (SPBPtr inParamPtr) {
    VarsPtr         myVarsPtr;
    OSErr           err;
 
    #if !GENERATINGCFM
        long        oldA5;
        oldA5 = SetA5 (((VarsPtr)inParamPtr->userLong)->myA5);
    #endif
 
    myVarsPtr = (VarsPtr)inParamPtr->userLong;
 
    //setting this now will avoid a race condition of the record finishing before
    //we can toggle which buffer to use
    myVarsPtr->whichBuffer = !myVarsPtr->whichBuffer;
 
    //If the last write returned with no error then continue recording
    if (myVarsPtr->pb0->pb.ioParam.ioResult == noErr && myVarsPtr->pb1->pb.ioParam.ioResult == noErr) {
        //If we are aborting (stopping) the recording, we still want to write the last buffer
        if (inParamPtr->error == noErr || inParamPtr->error == abortErr) {
            if (myVarsPtr->whichBuffer == 1) {
                myVarsPtr->pb0->pb.ioParam.ioBuffer = myVarsPtr->recBuffer0;
                myVarsPtr->pb0->pb.ioParam.ioReqCount = myVarsPtr->recordRec->count;
                myVarsPtr->totalBytes += myVarsPtr->recordRec->count;
                if (myVarsPtr->pb0->pbInUse == false) {
                    myVarsPtr->pb0->pbInUse = true;
                    err = PBWriteAsync(&myVarsPtr->pb0->pb);
                }
                inParamPtr->bufferPtr = myVarsPtr->recBuffer1;
                if (inParamPtr->error == noErr) {
                    err = SPBRecord (inParamPtr, true);
                    if (err != noErr)
                        DebugStr("\pSPBRecord1 failed");
                }
            } else {
                myVarsPtr->pb1->pb.ioParam.ioBuffer = myVarsPtr->recBuffer1;
                myVarsPtr->pb1->pb.ioParam.ioReqCount = myVarsPtr->recordRec->count;
                myVarsPtr->totalBytes += myVarsPtr->recordRec->count;
                if (myVarsPtr->pb1->pbInUse == false) {
                    myVarsPtr->pb1->pbInUse = true;
                    err = PBWriteAsync (&myVarsPtr->pb1->pb);
                }
                inParamPtr->bufferPtr = myVarsPtr->recBuffer0;
                if (inParamPtr->error == noErr) {
                    err = SPBRecord (inParamPtr, true);
                    if (err != noErr)
                        DebugStr("\pSPBRecord2 failed");
                }
            }
            myVarsPtr->theErr = err;
        }
    } else {
        //There was an error from PBWrite, return the error in theErr of our structure
        if (myVarsPtr->pb0->pb.ioParam.ioResult != noErr)
            myVarsPtr->theErr = myVarsPtr->pb0->pb.ioParam.ioResult;
        else myVarsPtr->theErr = myVarsPtr->pb1->pb.ioParam.ioResult;
    }
 
    #if !GENERATINGCFM
        oldA5 = SetA5 (oldA5);
    #endif
 
    return;
}
 
/*
    These routines are here just for error checking in this example,
    you may wish to update a status record or something similar.
 
    What do you do if you get a write error during async recording?
    For this example we will simply terminate the recording process,
    you should probably display an error.  Since the myParamBlockRec
    doesn't contain the sound input device number we will check for
    an error in the record completion routine and not start a new
    recording if there was an error.  You can call SPBStopRecording
    at interrupt time, but that isn't needed in this example.
 
    These routines display the error, but the record completion
    routine checks the parameter blocks to make sure there was no
    error before continuing the recording.
*/
#if GENERATINGCFM
pascal void MyPB0WriteComp (myParmBlkPtr passedPB)
#else 
pascal void MyPB0WriteComp (myParmBlkPtr passedPB:__a0)
#endif
{
    if (passedPB->pb.ioParam.ioResult != noErr) {
        DebugStr("\pPBWrite0 failed!");
    }
 
    passedPB->pbInUse = false;
}
 
#if GENERATINGCFM
pascal void MyPB1WriteComp (myParmBlkPtr passedPB)
#else 
pascal void MyPB1WriteComp (myParmBlkPtr passedPB:__a0)
#endif
{
    if (passedPB->pb.ioParam.ioResult != noErr) {
        DebugStr("\pPBWrite1 failed!");
    }
 
    passedPB->pbInUse = false;
}