/*
 * $Id$
 * Portable Audio I/O Library for Macintosh
 *
 * Based on the Open Source API proposed by Ross Bencina
 * Copyright (c) 1999-2000 Phil Burk
 *
 * Special thanks to Chris Rolfe for his many helpful suggestions, bug fixes,
 * and code contributions.
 * Thanks also to Tue Haste Andersen, Alberto Ricci, Nico Wald,
 * Roelf Toxopeus and Tom Erbe for testing the code and making
 * numerous suggestions.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * Any person wishing to distribute modifications to the Software is
 * requested to send the modifications to the original developer so that
 * they can be incorporated into the canonical version.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/* Modification History
   PLB20010415 - ScanInputDevices was setting sDefaultOutputDeviceID instead of sDefaultInputDeviceID
   PLB20010415 - Device Scan was crashing for anything other than siBadSoundInDevice, but some Macs may return other errors!
   PLB20010420 - Fix TIMEOUT in record mode.
   PLB20010420 - Change CARBON_COMPATIBLE to TARGET_API_MAC_CARBON
   PLB20010907 - Pass unused event to WaitNextEvent to prevent Mac OSX crash. Thanks Dominic Mazzoni.
   PLB20010908 - Use requested number of input channels. Thanks Dominic Mazzoni.
   PLB20011009 - Use NewSndCallBackUPP() for CARBON
   PLB20020417 - I used to call Pa_GetMinNumBuffers() which doesn't take into account the
                 variable minFramesPerHostBuffer. Now I call PaMac_GetMinNumBuffers() which will
                 give lower latency when virtual memory is turned off.
                 Thanks Kristoffer Jensen and Georgios Marentakis for spotting this bug.
   PLB20020423 - Use new method to calculate CPU load similar to other ports. Based on num frames calculated.
                 Fixed Pa_StreamTime(). Now estimates how many frames have played based on MicroSecond timer.
                 Added PA_MAX_USAGE_ALLOWED to prevent Mac form hanging when CPU load approaches 100%.
   PLB20020424 - Fixed return value in Pa_StreamTime
*/

/*
COMPATIBILITY
This Macintosh implementation is designed for use with Mac OS 7, 8 and
9 on PowerMacs, and OS X if compiled with CARBON
 
OUTPUT
A circular array of CmpSoundHeaders is used as a queue. For low latency situations
there will only be two small buffers used. For higher latency, more and larger buffers
may be used.
To play the sound we use SndDoCommand() with bufferCmd. Each buffer is followed
by a callbackCmd which informs us when the buffer has been processsed.
 
INPUT
The SndInput Manager SPBRecord call is used for sound input. If only
input is used, then the PA user callback is called from the Input completion proc.
For full-duplex, or output only operation, the PA callback is called from the
HostBuffer output completion proc. In that case, input sound is passed to the
callback by a simple FIFO.
 
TODO:
O- Add support for native sample data formats other than int16.
O- Review buffer sizing. Should it be based on result of siDeviceBufferInfo query?
O- Determine default devices somehow.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <math.h>

/* Mac specific includes */
#include "OSUtils.h"
#include <MacTypes.h>
#include <Math64.h>
#include <Errors.h>
#include <Sound.h>
#include <SoundInput.h>
#include <SoundComponents.h>
#include <Devices.h>
#include <DateTimeUtils.h>
#include <Timer.h>
#include <Gestalt.h>

#include "portaudio.h"
#include "pa_host.h"
#include "pa_trace.h"

#ifndef FALSE
 #define FALSE  (0)
 #define TRUE   (!FALSE)
#endif

/* #define TARGET_API_MAC_CARBON (1) */

/*
 * Define maximum CPU load that will be allowed. User callback will
 * be skipped if load exceeds this limit. This is to prevent the Mac
 * from hanging when the CPU is hogged by the sound thread.
 * On my PowerBook G3, the mac hung when I used 94% of CPU ( usage = 0.94 ).
 */
#define PA_MAX_USAGE_ALLOWED    (0.92)

/* Debugging output macros. */
#define PRINT(x) { printf x; fflush(stdout); }
#define ERR_RPT(x) PRINT(x)
#define DBUG(x)   /* PRINT(x) /**/
#define DBUGX(x)  /* PRINT(x) /**/

#define MAC_PHYSICAL_FRAMES_PER_BUFFER   (512)  /* Minimum number of stereo frames per SoundManager double buffer. */
#define MAC_VIRTUAL_FRAMES_PER_BUFFER   (4096) /* Need this many when using Virtual Memory for recording. */
#define PA_MIN_NUM_HOST_BUFFERS            (2)
#define PA_MAX_NUM_HOST_BUFFERS           (16)   /* Do not exceed!! */
#define PA_MAX_DEVICE_INFO                (32)

/* Conversions for 16.16 fixed point code. */
#define DoubleToUnsignedFixed(x) ((UnsignedFixed) ((x) * 65536.0))
#define UnsignedFixedToDouble(fx) (((double)(fx)) * (1.0/(1<<16)))

/************************************************************************************/
/****************** Structures ******************************************************/
/************************************************************************************/
/* Use for passing buffers from input callback to output callback for processing. */
typedef struct MultiBuffer
{
    char    *buffers[PA_MAX_NUM_HOST_BUFFERS];
    int      numBuffers;
    int      nextWrite;
    int      nextRead;
}
MultiBuffer;

/* Define structure to contain all Macintosh specific data. */
typedef struct PaHostSoundControl
{
    UInt64                  pahsc_EntryCount;
	double                  pahsc_InverseMicrosPerHostBuffer; /* 1/Microseconds of real-time audio per user buffer. */

    /* Use char instead of Boolean for atomic operation. */
    volatile char           pahsc_IsRecording;   /* Recording in progress. Set by foreground. Cleared by background. */
    volatile char           pahsc_StopRecording; /* Signal sent to background. */
    volatile char           pahsc_IfInsideCallback;
    /* Input */
    SPB                     pahsc_InputParams;
    SICompletionUPP         pahsc_InputCompletionProc;
    MultiBuffer             pahsc_InputMultiBuffer;
    int32                   pahsc_BytesPerInputHostBuffer;
    int32                   pahsc_InputRefNum;
    /* Output */
    CmpSoundHeader          pahsc_SoundHeaders[PA_MAX_NUM_HOST_BUFFERS];
    int32                   pahsc_BytesPerOutputHostBuffer;
    SndChannelPtr           pahsc_Channel;
    SndCallBackUPP          pahsc_OutputCompletionProc;
    int32                   pahsc_NumOutsQueued;
    int32                   pahsc_NumOutsPlayed;
    PaTimestamp             pahsc_NumFramesDone;
    UInt64                  pahsc_WhenFramesDoneIncremented;
    /* Init Time -------------- */
    int32                   pahsc_NumHostBuffers;
    int32                   pahsc_FramesPerHostBuffer;
    int32                   pahsc_UserBuffersPerHostBuffer;
    int32                   pahsc_MinFramesPerHostBuffer; /* Can vary depending on virtual memory usage. */
}
PaHostSoundControl;

/* Mac specific device information. */
typedef struct internalPortAudioDevice
{
    long                    pad_DeviceRefNum;
    long                    pad_DeviceBufferSize;
    Component               pad_Identifier;
    PaDeviceInfo            pad_Info;
}
internalPortAudioDevice;

/************************************************************************************/
/****************** Data ************************************************************/
/************************************************************************************/
static int                 sNumDevices = 0;
static internalPortAudioDevice sDevices[PA_MAX_DEVICE_INFO] = { 0 };
static int32               sPaHostError = 0;
static int                 sDefaultOutputDeviceID;
static int                 sDefaultInputDeviceID;

