/* FluidSynth - A Software Synthesizer
 *
 * Copyright (C) 2003  Peter Hanappe and others.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307, USA
 */

/* fluid_coreaudio.c
 *
 * Driver for the Apple's CoreAudio on MacOS X
 *
 */

#include "fluid_synth.h"
#include "fluid_midi.h"
#include "fluid_adriver.h"
#include "fluid_mdriver.h"
#include "fluid_settings.h"

#include "config.h"

#if COREAUDIO_SUPPORT
#include <AudioToolbox/AudioToolbox.h>
#include <AudioUnit/AudioUnit.h>

/*
 * fluid_core_audio_driver_t
 *
 */
typedef struct {
  fluid_audio_driver_t driver;
	AURenderCallbackStruct inputProc;
	AudioUnit audioUnit;
  AudioStreamBasicDescription format;
  fluid_audio_func_t callback;
  void* data;
  unsigned int buffer_size;
  float* buffers[2];
  double phase;
} fluid_core_audio_driver_t;

fluid_audio_driver_t* new_fluid_core_audio_driver(fluid_settings_t* settings, fluid_synth_t* synth);

fluid_audio_driver_t* new_fluid_core_audio_driver2(fluid_settings_t* settings,
						      fluid_audio_func_t func,
						      void* data);

OSStatus fluid_core_audio_callback(void* inRefCon,
           AudioUnitRenderActionFlags* ioActionFlags,
           const AudioTimeStamp* inTimeStamp,
           UInt32 inBusNumber,
           UInt32 inNumberFrames,
           AudioBufferList* data);

int delete_fluid_core_audio_driver(fluid_audio_driver_t* p);


/**************************************************************
 *
 *        CoreAudio audio driver
 *
 */

void
fluid_core_audio_driver_settings(fluid_settings_t* settings)
{
/*   fluid_settings_register_str(settings, "audio.coreaudio.device", "default", 0, NULL, NULL); */
}

/*
 * new_fluid_core_audio_driver
 */
fluid_audio_driver_t*
new_fluid_core_audio_driver(fluid_settings_t* settings, fluid_synth_t* synth)
{
  return new_fluid_core_audio_driver2(settings,
					  (fluid_audio_func_t) fluid_synth_process,
					  (void*) synth);
}

#define kOutputBus 0
#define kInputBus 1

/*
 * new_fluid_core_audio_driver2
 */
fluid_audio_driver_t*
new_fluid_core_audio_driver2(fluid_settings_t* settings, fluid_audio_func_t func, void* data)
{
  fluid_core_audio_driver_t* dev = NULL;
  OSStatus status;
  double sample_rate;

  fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate);

  if (!fluid_settings_str_equal(settings, "audio.sample-format", "16bits")) {
    FLUID_LOG(FLUID_ERR, "Unhandled sample format");
    return NULL;
  }

  dev = FLUID_NEW(fluid_core_audio_driver_t);
  if (dev == NULL) {
    FLUID_LOG(FLUID_ERR, "Out of memory");
    return NULL;
  }
  FLUID_MEMSET(dev, 0, sizeof(fluid_core_audio_driver_t));

  dev->callback = func;
  dev->data = data;

  // Describe audio component
  AudioComponentDescription desc;
  desc.componentType = kAudioUnitType_Output;
  desc.componentSubType = kAudioUnitSubType_RemoteIO;
  desc.componentFlags = 0;
  desc.componentFlagsMask = 0;
  desc.componentManufacturer = kAudioUnitManufacturer_Apple;

  AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);
  status = AudioComponentInstanceNew(inputComponent, &dev->audioUnit);
  //checkStatus(status);

  UInt32 flag = 1;
  // Enable IO for playback
  status = AudioUnitSetProperty(dev->audioUnit, 
				  kAudioOutputUnitProperty_EnableIO, 
				  kAudioUnitScope_Output, 
				  kOutputBus,
				  &flag, 
				  sizeof(flag));
  if (status != noErr) {
    FLUID_LOG(FLUID_ERR, "Failed to get kAudioOutputUnitProperty_EnableIO");
    goto error_recovery;
  }

  // Describe format

  AudioStreamBasicDescription audioFormat;
  audioFormat.mSampleRate = sample_rate;
  audioFormat.mFormatID	= kAudioFormatLinearPCM;
  audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
  audioFormat.mFramesPerPacket = 1;
  audioFormat.mChannelsPerFrame = 2;
  audioFormat.mBitsPerChannel = 16;
  audioFormat.mBytesPerPacket = 4;
  audioFormat.mBytesPerFrame = 4;
  dev->format = audioFormat;

  status = AudioUnitSetProperty(dev->audioUnit, 
				  kAudioUnitProperty_StreamFormat, 
				  kAudioUnitScope_Input, 
				  kOutputBus, 
				  &audioFormat, 
				  sizeof(audioFormat));
  if (status != noErr) {
    FLUID_LOG(FLUID_ERR, "Failed to get kAudioUnitProperty_StreamFormat");
    goto error_recovery;
  }

  // Set output callback
  AURenderCallbackStruct callbackStruct;
  callbackStruct.inputProc = fluid_core_audio_callback;
  callbackStruct.inputProcRefCon = dev;
  status = AudioUnitSetProperty(dev->audioUnit, 
				  kAudioUnitProperty_SetRenderCallback, 
				  kAudioUnitScope_Global, 
				  kOutputBus,
				  &callbackStruct, 
				  sizeof(callbackStruct));

