/* -*- c-file-style: "gnu" -*-
    TiMidity++ -- MIDI to WAVE converter and player
    Copyright (C) 1999-2002 Masanao Izumo <mo@goice.co.jp>
    Copyright (C) 1995 Tuukka Toivonen <tt@cgs.fi>
    ALSA 0.[56] support by Katsuhiro Ueno <katsu@blue.sky.or.jp>

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

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    darwin_a.c

    Functions to play sound on the Darwin audio driver
    by T.Nogami <t-nogami@happy.email.ne.jp>
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#ifdef AU_DARWIN

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#ifndef NO_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#include <CoreAudio/AudioHardware.h>

#include "timidity.h"
#include "common.h"
#include "output.h"
#include "controls.h"
#include "timer.h"
#include "instrum.h"
#include "playmidi.h"
#include "miditrace.h"

static int open_output(void); /* 0=success, 1=warning, -1=fatal error */
static void close_output(void);
static int32 output_data(const uint8 *buf, int32 in_bytes);
static int acntl(int request, void *arg);
static off_size_t total_bytes;
float output_volume = 1.0;

int	    mac_buf_using_num;

/* export the playback mode */

/*********************************************************************/
#define dpm darwin_play_mode

PlayMode dpm = {
  DEFAULT_RATE, PE_16BIT | PE_SIGNED, PF_PCM_STREAM | PF_CAN_TRACE,
  -1,
  { 0 }, /* default: get all the buffer fragments you can */
  "Mac OS X pcm device", 'd',
  NULL,
  open_output,
  close_output,
  output_data,
  acntl
};

#define FailWithAction(cond, action, handler)				\
	if (cond) {							\
		{ action; }						\
		goto handler;						\
	}

//#define DEBUG_DARWIN_A(x)   /*nothing*/
#define DEBUG_DARWIN_A(x)   ctl->cmsg x;

/*********************************************************************/

typedef struct {
    Boolean		soundPlaying;
    AudioDeviceID	device;			// the default device
    UInt32	        deviceBufferSize;
                  // bufferSize returned by kAudioDevicePropertyBufferSize
    AudioStreamBasicDescription		deviceFormat;
                  // info about the default device
#define BUFNUM 256
#define BUFLEN 4096
    char             buffer[BUFNUM][BUFLEN];   //1 buffer size = 4096bytes
    unsigned short   buffer_len[BUFNUM];
    volatile int     currBuf, nextBuf;
    int		     samples, get_samples;
} appGlobals, *appGlobalsPtr, **appGlobalsHandle;

static appGlobals	globals;


/*********************************************************************/
OSStatus appIOProc(AudioDeviceID  inDevice, const AudioTimeStamp *inNow,
                    const AudioBufferList *inInputData,
		    const AudioTimeStamp *inInputTime,
                    AudioBufferList *outOutputData,
		    const AudioTimeStamp *inOutputTime, void *dummy)
{
    int       next_curr;
    int       trans_len;

    if (globals.currBuf == globals.nextBuf) {  //end of play
        outOutputData->mNumberBuffers = 0;
        memset(outOutputData->mBuffers[0].mData, 0, globals.deviceBufferSize);
        globals.soundPlaying = 0;
        return 0;
    }

    trans_len = globals.buffer_len[globals.currBuf];
    if (output_volume == 1.0) {
        memcpy(outOutputData->mBuffers[0].mData,
             &globals.buffer[globals.currBuf][0],
             trans_len);   // move data into output data buffer
    } else {
        float   *src = (float*)&globals.buffer[globals.currBuf][0],
                *dst = outOutputData->mBuffers[0].mData;
        int     i, quant = trans_len/sizeof(float);
        for (i = 0; i < quant; i++) {
            *dst++ = (*src++)*output_volume;
        }
    }
    outOutputData->mBuffers[0].mDataByteSize = trans_len;

    outOutputData->mNumberBuffers = 1;
    globals.samples += trans_len
                              / globals.deviceFormat.mBytesPerPacket;

    next_curr = globals.currBuf + 1;
    next_curr %= BUFNUM;
    mac_buf_using_num--;
    globals.currBuf = next_curr;

    return 0; //no err
}