/************************************************************************************/
/****************** Prototypes ******************************************************/
/************************************************************************************/
static PaError PaMac_TimeSlice( internalPortAudioStream   *past,  int16 *macOutputBufPtr );
static PaError PaMac_CallUserLoop( internalPortAudioStream   *past, int16 *outPtr );
static PaError PaMac_RecordNext( internalPortAudioStream   *past );
static void    PaMac_StartLoadCalculation( internalPortAudioStream   *past );
static int     PaMac_GetMinNumBuffers( int minFramesPerHostBuffer, int framesPerBuffer, double sampleRate );
static double *PaMac_GetSampleRatesFromHandle ( int numRates, Handle h );
static PaError PaMac_ScanInputDevices( void );
static PaError PaMac_ScanOutputDevices( void );
static PaError PaMac_QueryOutputDeviceInfo( Component identifier, internalPortAudioDevice *ipad );
static PaError PaMac_QueryInputDeviceInfo( Str255 deviceName, internalPortAudioDevice *ipad );
static void    PaMac_InitSoundHeader( internalPortAudioStream   *past, CmpSoundHeader *sndHeader );
static void    PaMac_EndLoadCalculation( internalPortAudioStream   *past );
static void    PaMac_PlayNext ( internalPortAudioStream *past, int index );
static long    PaMac_FillNextOutputBuffer( internalPortAudioStream   *past, int index );
static pascal void PaMac_InputCompletionProc(SPBPtr recParams);
static pascal void PaMac_OutputCompletionProc (SndChannelPtr theChannel, SndCommand * theCmd);
static PaError PaMac_BackgroundManager( internalPortAudioStream   *past, int index );
long PaHost_GetTotalBufferFrames( internalPortAudioStream   *past );
static int     Mac_IsVirtualMemoryOn( void );
static void    PToCString(unsigned char* inString, char* outString);
char *MultiBuffer_GetNextWriteBuffer( MultiBuffer *mbuf );
char *MultiBuffer_GetNextReadBuffer( MultiBuffer *mbuf );
int   MultiBuffer_GetNextReadIndex( MultiBuffer *mbuf );
int   MultiBuffer_GetNextWriteIndex( MultiBuffer *mbuf );
int   MultiBuffer_IsWriteable(  MultiBuffer *mbuf );
int   MultiBuffer_IsReadable(  MultiBuffer *mbuf );
void  MultiBuffer_AdvanceReadIndex(  MultiBuffer *mbuf );
void  MultiBuffer_AdvanceWriteIndex(  MultiBuffer *mbuf );

/*************************************************************************
** Simple FIFO index control for multiple buffers.
** Read and Write indices range from 0 to 2N-1.
** This allows us to distinguish between full and empty.
*/
char *MultiBuffer_GetNextWriteBuffer( MultiBuffer *mbuf )
{
    return mbuf->buffers[mbuf->nextWrite % mbuf->numBuffers];
}
char *MultiBuffer_GetNextReadBuffer( MultiBuffer *mbuf )
{
    return mbuf->buffers[mbuf->nextRead % mbuf->numBuffers];
}
int MultiBuffer_GetNextReadIndex( MultiBuffer *mbuf )
{
    return mbuf->nextRead % mbuf->numBuffers;
}
int MultiBuffer_GetNextWriteIndex( MultiBuffer *mbuf )
{
    return mbuf->nextWrite % mbuf->numBuffers;
}

int MultiBuffer_IsWriteable(  MultiBuffer *mbuf )
{
    int bufsFull = mbuf->nextWrite - mbuf->nextRead;
    if( bufsFull < 0 ) bufsFull += (2 * mbuf->numBuffers);
    return (bufsFull < mbuf->numBuffers);
}
int MultiBuffer_IsReadable(  MultiBuffer *mbuf )
{
    int bufsFull = mbuf->nextWrite - mbuf->nextRead;
    if( bufsFull < 0 ) bufsFull += (2 * mbuf->numBuffers);
    return (bufsFull > 0);
}
void MultiBuffer_AdvanceReadIndex(  MultiBuffer *mbuf )
{
    int temp = mbuf->nextRead + 1;
    mbuf->nextRead = (temp >= (2 * mbuf->numBuffers)) ? 0 : temp;
}
void MultiBuffer_AdvanceWriteIndex(  MultiBuffer *mbuf )
{
    int temp = mbuf->nextWrite + 1;
    mbuf->nextWrite = (temp >= (2 * mbuf->numBuffers)) ? 0 : temp;
}

/*************************************************************************
** String Utility by Chris Rolfe
*/
static void PToCString(unsigned char* inString, char* outString)
{
    long i;
    for(i=0; i<inString[0]; i++)  /* convert Pascal to C string */
        outString[i] = inString[i+1];
    outString[i]=0;
}

/*************************************************************************/
PaError PaHost_Term( void )
{
    int           i;
    PaDeviceInfo *dev;
    double       *rates;
    /* Free any allocated sample rate arrays. */
    for( i=0; i<sNumDevices; i++ )
    {
        dev =  &sDevices[i].pad_Info;
        rates = (double *) dev->sampleRates;
        if( (rates != NULL) ) free( rates ); /* MEM_011 */
        dev->sampleRates = NULL;
        if( dev->name != NULL ) free( (void *) dev->name ); /* MEM_010 */
        dev->name = NULL;
    }
    sNumDevices = 0;
    return paNoError;
}

/*************************************************************************
 PaHost_Init() is the library initialization function - call this before
    using the library.
*/
PaError PaHost_Init( void )
{
    PaError err;
    NumVersionVariant version;

    version.parts = SndSoundManagerVersion();
    DBUG(("SndSoundManagerVersion = 0x%x\n", version.whole));

    /* Have we already initialized the device info? */
    err = (PaError) Pa_CountDevices();
    if( err < 0 ) return err;
    else return paNoError;
}

/*************************************************************************
 PaMac_ScanOutputDevices() queries the properties of all output devices.
*/
static PaError PaMac_ScanOutputDevices( void )
{
    PaError       err;
    Component     identifier=0;
    ComponentDescription criteria = { kSoundOutputDeviceType, 0, 0, 0, 0 };
    long       numComponents, i;

    /* Search the system linked list for output components  */
    numComponents = CountComponents (&criteria);
    identifier = 0;
    sDefaultOutputDeviceID = sNumDevices; /* FIXME - query somehow */
    for (i = 0; i < numComponents; i++)
    {
        /* passing nil returns first matching component. */
        identifier = FindNextComponent( identifier, &criteria);
        sDevices[sNumDevices].pad_Identifier = identifier;

        /* Set up for default OUTPUT devices. */
        err = PaMac_QueryOutputDeviceInfo( identifier, &sDevices[sNumDevices] );
        if( err < 0 ) return err;
        else  sNumDevices++;

    }

    return paNoError;
}

/*************************************************************************
 PaMac_ScanInputDevices() queries the properties of all input devices.
*/
static PaError PaMac_ScanInputDevices( void )
{
    Str255     deviceName;
    int        count;
    Handle     iconHandle;
    PaError    err;
    OSErr      oserr;
    count = 1;
    sDefaultInputDeviceID = sNumDevices; /* FIXME - query somehow */ /* PLB20010415 - was setting sDefaultOutputDeviceID */
    while(true)
    {
        /* Thanks Chris Rolfe and Alberto Ricci for this trick. */
        oserr = SPBGetIndexedDevice(count++, deviceName, &iconHandle);
        DBUG(("PaMac_ScanInputDevices: SPBGetIndexedDevice returned %d\n", oserr ));
#if 1
        /* PLB20010415 - was giving error for anything other than siBadSoundInDevice, but some Macs may return other errors! */
        if(oserr != noErr) break; /* Some type of error is expected when count > devices */
#else
        if(oserr == siBadSoundInDevice)
        {  /* it's expected when count > devices */
            oserr = noErr;
            break;
        }
        if(oserr != noErr)
        {
            ERR_RPT(("ERROR: SPBGetIndexedDevice(%d,,) returned %d\n", count-1, oserr ));
            sPaHostError = oserr;
            return paHostError;
        }
#endif
        DisposeHandle(iconHandle);   /* Don't need the icon */

        err = PaMac_QueryInputDeviceInfo( deviceName, &sDevices[sNumDevices] );
        DBUG(("PaMac_ScanInputDevices: PaMac_QueryInputDeviceInfo returned %d\n", err ));
        if( err < 0 ) return err;
        else if( err == 1 ) sNumDevices++;
    }

    return paNoError;
}