dev->buffer_size = 4096;//FIXME

  dev->buffers[0] = FLUID_ARRAY(float, dev->buffer_size);
  dev->buffers[1] = FLUID_ARRAY(float, dev->buffer_size);

  status = AudioUnitInitialize(dev->audioUnit);
  status = AudioOutputUnitStart(dev->audioUnit);

  return (fluid_audio_driver_t*) dev;

error_recovery:

  delete_fluid_core_audio_driver((fluid_audio_driver_t*) dev);
  return NULL;
}

/*
 * delete_fluid_core_audio_driver
 */
int
delete_fluid_core_audio_driver(fluid_audio_driver_t* p)
{
  fluid_core_audio_driver_t* dev = (fluid_core_audio_driver_t*) p;

  if (dev == NULL) {
    return FLUID_OK;
  }

  AudioOutputUnitStop(dev->audioUnit);

  if (dev->buffers[0]) {
    FLUID_FREE(dev->buffers[0]);
  }
  if (dev->buffers[1]) {
    FLUID_FREE(dev->buffers[1]);
  }

  FLUID_FREE(dev);

  return FLUID_OK;
}

OSStatus
fluid_core_audio_callback(void* inRefCon,
           AudioUnitRenderActionFlags* ioActionFlags,
           const AudioTimeStamp* inTimeStamp,
           UInt32 inBusNumber,
           UInt32 inNumberFrames,
           AudioBufferList* out)
{
  int i, k;
  fluid_core_audio_driver_t* dev = (fluid_core_audio_driver_t*) inRefCon;
  int len = dev->buffer_size / dev->format.mBytesPerFrame;
len = out->mBuffers[0].mDataByteSize / dev->format.mBytesPerFrame;
  SInt16* buffer = out->mBuffers[0].mData;

  if (dev->callback)
  {
    float* left = dev->buffers[0];
    float* right = dev->buffers[1];

    (*dev->callback)(dev->data, len, 0, NULL, 2, dev->buffers);

    for (i = 0, k = 0; i < len; i++) {
      buffer[k++] = 32768*left[i];
      buffer[k++] = 32768*right[i];
//      buffer[k++] = left[i];
//      buffer[k++] = right[i];
    }
  }
  else fluid_synth_write_s16((fluid_synth_t*) dev->data, len, buffer, 0, 2,
			       buffer, 1, 2);
  return noErr;
}


#endif /* #if COREAUDIO_SUPPORT */