/*********************************************************************/
static void init_variable()
{
    globals.samples = globals.get_samples = 0;
    globals.currBuf = globals.nextBuf = 0;
    globals.soundPlaying = 0;
    mac_buf_using_num = 0;
}

/*********************************************************************/
/*return value == 0 sucess
 *             == -1 fails
 */
static int open_output(void)
{
    OSStatus				err = 0; //no err
    UInt32				count,
                                        bufferSize;
    AudioDeviceID			device = kAudioDeviceUnknown;
    AudioStreamBasicDescription		format;

    if (!dpm.name) {
        dpm.name = safe_strdup("darwin");
    }

    // get the default output device for the HAL
    count = sizeof(globals.device);
    // it is required to pass the size of the data to be returned
    err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
                                   &count, (void*) &device);
    if (err != 0) goto Bail;

    // get the buffersize that the default device uses for IO
    count = sizeof(globals.deviceBufferSize);
              // it is required to pass the size of the data to be returned
    err = AudioDeviceGetProperty(device, 0, 0, kAudioDevicePropertyBufferSize,
                                 &count, &bufferSize);
    if (err != 0) goto Bail;

    if (globals.deviceBufferSize>BUFLEN) {
        fprintf(stderr, "globals.deviceBufferSize NG: %ld\n",
                globals.deviceBufferSize);
        exit(1);
    }

         // get a description of the data format used by the default device
    count = sizeof(globals.deviceFormat);
         // it is required to pass the size of the data to be returned
    err = AudioDeviceGetProperty(device, 0, 0,
                                 kAudioDevicePropertyStreamFormat,
                                 &count, &format);
    if (err != 0) goto Bail;
    FailWithAction(format.mFormatID != kAudioFormatLinearPCM, err = -1, Bail);
                    // bail if the format is not linear pcm

    // everything is ok so fill in these globals
    globals.device = device;
    globals.deviceBufferSize = bufferSize;
    globals.deviceFormat = format;
    init_variable();

    err = AudioDeviceAddIOProc(globals.device, appIOProc, 0);
                    // setup our device with an IO proc
    if (err != 0) goto Bail;

    globals.deviceFormat.mSampleRate = dpm.rate;

#if 0
    globals.deviceFormat.mFormatFlags =  kLinearPCMFormatFlagIsBigEndian
                                       | kLinearPCMFormatFlagIsPacked
                                       | kLinearPCMFormatFlagIsSignedInteger;
    globals.deviceFormat.mBytesPerPacket = 4;
    globals.deviceFormat.mBytesPerFrame = 4;
    globals.deviceFormat.mBitsPerChannel = 0x10;

    err = AudioDeviceSetProperty(device, &inWhen, 0, 0,
                                 kAudioDevicePropertyStreamFormat,
                                 count, &globals.deviceFormat);
    if (err != 0) goto Bail;
#endif

#if 0
    fprintf(stderr, "deviceBufferSize = %d\n", globals.deviceBufferSize);
    fprintf(stderr, "mSampleRate = %g\n", globals.deviceFormat.mSampleRate);
    fprintf(stderr, "mFormatID = 0x%08x\n", globals.deviceFormat.mFormatID);
    fprintf(stderr, "mFormatFlags = 0x%08x\n",
            globals.deviceFormat.mFormatFlags);
    fprintf(stderr, "mBytesPerPacket = 0x%08x\n",
            globals.deviceFormat.mBytesPerPacket);
    fprintf(stderr, "mBytesPerFrame = 0x%08x\n",
            globals.deviceFormat.mBytesPerFrame);
    fprintf(stderr, "mBitsPerChannel = 0x%08x\n",
            globals.deviceFormat.mBitsPerChannel);
#endif

Bail:
    return (err);
}