/* Sample rate info returned by using siSampleRateAvailable selector in SPBGetDeviceInfo() */
/* Thanks to Chris Rolfe for help with this query. */
#pragma options align=mac68k
typedef  struct
{
    int16                   numRates;
    UnsignedFixed           (**rates)[]; /* Handle created by SPBGetDeviceInfo */
}
SRateInfo;
#pragma options align=reset

/*************************************************************************
** PaMac_QueryOutputDeviceInfo()
** Query information about a named output device.
** Clears contents of ipad and writes info based on queries.
** Return one if OK,
**        zero if device cannot be used,
**        or negative error.
*/
static PaError PaMac_QueryOutputDeviceInfo( Component identifier, internalPortAudioDevice *ipad )
{
    int     len;
    OSErr   err;
    PaDeviceInfo *dev =  &ipad->pad_Info;
    SRateInfo srinfo = {0};
    int     numRates;
    ComponentDescription tempD;
    Handle nameH=nil, infoH=nil, iconH=nil;

    memset( ipad, 0, sizeof(internalPortAudioDevice) );

    dev->structVersion = 1;
    dev->maxInputChannels = 0;
    dev->maxOutputChannels = 2;
    dev->nativeSampleFormats = paInt16; /* FIXME - query to see if 24 or 32 bit data can be handled. */

    /* Get sample rates supported. */
    err = GetSoundOutputInfo(identifier, siSampleRateAvailable, (Ptr) &srinfo);
    if(err != noErr)
    {
        ERR_RPT(("Error in PaMac_QueryOutputDeviceInfo: GetSoundOutputInfo siSampleRateAvailable returned %d\n", err ));
        goto error;
    }
    numRates = srinfo.numRates;
    DBUG(("PaMac_QueryOutputDeviceInfo: srinfo.numRates = 0x%x\n", srinfo.numRates ));
    if( numRates == 0 )
    {
        dev->numSampleRates = -1;
        numRates = 2;
    }
    else
    {
        dev->numSampleRates = numRates;
    }
    dev->sampleRates = PaMac_GetSampleRatesFromHandle( numRates, (Handle) srinfo.rates );
    /* SPBGetDeviceInfo created the handle, but it's OUR job to release it. */
    DisposeHandle((Handle) srinfo.rates);

    /* Device name */
    /*  we pass an existing handle for the component name;
     we don't care about the info (type, subtype, etc.) or icon, so set them to nil */
    infoH = nil;
    iconH = nil;
    nameH = NewHandle(0);
    if(nameH == nil)  return paInsufficientMemory;
    err = GetComponentInfo(identifier, &tempD, nameH, infoH, iconH);
    if (err)
    {
        ERR_RPT(("Error in PaMac_QueryOutputDeviceInfo: GetComponentInfo returned %d\n", err ));
        goto error;
    }
    len = (*nameH)[0] + 1;
    dev->name = (char *) malloc(len);  /* MEM_010 */
    if( dev->name == NULL )
    {
        DisposeHandle(nameH);
        return paInsufficientMemory;
    }
    else
    {
        PToCString((unsigned char *)(*nameH), (char *) dev->name);
        DisposeHandle(nameH);
    }

    DBUG(("PaMac_QueryOutputDeviceInfo: dev->name = %s\n", dev->name ));
    return paNoError;

error:
    sPaHostError = err;
    return paHostError;

}

