
/*
 *  MacCoreAudio.c
 *  csound
 *
 *  Created by ma++ on Thu Mar 07 2002.
 *
 */

#include <CoreAudio/CoreAudio.h>
#include "cs.h"

static AudioDeviceID gInputDevice;
static AudioDeviceID gOutputDevice;
static AudioStreamBasicDescription gInputDesc;
static AudioStreamBasicDescription gOutputDesc;
static AudioDeviceIOProc gInputProc = NULL;
static AudioDeviceIOProc gOutputProc = NULL;
static long gOutNChannels = 0;
static long gOutFramesize = 0;

extern int csoundMain(void*,int argc, char **argv);
extern int runincomponents;
extern int fillbuffer(int sensEventRate);
extern int cleanup(void);
extern OPARMS  O_;
extern GLOBALS cglob_;
extern void remove_tmpfiles(void);

int run_status = 0;
#ifdef never
int main(int argc, char **argv)
{
    O = O_;
    cglob = cglob_;
    atexit(remove_tmpfiles);
    /* call main in main.c - will only do the initialization stuff */
    /* we call back into the main inside our audio callback */
    runincomponents = 1;
    run_status = csoundMain(NULL, argc, argv);
    if (strcmp(O.outfilename,"devaudio") == 0 ||
        strcmp(O.outfilename,"dac") == 0) {
      /* now just loop filling buffer until done */
      while (run_status == 0) {
        sleep(1);
      }
    }
    else {
      while(!csoundPerformKsmps(NULL));
    }
    cleanup();
    return 0;
}
#endif

/*****************************************************************************
 *	CoreAudio Callbacks
 ****************************************************************************/


/* our callback routine. */
static OSStatus CoreAudioInputProc(AudioDeviceID 		inDevice,
                                   const AudioTimeStamp*	inNow,
                                   const AudioBufferList*	inInputData,
                                   const AudioTimeStamp*	inInputTime,
                                   AudioBufferList*		outOutputData,
                                   const AudioTimeStamp*	inOutputTime,
                                   void*			inClientData)
{
    /*	int i; */

    /* consume input */
    /*  if (inInputTime->mSampleTime) */

    return kAudioHardwareNoError;

}


/* our callback routine. */
void *csoundOutput = NULL;
void *inBuf = NULL;
long bufSize = 0;
static OSStatus CoreAudioOutputProc(AudioDeviceID 		inDevice,
                                    const AudioTimeStamp*	inNow,
                                    const AudioBufferList*	inInputData,
                                    const AudioTimeStamp*	inInputTime,
                                    AudioBufferList*		outOutputData,
                                    const AudioTimeStamp*	inOutputTime,
                                    void*			inClientData)
{
    /* produce output */
    if (inOutputTime && inOutputTime->mSampleTime && run_status == 0) {
      float *in = csoundOutput;
      float *out = outOutputData->mBuffers[0].mData;
      long channels = outOutputData->mBuffers[0].mNumberChannels;
      long bytes = outOutputData->mBuffers[0].mDataByteSize;
      long framebytes = channels*sizeof(float);

      run_status = fillbuffer(1);

      while (bytes) {
        memcpy(out, in, gOutFramesize);

        in += gOutNChannels;
        bytes -= framebytes;
        out += channels;
      }
    }

    return kAudioHardwareNoError;

}


/*****************************************************************************
 *	RealTime Audio Hooks
 ****************************************************************************/

/* initialize our output -- this is a simple implementation:                */
/* - csound only uses the default devices [given in the sound control panel]*/
/* - no coreaudio notifications are used, so csound is not very friendly to */
/*      other applications and may even have problems if the device's       */
/*      settings are changed elsewhere                                      */
/* - the device's buffer size is set equal to csound's, if the device does  */
/*      not support that size, we exit.                                     */
/* - ksmps must be smaller than the buffer size                             */

