/*
    rtcore.c:

    Copyright (C) 2002 matt ingalls, John ffitch

    This file is part of Csound.

    The Csound Library is free software; you can redistribute it
    and/or modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    Csound is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with Csound; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA
*/


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

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

#define TRUE    1
#define FALSE   0

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;
static int      gBufferReady = 0;

#if defined (RTAUDIO) && !defined (PACKAGE)
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 status = 0;

/* Don't use this main routine with a GNU Build System. */
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;
    status = csoundMain(NULL, argc, argv);
    if (O.outfilename!=NULL &&
        (strcmp(O.outfilename,"devaudio") == 0 ||
         strcmp(O.outfilename,"dac") == 0)) {
      /* now just loop filling buffer until done */
      while (status == 0) {
        sleep(1);
      }
    }
    else if (status == 0) {
      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 defined(PACKAGE)
    if (inOutputTime && inOutputTime->mSampleTime) {
#else
    if (inOutputTime && inOutputTime->mSampleTime && status == 0) {
#endif
      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);

#if !defined(PACKAGE)
      status = fillbuffer(1);
#else
        while (!gBufferReady)  usleep(1);
#endif
      while (bytes) {
        memcpy(out, in, gOutFramesize);

        in += gOutNChannels;
        bytes -= framebytes;
        out += channels;
      }
#if defined(PACKAGE)
        gBufferReady = FALSE;
#endif
    }

    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");
#if defined(PACKAGE)
    gBufferReady = FALSE;
#endif

    /* 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.. */

#if defined(PACKAGE)
    while (gBufferReady)  usleep(1);
    memcpy((char *)csoundOutput, outbuf, nbytes);
    gBufferReady = TRUE;
#else
    memcpy((char *)csoundOutput, outbuf, nbytes);
#endif
}

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