/*************************************************************************
** PaMac_QueryInputDeviceInfo()
** Query information about a named input device.
** Clears contents of ipad and writes info based on queries.
** Return one if OK,
**        zero if device cannot be used,
**        or negative error.
*/
static PaError PaMac_QueryInputDeviceInfo( Str255 deviceName, internalPortAudioDevice *ipad )
{
    PaError result = paNoError;
    int     len;
    OSErr   err;
    long    mRefNum = 0;
    long    tempL;
    int16   tempS;
    Fixed   tempF;
    PaDeviceInfo *dev =  &ipad->pad_Info;
    SRateInfo srinfo = {0};
    int     numRates;

    memset( ipad, 0, sizeof(internalPortAudioDevice) );
    dev->maxOutputChannels = 0;

    /* Open device based on name. If device is in use, it may not be able to open in write mode. */
    err = SPBOpenDevice( deviceName, siWritePermission, &mRefNum);
    if (err)
    {
        /* If device is in use, it may not be able to open in write mode so try read mode. */
        err = SPBOpenDevice( deviceName, siReadPermission, &mRefNum);
        if (err)
        {
            ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBOpenDevice returned %d\n", err ));
            sPaHostError = err;
            return paHostError;
        }
    }

    /* Define macros for printing out device info. */
#define PrintDeviceInfo(selector,var) \
    err = SPBGetDeviceInfo(mRefNum, selector, (Ptr) &var); \
    if (err) { \
        DBUG(("query %s failed\n", #selector )); \
    }\
    else { \
        DBUG(("query %s = 0x%x\n", #selector, var )); \
    }

    PrintDeviceInfo( siContinuous, tempS );
    PrintDeviceInfo( siAsync, tempS );
    PrintDeviceInfo( siNumberChannels, tempS );
    PrintDeviceInfo( siSampleSize, tempS );
    PrintDeviceInfo( siSampleRate, tempF );
    PrintDeviceInfo( siChannelAvailable, tempS );
    PrintDeviceInfo( siActiveChannels, tempL );
    PrintDeviceInfo( siDeviceBufferInfo, tempL );

    err = SPBGetDeviceInfo(mRefNum, siActiveChannels, (Ptr) &tempL);
    if (err == 0) DBUG(("%s = 0x%x\n", "siActiveChannels", tempL ));
    /* Can we use this device? */
    err = SPBGetDeviceInfo(mRefNum, siAsync, (Ptr) &tempS);
    if (err)
    {
        ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siAsync returned %d\n", err ));
        goto error;
    }
    if( tempS == 0 ) goto useless; /* Does not support async recording so forget about it. */

    err = SPBGetDeviceInfo(mRefNum, siChannelAvailable, (Ptr) &tempS);
    if (err)
    {
        ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siChannelAvailable returned %d\n", err ));
        goto error;
    }
    dev->maxInputChannels = tempS;

    /* Get sample rates supported. */
    err = SPBGetDeviceInfo(mRefNum, siSampleRateAvailable, (Ptr) &srinfo);
    if (err)
    {
        ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siSampleRateAvailable returned %d\n", err ));
        goto error;
    }

    numRates = srinfo.numRates;
    DBUG(("numRates = 0x%x\n", numRates ));
    if( numRates == 0 )
    {
        dev->numSampleRates = -1;
        numRates = 2;
    }
    else
    {
        dev->numSampleRates = numRates;
    }
    dev->sampleRates = PaMac_GetSampleRatesFromHandle( numRates, (Handle) srinfo.rates );
    /* SPBGetDeviceInfo created the handle, but it's OUR job to release it. */
    DisposeHandle((Handle) srinfo.rates);

    /* Get size of device buffer. */
    err = SPBGetDeviceInfo(mRefNum, siDeviceBufferInfo, (Ptr) &tempL);
    if (err)
    {
        ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siDeviceBufferInfo returned %d\n", err ));
        goto error;
    }
    ipad->pad_DeviceBufferSize = tempL;
    DBUG(("siDeviceBufferInfo = %d\n", tempL ));

    /* Set format based on sample size. */
    err = SPBGetDeviceInfo(mRefNum, siSampleSize, (Ptr) &tempS);
    if (err)
    {
        ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siSampleSize returned %d\n", err ));
        goto error;
    }
    switch( tempS )
    {
    case 0x0020:
        dev->nativeSampleFormats = paInt32;  /* FIXME - warning, code probably won't support this! */
        break;
    case 0x0010:
    default: /* FIXME - What about other formats? */
        dev->nativeSampleFormats = paInt16;
        break;
    }
    DBUG(("nativeSampleFormats = %d\n", dev->nativeSampleFormats ));

    /* Device name */
    len = deviceName[0] + 1;  /* Get length of Pascal string */
    dev->name = (char *) malloc(len);  /* MEM_010 */
    if( dev->name == NULL )
    {
        result = paInsufficientMemory;
        goto cleanup;
    }
    PToCString(deviceName, (char *) dev->name);
    DBUG(("deviceName = %s\n", dev->name ));
    result = (PaError) 1;
    /* All done so close up device. */
cleanup:
    if( mRefNum )  SPBCloseDevice(mRefNum);
    return result;

error:
    if( mRefNum )  SPBCloseDevice(mRefNum);
    sPaHostError = err;
    return paHostError;

useless:
    if( mRefNum )  SPBCloseDevice(mRefNum);
    return (PaError) 0;
}

/*************************************************************************
** Allocate a double array and fill it with listed sample rates.
*/
static double * PaMac_GetSampleRatesFromHandle ( int numRates, Handle h )
{
    OSErr   err = noErr;
    SInt8   hState;
    int     i;
    UnsignedFixed *fixedRates;
    double *rates = (double *) malloc( numRates * sizeof(double) ); /* MEM_011 */
    if( rates == NULL ) return NULL;
    /* Save and restore handle state as suggested by TechNote at:
            http://developer.apple.com/technotes/tn/tn1122.html
    */
    hState = HGetState (h);
    if (!(err = MemError ()))
    {
        HLock (h);
        if (!(err = MemError ( )))
        {
            fixedRates = (UInt32 *) *h;
            for( i=0; i<numRates; i++ )
            {
                rates[i] = UnsignedFixedToDouble(fixedRates[i]);
            }

            HSetState (h,hState);
            err = MemError ( );
        }
    }
    if( err )
    {
        free( rates );
        ERR_RPT(("Error in PaMac_GetSampleRatesFromHandle = %d\n", err ));
    }
    return rates;
}

/*************************************************************************/
int Pa_CountDevices()
{
    PaError err;
    DBUG(("Pa_CountDevices()\n"));
    /* If no devices, go find some. */
    if( sNumDevices <= 0 )
    {
        err = PaMac_ScanOutputDevices();
        if( err != paNoError ) goto error;
        err = PaMac_ScanInputDevices();
        if( err != paNoError ) goto error;
    }
    return sNumDevices;

error:
    PaHost_Term();
    DBUG(("Pa_CountDevices: returns %d\n", err ));
    return err;

}

/*************************************************************************/
const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id )
{
    if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL;
    return &sDevices[id].pad_Info;
}
/*************************************************************************/
PaDeviceID Pa_GetDefaultInputDeviceID( void )
{
    return sDefaultInputDeviceID;
}

/*************************************************************************/
PaDeviceID Pa_GetDefaultOutputDeviceID( void )
{
    return sDefaultOutputDeviceID;
}

/********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/
static void PaMac_StartLoadCalculation( internalPortAudioStream   *past )
{
    PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
    UnsignedWide widePad;
    if( pahsc == NULL ) return;
    /* Query system timer for usage analysis and to prevent overuse of CPU. */
    Microseconds( &widePad );
    pahsc->pahsc_EntryCount = UnsignedWideToUInt64( widePad );
}

/******************************************************************************
** Measure fractional CPU load based on real-time it took to calculate
** buffers worth of output.
*/
/**************************************************************************/
static void PaMac_EndLoadCalculation( internalPortAudioStream   *past )
{
    UnsignedWide widePad;
    UInt64    currentCount;
    long      usecsElapsed;
    double    newUsage;
    PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
    if( pahsc == NULL ) return;
    
    /* Measure CPU utilization during this callback. Note that this calculation
    ** assumes that we had the processor the whole time.
    */
#define LOWPASS_COEFFICIENT_0   (0.95)
#define LOWPASS_COEFFICIENT_1   (0.99999 - LOWPASS_COEFFICIENT_0)
    Microseconds( &widePad );
    currentCount = UnsignedWideToUInt64( widePad );

	usecsElapsed = (long) U64Subtract(currentCount, pahsc->pahsc_EntryCount);
	
        /* Use inverse because it is faster than the divide. */
	newUsage =  usecsElapsed * pahsc->pahsc_InverseMicrosPerHostBuffer;
	
	past->past_Usage = (LOWPASS_COEFFICIENT_0 * past->past_Usage) +
                           (LOWPASS_COEFFICIENT_1 * newUsage);
 
}

/***********************************************************************
** Called by Pa_StartStream()
*/
PaError PaHost_StartInput( internalPortAudioStream   *past )
{
    PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
    pahsc->pahsc_IsRecording = 0;
    pahsc->pahsc_StopRecording = 0;
    pahsc->pahsc_InputMultiBuffer.nextWrite = 0;
    pahsc->pahsc_InputMultiBuffer.nextRead = 0;
    return PaMac_RecordNext( past );
}

/***********************************************************************
** Called by Pa_StopStream().
** May be called during error recovery or cleanup code
** so protect against NULL pointers.
*/
PaError PaHost_StopInput( internalPortAudioStream   *past, int abort )
{
    int32   timeOutMsec;
    PaError result = paNoError;
    OSErr   err = 0;
    long    mRefNum;
    PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
    if( pahsc == NULL ) return paNoError;

    (void) abort;

    mRefNum = pahsc->pahsc_InputRefNum;

    DBUG(("PaHost_StopInput: mRefNum = %d\n", mRefNum ));
    if( mRefNum )
    {
        DBUG(("PaHost_StopInput: pahsc_IsRecording = %d\n", pahsc->pahsc_IsRecording ));
        if( pahsc->pahsc_IsRecording )
        {
            /* PLB20010420 - Fix TIMEOUT in record mode. */
            pahsc->pahsc_StopRecording = 1; /* Request that we stop recording. */
            err = SPBStopRecording(mRefNum);
            DBUG(("PaHost_StopInput: then pahsc_IsRecording = %d\n", pahsc->pahsc_IsRecording ));

            /* Calculate timeOut longer than longest time it could take to play one buffer. */
            timeOutMsec = (int32) ((1500.0 * pahsc->pahsc_FramesPerHostBuffer) / past->past_SampleRate);
            /* Keep querying sound channel until it is no longer busy playing. */
            while( !err && pahsc->pahsc_IsRecording && (timeOutMsec > 0))
            {
                Pa_Sleep(20);
                timeOutMsec -= 20;
            }
            if( timeOutMsec <= 0 )
            {
                ERR_RPT(("PaHost_StopInput: timed out!\n"));
                return paTimedOut;
            }
        }
    }
    if( err )
    {
        sPaHostError = err;
        result = paHostError;
    }

    DBUG(("PaHost_StopInput: finished.\n", mRefNum ));
    return result;
}

/***********************************************************************/
static void PaMac_InitSoundHeader( internalPortAudioStream   *past, CmpSoundHeader *sndHeader )
{
    sndHeader->numChannels = past->past_NumOutputChannels;
    sndHeader->sampleRate = DoubleToUnsignedFixed(past->past_SampleRate);
    sndHeader->loopStart = 0;
    sndHeader->loopEnd = 0;
    sndHeader->encode = cmpSH;
    sndHeader->baseFrequency = kMiddleC;
    sndHeader->markerChunk = nil;
    sndHeader->futureUse2 = nil;
    sndHeader->stateVars = nil;
    sndHeader->leftOverSamples = nil;
    sndHeader->compressionID = 0;
    sndHeader->packetSize = 0;
    sndHeader->snthID = 0;
    sndHeader->sampleSize = 8 * sizeof(int16); // FIXME - might be 24 or 32 bits some day;
    sndHeader->sampleArea[0] = 0;
    sndHeader->format = kSoundNotCompressed;
}

static void SetFramesDone( PaHostSoundControl *pahsc, PaTimestamp framesDone )
{
	UnsignedWide     now;
 	Microseconds( &now );
 	pahsc->pahsc_NumFramesDone = framesDone;
 	pahsc->pahsc_WhenFramesDoneIncremented = UnsignedWideToUInt64( now );
}

/***********************************************************************/
PaError PaHost_StartOutput( internalPortAudioStream   *past )
{
    SndCommand  pauseCommand;
    SndCommand  resumeCommand;
    int          i;
    OSErr         error;
    PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
    if( pahsc == NULL ) return paInternalError;
    if( pahsc->pahsc_Channel == NULL ) return paInternalError;

    past->past_StopSoon = 0;
    past->past_IsActive = 1;
    pahsc->pahsc_NumOutsQueued = 0;
    pahsc->pahsc_NumOutsPlayed = 0;
    
    SetFramesDone( pahsc, 0.0 );

    /* Pause channel so it does not do back ground processing while we are still filling the queue. */
    pauseCommand.cmd = pauseCmd;
    pauseCommand.param1 = pauseCommand.param2 = 0;
    error = SndDoCommand (pahsc->pahsc_Channel, &pauseCommand, true);
    if (noErr != error) goto exit;

    /* Queue all of the buffers so we start off full. */
    for (i = 0; i<pahsc->pahsc_NumHostBuffers; i++)
    {
        PaMac_PlayNext( past, i );
    }
    
    /* Resume channel now that the queue is full. */
    resumeCommand.cmd = resumeCmd;
    resumeCommand.param1 = resumeCommand.param2 = 0;
    error = SndDoImmediate( pahsc->pahsc_Channel, &resumeCommand );
    if (noErr != error) goto exit;

    return paNoError;
exit:
    past->past_IsActive = 0;
    sPaHostError = error;
    ERR_RPT(("Error in PaHost_StartOutput: SndDoCommand returned %d\n", error ));
    return paHostError;
}

/*******************************************************************/
long PaHost_GetTotalBufferFrames( internalPortAudioStream   *past )
{
    PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
    return (long) (pahsc->pahsc_NumHostBuffers * pahsc->pahsc_FramesPerHostBuffer);
}

/***********************************************************************
** Called by Pa_StopStream().
** May be called during error recovery or cleanup code
** so protect against NULL pointers.
*/
PaError PaHost_StopOutput( internalPortAudioStream   *past, int abort )
{
    int32 timeOutMsec;
    PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
    if( pahsc == NULL ) return paNoError;
    if( pahsc->pahsc_Channel == NULL ) return paNoError;

    DBUG(("PaHost_StopOutput()\n"));
    if( past->past_IsActive == 0 ) return paNoError;

    /* Set flags for callback function to see. */
    if( abort ) past->past_StopNow = 1;
    past->past_StopSoon = 1;
    /* Calculate timeOut longer than longest time it could take to play all buffers. */
    timeOutMsec = (int32) ((1500.0 * PaHost_GetTotalBufferFrames( past )) / past->past_SampleRate);
    /* Keep querying sound channel until it is no longer busy playing. */
    while( past->past_IsActive && (timeOutMsec > 0))
    {
        Pa_Sleep(20);
        timeOutMsec -= 20;
    }
    if( timeOutMsec <= 0 )
    {
        ERR_RPT(("PaHost_StopOutput: timed out!\n"));
        return paTimedOut;
    }
    else return paNoError;
}

/***********************************************************************/
PaError PaHost_StartEngine( internalPortAudioStream   *past )
{
    (void) past; /* Prevent unused variable warnings. */
    return paNoError;
}

/***********************************************************************/
PaError PaHost_StopEngine( internalPortAudioStream   *past, int abort )
{
    (void) past; /* Prevent unused variable warnings. */
    (void) abort; /* Prevent unused variable warnings. */
    return paNoError;
}
/***********************************************************************/
PaError PaHost_StreamActive( internalPortAudioStream   *past )
{
    PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
    return (PaError) ( past->past_IsActive + pahsc->pahsc_IsRecording );
}
int Mac_IsVirtualMemoryOn( void )
{
    long  attr;
    OSErr result = Gestalt( gestaltVMAttr, &attr );
    DBUG(("gestaltVMAttr : 0x%x\n", attr ));
    return ((attr >> gestaltVMHasPagingControl ) & 1);
}

/*******************************************************************
* Determine number of host Buffers
* and how many User Buffers we can put into each host buffer.
*/
static void PaHost_CalcNumHostBuffers( internalPortAudioStream *past )
{
    PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
    int32  minNumBuffers;
    int32  minFramesPerHostBuffer;
    int32  minTotalFrames;
    int32  userBuffersPerHostBuffer;
    int32  framesPerHostBuffer;
    int32  numHostBuffers;
    
    minFramesPerHostBuffer = pahsc->pahsc_MinFramesPerHostBuffer;
    minFramesPerHostBuffer = (minFramesPerHostBuffer + 7) & ~7;
    DBUG(("PaHost_CalcNumHostBuffers: minFramesPerHostBuffer = %d\n", minFramesPerHostBuffer ));
    
    /* Determine number of user buffers based on minimum latency. */
	/* PLB20020417 I used to call Pa_GetMinNumBuffers() which doesn't take into account the
	**    variable minFramesPerHostBuffer. Now I call PaMac_GetMinNumBuffers() which will
	**    gove lower latency when virtual memory is turned off. */
    /* minNumBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); WRONG */
    minNumBuffers = PaMac_GetMinNumBuffers( minFramesPerHostBuffer, past->past_FramesPerUserBuffer, past->past_SampleRate );
    
    past->past_NumUserBuffers = ( minNumBuffers > past->past_NumUserBuffers ) ? minNumBuffers : past->past_NumUserBuffers;
    DBUG(("PaHost_CalcNumHostBuffers: min past_NumUserBuffers = %d\n", past->past_NumUserBuffers ));
    minTotalFrames = past->past_NumUserBuffers * past->past_FramesPerUserBuffer;
    
    /* We cannot make the buffers too small because they may not get serviced quickly enough. */
    if( (int32) past->past_FramesPerUserBuffer < minFramesPerHostBuffer )
    {
        userBuffersPerHostBuffer =
            (minFramesPerHostBuffer + past->past_FramesPerUserBuffer - 1) /
            past->past_FramesPerUserBuffer;
    }
    else
    {
        userBuffersPerHostBuffer = 1;
    }
    framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer;
    
    /* Calculate number of host buffers needed. Round up to cover minTotalFrames. */
    numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer;
    /* Make sure we have enough host buffers. */
    if( numHostBuffers < PA_MIN_NUM_HOST_BUFFERS)
    {
        numHostBuffers = PA_MIN_NUM_HOST_BUFFERS;
    }
    else
    {
        /* If we have too many host buffers, try to put more user buffers in a host buffer. */
        while(numHostBuffers > PA_MAX_NUM_HOST_BUFFERS)
        {
            userBuffersPerHostBuffer += 1;
            framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer;
            numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer;
        }
    }

    pahsc->pahsc_UserBuffersPerHostBuffer = userBuffersPerHostBuffer;
    pahsc->pahsc_FramesPerHostBuffer = framesPerHostBuffer;
    pahsc->pahsc_NumHostBuffers = numHostBuffers;
    DBUG(("PaHost_CalcNumHostBuffers: pahsc_UserBuffersPerHostBuffer = %d\n", pahsc->pahsc_UserBuffersPerHostBuffer ));
    DBUG(("PaHost_CalcNumHostBuffers: pahsc_NumHostBuffers = %d\n", pahsc->pahsc_NumHostBuffers ));
    DBUG(("PaHost_CalcNumHostBuffers: pahsc_FramesPerHostBuffer = %d\n", pahsc->pahsc_FramesPerHostBuffer ));
    DBUG(("PaHost_CalcNumHostBuffers: past_NumUserBuffers = %d\n", past->past_NumUserBuffers ));
}

/*******************************************************************/
PaError PaHost_OpenStream( internalPortAudioStream   *past )
{
    OSErr             err;
    PaError             result = paHostError;
    PaHostSoundControl *pahsc;
    int                 i;
    /* Allocate and initialize host data. */
    pahsc = (PaHostSoundControl *) PaHost_AllocateFastMemory(sizeof(PaHostSoundControl));
    if( pahsc == NULL )
    {
        return paInsufficientMemory;
    }
    past->past_DeviceData = (void *) pahsc;

    /* If recording, and virtual memory is turned on, then use bigger buffers to prevent glitches. */
    if( (past->past_NumInputChannels > 0)  && Mac_IsVirtualMemoryOn() )
    {
        pahsc->pahsc_MinFramesPerHostBuffer = MAC_VIRTUAL_FRAMES_PER_BUFFER;
    }
    else
    {
        pahsc->pahsc_MinFramesPerHostBuffer = MAC_PHYSICAL_FRAMES_PER_BUFFER;
    }

    PaHost_CalcNumHostBuffers( past );
    
    /* Setup constants for CPU load measurement. */
    pahsc->pahsc_InverseMicrosPerHostBuffer = past->past_SampleRate / (1000000.0 * 	pahsc->pahsc_FramesPerHostBuffer);

    /* ------------------ OUTPUT */
    if( past->past_NumOutputChannels > 0 )
    {
        /* Create sound channel to which we can send commands. */
        pahsc->pahsc_Channel = 0L;
        err = SndNewChannel(&pahsc->pahsc_Channel, sampledSynth, 0, nil); /* FIXME - use kUseOptionalOutputDevice if not default. */
        if(err != 0)
        {
            ERR_RPT(("Error in PaHost_OpenStream: SndNewChannel returned 0x%x\n", err ));
            goto error;
        }

        /* Install our callback function pointer straight into the sound channel structure */
        /* Use new CARBON name for callback procedure. */
#if TARGET_API_MAC_CARBON
        pahsc->pahsc_OutputCompletionProc = NewSndCallBackUPP(PaMac_OutputCompletionProc);
#else
        pahsc->pahsc_OutputCompletionProc = NewSndCallBackProc(PaMac_OutputCompletionProc);
#endif

        pahsc->pahsc_Channel->callBack = pahsc->pahsc_OutputCompletionProc;

        pahsc->pahsc_BytesPerOutputHostBuffer = pahsc->pahsc_FramesPerHostBuffer * past->past_NumOutputChannels * sizeof(int16);
        for (i = 0; i<pahsc->pahsc_NumHostBuffers; i++)
        {
            char *buf = (char *)PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerOutputHostBuffer);
            if (buf == NULL)
            {
                ERR_RPT(("Error in PaHost_OpenStream: could not allocate output buffer. Size = \n", pahsc->pahsc_BytesPerOutputHostBuffer ));
                goto memerror;
            }

            PaMac_InitSoundHeader( past, &pahsc->pahsc_SoundHeaders[i] );
            pahsc->pahsc_SoundHeaders[i].samplePtr = buf;
            pahsc->pahsc_SoundHeaders[i].numFrames = (unsigned long) pahsc->pahsc_FramesPerHostBuffer;

        }
    }
#ifdef SUPPORT_AUDIO_CAPTURE
    /* ------------------ INPUT */
    /* Use double buffer scheme that matches output. */
    if( past->past_NumInputChannels > 0 )
    {
        int16   tempS;
        long    tempL;
        Fixed   tempF;
        long    mRefNum;
        unsigned char noname = 0; /* FIXME - use real device names. */
#if TARGET_API_MAC_CARBON
        pahsc->pahsc_InputCompletionProc = NewSICompletionUPP((SICompletionProcPtr)PaMac_InputCompletionProc);
#else
        pahsc->pahsc_InputCompletionProc = NewSICompletionProc((ProcPtr)PaMac_InputCompletionProc);
#endif
        pahsc->pahsc_BytesPerInputHostBuffer = pahsc->pahsc_FramesPerHostBuffer * past->past_NumInputChannels * sizeof(int16);
        for (i = 0; i<pahsc->pahsc_NumHostBuffers; i++)
        {
            char *buf = (char *) PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerInputHostBuffer);
            if ( buf == NULL )
            {
                ERR_RPT(("PaHost_OpenStream: could not allocate input  buffer. Size = \n", pahsc->pahsc_BytesPerInputHostBuffer ));
                goto memerror;
            }
            pahsc->pahsc_InputMultiBuffer.buffers[i] = buf;
        }
        pahsc->pahsc_InputMultiBuffer.numBuffers = pahsc->pahsc_NumHostBuffers;

        err = SPBOpenDevice( (const unsigned char *) &noname, siWritePermission, &mRefNum); /* FIXME - use name so we get selected device */
        // FIXME err = SPBOpenDevice( (const unsigned char *) sDevices[past->past_InputDeviceID].pad_Info.name, siWritePermission, &mRefNum);
        if (err) goto error;
        pahsc->pahsc_InputRefNum = mRefNum;
        DBUG(("PaHost_OpenStream: mRefNum = %d\n", mRefNum ));

        /* Set input device characteristics. */
        tempS = 1;
        err = SPBSetDeviceInfo(mRefNum, siContinuous, (Ptr) &tempS);
        if (err)
        {
            ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siContinuous returned %d\n", err ));
            goto error;
        }

        tempL = 0x03;
        err = SPBSetDeviceInfo(mRefNum, siActiveChannels, (Ptr) &tempL);
        if (err)
        {
            DBUG(("PaHost_OpenStream: setting siActiveChannels returned 0x%x. Error ignored.\n", err ));
        }

        /* PLB20010908 - Use requested number of input channels. Thanks Dominic Mazzoni. */
        tempS = past->past_NumInputChannels;
        err = SPBSetDeviceInfo(mRefNum, siNumberChannels, (Ptr) &tempS);
        if (err)
        {
            ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siNumberChannels returned %d\n", err ));
            goto error;
        }

        tempF = ((unsigned long)past->past_SampleRate) << 16;
        err = SPBSetDeviceInfo(mRefNum, siSampleRate, (Ptr) &tempF);
        if (err)
        {
            ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siSampleRate returned %d\n", err ));
            goto error;
        }

        /* Setup record-parameter block */
        pahsc->pahsc_InputParams.inRefNum          = mRefNum;
        pahsc->pahsc_InputParams.milliseconds      = 0;   // not used
        pahsc->pahsc_InputParams.completionRoutine = pahsc->pahsc_InputCompletionProc;
        pahsc->pahsc_InputParams.interruptRoutine  = 0;
        pahsc->pahsc_InputParams.userLong          = (long) past;
        pahsc->pahsc_InputParams.unused1           = 0;
    }
#endif /* SUPPORT_AUDIO_CAPTURE */
    DBUG(("PaHost_OpenStream: complete.\n"));
    return paNoError;

error:
    PaHost_CloseStream( past );
    ERR_RPT(("PaHost_OpenStream: sPaHostError =  0x%x.\n", err ));
    sPaHostError = err;
    return paHostError;

memerror:
    PaHost_CloseStream( past );
    return paInsufficientMemory;
}