#include "soundio.h"
void playopen_(int nchanls, int dsize, float sr, int scale)
{                               /* open for audio output */
    OSStatus err = kAudioHardwareNoError;
    AudioHardwarePropertyID propID;
    UInt32 size, bufSize = 0;
    AudioStreamBasicDescription *stream;
    AudioBufferList *bufList;
    Boolean outWritable;

    O.outformat = AE_FLOAT;     /* Must have floats to work */
    dsize = O.outsampsiz = getsizformat(O.outformat);
    gOutNChannels = nchanls;
    gOutFramesize = gOutNChannels*dsize;

    /* make sure our buffer is bigger than ksmps */
    if (ksmps > O.outbufsamps) {
      printf("Error: Your current settings are: -b%d with ksmps = %d\n",
             O.outbufsamps, ksmps);
      die("The audio buffersize (-b) cannot be smaller than orchestra's ksmps.\n ");
    }

    /* get info on our default output device */
    size = sizeof(AudioDeviceID);
    propID = kAudioHardwarePropertyDefaultOutputDevice;
    err = AudioHardwareGetProperty(propID, &size, &gOutputDevice);
    if (err != kAudioHardwareNoError) {
      die("Error Getting Default Output Device\n");
    }
    size = sizeof(AudioStreamBasicDescription);
    err =  AudioDeviceGetProperty(gOutputDevice, 0, 0,
                                  kAudioDevicePropertyStreamFormat,
                                  &size, &gOutputDesc);
    if (err != kAudioHardwareNoError) {
      die("Error Getting Output Device Description\n");
    }


    /* change the buffersize to fit this csound job */
    size = sizeof(UInt32);
    bufSize = gOutputDesc.mChannelsPerFrame*dsize*(O.outbufsamps/nchanls);
    err = AudioDeviceSetProperty(gOutputDevice, NULL, 0, 0,
                                 kAudioDevicePropertyBufferSize,
                                 size, &bufSize);
    if (err != kAudioHardwareNoError) {
        die("Error Setting CoreAudio's Buffersize\n");
    }


    /* allocate our copy-to buffer */
    csoundOutput = calloc(O.outbufsamps*dsize, 1);
    if (!csoundOutput)
      die("Error Creating Output Buffer\n");


    /* startup the output device */
    gOutputProc = CoreAudioOutputProc;
    err = AudioDeviceAddIOProc(gOutputDevice, gOutputProc, NULL);
    if (err != kAudioHardwareNoError) {
      die("Error Adding Output Device Callback\n");
    }
    err = AudioDeviceStart(gOutputDevice, gOutputProc);
    if (err != kAudioHardwareNoError) {
      die("Error Starting Output Device\n");
    }
}

/* initialize our input -- we make the assumption that playopen() has already */
/* been called, so we skip some of the safety checks */
void recopen_(int nchanls, float sr, int scale)
{
    OSStatus err = kAudioHardwareNoError;
    AudioHardwarePropertyID propID;
    UInt32 size = 0;

    /* now get info on our default input device */
    propID = kAudioHardwarePropertyDefaultInputDevice;
    size = sizeof(AudioDeviceID);
    err = AudioHardwareGetProperty(propID, &size, &gInputDevice);
    if (err != kAudioHardwareNoError) {
        die("Error Getting Default Input Device\n");
    }
    size = sizeof(AudioStreamBasicDescription);
    err =  AudioDeviceGetProperty(gInputDevice, 0, 1,
                                  kAudioDevicePropertyStreamFormat,
                                  &size, &gInputDesc);
    if (err != kAudioHardwareNoError) {
      die("Error Getting Input Device Description\n");
    }

    /* if the input device is the same as the output device, then the input */
    /*   is handled in the same callback as the output, otherwise we need   */
    /*   to startup the input device.                                       */
    if (gInputDevice != gOutputDevice) {
      gInputProc = CoreAudioInputProc;
      err = AudioDeviceAddIOProc(gInputDevice, gInputProc, NULL);
      if (err != kAudioHardwareNoError) {
        die("Error Adding Input Device Callback\n");
      }
      err = AudioDeviceStart(gInputDevice, gInputProc);
      if (err != kAudioHardwareNoError) {
        die("Error Starting Input Device\n");
      }
    }
}

void rtplay_(char *outbuf, int nbytes)
{
    /* if we make sure -b == coreaudio's output stream buffer size */
    /* and ksmps <= -b, then we dont have to do anything here. */
    /* a better, more general circular-buffer approach would be better.. */

    memcpy((char *)csoundOutput, outbuf, nbytes);
}

int rtrecord_(char *inbuf, int nbytes)
{
    /*	if (gData.ReadAudio) */
    /*		return (*gData.ReadAudio)(gData.owner, inbuf, nbytes); */
    /*	else */
    return(0);
}

void rtclose_(void)              /* close the I/O device entirely  */
{
    OSStatus err = kAudioHardwareNoError;

    if (gInputDevice) {
      err = AudioDeviceStop(gInputDevice, gInputProc);
      if (err != kAudioHardwareNoError) {
        die("Error Stopping Input Device\n");
      }
    }
    if (gOutputDevice) {
      err = AudioDeviceStop(gOutputDevice, gOutputProc);
      if (err != kAudioHardwareNoError) {
        die("Error Stopping Output Device\n");
      }
    }

    free(csoundOutput);
}
