PCISoundInputDriver.c

/*
    File:       PCISoundInputDriver.c
    
    Contains:   Sample PCI sound input hardware driver
 
    Author:     <MC>
 
    Copyright:  © Copyright 1996-2001 Apple Computer, Inc. All rights reserved.
    
    Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
                ("Apple") in consideration of your agreement to the following terms, and your
                use, installation, modification or redistribution of this Apple software
                constitutes acceptance of these terms.  If you do not agree with these terms,
                please do not use, install, modify or redistribute this Apple software.
 
                In consideration of your agreement to abide by the following terms, and subject
                to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
                copyrights in this original Apple software (the "Apple Software"), to use,
                reproduce, modify and redistribute the Apple Software, with or without
                modifications, in source and/or binary forms; provided that if you redistribute
                the Apple Software in its entirety and without modifications, you must retain
                this notice and the following text and disclaimers in all such redistributions of
                the Apple Software.  Neither the name, trademarks, service marks or logos of
                Apple Computer, Inc. may be used to endorse or promote products derived from the
                Apple Software without specific prior written permission from Apple.  Except as
                expressly stated in this notice, no other rights or licenses, express or implied,
                are granted by Apple herein, including but not limited to any patent rights that
                may be infringed by your derivative works or by other works in which the Apple
                Software may be incorporated.
 
                The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
                WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
                WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
                PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
                COMBINATION WITH YOUR PRODUCTS.
 
                IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
                CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
                GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
                ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
                OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
                (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
                ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
                
    Change History (most recent first):
                05/02/01    JAS Fixed problems in HWIntProc. Fixed bug in DriverStausCmd:siInputSourceNames
                                to return a copy of the handle to the input source names. Updated to CW Pro 6.
 
                8/3/1999    KG  Updated for Metrowerks Codewarror Pro 2.1
 
*/
 
 
#include <Aliases.h>
#include <DriverServices.h>
#include <DriverGestalt.h>
#include <NameRegistry.h>
#include <Resources.h>
#include <Sound.h>
#include <SoundInput.h>
#include <TextUtils.h>
#include <Timer.h>
 
#include <stdio.h>
#include <Strings.h>
 
// This is what our input parameter looks like
#pragma options align=mac68k
struct SoundParam {
    QElemPtr                        qLink;
    short                           qType;
    short                           ioTrap;
    Ptr                             ioCmdAddr;
    IOCompletionUPP                 ioCompletion;
    OSErr                           ioResult;
    StringPtr                       ioNamePtr;
    short                           ioVRefNum;
    short                           ioCRefNum;
    short                           csCode;
    short                           csParam[11];
};
typedef struct SoundParam SoundParam, *SoundParamPtr;
#pragma options align=reset
 
// tell the OS all about us
 
extern DriverDescription TheDriverDescription = {
  kTheDescriptionSignature,
  kInitialDriverDescriptor,
  // DriverType
  "\pTestSoundInputDriver",
  1, 0, developStage, 1,
  // DriverOSRuntimeInfo
  0
  | (1 * kDriverIsLoadedUponDiscovery)      /* Loader runtime options */
  | (1 * kDriverIsOpenedUponLoad)           /* Opened when loaded */
  | (0 * kDriverIsUnderExpertControl)       /* No I/O expert to handle loads/opens */
  | (0 * kDriverIsConcurrent)               /* Not concurrent yet */
  | (0 * kDriverQueuesIOPB),                /* Not internally queued yet */
  "\p.TestSoundInputDriver",                /* Str31 driverName (OpenDriver param) */
  0, 0, 0, 0, 0, 0, 0, 0,                   /* UInt32 driverDescReserved[8] */
  // DriverOSService
  1,                                        /* ServiceCount nServices */
  // DriverServiceInfo
  kServiceCategoryNdrvDriver,               /* OSType serviceCategory */
  kNdrvTypeIsGeneric,                       /* OSType serviceType */
    1, 0, developStage, 1,                  /* major, minor, stage, rev */
};
 
//For debugging information
#define DEBUG       0
#define DEBUG2      0
#define FULLDEBUG   0
 
#define kSamplesInBuffer    1024
#define kSilence            128
#define kAmplitude          75
 
#define kSquareSource       1
#define kSawSource          2
#define kSilenceSource      3
 
#define kNumRates           3
#define kNumSizes           2
#define kNumCompressions    1
 
typedef struct myTMTask {
    TMTask      theTask;
    ParmBlkPtr  pb;
} myTMTask;
 
 
//----------------------------------------------------------------------------------
//                                  Function Prototypes
//----------------------------------------------------------------------------------
 
pascal OSErr FragInit               (CFragInitBlockPtr initInfo);
pascal void FragTerm                (void);
 
static OSStatus DriverKillIOCmd     (ParmBlkPtr pb);
extern OSErr DoDriverIO             (AddressSpaceID addressSpaceID, IOCommandID ioCommandID, IOCommandContents ioCommandContents, IOCommandCode ioCommandCode, IOCommandKind ioCommandKind);
 
static pascal void HWIntProc        (QElemPtr passedPtr);
static void MakeSquareWave          (long duration, long numberSamples, UnsignedFixed sampleRate, long frequency, short sampleSize, short numChannels);
static void MakeSawWave             (long duration, long numberSamples, UnsignedFixed sampleRate, long frequency, short sampleSize, short numChannels);
static OSErr DoOptionsDialog        (void);
static void SetHardwareToDefault    (void);
 
 
//----------------------------------------------------------------------------------
//                                  Globals
//----------------------------------------------------------------------------------
 
//Hardware specific globals
SInt16          gMaxAmplitude,
                gContinuousOn,
                gLevelMeterOn,
                gTwosComplementOn,
                gAGCOn,
                gPlayThruVolume,
                gVoxRecordingOn,
                gVoxStoppingOn,
                gPauseState,
                gNumberChannels,
                gSampleSize,
                gVOXStartTrigger,
                gVOXStopTrigger,
                gVOXDelay,
                gCompressionFactor,
                gCurrentInputSource     = kSquareSource;
SInt32          gActiveChannels;            //channels to record from 0x01 == only left, 0x02 == only right, 0x03 == both channels
UnsignedFixed   gSampleRate;
Fixed           gInputGain,
                gLeftInputGain,
                gRightInputGain;
OSType          gCompressionType,
                gRecordingQuality;
SIInterruptUPP  gUserInterruptProc      = nil;
 
//Globals needed to simulate our hardware
myTMTask        gSimulatedHWInterrupt;
TimerUPP        gSimulatedHWIntProc     = nil;
SInt32          gInterruptFreq          = 0,
                gSamplesWritten         = 0;
SInt16          gNextSaw16Sample        = -((kAmplitude * 0.01) * 32767);   //starting point for saw tooth wave
SInt8           gNextSaw8Sample         = kSilence - kAmplitude;    //starting point for saw tooth wave
Ptr             gSoundBuffer            = nil;  //our hardware's internal buffer
                                                
//Driver specific globals
Boolean         gStopRecording          = false;
DriverRefNum    gDrvrRefNum;
Handle          gDrvrIcon,
                gInputSourceNames;
AliasHandle     gAliasToDriver;
StringHandle    gDriverName;
 
//----------------------------------------------------------------------------------
//                                  CFM Init
//----------------------------------------------------------------------------------
 
pascal OSErr FragInit (CFragInitBlockPtr initInfo) {
    Handle              iconHandle,
                        strHandle;
    StringHandle        driverName;
    SInt16              resRefNum;
    OSErr               err         = noErr;
 
    if (initInfo->fragLocator.where == kDataForkCFragLocator || initInfo->fragLocator.where == kResourceCFragLocator) {
        resRefNum = FSpOpenResFile (initInfo->fragLocator.u.onDisk.fileSpec, fsRdPerm);
        err = ResError();
        if (err != noErr) {
        #if DEBUG
            SysDebugStr ("\p!!Error returned from FSpOpenResFile!!");
        #endif
        }
 
        if (resRefNum == -1) {
        #if DEBUG
            SysDebugStr ("\p!!Error resRefNum is -1!!");
        #endif
        }
    }
 
    if (resRefNum != -1 && err == noErr) {
        iconHandle = Get1Resource ('ICN#', 128);
        err = ResError ();
        if (iconHandle == nil) {
        #if DEBUG
            SysDebugStr ("\p!!iconHandle is nil!!");
        #endif
        }
 
        if (err != noErr) {
        #if DEBUG
            SysDebugStr ("\p!!Error from Get1Resource('ICN#', 128)!!");
        #endif
        }
 
        if (iconHandle != nil) {
            gDrvrIcon = NewHandleSys (GetHandleSize (iconHandle));
            BlockMoveData (*iconHandle, *gDrvrIcon, GetHandleSize (iconHandle));
            ReleaseResource (iconHandle);
        } else if (err == noErr) {
            err = resNotFound;
        #if DEBUG
            SysDebugStr ("\p!!Error Get1Resource('ICN#', 128), resNotFound!!");
        #endif
        }
 
        strHandle = Get1Resource ('STR#', 128);
        err = ResError ();
        if (strHandle == nil) {
        #if DEBUG
            SysDebugStr ("\p!!strHandle is nil!!");
        #endif
        }
 
        if (err != noErr) {
        #if DEBUG
            SysDebugStr ("\p!!Error from Get1Resource('STR#', 128)!!");
        #endif
        }
 
        if (strHandle != nil) {
            gInputSourceNames = NewHandleSys (GetHandleSize (strHandle));
            BlockMoveData (*strHandle, *gInputSourceNames, GetHandleSize (strHandle));
            ReleaseResource (strHandle);
        } else if (err == noErr) {
            err = resNotFound;
        #if DEBUG
            SysDebugStr ("\p!!Error Get1Resource('STR#', 128), resNotFound!!");
        #endif
        }
 
        driverName = GetString (128);
        err = ResError ();
        if (driverName == nil) {
        #if DEBUG
            SysDebugStr ("\p!!driverName is nil!!");
        #endif
        }
 
        if (err != noErr) {
        #if DEBUG
            SysDebugStr ("\p!!Error from GetString(128)!!");
        #endif
        }
 
        if (driverName != nil) {
            gDriverName = (StringHandle)NewHandleSys (GetHandleSize ((Handle)driverName));
            BlockMoveData (*driverName, *gDriverName, GetHandleSize ((Handle)driverName));
            ReleaseResource ((Handle)driverName);
        } else if (err == noErr) {
            err = resNotFound;
        #if DEBUG
            SysDebugStr ("\p!!Error GetString(128), resNotFound!!");
        #endif
        }
 
        CloseResFile (resRefNum);
    }
 
    if (err == noErr) {
        //make an alias to ourselves so that we can open our driver later if needed
        err = NewAlias (nil, initInfo->fragLocator.u.onDisk.fileSpec, &gAliasToDriver);
    }
 
#if DEBUG
    if (err != noErr) {
        SysDebugStr ("\p!!Error in FragInit!!");
    }
#endif
 
    return (err);
}
 