/***********************************************************************
** Called by Pa_CloseStream().
** May be called during error recovery or cleanup code
** so protect against NULL pointers.
*/
PaError PaHost_CloseStream( internalPortAudioStream   *past )
{
    PaError result = paNoError;
    OSErr   err = 0;
    int     i;
    PaHostSoundControl *pahsc;

    DBUG(("PaHost_CloseStream( 0x%x )\n", past ));

    if( past == NULL ) return paBadStreamPtr;

    pahsc = (PaHostSoundControl *) past->past_DeviceData;
    if( pahsc == NULL ) return paNoError;

    if( past->past_NumOutputChannels > 0 )
    {
        /* TRUE means flush now instead of waiting for quietCmd to be processed. */
        if( pahsc->pahsc_Channel != NULL ) SndDisposeChannel(pahsc->pahsc_Channel, TRUE);
        {
            for (i = 0; i<pahsc->pahsc_NumHostBuffers; i++)
            {
                Ptr p = (Ptr) pahsc->pahsc_SoundHeaders[i].samplePtr;
                if( p != NULL ) PaHost_FreeFastMemory( p, pahsc->pahsc_BytesPerOutputHostBuffer );
            }
        }
    }

    if( past->past_NumInputChannels > 0 )
    {
        if( pahsc->pahsc_InputRefNum )
        {
            err = SPBCloseDevice(pahsc->pahsc_InputRefNum);
            pahsc->pahsc_InputRefNum = 0;
            if( err )
            {
                sPaHostError = err;
                result = paHostError;
            }
        }
        {
            for (i = 0; i<pahsc->pahsc_InputMultiBuffer.numBuffers; i++)
            {
                Ptr p = (Ptr) pahsc->pahsc_InputMultiBuffer.buffers[i];
                if( p != NULL ) PaHost_FreeFastMemory( p, pahsc->pahsc_BytesPerInputHostBuffer );
            }
        }
    }

    past->past_DeviceData = NULL;
    PaHost_FreeFastMemory( pahsc, sizeof(PaHostSoundControl) );

    DBUG(("PaHost_CloseStream: complete.\n", past ));
    return result;
}
/*************************************************************************/
int Pa_GetMinNumBuffers( int framesPerUserBuffer, double sampleRate )
{
/* We use the MAC_VIRTUAL_FRAMES_PER_BUFFER because we might be recording.
** This routine doesn't have enough information to determine the best value
** and is being depracated. */
    return PaMac_GetMinNumBuffers( MAC_VIRTUAL_FRAMES_PER_BUFFER, framesPerUserBuffer, sampleRate );
}
/*************************************************************************/
static int PaMac_GetMinNumBuffers( int minFramesPerHostBuffer, int framesPerUserBuffer, double sampleRate )
{
    int minUserPerHost = ( minFramesPerHostBuffer + framesPerUserBuffer - 1) / framesPerUserBuffer;
    int numBufs = PA_MIN_NUM_HOST_BUFFERS * minUserPerHost;
    if( numBufs < PA_MIN_NUM_HOST_BUFFERS ) numBufs = PA_MIN_NUM_HOST_BUFFERS;
    (void) sampleRate;
    return numBufs;
}