/*********************************************************************/
static int32 output_data(const uint8 *buf, int32 in_bytes)
{
    OSStatus		err = 0;
    size_t      next_nextbuf, out_bytes;
    size_t      inBytesPerQuant, max_quant, max_outbytes, out_quant, i;
    float       maxLevel;


    //quant  : 1 value
    //packet : 1 pair of quant(stereo data)

    if (dpm.encoding & PE_16BIT) {
            inBytesPerQuant = 2;
            maxLevel = 32768.0;
    } else if (dpm.encoding & PE_24BIT) {
            inBytesPerQuant = 3;
            maxLevel = 8388608.0;
    } else {
            ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
                "Sorry, not support 8bit sound.");
            exit(1);
    }

    max_outbytes = globals.deviceBufferSize;
    max_quant    = max_outbytes / sizeof(float);


 redo:
    out_quant = in_bytes/inBytesPerQuant;
    out_bytes = out_quant * sizeof(float);
    if (out_bytes > max_outbytes) {
        out_bytes = max_outbytes;
    }
    out_quant = out_bytes/sizeof(float);
    out_quant &= 0xfffffffe;  //trunc to eaven number for stereo
    out_bytes = out_quant * sizeof(float);

    next_nextbuf = globals.nextBuf + 1;
    next_nextbuf %= BUFNUM;

    while (globals.currBuf == next_nextbuf) { //queue full
        usleep(100000); //0.1sec
    }

    switch (inBytesPerQuant) {

      case 2:
        for (i = 0; i < out_quant; i++) {
            ((float*)(globals.buffer[globals.nextBuf]))[i] =
                              ((short*)buf)[i]/maxLevel;
        }
	   break;

      case 3:
	    for (i = 0; i < out_quant; i++) {
	              ((float*)(globals.buffer[globals.nextBuf]))[i] =
                             (*(int32*)&buf[i * 3] >> 8) / maxLevel;
	    }
	    break;
    }

    globals.buffer_len[globals.nextBuf] = out_bytes;

    if (globals.soundPlaying == 0) {
      err = AudioDeviceStart(globals.device, appIOProc);
      if (err != 0) goto Bail;
                        // start playing sound through the device
      globals.soundPlaying = 1;   // set the playing status global to true

    }



    globals.nextBuf = next_nextbuf;


    in_bytes -= out_quant * inBytesPerQuant;
    buf += out_quant * inBytesPerQuant;
    mac_buf_using_num++;
    globals.get_samples += out_bytes/globals.deviceFormat.mBytesPerPacket;

    if (in_bytes) {
        goto redo;
    }

 Bail:
    return (err);
}

/*********************************************************************/
static void close_output(void)
{
    OSStatus 	err = 0;

    err = AudioDeviceStop(globals.device, appIOProc);
                            // stop playing sound through the device
    if (err != 0) goto Bail;

    err = AudioDeviceRemoveIOProc(globals.device, appIOProc);
                       // remove the IO proc from the device
    if (err != 0) goto Bail;

    globals.soundPlaying = 0;
                    // set the playing status global to false
Bail:;
}

/*********************************************************************/
static int acntl(int request, void *arg)
{
    switch (request) {
      case PM_REQ_GETFRAGSIZ:
	if (dpm.encoding & PE_24BIT) {
	  *((int*)arg) = 3072;
	} else {
	  *((int*)arg) = BUFLEN;
	}
	return 0;


    case PM_REQ_GETSAMPLES:
          *((int*)arg) = globals.samples;
          return 0;

    case PM_REQ_DISCARD:
          AudioDeviceStop(globals.device, appIOProc);
	  init_variable();
          return 0;

    case PM_REQ_PLAY_START:
	init_variable();
	return 0;

    case PM_REQ_FLUSH:
    case PM_REQ_OUTPUT_FINISH:
        while (globals.soundPlaying) {
	    trace_loop();
	    usleep(1000);
	}
	init_variable();
	return 0;

    default:
        break;
    }
    return -1;
}

#endif /* AU_DARWIN */