pascal void FragTerm (void) {
    if (gDrvrIcon != nil) {
        DisposeHandle (gDrvrIcon);
        gDrvrIcon = nil;
    }
 
    if (gInputSourceNames != nil) {
        DisposeHandle (gInputSourceNames);
        gInputSourceNames = nil;
    }
 
    if (gAliasToDriver != nil) {
        DisposeHandle ((Handle)gAliasToDriver);
        gAliasToDriver = nil;
    }
}
 
//----------------------------------------------------------------------------------
//                                  Driver calls
//----------------------------------------------------------------------------------
 
/*
    Always run at task level, can allocate and move memory.
*/
static OSStatus DriverInitializeCmd (AddressSpaceID addressSpaceID, DriverInitInfoPtr initialInfo) {
#pragma unused (addressSpaceID, initialInfo)
    OSStatus            err = noErr;
 
#if DEBUG
    SysDebugStr ("\pin DriverInitializeCmd;g");
#endif
 
    gDrvrRefNum = initialInfo->refNum;
 
#if DEBUG
    if (err != noErr) {
        SysDebugStr ("\p!!Error in DriverInitializeCmd!!");
    }
#if FULLDEBUG
    SysDebugStr ("\pleaving DriverInitializeCmd;g");
#endif
#endif
 
    return (err);
}
 
/*
    Always run at task level, can allocate and move memory.
*/
static OSStatus DriverFinalizeCmd (DriverFinalInfoPtr finalInfo) {
#pragma unused (finalInfo)
    OSStatus            err = noErr;
 
#if DEBUG
    SysDebugStr ("\pin DriverFinalizeCmd;g");
#endif
 
#if DEBUG
    if (err != noErr) {
        SysDebugStr ("\p!!Error in DriverFinalizeCmd!!");
    }
#if FULLDEBUG
    SysDebugStr ("\pleaving DriverFinalizeCmd;g");
#endif
#endif
 
    return (err);
}
 
/*
    Always run at task level, can allocate and move memory.
*/
static OSStatus DriverSupersededCmd (DriverSupersededInfoPtr supersededInfo) {
#pragma unused (supersededInfo)
 
    OSErr           err = noErr;
 
#if DEBUG
    SysDebugStr ("\pin DriverSupersededCmd;g");
#endif
 
#if DEBUG
    if (err != noErr) {
        SysDebugStr ("\p!!Error in DriverSupersededCmd!!");
    }
#if FULLDEBUG
    SysDebugStr ("\pleaving DriverSupersededCmd;g");
#endif
#endif
 
    return (err);
}
 
/*
    Always run at task level, can allocate and move memory.
*/
static OSStatus DriverReplaceCmd (AddressSpaceID addressSpaceID, DriverReplaceInfoPtr replaceInfo) {
#pragma unused (addressSpaceID, replaceInfo)
    OSStatus            err = noErr;
 
#if DEBUG
    SysDebugStr ("\pin DriverReplaceCmd;g");
#endif
 
#if DEBUG
    if (err != noErr) {
        SysDebugStr ("\p!!Error in DriverReplaceCmd!!");
    }
#if FULLDEBUG
    SysDebugStr ("\pleaving DriverReplaceCmd;g");
#endif
#endif
 
    return (err);
}
 
/*
    Always run at task level, can allocate and move memory.
*/
static OSStatus DriverOpenCmd (AddressSpaceID addressSpaceID, ParmBlkPtr pb) {
#pragma unused (addressSpaceID, pb)
    OSStatus            err = noErr;
 
#if DEBUG
    SysDebugStr ("\pin DriverOpenCmd;g");
#endif
 
    gSoundBuffer = NewPtrSys (kSamplesInBuffer * 4);    //room for 16 bit stereo samples
 
    if (gSoundBuffer != nil) {
        gSimulatedHWIntProc = NewTimerProc (HWIntProc);
        SetHardwareToDefault ();
 
        err = SPBSignInDevice (gDrvrRefNum, *gDriverName);
    } else {
    #if DEBUG
        SysDebugStr ("\p!!couldn't allocate memory for our buffer!!");
    #endif
        err = MemError ();
    }
 
#if DEBUG
    if (err != noErr) {
        SysDebugStr ("\p!!Error in DriverOpenCmd!!");
    }
#if FULLDEBUG
    SysDebugStr ("\pleaving DriverOpenCmd;g");
#endif
#endif
 
    return (err);
}
 
/*
    Always run at task level, can allocate and move memory.
*/
static OSStatus DriverCloseCmd (ParmBlkPtr pb) {
#pragma unused (pb)
    OSStatus            err = noErr;
 
#if DEBUG
    SysDebugStr ("\pin DriverCloseCmd;g");
#endif
 
    (void)SPBSignOutDevice (gDrvrRefNum);
    DisposePtr (gSoundBuffer);
    gSoundBuffer = nil;
    DisposeRoutineDescriptor (gSimulatedHWIntProc);
    gSimulatedHWIntProc = nil;
 
#if FULLDEBUG
    SysDebugStr ("\pleaving DriverCloseCmd;g");
#endif
 
    return (err);
}
 