/*************************************************************************/
void Pa_Sleep( int32 msec )
{
    EventRecord   event;
    int32 sleepTime, endTime;
    /* Convert to ticks. Round up so we sleep a MINIMUM of msec time. */
    sleepTime = ((msec * 60) + 999) / 1000;
    if( sleepTime < 1 ) sleepTime = 1;
    endTime = TickCount() + sleepTime;
    do
    {
        DBUGX(("Sleep for %d ticks.\n", sleepTime ));
        /* Use WaitNextEvent() to sleep without getting events. */
        /* PLB20010907 - Pass unused event to WaitNextEvent instead of NULL to prevent
         * Mac OSX crash. Thanks Dominic Mazzoni. */
        WaitNextEvent( 0, &event, sleepTime, NULL );
        sleepTime = endTime - TickCount();
    }
    while( sleepTime > 0 );
}
/*************************************************************************/
int32 Pa_GetHostError( void )
{
    int32 err = sPaHostError;
    sPaHostError = 0;
    return err;
}

/*************************************************************************
 * Allocate memory that can be accessed in real-time.
 * This may need to be held in physical memory so that it is not
 * paged to virtual memory.
 * This call MUST be balanced with a call to PaHost_FreeFastMemory().
 */
void *PaHost_AllocateFastMemory( long numBytes )
{
    void *addr = NewPtrClear( numBytes );
    if( (addr == NULL) || (MemError () != 0) ) return NULL;

#if (TARGET_API_MAC_CARBON == 0)
    if( HoldMemory( addr, numBytes ) != noErr )
    {
        DisposePtr( (Ptr) addr );
        return NULL;
    }
#endif
    return addr;
}

/*************************************************************************
 * Free memory that could be accessed in real-time.
 * This call MUST be balanced with a call to PaHost_AllocateFastMemory().
 */
void PaHost_FreeFastMemory( void *addr, long numBytes )
{
    if( addr == NULL ) return;
#if TARGET_API_MAC_CARBON
    (void) numBytes;
#else
    UnholdMemory( addr, numBytes );
#endif
    DisposePtr( (Ptr) addr );
}

/*************************************************************************/
PaTimestamp Pa_StreamTime( PortAudioStream *stream )
{
	PaTimestamp   framesDone1;
	PaTimestamp   framesDone2;
	UInt64        whenIncremented;
	UnsignedWide  now;
	UInt64        now64;
	long          microsElapsed;
	long          framesElapsed;
	
    PaHostSoundControl *pahsc;
    internalPortAudioStream   *past = (internalPortAudioStream *) stream;
    if( past == NULL ) return paBadStreamPtr;
    pahsc = (PaHostSoundControl *) past->past_DeviceData;
    
/* Capture information from audio thread.
 * We have to be careful that we don't get interrupted in the middle.
 * So we grab the pahsc_NumFramesDone twice and make sure it didn't change.
 */
 	do
 	{
 		framesDone1 = pahsc->pahsc_NumFramesDone;
 		whenIncremented = pahsc->pahsc_WhenFramesDoneIncremented;
 		framesDone2 = pahsc->pahsc_NumFramesDone;
 	} while( framesDone1 != framesDone2 );
 	
 /* Calculate how many microseconds have elapsed and convert to frames. */
 	Microseconds( &now );
 	now64 = UnsignedWideToUInt64( now );
 	microsElapsed = U64Subtract( now64, whenIncremented );
 	framesElapsed = microsElapsed * past->past_SampleRate * 0.000001;
 	
	return framesDone1 + framesElapsed;
}

/**************************************************************************
** Callback for Input, SPBRecord()
*/
int gRecordCounter = 0;
int gPlayCounter = 0;
pascal void PaMac_InputCompletionProc(SPBPtr recParams)
{
    PaError                    result = paNoError;
    int                        finished = 1;
    internalPortAudioStream   *past;
    PaHostSoundControl        *pahsc;

    gRecordCounter += 1; /* debug hack to see if engine running */

    /* Get our PA data from Mac structure. */
    past = (internalPortAudioStream *) recParams->userLong;
    if( past == NULL ) return;

    if( past->past_Magic != PA_MAGIC )
    {
        AddTraceMessage("PaMac_InputCompletionProc: bad MAGIC, past", (long) past );
        AddTraceMessage("PaMac_InputCompletionProc: bad MAGIC, magic", (long) past->past_Magic );
        goto error;
    }
    pahsc = (PaHostSoundControl *) past->past_DeviceData;
    past->past_NumCallbacks += 1;

    /* Have we been asked to stop recording? */
    if( (recParams->error == abortErr) || pahsc->pahsc_StopRecording ) goto error;

    /* If there are no output channels, then we need to call the user callback function from here.
     * Otherwise we will call the user code during the output completion routine.
     */
    if(past->past_NumOutputChannels == 0)
    {
		SetFramesDone( pahsc,
			pahsc->pahsc_NumFramesDone + pahsc->pahsc_FramesPerHostBuffer );
        result = PaMac_CallUserLoop( past, NULL );
    }

    /* Did user code ask us to stop? If not, issue another recording request. */
    if( (result == paNoError) && (pahsc->pahsc_StopRecording == 0) )
    {
        result = PaMac_RecordNext( past );
        if( result != paNoError ) pahsc->pahsc_IsRecording = 0;
    }
    else goto error;

    return;

error:
    pahsc->pahsc_IsRecording = 0;
    pahsc->pahsc_StopRecording = 0;
    return;
}