/*
    May run at interrupt level, CANNOT allocate or move memory.
*/
static OSStatus DriverControlCmd (AddressSpaceID addressSpaceID, IOCommandID ioCommandID, IOCommandKind ioCommandKind, SoundParamPtr pb) {
#pragma unused (addressSpaceID, ioCommandID, ioCommandKind)
    OSStatus            err         = noErr;
    OSType              selector    = 0;
 
#if FULLDEBUG
    SysDebugStr ("\pin DriverControlCmd;g");
#endif
 
 
    // if they call SPBStopRecording, it comes to us as killIO
    // so call our killIO routine
 
    if (pb->csCode == killCode) {
        err = DriverKillIOCmd ((ParmBlkPtr)pb);
    }
    
    else if (pb->csCode == 2) { 
        // indicates that sound input information selector is in first 4 bytes of csParam
        selector = ((OSType*)pb->csParam)[0];
    }
 
    switch (selector) {
        //Control calls that are required to be supported
        case siCompressionType:
            //Set the compression type. Some devices allow the incoming samples to be
            //compressed before being placed in your applicationÕs input buffer. The
            //infoData parameter points to a buffer of type OSType, which is the
            //compression type.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siCompressionType");
        #endif
            if (((OSType*)pb->csParam)[1] != 'NONE') {
                err = siInvalidCompression; //we don't support compression
            }
            break;
        case siContinuous:
            //Set the state of continuous recording from this device. If recording
            //is being turned off, the driver stops recording samples to its internal buffer.
            //Only sound input device drivers that support asynchronous recording support
            //continuous recording. The infoData parameter points to an integer, which is
            //the state of continuous recording (0 is off, 1 is on).
        #if DEBUG2
            SysDebugStr ("\pcontrol: siContinuous");
        #endif
            if (((SInt16*)pb->csParam)[2] == 0 || ((SInt16*)pb->csParam)[2] == 1) {
                gContinuousOn = ((SInt16*)pb->csParam)[2];
            } else {
                err = paramErr;
            }
            break;
        case siLevelMeterOnOff:
            //Set the current state of the level meter. For calls to set the level meter,
            //the infoData parameter points to an integer that indicates whether the level meter
            //is off (0) or on (1).
        #if DEBUG2
            SysDebugStr ("\pcontrol: siLevelMeterOnOff");
        #endif
            if (((SInt16*)pb->csParam)[2] == 0 || ((SInt16*)pb->csParam)[2] == 1) {
                gLevelMeterOn = ((SInt16*)pb->csParam)[2];
            } else {
                err = paramErr;
            }
            break;
        case siNumberChannels:
            //Set the number of channels this device is to record. The infoData parameter points
            //to an integer, which indicates the number of channels. Note that this selector
            //determines the format of the data stream output by the driver. If the number of
            //channels is 1, the driver should output monophonic data in response to a Read call.
            //If the number of channels is 2, the driver should output interleaved stereo data.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siNumberChannels");
        #endif
            if (((SInt16*)pb->csParam)[2] >= 1 && ((SInt16*)pb->csParam)[2] <= 2) {
                gNumberChannels = ((SInt16*)pb->csParam)[2];
            } else {
                err = notEnoughHardwareErr;
            }
            break;
        case siRecordingQuality:
            //Set the current quality of recorded sound. The infoData parameter points to a buffer
            //of type OSType, which is the recording quality. Currently foud qualities are supported,
            //defined by these constants:
            //          siCDQuality                         = 'cd  ',               /*44.1kHz, stereo, 16 bit*/
            //          siBestQuality                       = 'best';               /*22kHz, mono, 8 bit*/
            //          siBetterQuality                     = 'betr';               /*22kHz, mono, MACE 3:1*/
            //          siGoodQuality                       = 'good';
            //These qualities are defined by the sound input device driver. Usually best means
            //monaural, 8-bit, 22 kHz, sound with no compression.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siRecordingQuality");
        #endif
            switch (((OSType*)pb->csParam)[1]) {
                case siCDQuality:
                    gRecordingQuality = siCDQuality;
                    gSampleRate = rate44khz;
                    gSampleSize = 16;
                    gNumberChannels = 2;
                    gCompressionType = 'NONE';
                    break;
                case siBestQuality:
                    gRecordingQuality = siBestQuality;
                    gSampleRate = rate22050hz;
                    gSampleSize = 8;
                    gNumberChannels = 1;
                    gCompressionType = 'NONE';
                    break;
                case siBetterQuality:
                    gRecordingQuality = siBetterQuality;
                    gSampleRate = rate22050hz;
                    gSampleSize = 8;
                    gNumberChannels = 1;
                    gCompressionType = 'NONE';
                    break;
                case siGoodQuality:
                    gRecordingQuality = siGoodQuality;
                    gSampleRate = rate11025hz;
                    gSampleSize = 8;
                    gNumberChannels = 1;
                    gCompressionType = 'NONE';
                    break;
                default:
                    err = siUnknownQuality;
            }
            break;
        case siSampleRate:
            //Set the sample rate to be produced by this device. The sample rate must be in the
            //range 0 to 65535.65535 Hz. The sample rate is declared as a Fixed data type. In
            //order to accommodate sample rates greater than 32 kHz, the most significant bit is
            //not treated as a sign bit; instead, that bit is interpreted as having the value 32,768.
            //The infoData parameter points to a buffer of type Fixed, which is the sample rate.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siSampleRate");
        #endif
            if (((UnsignedFixed*)pb->csParam)[1] == rate44khz || ((UnsignedFixed*)pb->csParam)[1] == rate22050hz || ((UnsignedFixed*)pb->csParam)[1] == rate11025hz) {
                gSampleRate = ((UnsignedFixed*)pb->csParam)[1];
            } else {
                err = siInvalidSampleRate;
            }
            break;
        case siSampleSize:
            //Set the sample size to be produced by this device. Because some compression formats
            //require specific sample sizes, this selector might return an error when compression
            //is used. The infoData parameter points to an integer, which is the sample size.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siSampleSize");
        #endif
            if (((SInt16*)pb->csParam)[2] == 8 || ((SInt16*)pb->csParam)[2] == 16) {
                gSampleSize = ((SInt16*)pb->csParam)[2];
            } else {
                err = siInvalidSampleSize;
            }
            break;
        case siTwosComplementOnOff:
            //Set the current state of the twoÕs complement feature. This selector only applies to
            //8-bit data. (16-bit samples are always stored in twoÕs complement format.) If on, the
            //driver stores all samples in the application buffer as twoÕs complement values
            //(that is, Ð128 to 127). Otherwise, the driver stores the samples as offset binary
            //values (that is, 0 to 255). The infoData parameter points to an integer, which is the
            //current state of the twoÕs complement feature (1 if twoÕs complement output is desired,
            //0 otherwise).
        #if DEBUG2
            SysDebugStr ("\pcontrol: siTwosComplementOnOff");
        #endif
            if (((SInt16*)pb->csParam)[2] == 0 || ((SInt16*)pb->csParam)[2] == 1) {
                gTwosComplementOn = ((SInt16*)pb->csParam)[2];
            } else {
                err = paramErr;
            }
            break;
 
        //Sound Manager only calls
        case siCloseDriver:
            //The Sound Input Manager sends this selector when it closes a device previously
            //opened with write permission. The sound input device driver should stop any
            //recording in progress, deallocate the input hardware, and initialize local
            //variables to default settings.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siCloseDriver");
        #endif
            //We will actually stop the hardware at the next hardware interrupt
            gStopRecording = true;
            break;
        case siInitializeDriver:
            //The Sound Input Manager sends this selector when it opens a sound input device
            //with write permission. The sound input device driver initializes local variables
            //and prepares to start recording. If possible, the driver initializes the device
            //to a sampling rate of 22 kHz, a sample size of 8 bits, mono recording, no compression,
            //automatic gain control on, and all other features off.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siInitializeDriver");
        #endif
            SetHardwareToDefault ();
            break;
        case siPauseRecording:
            //The Sound Input Manager uses this selector to set the current pause state.
            //The sound input device driver continues recording but does not store the sampled
            //data in a buffer. The infoData parameter points to an integer, which indicates
            //the state of pausing (0 is off, 1 is on).
        #if DEBUG2
            SysDebugStr ("\pcontrol: siPauseRecording");
        #endif
            gPauseState = ((SInt16*)pb->csParam)[2];
            break;
        case siUserInterruptProc:
            //The Sound Input Manager sends this selector to specify the sound input interrupt
            //routine that the sound input device driver should call. The infoData parameter
            //points to a procedure pointer, which is the address of the sound input interrupt routine.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siUserInterruptProc");
        #endif
            gUserInterruptProc = ((SIInterruptUPP*)pb->csParam)[1];
            break;
 
        //Control calls that can be optionally supported
        case siActiveChannels:
            //Set the channels to record from. When setting the active channels, the data passed
            //in is a long integer that is interpreted as a bitmap describing the channels to record
            //from. For example, if bit 0 is set, then the first channel is made active. The samples
            //for each active channel are interleaved in the applicationÕs buffer.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siActiveChannels");
        #endif
            if (((SInt32*)pb->csParam)[1] >= 0x01 && ((SInt32*)pb->csParam)[1] <= 0x03) {
                gActiveChannels = ((SInt32*)pb->csParam)[1];
            } else {
                err = notEnoughHardwareErr;
            }
            break;
        case siAGCOnOff:
            //Set the current state of the automatic gain control feature. The infoData parameter
            //points to an integer, which is 0 if gain control is off and 1 if it is on.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siAGCOnOff");
        #endif
            if (((SInt16*)pb->csParam)[2] == 0 || ((SInt16*)pb->csParam)[2] == 1) {
                gAGCOn = ((SInt16*)pb->csParam)[2];
            } else {
                err = paramErr;
            }
            break;
        case siInputGain:
            //Set the current sound input gain. If the available hardware allows adjustment of the
            //recording gain, this selector lets you get and set the gain. In response to a Control
            //call, a sound input driver sets the gain level used for all subsequent recording to the
            //specified value. The infoData parameter points to a 4-byte value of type Fixed ranging
            //from 0.5 to 1.5, where 1.5 specifies maximum gain.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siInputGain");
        #endif
            if (((Fixed*)pb->csParam)[1] >= (Fixed)0.5 && ((Fixed*)pb->csParam)[1] <= (Fixed)1.5) {
                gInputGain = ((Fixed*)pb->csParam)[1];
            } else {
                err = paramErr;
            }
            break;
        case siInputSource:
            //Set the current sound input source. If the available hardware allows recording from more
            //than one source, this selector lets you get and set the source. In response to a Control
            //call, a sound input driver sets the source of all subsequent recording to the value passed
            //in. If the value is less than 1 or greater than the number of input sources, the driver
            //returns paramErr; if the driver supports only one source, it returns siUnknownInfoType. The
            //infoData parameter points to an integer, which is the index of the current sound input source.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siInputSource");
        #endif
            if (((SInt16*)pb->csParam)[2] >= 1 && ((SInt16*)pb->csParam)[2] <= 3) {
                gCurrentInputSource = ((SInt16*)pb->csParam)[2];
            } else {
                err = notEnoughHardwareErr;
            }
            break;
        case siOptionsDialog:
            //Cause the driver to display the Options dialog box (SPBSetDeviceInfo). This dialog box
            //is designed to allow the user to configure device-specific features of the sound input
            //hardware. With SPBSetDeviceInfo, the infoData parameter is unused.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siOptionsDialog");
        #endif
            err = DoOptionsDialog ();
            break;
        case siPlayThruOnOff:
            //Set the current play-through state and volume. The infoData parameter points to an integer,
            //which indicates the current play-through volume (1 to 7). If that integer is 0, then
            //play-through is off.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siPlayThruOnOff");
        #endif
            err = notEnoughHardwareErr;
            //  If our (fake) hardware actually did playthrough, this would be useful...
        /*
            if (((short*)pb->csParam)[2] >= 0 || ((short*)pb->csParam)[2] <= 7) {
                gPlayThruVolume = ((short*)pb->csParam)[2];
            } else {
                err = notEnoughHardwareErr;
            }
        */
            break;
        case siStereoInputGain:
            //Set the current stereo sound input gain. If the available hardware allows adjustment of
            //the recording gain, this selector lets you get and set the gain for each of two channels
            //(left or right). In response to a Status call, a sound input driver should return the
            //current gain setting for the specified channel. In response to a Control call, a sound input
            //driver should set the gain level used for all subsequent recording to the specified value.
            //The infoData parameter points to two 4-byte values of type Fixed ranging from 0.5 to 1.5,
            //where 1.5 specifies maximum gain. The first of these values is equivalent to the gain for
            //the left channel and the second value is equivalent to the gain for the right channel.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siStereoInputGain");
        #endif
            if (((Fixed*)pb->csParam)[1] <= 1.5 && ((Fixed*)pb->csParam)[1] >= 0.5 && ((Fixed*)pb->csParam)[2] <= 1.5 && ((Fixed*)pb->csParam)[2] >= 0.5) {
                gLeftInputGain = ((Fixed*)pb->csParam)[1];
                gRightInputGain = ((Fixed*)pb->csParam)[2];
            } else {
                err = paramErr;
            }
            break;
        case siVoxRecordInfo:
            //Set the current VOX recording parameters. The infoData parameter points to two integers.
            //The first integer indicates whether VOX recording is on or off (0 if off, 1 if on). The
            //second integer indicates the VOX record trigger value. Trigger values range from 0 to 255
            //(0 is trigger immediately, 255 is trigger only on full volume).
        #if DEBUG2
            SysDebugStr ("\pcontrol: siVoxRecordInfo");
        #endif
            if (((SInt16*)pb->csParam)[2] == 0 || ((SInt16*)pb->csParam)[2] == 1) {
                gVoxRecordingOn = ((SInt16*)pb->csParam)[2];
            } else {
                err = paramErr;
            }
 
            if (gVoxRecordingOn == 1 && (((SInt16*)pb->csParam)[3] <= 255 && ((SInt16*)pb->csParam)[3] >= 0)) {
                gVOXStartTrigger = ((SInt16*)pb->csParam)[3];
            } else {
                err = paramErr;
            }
            break;
        case siVoxStopInfo:
            //Set the current VOX stopping parameters. The infoData parameter points to three integers.
            //The first integer indicates whether VOX stopping is on or off (0 if off, 1 if on). The
            //second integer indicates the VOX stop trigger value. Trigger values range from 0 to 255
            //(255 is stop immediately, 0 is stop only on total silence). The third integer indicates
            //how many milliseconds the trigger value must be continuously valid for recording to be stopped.
            //Delay values range from 0 to 65,535.
        #if DEBUG2
            SysDebugStr ("\pcontrol: siVoxStopInfo");
        #endif
            if (((SInt16*)pb->csParam)[2] == 0 || ((SInt16*)pb->csParam)[2] == 1) {
                gVoxStoppingOn = ((SInt16*)pb->csParam)[2];
            } else {
                err = paramErr;
            }
 
            if (gVoxStoppingOn == 1 && (((SInt16*)pb->csParam)[3] <= 255 && ((SInt16*)pb->csParam)[3] >= 0)) {
                gVOXStopTrigger = ((SInt16*)pb->csParam)[3];
                gVOXDelay = ((SInt16*)pb->csParam)[4];
            } else {
                err = paramErr;
            }
            break;
 
        //Unknown or unsupported control call
        default:
        #if DEBUG
            {
                Str255  errString = "\pcontrol: unknown selector -> XXXX";
                OSType* typePtr;
                
                typePtr = (OSType*)(&errString[30]);
                *typePtr = selector;
                SysDebugStr ( errString );
            }
//          SysDebugStr ("\pcontrol: unknown selector");
        #endif
            err = controlErr;
    }
 
#if DEBUG
    if (err != noErr && err != controlErr) {
        unsigned char       errorcode[11];
        sprintf ((char*)errorcode, "0x%0.8X", err);
        errorcode[0] = 10;
        SysDebugStr (errorcode);
        SysDebugStr ("\p!!Error in DriverControlCmd!!");
    }
#if FULLDEBUG
    SysDebugStr ("\pleaving DriverControlCmd;g");
#endif
#endif
 
    return (err);
}
 