/***********************************************************************
** Called by either input or output completion proc.
** Grabs input data if any present, and calls PA conversion code,
** that in turn calls user code.
*/
static PaError PaMac_CallUserLoop( internalPortAudioStream   *past, int16 *outPtr )
{
    PaError             result = paNoError;
    PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
    int16              *inPtr = NULL;
    int                 i;
    

    /* Advance read index for sound input FIFO here, independantly of record/write process. */
    if(past->past_NumInputChannels > 0)
    {
        if( MultiBuffer_IsReadable( &pahsc->pahsc_InputMultiBuffer ) )
        {
            inPtr = (int16 *)  MultiBuffer_GetNextReadBuffer( &pahsc->pahsc_InputMultiBuffer );
            MultiBuffer_AdvanceReadIndex( &pahsc->pahsc_InputMultiBuffer );
        }
    }

    /* Call user code enough times to fill buffer. */
    if( (inPtr != NULL) || (outPtr != NULL) )
    {
        PaMac_StartLoadCalculation( past ); /* CPU usage */

#ifdef PA_MAX_USAGE_ALLOWED
	/* If CPU usage exceeds limit, skip user callback to prevent hanging CPU. */
		if( past->past_Usage > PA_MAX_USAGE_ALLOWED )
	    {
	    	past->past_FrameCount += (PaTimestamp) pahsc->pahsc_FramesPerHostBuffer;
		}
		else
#endif
		{
		
	        for( i=0; i<pahsc->pahsc_UserBuffersPerHostBuffer; i++ )
	        {
	            result = (PaError) Pa_CallConvertInt16( past, inPtr, outPtr );
	            if( result != 0)
	            {
	                /* Recording might be in another process, so tell it to stop with a flag. */
	                pahsc->pahsc_StopRecording = pahsc->pahsc_IsRecording;
	                break;
	            }
	            /* Advance sample pointers. */
	            if(inPtr != NULL) inPtr += past->past_FramesPerUserBuffer * past->past_NumInputChannels;
	            if(outPtr != NULL) outPtr += past->past_FramesPerUserBuffer * past->past_NumOutputChannels;
	        }
	    }
		
        PaMac_EndLoadCalculation( past );
    }
    return result;
}

/***********************************************************************
** Setup next recording buffer in FIFO and issue recording request to Snd Input Manager.
*/
static PaError PaMac_RecordNext( internalPortAudioStream   *past )
{
    PaError  result = paNoError;
    OSErr     err;
    PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
    /* Get pointer to next buffer to record into. */
    pahsc->pahsc_InputParams.bufferPtr  = MultiBuffer_GetNextWriteBuffer( &pahsc->pahsc_InputMultiBuffer );

    /* Advance write index if there is room. Otherwise keep writing same buffer. */
    if( MultiBuffer_IsWriteable( &pahsc->pahsc_InputMultiBuffer ) )
    {
        MultiBuffer_AdvanceWriteIndex( &pahsc->pahsc_InputMultiBuffer );
    }

    AddTraceMessage("PaMac_RecordNext: bufferPtr", (long) pahsc->pahsc_InputParams.bufferPtr );
    AddTraceMessage("PaMac_RecordNext: nextWrite", pahsc->pahsc_InputMultiBuffer.nextWrite );

    /* Setup parameters and issue an asynchronous recording request. */
    pahsc->pahsc_InputParams.bufferLength      = pahsc->pahsc_BytesPerInputHostBuffer;
    pahsc->pahsc_InputParams.count             = pahsc->pahsc_BytesPerInputHostBuffer;
    err = SPBRecord(&pahsc->pahsc_InputParams, true);
    if( err )
    {
        AddTraceMessage("PaMac_RecordNext: SPBRecord error ", err );
        sPaHostError = err;
        result = paHostError;
    }
    else
    {
        pahsc->pahsc_IsRecording = 1;
    }
    return result;
}

/**************************************************************************
** Callback for Output Playback()
** Return negative error, 0 to continue, 1 to stop.
*/
long    PaMac_FillNextOutputBuffer( internalPortAudioStream   *past, int index )
{
    PaHostSoundControl  *pahsc;
    long                 result = 0;
    int                  finished = 1;
    char                *outPtr;

    gPlayCounter += 1; /* debug hack */

    past->past_NumCallbacks += 1;
    pahsc = (PaHostSoundControl *) past->past_DeviceData;
    if( pahsc == NULL ) return -1;
    /* Are we nested?! */
    if( pahsc->pahsc_IfInsideCallback ) return 0;
    pahsc->pahsc_IfInsideCallback = 1;
    /* Get pointer to buffer to fill. */
    outPtr = pahsc->pahsc_SoundHeaders[index].samplePtr;
    /* Combine with any sound input, and call user callback. */
    result = PaMac_CallUserLoop( past, (int16 *) outPtr );

    pahsc->pahsc_IfInsideCallback = 0;
    return result;
}

/*************************************************************************************
** Called by SoundManager when ready for another buffer.
*/
static pascal void PaMac_OutputCompletionProc (SndChannelPtr theChannel, SndCommand * theCallBackCmd)
{
    internalPortAudioStream *past;
    PaHostSoundControl      *pahsc;
    (void) theChannel;
    (void) theCallBackCmd;

    /* Get our data from Mac structure. */
    past = (internalPortAudioStream *) theCallBackCmd->param2;
    if( past == NULL ) return;

    pahsc = (PaHostSoundControl *) past->past_DeviceData;
    pahsc->pahsc_NumOutsPlayed += 1;

	SetFramesDone( pahsc,
			pahsc->pahsc_NumFramesDone + pahsc->pahsc_FramesPerHostBuffer );
			
    PaMac_BackgroundManager( past, theCallBackCmd->param1 );
}

/*******************************************************************/
static PaError PaMac_BackgroundManager( internalPortAudioStream   *past, int index )
{
    PaError      result = paNoError;
    PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
    /* Has someone asked us to abort by calling Pa_AbortStream()? */
    if( past->past_StopNow )
    {
        SndCommand  command;
        /* Clear the queue of any pending commands. */
        command.cmd = flushCmd;
        command.param1 = command.param2 = 0;
        SndDoImmediate( pahsc->pahsc_Channel, &command );
        /* Then stop currently playing buffer, if any. */
        command.cmd = quietCmd;
        SndDoImmediate( pahsc->pahsc_Channel, &command );
        past->past_IsActive = 0;
    }
    /* Has someone asked us to stop by calling Pa_StopStream()
     * OR has a user callback returned '1' to indicate finished.
     */
    else if( past->past_StopSoon )
    {
        if( (pahsc->pahsc_NumOutsQueued - pahsc->pahsc_NumOutsPlayed) <= 0 )
        {
            past->past_IsActive = 0; /* We're finally done. */
        }
    }
    else
    {
        PaMac_PlayNext( past, index );
    }
    return result;
}

/*************************************************************************************
** Fill next buffer with sound and queue it for playback.
*/
static void PaMac_PlayNext ( internalPortAudioStream *past, int index )
{
    OSErr                  error;
    long                     result;
    SndCommand               playCmd;
    SndCommand           callbackCmd;
    PaHostSoundControl      *pahsc = (PaHostSoundControl *) past->past_DeviceData;

    /* If this was the last buffer, or abort requested, then just be done. */
    if ( past->past_StopSoon ) goto done;
    
    /* Load buffer with sound. */
    result = PaMac_FillNextOutputBuffer ( past, index );
    if( result > 0 ) past->past_StopSoon = 1; /* Stop generating audio but wait until buffers play. */
    else if( result < 0 ) goto done;
    
    /* Play the next buffer. */
    playCmd.cmd = bufferCmd;
    playCmd.param1 = 0;
    playCmd.param2 = (long) &pahsc->pahsc_SoundHeaders[ index ];
    error = SndDoCommand (pahsc->pahsc_Channel, &playCmd, true );
    if( error != noErr ) goto gotError;
    
    /* Ask for a callback when it is done. */
    callbackCmd.cmd = callBackCmd;
    callbackCmd.param1 = index;
    callbackCmd.param2 = (long)past;
    error = SndDoCommand (pahsc->pahsc_Channel, &callbackCmd, true );
    if( error != noErr ) goto gotError;
    pahsc->pahsc_NumOutsQueued += 1;

    return;

gotError:
    sPaHostError = error;
done:
    return;
}