/*
    May run at interrupt level, CANNOT allocate or move memory.
*/
static OSStatus DriverStatusCmd (AddressSpaceID addressSpaceID, IOCommandID ioCommandID, IOCommandKind ioCommandKind, SoundParamPtr pb) {
#pragma unused (addressSpaceID, ioCommandID, ioCommandKind)
    OSStatus            err         = noErr;
    OSType              selector    = 0;
    Handle              iconHandle,
                        compressionAvailable,
                        sampleRateAvailable,
                        sampleSizeAvailable;
    SoundInfoList       info;
 
#if FULLDEBUG
    SysDebugStr ("\pin DriverStatusCmd;g");
#endif
 
    if (pb->csCode == 2) {
        selector = ((OSType*)pb->csParam)[0];
    }
 
    switch (selector) {
        //Status calls that are required to be supported
        case siAsync:
            //Determine whether the driver supports asynchronous recording functions. The infoData parameter
            //points to an integer, which is 0 if the driver supports synchronous calls only and 1 otherwise.
            //Some sound input drivers do not support asynchronous recording at all, and some might support
            //asynchronous recording only on certain hardware configurations.
        #if DEBUG2
            SysDebugStr ("\pstatus: siAsync;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = 1;  //we can do async recording
            break;
        case siChannelAvailable:
            //Get the maximum number of channels this device can record. The infoData parameter points to an
            //integer, which is the number of available channels.
        #if DEBUG2
            SysDebugStr ("\pstatus: siChannelAvailable;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = 2;  //we can do stereo (note: Sound Manager doesn't currently work with more than 2 channels)
            break;
        case siCompressionAvailable:
            //Get the number and list of compression types this device can produce. The infoData parameter
            //points to an integer, which is the number of compression types, followed by a handle. The
            //handle references a list of compression types, each of type OSType.
        #if DEBUG2
            SysDebugStr ("\pstatus: siCompressionAvailable;g");
        #endif
            compressionAvailable = NewHandle (sizeof (OSType) * kNumCompressions);
            if (compressionAvailable != nil) {
                ((OSType*)*compressionAvailable)[0] = 'NONE';
                info.count = kNumCompressions;
                info.infoHandle = compressionAvailable;
                ((SInt32*)pb->csParam)[0] = sizeof (SoundInfoList);     //number of bytes being returned
                *(SoundInfoList*)(pb->csParam+2) = info;
            }
            break;
        case siCompressionFactor:
            //Get the compression factor of the current compression type. For example, the compression factor
            //for MACE 3:1 compression is 3. If a sound input device driver supports only compression type 'NONE',
            //the returned compression type is 1. The infoData parameter points to an integer, which is the
            //compression factor.
        #if DEBUG2
            SysDebugStr ("\pstatus: siCompressionFactor;g");
        #endif
            if (gCompressionType == 'NONE') {
                ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
                ((SInt16*)pb->csParam)[2] = 1;  //no compression equals 1:1
            }
            break;
        case siCompressionType:
            //Get the compression type. Some devices allow the incoming samples to be compressed before being
            //placed in your applicationÕs input buffer. The infoData parameter points to a buffer of type
            //OSType, which is the compression type.
        #if DEBUG2
            SysDebugStr ("\pstatus: siCompressionType;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 4;  //number of bytes being returned
            ((OSType*)pb->csParam)[1] = gCompressionType;
            break;
        case siContinuous:
            //Get the state of continuous recording from this device. If recording is being turned off, the
            //driver stops recording samples to its internal buffer. Only sound input device drivers that
            //support asynchronous recording support continuous recording. The infoData parameter points to an
            //integer, which is the state of continuous recording (0 is off, 1 is on).
        #if DEBUG2
            SysDebugStr ("\pstatus: siContinuous;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = gContinuousOn;
            break;
        case siDeviceBufferInfo:
            //Get the size of the deviceÕs internal buffer. This information can be useful when you want to
            //modify sound input data at interrupt time. Note, however, that if a driver is recording continuously,
            //then the size of the buffer passed to your sound input interrupt routine might be greater than the
            //size this selector returns because data recorded between calls to SPBRecord as well as recorded
            //during calls to SPBRecord will be sent to your interrupt routine. The infoData parameter points to
            //a long integer, which is the size of the deviceÕs internal buffer.
        #if DEBUG2
            SysDebugStr ("\pstatus: siDeviceBufferInfo;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 4;  //number of bytes being returned
            ((SInt32*)pb->csParam)[1] = kSamplesInBuffer * gNumberChannels * (gSampleSize / 8);
            break;
        case siDeviceConnected:
            //Get the state of the device connection. The infoData parameter points to an integer, which is one
            //of the following constants:
            //          siDeviceIsConnected                 = 1;
            //          siDeviceNotConnected                = 0;
            //          siDontKnowIfConnected               = -1;
            //The siDeviceIsConnected constant indicates that the device is connected and ready. The
            //siDeviceNotConnected constant indicates that the device is not connected. The siDontKnowIfConnected
            //constant indicates that the Sound Input Manager cannot determine whether the device is connected.
        #if DEBUG2
            SysDebugStr ("\pstatus: siDeviceConnected;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = siDeviceIsConnected;
            break;
        case siDeviceIcon:
            //Get the deviceÕs icon and icon mask. In response to a Status call, a sound input device driver
            //should return, in the location specified by the infoData parameter, a handle to a block of memory
            //that contains the icon and its mask in the format of an 'ICN#' resource. It is the driverÕs
            //responsibility to allocate that block of memory, but it should not releasee it. The software issuing
            //this selector is responsible for disposing of the handle. As a result, a device driver should detach
            //any resource handles (by calling DetachResource) before returning them to the caller.
        #if DEBUG2
            SysDebugStr ("\pstatus: siDeviceIcon;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 4;  //number of bytes being returned
            iconHandle = gDrvrIcon;
            err = HandToHand (&iconHandle);
            if (err != noErr) {
                SysDebugStr ("\pHandToHand returned an error");
            }
            ((Handle*)pb->csParam)[1] = iconHandle;
            break;
        case siDeviceName:
            //Get the name of the sound input device. Your application must pass a pointer to a buffer that will
            //be filled in with the deviceÕs name. The buffer needs to be large enough to hold a Str255 data type.
        #if DEBUG2
            SysDebugStr ("\pstatus: siDeviceName;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 0;  //Sound Manager doesn't have to copy any data
            BlockMoveData (gDriverName[0], ((Handle*)pb->csParam)[1], *gDriverName[0]+1);
            break;
        case siLevelMeterOnOff:
            //Get the current state of the level meter. To get the level meter setting, the infoData parameter
            //points to two integers; the first integer indicates the state of the level meter, and the second
            //integer contains the level value of the meter. The level meter setting is an integer that ranges
            //from 0 (no volume) to 255 (full volume). 
        #if DEBUG2
            SysDebugStr ("\pstatus: siLevelMeterOnOff;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 4;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = gLevelMeterOn;
            ((SInt16*)pb->csParam)[3] = 0;
            break;
        case siNumberChannels:
            //Get the number of channels this device is to record. The infoData parameter points to an integer,
            //which indicates the number of channels. Note that this selector determines the format of the data
            //stream output by the driver. If the number of channels is 1, the driver should output monophonic data
            //in response to a Read call. If the number of channels is 2, the driver should output interleaved
            //stereo data.
        #if DEBUG2
            SysDebugStr ("\pstatus: siNumberChannels;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = gNumberChannels;
            break;
        case siRecordingQuality:
            //Get the current quality of recorded sound. The infoData parameter points to a buffer of type
            //OSType, which is the recording quality. Currently four qualities are supported, defined by
            //these constants:
            //          siCDQuality                         = 'cd  ',               /*44.1kHz, stereo, 16 bit*/
            //          siBestQuality                       = 'best';               /*22kHz, mono, 8 bit*/
            //          siBetterQuality                     = 'betr';               /*22kHz, mono, MACE 3:1*/
            //          siGoodQuality                       = 'good';
            //These qualities are defined by the sound input device driver. Usually best means monaural,
            //8-bit, 22 kHz, sound with no compression.
        #if DEBUG2
            SysDebugStr ("\pstatus: siRecordingQuality;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 4;  //number of bytes being returned
            ((OSType*)pb->csParam)[1] = gRecordingQuality;
            break;
        case siSampleRate:
            //Get the sample rate to be produced by this device. The sample rate must be in the range 0 to
            //65535.65535 Hz. The sample rate is declared as a Fixed data type. In order to accommodate sample
            //rates greater than 32 kHz, the most significant bit is not treated as a sign bit; instead, that
            //bit is interpreted as having the value 32,768. The infoData parameter points to a buffer of type
            //Fixed, which is the sample rate.
        #if DEBUG2
            SysDebugStr ("\pstatus: siSampleRate;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 4;  //number of bytes being returned
            ((UnsignedFixed*)pb->csParam)[1] = gSampleRate;
            break;
        case siSampleRateAvailable:
            //Get the range of sample rates this device can produce. The infoData parameter points to an integer,
            //which is the number of sample rates the device supports, followed by a handle. The handle references
            //a list of sample rates, each of type Fixed. If the device can record a range of sample rates, the
            //number of sample rates is set to 0 and the handle contains two rates, the minimum and the maximum
            //of the range of sample rates. Otherwise, a list is returned that contains the sample rates supported.
            //In order to accommodate sample rates greater than 32 kHz, the most significant bit is not treated as
            //a sign bit; instead, that bit is interpreted as having the value 32,768.
        #if DEBUG2
            SysDebugStr ("\pstatus: siSampleRateAvailable;g");
        #endif
            sampleRateAvailable = NewHandle (sizeof (UnsignedFixed) * kNumRates);
            if (sampleRateAvailable != nil) {
                ((UnsignedFixed*)*sampleRateAvailable)[0] = rate44khz;
                ((UnsignedFixed*)*sampleRateAvailable)[1] = rate22050hz;
                ((UnsignedFixed*)*sampleRateAvailable)[2] = rate11025hz;
                info.count = kNumRates;
                info.infoHandle = sampleRateAvailable;
                ((SInt32*)pb->csParam)[0] = sizeof (SoundInfoList);     //number of bytes being returned
                *(SoundInfoList*)(pb->csParam+2) = info;
            }
            break;
        case siSampleSize:
            //Get the sample size to be produced by this device. Because some compression formats require specific
            //sample sizes, this selector might return an error when compression is used. The infoData parameter
            //points to an integer, which is the sample size.
        #if DEBUG2
            SysDebugStr ("\pstatus: siSampleSize;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = gSampleSize;
            break;
        case siSampleSizeAvailable:
            //Get the range of sample sizes this device can produce. The infoData parameter points to an integer,
            //which is the number of sample sizes the device supports, followed by a handle. The handle references
            //a list of sample sizes, each of type Integer.
        #if DEBUG2
            SysDebugStr ("\pstatus: siSampleSizeAvailable;g");
        #endif
            sampleSizeAvailable = NewHandle (sizeof (SInt16) * kNumSizes);
            if (sampleSizeAvailable != nil) {
                ((SInt16*)*sampleSizeAvailable)[0] = 8;
                ((SInt16*)*sampleSizeAvailable)[1] = 16;
                info.count = kNumSizes;
                info.infoHandle = sampleSizeAvailable;
                ((SInt32*)pb->csParam)[0] = sizeof (SoundInfoList);     //number of bytes being returned
                *(SoundInfoList*)(pb->csParam+2) = info;
            }
            break;
        case siTwosComplementOnOff:
            //Get the current state of the twoÕs complement feature. This selector only applies to 8-bit data.
            //(16-bit samples are always stored in twoÕs complement format.) If on, the driver stores all samples
            //in the application buffer as twoÕs complement values (that is, Ð128 to 127). Otherwise, the driver
            //stores the samples as offset binary values (that is, 0 to 255). The infoData parameter points to an
            //integer, which is the current state of the twoÕs complement feature (1 if twoÕs complement output
            //is desired, 0 otherwise).
        #if DEBUG2
            SysDebugStr ("\pstatus: siTwosComplementOnOff;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = gTwosComplementOn;
            break;
 
        //Sound Manager only call
        case siPauseRecording:
            //The Sound Input Manager uses this selector to get the current pause state.
            //The sound input device driver continues recording but does not store the sampled
            //data in a buffer. The infoData parameter points to an integer, which indicates
            //the state of pausing (0 is off, 1 is on).
        #if DEBUG2
            SysDebugStr ("\pcontrol: siPauseRecording;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = gPauseState;
            break;
 
        //Status calls that can be optionally supported
        case siActiveChannels:
            //Get the channels to record from. When reading the active channels, the data returned is
            //a bitmap of the active channels.
        #if DEBUG2
            SysDebugStr ("\pstatus: siActiveChannels;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 4;  //number of bytes being returned
            ((SInt32*)pb->csParam)[1] = gActiveChannels;
            break;
        case siActiveLevels:
            //Get the current signal level for each active channel. The infoData parameter points to an array
            //of integers, the size of which depends on the number of active channels. You can determine how
            //many channels are active by calling SPBGetDeviceInfo with the siNumberChannels selector.
        #if DEBUG2
            SysDebugStr ("\pstatus: siActiveLevels;g");
        #endif
            if (gNumberChannels == 1) {
                ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
                ((SInt16*)pb->csParam)[2] = 0;
            } else if (gNumberChannels == 2) {
                ((SInt32*)pb->csParam)[0] = 4;  //number of bytes being returned
                ((SInt16*)pb->csParam)[2] = 0;
                ((SInt16*)pb->csParam)[3] = 0;
            }
            break;
        case siAGCOnOff:
            //Get the current state of the automatic gain control feature. The infoData parameter points to an
            //integer, which is 0 if gain control is off and 1 if it is on.
        #if DEBUG2
            SysDebugStr ("\pstatus: siAGCOnOff;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = gAGCOn;
            break;
        case siCompressionHeader:
            //Get a compressed sound header for the current recording settings. Your application passes in a
            //pointer to a compressed sound header and the driver fills it in. Before calling SPBGetDeviceInfo
            //with this selector, you should set the numFrames field of the compressed sound header to the number
            //of bytes in the sound. When SPBGetDeviceInfo returns successfully, that field contains the number
            //of sample frames in the sound. This selector is needed only by drivers that use compression types
            //that are not directly supported by Apple. If you call this selector after recording a sound, your
            //application can get enough information about the sound to play it or save it in a file. The infoData
            //parameter points to a compressed sound header.
        #if DEBUG2
            SysDebugStr ("\pstatus: siCompressionHeader;g");
        #endif
            err = siUnknownInfoType;    //we don't support compression
            break;
        case siCompressionNames:
            //Get a list of names of the compression types supported by the sound input device. In response to a
            //Status call, a sound input device driver returns, in the location specified by the infoData parameter,
            //a handle to a block of memory that contains the names of all compression types supported by the driver.
            //It is the driverÕs responsibility to allocate that block of memory, but it should not release it. The
            //software issuing this selector is responsible for disposing of the handle. As a result, a device driver
            //must detach any resource handles (by calling DetachResource) before returning them to the caller. The
            //data in the handle has the same format as an 'STR#' resource: a two-byte count of the strings in the
            //resource, followed by the strings themselves. The strings should occur in the same order as the
            //compression types returned by the siCompressionAvailable selector. If the driver does not support
            //compression, it returns siUnknownInfoType. If the driver supports compression but for some reason not
            //all compression types are currently selectable, it returns a list of all available compression types.
        #if DEBUG2
            SysDebugStr ("\pstatus: siCompressionNames;g");
        #endif
            err = siUnknownInfoType;    //we don't support compression
            break;
        case siInputGain:
            //Get the current sound input gain. If the available hardware allows adjustment of the recording gain,
            //this selector lets you get and set the gain. In response to a Status call, a sound input driver returns
            //the current gain setting. The infoData parameter points to a 4-byte value of type Fixed ranging from
            //0.5 to 1.5, where 1.5 specifies maximum gain.
        #if DEBUG2
            SysDebugStr ("\pstatus: siInputGain;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 4;  //number of bytes being returned
            ((Fixed*)pb->csParam)[1] = gInputGain;
            break;
        case siInputSource:
            //Get the current sound input source. If the available hardware allows recording from more than one
            //source, this selector lets you get and set the source. In response to a Status call, a sound input
            //driver returns the current source value; if the driver supports only one source, it returns
            //siUnknownInfoType.
        #if DEBUG2
            SysDebugStr ("\pstatus: siInputSource;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = gCurrentInputSource;
            break;
        case siInputSourceNames:
            //Get a list of the names of all the sound input sources supported by the sound input device. In
            //response to a Status call, a sound input device driver returns, in the location specified by the
            //infoData parameter, a handle to a block of memory that contains the names of all sound sources
            //supported by the driver. It is the driverÕs responsibility to allocate that block of memory, but it
            //should not release it. The software issuing this selector is responsible for disposing of the handle.
            //As a result, a device driver must detach any resource handles (by calling DetachResource) before
            //returning them to the caller. The data in the handle has the same format as an 'STR#' resource: a
            //two-byte count of the strings in the resource, followed by the strings themselves. The strings should
            //occur in the same order as the input sources returned by the siInputSource selector. If the driver
            //supports only one source, it returns siUnknownInfoType. If the driver supports more than one source
            //but for some reason not all of them are currently selectable, it returns a list of all available
            //input sources.
        #if DEBUG2
            SysDebugStr ("\pstatus: siInputSourceNames;g");
        #endif
 
            {
                Handle h;
                OSErr   err;
                
 
                h = gInputSourceNames;              // handle we want to copy
                err = HandToHand( &h );             // copy it
                
                if( !err )
                {
                    ((SInt32*)pb->csParam)[0] = 4;  //number of bytes being returned
                    ((Handle*)pb->csParam)[1] = h;  //handle to data
                }
                else
                ((SInt32*)pb->csParam)[0] = 0;  
            }
 
            break;
        case siOptionsDialog:
            //Determine whether the driver supports an Options dialog box (SPBGetDeviceInfo). This dialog box is
            //designed to allow the user to configure device-specific features of the sound input hardware. With
            //SPBGetDeviceInfo, the infoData parameter points to an integer, which indicates whether the driver
            //supports an Options dialog box (1 if it supports it, 0 otherwise).
        #if DEBUG2
            SysDebugStr ("\pstatus: siOptionsDialog;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = 1;  //we have a dialog
            break;
        case siPlayThruOnOff:
            //Get the current play-through state and volume. The infoData parameter points to an integer,
            //which indicates the current play-through volume (1 to 7). If that integer is 0, then
            //play-through is off.
        #if DEBUG2
            SysDebugStr ("\pstatus: siPlayThruOnOff;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 2;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = gPlayThruVolume;
            break;
        case siStereoInputGain:
            //Get the current stereo sound input gain. If the available hardware allows adjustment of the recording
            //gain, this selector lets you get and set the gain for each of two channels (left or right). In response
            //to a Status call, a sound input driver should return the current gain setting for the specified channel.
            //The infoData parameter points to two 4-byte values of type Fixed ranging from 0.5 to 1.5, where 1.5
            //specifies maximum gain. The first of these values is equivalent to the gain for the left channel and the
            //second value is equivalent to the gain for the right channel.
        #if DEBUG2
            SysDebugStr ("\pstatus: siStereoInputGain;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 8;  //number of bytes being returned
            ((Fixed*)pb->csParam)[1] = gLeftInputGain;
            ((Fixed*)pb->csParam)[2] = gRightInputGain;
            break;
        case siVoxRecordInfo:
            //Get the current VOX recording parameters. The infoData parameter points to two integers. The first
            //integer indicates whether VOX recording is on or off (0 if off, 1 if on). The second integer indicates
            //the VOX record trigger value. Trigger values range from 0 to 255 (0 is trigger immediately, 255 is
            //trigger only on full volume).
        #if DEBUG2
            SysDebugStr ("\pstatus: siVoxRecordInfo;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 4;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = gVoxRecordingOn;
            ((SInt16*)pb->csParam)[3] = gVOXStartTrigger;
            break;
        case siVoxStopInfo:
            //Get the current VOX stopping parameters. The infoData parameter points to three integers. The
            //first integer indicates whether VOX stopping is on or off (0 if off, 1 if on). The second integer
            //indicates the VOX stop trigger value. Trigger values range from 0 to 255 (255 is stop immediately, 0
            //is stop only on total silence). The third integer indicates how many milliseconds the trigger value must
            //be continuously valid for recording to be stopped. Delay values range from 0 to 65,535.
        #if DEBUG2
            SysDebugStr ("\pstatus: siVoxStopInfo;g");
        #endif
            ((SInt32*)pb->csParam)[0] = 6;  //number of bytes being returned
            ((SInt16*)pb->csParam)[2] = gVoxStoppingOn;
            ((SInt16*)pb->csParam)[3] = gVOXStopTrigger;
            ((SInt16*)pb->csParam)[4] = gVOXDelay;
            break;
 
        //Unknown or unsupported status call
        default:
        #if DEBUG
            SysDebugStr ("\pstatus: unknown selector;g");
        #endif
            err = statusErr;
    }
 
#if DEBUG
    if (err != noErr && err != statusErr && err != siUnknownInfoType) {
        SysDebugStr ("\p!!Error in DriverStatusCmd!!");
    }
#if FULLDEBUG
    SysDebugStr ("\pleaving DriverStatusCmd;g");
#endif
#endif
 
    return (err);
}
 
/*
    May run at interrupt level, CANNOT allocate or move memory.
*/
static OSStatus DriverReadCmd (AddressSpaceID addressSpaceID, IOCommandID ioCommandID, IOCommandKind ioCommandKind, ParmBlkPtr pb) {
#pragma unused (addressSpaceID, ioCommandID, ioCommandKind)
    OSStatus            err = noErr;
    double          samplesInBuffer = kSamplesInBuffer,temp, temp2;
 
#if DEBUG
    SysDebugStr ("\pin DriverReadCmd;g");
#endif
 
    gSimulatedHWInterrupt.theTask.tmAddr = gSimulatedHWIntProc;
    gSimulatedHWInterrupt.theTask.tmWakeUp = 0;
    gSimulatedHWInterrupt.pb = pb;
 
    temp = ((gSampleRate >> 16) / samplesInBuffer);
 
    temp2 = -(1000000.0 / temp);    //calculate time in microseconds
    gInterruptFreq = temp2;
 
    InsXTime ((QElemPtr)&gSimulatedHWInterrupt);
    PrimeTime ((QElemPtr)&gSimulatedHWInterrupt, gInterruptFreq);   //wait for the time it takes to "record" one buffer
 
    err = ioInProgress;
 
#if DEBUG
    if (err != noErr && err != ioInProgress) {
        SysDebugStr ("\p!!Error in DriverReadCmd!!");
    }
#if FULLDEBUG
    SysDebugStr ("\pleaving DriverReadCmd;g");
#endif
#endif
 
    return (err);
}
 
/*
    May run at interrupt level, CANNOT allocate or move memory.
*/
static OSStatus DriverWriteCmd (AddressSpaceID addressSpaceID, IOCommandID ioCommandID, IOCommandKind ioCommandKind, ParmBlkPtr pb) {
#pragma unused (addressSpaceID, ioCommandID, ioCommandKind, pb)
    OSStatus            err;
 
#if DEBUG
    SysDebugStr ("\pin DriverWriteCmd;g");
#endif
 
    //Sound input drivers don't have a write command.
    err = paramErr;
 
#if DEBUG
    if (err != noErr) {
        SysDebugStr ("\p!!Error in DriverWriteCmd!!");
    }
#if FULLDEBUG
    SysDebugStr ("\pleaving DriverWriteCmd;g");
#endif
#endif
 
    return (err);
}
 
/*
    May run at interrupt level, CANNOT allocate or move memory.
*/
static OSStatus DriverKillIOCmd (ParmBlkPtr pb) {
#pragma unused (pb)
    OSStatus            err = noErr;
 
#if DEBUG
    SysDebugStr ("\pin DriverKillIOCmd");
#endif
 
    gStopRecording = true;  //when our next hardware "interrupt" fires we will stop the "hardware"
    if (gSimulatedHWIntProc != nil) {
        RmvTime ((QElemPtr)&gSimulatedHWInterrupt);
    }
 
#if DEBUG
    if (err != noErr) {
        SysDebugStr ("\p!!Error in DriverKillIOCmd!!");
    }
#if FULLDEBUG
    SysDebugStr ("\pleaving DriverKillIOCmd;g");
#endif
#endif
 
    return (err);
}
 
/*
    May run at interrupt level, CANNOT allocate or move memory.
*/
extern OSErr DoDriverIO(AddressSpaceID addressSpaceID, IOCommandID ioCommandID, IOCommandContents ioCommandContents, IOCommandCode ioCommandCode, IOCommandKind ioCommandKind) {
    OSStatus        status;
    OSErr           err     = noErr;
 
#if FULLDEBUG
    SysDebugStr ("\pin DoDriverIO;g");
#endif
 
    /*
        Note: Initialize, Open, KillIO, Close, and Finalize are either synchronous
        or immediate. Read, Write, Control, and Status may be immediate,
        synchronous, or asynchronous.
    */
    switch (ioCommandCode) {
        case kInitializeCommand:        /* Always immediate */
            status = DriverInitializeCmd (addressSpaceID, ioCommandContents.initialInfo);
            break;
        case kFinalizeCommand:          /* Always immediate */
            status = DriverFinalizeCmd (ioCommandContents.finalInfo);
            break;
        case kSupersededCommand:        /* Always immediate */
            status = DriverSupersededCmd (ioCommandContents.supersededInfo);
            break;
        case kReplaceCommand:           /* Always immediate, replace an old driver */
            status = DriverReplaceCmd (addressSpaceID, ioCommandContents.replaceInfo);
            break;
        case kOpenCommand:              /* Always immediate */
            status = DriverOpenCmd (addressSpaceID, ioCommandContents.pb);
            break;
        case kCloseCommand:             /* Always immediate */
            status = DriverCloseCmd (ioCommandContents.pb);
            break;
        case kControlCommand:
            status = DriverControlCmd (addressSpaceID, ioCommandID, ioCommandKind, (SoundParamPtr) ioCommandContents.pb);
            break;
        case kStatusCommand:
            status = DriverStatusCmd (addressSpaceID, ioCommandID, ioCommandKind, (SoundParamPtr) ioCommandContents.pb);
            break;
        case kReadCommand:
            status = DriverReadCmd (addressSpaceID, ioCommandID, ioCommandKind, ioCommandContents.pb);
            break;
        case kWriteCommand:
            status = DriverWriteCmd (addressSpaceID, ioCommandID, ioCommandKind, ioCommandContents.pb);
            break;
        case kKillIOCommand:            /* Always immediate */
            status = DriverKillIOCmd (ioCommandContents.pb);
            break;
        default:
            status = paramErr;
            break;
    }
    if ((ioCommandKind & kImmediateIOCommandKind) != 0) {
        /* Immediate commands return the operation status and don't call IOCommandIsComplete */
    } else if (status == ioInProgress) {
        /* Perform the action and call IOCommandIsComplete at some later time */
        status = noErr;
    } else {
        err = status;
        status = IOCommandIsComplete (ioCommandID, err);
    }
 
#if DEBUG
    if (err == paramErr) {
        SysDebugStr ("\p!!Param Error in DoDriverIO!!");
    }
#if FULLDEBUG
    SysDebugStr ("\pleaving DoDriverIO;g");
#endif
#endif
 
    return (status);
}
 
static pascal void HWIntProc (QElemPtr passedPtr) {
    UInt32                  numSamples,
                            numBytesLeftToRecord;
    Boolean                 recordingComplete   = false;
    ParmBlkPtr              pb = ((myTMTask*)passedPtr)->pb;
    OSErr                   err;
 
#if DEBUG
    SysDebugStr ("\pin HWIntProc");
#endif
 
    err = noErr;
 
    if (gStopRecording == false) {
    
        if (gContinuousOn == true) {    // If continuous recording is on...
        
            if (pb->ioParam.ioBuffer != nil) {  // ...and we are given a buffer...
 
                // ...if the buffer passed in is larger than the number of byte we record each hardware interrupt...
                if (pb->ioParam.ioReqCount >= kSamplesInBuffer * (gSampleSize / 8) * gNumberChannels) {
                    // ...calculate the number of bytes left to record.
                    numBytesLeftToRecord = pb->ioParam.ioReqCount - pb->ioParam.ioActCount;
                }
                
                else {
                    // Return an error since the continuous ring buffer will loop if you don't ask for at
                    // least a hardware size buffer for each record.
                    err = paramErr;
                }
            } 
            
            else {
                // If they ask us to record into no buffer, they really just want a recording that goes on
                // infinitely until they call SPBStopRecording (which comes to us as killIO).
                // So setup to record a full hardware buffer
                numSamples = kSamplesInBuffer;
                numBytesLeftToRecord = kSamplesInBuffer * (gSampleSize / 8) * gNumberChannels;
            }
        } 
        
        else {  // If continuous recording is not on...
        
            if (pb->ioParam.ioBuffer != nil) {  // ...and we are given a buffer
                // ...calculate number of bytes to record. 
                numBytesLeftToRecord = pb->ioParam.ioReqCount - pb->ioParam.ioActCount;
            } 
            
            else {  // ...and we are NOT given a buffer, that's an error
                err = paramErr;
            }
        }
 
        // If the number of bytes requested is larger than our buffer, return the max number of samples that will fit in our buffer
        if (numBytesLeftToRecord > (kSamplesInBuffer * gNumberChannels * (gSampleSize / 8))) {
            numSamples = kSamplesInBuffer;
        } else {
            // otherwise figure out how many samples the requested number of bytes equals
            numSamples = numBytesLeftToRecord / (gNumberChannels * (gSampleSize / 8));
        }
 
        switch (gCurrentInputSource) {
            case 1:
                // Make a square wave
                MakeSquareWave (0, numSamples, gSampleRate, 1000, gSampleSize, gNumberChannels);
                break;
            case 2:
                // Make a saw tooth wave
                MakeSawWave (0, numSamples, gSampleRate, 1000, gSampleSize, gNumberChannels);
                break;
            case 3:
                // Make silence
                MakeSquareWave (0, numSamples, gSampleRate, 0, gSampleSize, gNumberChannels);
                break;
            default:
            #if DEBUG
                SysDebugStr ("\punknown input source in HWIntProc");
            #endif
                break;  // null statement so when DEBUG is false this switch still compiles
        }
 
        if (gTwosComplementOn == true && gSampleSize == 8) {
            SInt32 i;
            for (i = 0; i < numSamples; i++) {
                ((UInt32*)gSoundBuffer)[i] ^= 0x80808080;
            }
        }
        
        // If we have a client interrupt proc and we're not paused, call the proc
        if (gUserInterruptProc != nil && gPauseState == 0) {
            CallSIInterruptProc (gUserInterruptProc, pb->ioParam.ioMisc, gSoundBuffer, gMaxAmplitude, numSamples*(gSampleSize/8)*gNumberChannels);
        }
 
        //if we have a buffer and pausing is off, copy data to app's buffer
        if (pb->ioParam.ioBuffer != nil && gPauseState == 0) {
            BlockMoveData (gSoundBuffer, pb->ioParam.ioBuffer+pb->ioParam.ioActCount, numSamples*(gSampleSize/8)*gNumberChannels);
        }
 
        //If we are not paused, update the number of samples we have made
        if (gPauseState == 0) {
            pb->ioParam.ioActCount += numSamples*(gSampleSize/8)*gNumberChannels;
        }
 
        // If we have a buffer and actual bytes == requested bytes, recording is complete
        if (pb->ioParam.ioBuffer != nil && pb->ioParam.ioActCount == pb->ioParam.ioReqCount) {
            recordingComplete = true;
        }
    }
    
    // if we got an error, stop recording
    if (err != noErr) {
        gStopRecording = true;
    }
 
    if (recordingComplete == false && gStopRecording == false) {
        PrimeTime ((QElemPtr)passedPtr, gInterruptFreq);    // more data to record
    } else {
        // Have recorded requested number of bytes, stop the hardware (if continuous recording is not on)
        // Do a software interrupt so that we can call IOCommandIsComplete (which can only be called at
        // task level and software interrupt level.
        RmvTime ((QElemPtr)passedPtr);
        if (gStopRecording == true) {
            SetHardwareToDefault ();
        } else if (gContinuousOn == false) {
            gSamplesWritten = 0;
        }
        IOCommandIsComplete ((IOCommandID)pb->ioParam.ioCmdAddr, err);
    }
 
#if FULLDEBUG
    SysDebugStr ("\pleaving HWIntProc;g");
#endif
}
 
//Makes a square wave of the specified frequency and duration
//duration in 1/100 of a second, i.e. 500 for a five second tone
//frequency in samples per second, i.e. 8000 for an 8000Hz square wave (requires 16kHz sampling rate)
static void MakeSquareWave (SInt32 duration, SInt32 numberSamples, UnsignedFixed sampleRate, SInt32 frequency, SInt16 sampleSize, SInt16 numChannels) {
    SInt32          numSamples  = 0;
 
    if (duration != 0 && numberSamples == 0) {
        numSamples = (duration / 100.0) * (sampleRate >> 16);
    } else if (duration == 0 && numberSamples != 0) {
        numSamples = numberSamples;
    }
 
    if (numSamples > 0) {
        if (frequency == 0) {
            gMaxAmplitude = 0;
            //Generate silence of requested length
            if (sampleSize == 8) {
                UInt8 *buffer8 = (UInt8*)gSoundBuffer;
                UInt8 *end8 = buffer8 + (numSamples * numChannels);
                while (buffer8 < end8) {
                    *buffer8++ = kSilence;
                }
            } else if (sampleSize == 16) {
                UInt16 *buffer16 = (UInt16*)gSoundBuffer;
                UInt16 *end16 = buffer16 + (numSamples * numChannels);
                while (buffer16 < end16) {
                    *buffer16++ = 0;
                }
            } else {
            #if DEBUG
                SysDebugStr ("\pbad sample size in MakeSquareWave (silence)");
            #endif
            }
        } else if ((frequency * 2) <= sampleRate) {
            //Generate a square wave
            SInt32  samplesBeforeReverse =  (((sampleRate >> 16) / frequency) / 2) * numChannels;
 
            if (sampleSize == 8) {
                UInt8 *buffer8 = (UInt8*)gSoundBuffer;
                UInt8 *end8 = buffer8 + (numSamples * numChannels);
                gMaxAmplitude = kSilence + kAmplitude;
                while (buffer8 < end8) {
                    if (gSamplesWritten < samplesBeforeReverse) {
                        *buffer8++ = kSilence + kAmplitude;
                        gSamplesWritten++;
                    } else {
                        *buffer8++ = kSilence - kAmplitude;
                        if (gSamplesWritten == (samplesBeforeReverse * 2 - 1)) {
                            gSamplesWritten = 0;
                        } else {
                            gSamplesWritten++;
                        }
                    }
                }
            } else if (sampleSize == 16) {
                UInt16 *buffer16 = (UInt16*)gSoundBuffer;
                UInt16 *end16 = buffer16 + (numSamples * numChannels);
                gMaxAmplitude = (kAmplitude * 0.01) * 32767;
                while (buffer16 < end16) {
                    if (gSamplesWritten < samplesBeforeReverse) {
                        *buffer16++ = (kAmplitude * 0.01) * 32767;
                        gSamplesWritten++;
                    } else {
                        *buffer16++ = -((kAmplitude * 0.01) * 32767);
                        if (gSamplesWritten == (samplesBeforeReverse * 2 - 1)) {
                            gSamplesWritten = 0;
                        } else {
                            gSamplesWritten++;
                        }
                    }
                }
            } else {
            #if DEBUG
                SysDebugStr ("\pbad sample size in MakeSquareWave (square wave)");
            #endif
            }
        }
    }
}
 
static void MakeSawWave (SInt32 duration, SInt32 numberSamples, UnsignedFixed sampleRate, SInt32 frequency, SInt16 sampleSize, SInt16 numChannels) {
    SInt32          numSamples  = 0;
 
    if (duration != 0 && numberSamples == 0) {
        numSamples = (duration / 100.0) * (sampleRate >> 16);
    } else if (duration == 0 && numberSamples != 0) {
        numSamples = numberSamples;
    }
 
    if (numSamples > 0) {
        if ((frequency * 2) <= sampleRate) {
            //Generate a saw tooth wave
            SInt32  samplesBeforeReverse =  (((sampleRate >> 16) / frequency) / 2);
 
            if (sampleSize == 8) {
                UInt8 *buffer8 = (UInt8*)gSoundBuffer;
                UInt8 *end8 = buffer8 + (numSamples * numChannels);
                UInt8 increment = (kAmplitude*2) / samplesBeforeReverse;
                gMaxAmplitude = kSilence + kAmplitude;
                while (buffer8 < end8) {
                    if (gSamplesWritten < samplesBeforeReverse) {
                        *buffer8++ = gNextSaw8Sample;
                        if (numChannels == 2) {
                            *buffer8++ = gNextSaw8Sample;
                        }
                        gNextSaw8Sample += increment;
                        gSamplesWritten++;
                    } else {
                        *buffer8++ = gNextSaw8Sample;
                        if (numChannels == 2) {
                            *buffer8++ = gNextSaw8Sample;
                        }
                        gNextSaw8Sample -= increment;
                        if (gSamplesWritten == (samplesBeforeReverse * 2 - 1)) {
                            gSamplesWritten = 0;
                        } else {
                            gSamplesWritten++;
                        }
                    }
                }
            } else if (sampleSize == 16) {
                UInt16 *buffer16 = (UInt16*)gSoundBuffer;
                UInt16 *end16 = buffer16 + (numSamples * numChannels);
                UInt16 increment = (((kAmplitude * 0.01) * 32767)*2) / samplesBeforeReverse;
                gMaxAmplitude = (kAmplitude * 0.01) * 32767;
                while (buffer16 < end16) {
                    if (gSamplesWritten < samplesBeforeReverse) {
                        *buffer16++ = gNextSaw16Sample;
                        if (numChannels == 2) {
                            *buffer16++ = gNextSaw16Sample;
                        }
                        gNextSaw16Sample += increment;
                        gSamplesWritten++;
                    } else {
                        *buffer16++ = gNextSaw16Sample;
                        if (numChannels == 2) {
                            *buffer16++ = gNextSaw16Sample;
                        }
                        gNextSaw16Sample -= increment;
                        if (gSamplesWritten == (samplesBeforeReverse * 2 - 1)) {
                            gSamplesWritten = 0;
                        } else {
                            gSamplesWritten++;
                        }
                    }
                }
            } else {
            #if DEBUG
                SysDebugStr ("\pbad sample size in MakeSawWave");
            #endif
            }
        }
    }
}
 
static OSErr        DoOptionsDialog (void) {
    FSSpec              driverFSSpec;
    Rect                rect;
    ControlHandle       item        = nil;
    DialogRef           optionsDialog;
    OSErr               err;
    SInt16              itemHit     = 0,
                        itemType,
                        curRes,
                        resRef      = -1,
                        newInputSource = gCurrentInputSource;
    Boolean             wasChanged,
                        done        = false;
 
    curRes = CurResFile ();
    err = ResolveAlias (nil, gAliasToDriver, &driverFSSpec, &wasChanged);
    if (err == noErr) {
        resRef = FSpOpenResFile (&driverFSSpec, fsRdPerm);
        if (resRef != -1) {
            optionsDialog = GetNewDialog (128, nil, (GrafPtr)-1L);
        } else {
        #if DEBUG
            SysDebugStr ("\pFSpOpenResFile failed");
        #endif
        }
    } else {
    #if DEBUG
        SysDebugStr ("\pResolveAlias failed");
    #endif
    }
 
    if (optionsDialog != nil) {
        //gCurrentInputSource+5 is the item number of the corresponding radio buttons
        //so itemHit-5 is the input source number
        GetDialogItem (optionsDialog, gCurrentInputSource+5, &itemType, &(Handle)item, &rect);
        if (item != nil) {
            SetControlValue (item, 1);
            SetDialogDefaultItem (optionsDialog, 1);    //put the ring around the OK button
        } else {
        #if DEBUG
            SysDebugStr ("\pitem is nil!");
        #endif
        }
 
        ShowWindow (optionsDialog);
 
        do {
            ModalDialog (nil, &itemHit);
            switch (itemHit) {
                case 1:     //OK button
                    gCurrentInputSource = newInputSource;
                    done = true;
                    break;
                case 2:     //Cancel button
                    done = true;
                    break;
                case 6:     //Square wave
                case 7:     //Saw wave
                case 8:     //Silence
                    newInputSource = itemHit-5;
                    GetDialogItem (optionsDialog, 6, &itemType, &(Handle)item, &rect);
                    SetControlValue (item, itemHit == 6);
                    GetDialogItem (optionsDialog, 7, &itemType, &(Handle)item, &rect);
                    SetControlValue (item, itemHit == 7);
                    GetDialogItem (optionsDialog, 8, &itemType, &(Handle)item, &rect);
                    SetControlValue (item, itemHit == 8);
                    break;
            }
        } while (!done);
 
        DisposeDialog (optionsDialog);
        optionsDialog = nil;
    } else {
    #if DEBUG
        SysDebugStr ("\poptionsDialog is nil!");
    #endif
    }
 
    if (resRef != -1) {
        CloseResFile (resRef);
        UseResFile (curRes);
    }
 
    return err;
}
 
static void SetHardwareToDefault (void) {
    gContinuousOn           = 0;
    gLevelMeterOn           = 0;
    gTwosComplementOn       = 0;
    gAGCOn                  = 0;
    gPlayThruVolume         = 0;
    gVoxRecordingOn         = 0;
    gVoxStoppingOn          = 0;
    gPauseState             = 0;
    gNumberChannels         = 1;
    gSampleSize             = 8;
    gVOXStartTrigger        = 0;
    gVOXStopTrigger         = 0;
    gVOXDelay               = 0;
    gCompressionFactor      = 1;
//  gCurrentInputSource     = 1;    //don't change the current input source
    gActiveChannels         = 0x00000001;
    gSampleRate             = rate22050hz;
    gInputGain              = 0x00010000;
    gLeftInputGain          = 0x01000100;
    gRightInputGain         = 0x01000100;
    gCompressionType        = 'NONE';
    gRecordingQuality       = siBestQuality;
    gUserInterruptProc      = nil;
    gSamplesWritten         = 0;
    gNextSaw8Sample         = kSilence - kAmplitude;
    gNextSaw16Sample        = -((kAmplitude * 0.01) * 32767);
    gStopRecording          = false;
}