// PyEPL: hardware/sound/eplSound.cpp
//
// Copyright (C) 2003-2005 Michael J. Kahana
// Authors: Ian Schleifer, Per Sederberg, Aaron Geller, Josh Jacobs
// URL: http://memory.psych.upenn.edu/programming/pyepl
//
// Distributed under the terms of the GNU Lesser General Public License
// (LGPL). See the license.txt that came with this file.

#include "eplSound.h"

#include <iostream>
using std::cout;
using std::cerr;
using std::endl;

eplSound::eplSound(long recLen, long playLen, int sampRate, int bufSize)
{

  RtAudio *taudio;
  RtAudioDeviceInfo info;
  try {
    taudio = new RtAudio();
  }
  catch (RtError &error) {
    error.printMessage();
    exit(EXIT_FAILURE);
  }

  // must find default input/output device/s
  playDevice = 0;
  recDevice = 0;
  playChans = 0;
  recChans = 0;

  // find out if there are multiple devices
  int devices = taudio->getDeviceCount();

  // Loop and see how many default devices there are
  int numDefault = 0;
  int lastDefault = 0;
  for (int i=1; i<=devices; i++)
  {
    info = taudio->getDeviceInfo(i);
    if (info.isDefault)
    {
      // Increment the default
      numDefault++;

      // set the last default
      lastDefault = i;
    }
  }
  
  // if there is only one, then must do duplex and 2 channels
  if (numDefault == 1)
  {
    // verify that it is duplex
    info = taudio->getDeviceInfo(lastDefault);

    if (info.duplexChannels < 2)
    {
      // Error
      cerr << "The only available device does not support 2 channel full duplex!" << endl;
      isDuplex = 0;
      //exit(EXIT_FAILURE);

      // see if can play or record
      if (info.inputChannels > 0)
      {
	// is recording device
	// set the number of channels
	recChans = info.inputChannels;
	
	// set the recDevice
	recDevice = lastDefault;
      }
      else if (info.outputChannels > 0)
      {
	// is playing device
	playChans = info.outputChannels;
	
	// set the play device
	playDevice = lastDefault;
      }
    }
    else
    {
      // Can do duplex
      isDuplex = 1;
      playDevice = lastDefault;
      playChans = info.duplexChannels;
      recDevice = lastDefault;
      recChans = info.duplexChannels;
    }

  }
  else
  {
    // otherwise will not do duplex
    isDuplex = 0;

    // output is still internal but must find number of input channels
    for (int i=1; i<=devices; i++)
    {
      // get the info for the device
      try {
	info = taudio->getDeviceInfo(i);
      }
      catch (RtError &error) {
	error.printMessage();
	break;
      }

      // is it the default
      if (info.isDefault)
      {
	// see if input or output
	if (info.inputChannels > 0)
	{
	  // is recording device
	  // set the number of channels
	  recChans = info.inputChannels;

	  // set the recDevice
	  recDevice = i;
	}
	if (info.outputChannels > 0)
	{
	  // is playing device
	  playChans = info.outputChannels;

	  // set the play device
	  playDevice = i;
	}

// 	// can break out
// 	break;
      }
    }
  }

  // Make sure found out the number of input/output channels
  if (recChans == 0)
  {
    // No input chans discovered
    cerr << "No default input device with correct channel info was found!" << endl;
    cerr << "You will only be able to record sound." << endl;
      
    //exit(EXIT_FAILURE);
  }
  if (playChans == 0)
  {
    // No output chans discovered
    cerr << "No default output device with correct channel info was found!" << endl;
    cerr << "You will not be able to play sound." << endl;
      
    //exit(EXIT_FAILURE);
  }
  
  // clean up after info request
  delete taudio;
  

  // set some vars to defaults
  sampleRate = sampRate;
  bufferSize = bufSize;
  //device = DEVICE;

  audio = 0;
  playaudio = 0;
  recaudio = 0;


  // allocate space for data
  data = new audioBuffer(recLen,playLen,recChans,playChans,sampleRate);
  
  if (isDuplex)
  {
    // Open the realtime i/o device
    try {
      audio = new RtAudio(playDevice, playChans, recDevice, recChans,
			  FORMAT, sampleRate, &bufferSize, NUM_INTERNAL_BUFF);
    }
    catch (RtError &error) {
      error.printMessage();
      exit(EXIT_FAILURE);
    }
    
    // set the stream callback
    try {
      audio->setStreamCallback(&inout, (void *)data);
    }
    catch (RtError &error) {
      error.printMessage();
      exit(EXIT_FAILURE);
    }
  }
  else
  {
    // Open the realtime i/o device
    if (playChans > 0)
    {
      try {
	playaudio = new RtAudio(playDevice, playChans, 0, 0,
				FORMAT, sampleRate, &bufferSize, NUM_INTERNAL_BUFF);
      }
      catch (RtError &error) {
	error.printMessage();
	exit(EXIT_FAILURE);
      }
    
      // set the stream callback
      try {
	playaudio->setStreamCallback(&playcall, (void *)data);
      }
      catch (RtError &error) {
	error.printMessage();
	exit(EXIT_FAILURE);
      }
    }
    if (recChans > 0)
    {
      // Open the realtime i/o device
      try {
	recaudio = new RtAudio(0, 0, recDevice, recChans,
			       FORMAT, sampleRate, &bufferSize, NUM_INTERNAL_BUFF);
      }
      catch (RtError &error) {
	error.printMessage();
	exit(EXIT_FAILURE);
      }
      // set the stream callback
      try {
	recaudio->setStreamCallback(&reccall, (void *)data);
      }
      catch (RtError &error) {
	error.printMessage();
	exit(EXIT_FAILURE);
      }
    }
  }

  streaming=0;
}

eplSound::~eplSound()
{
  if (isDuplex)
  {
    audio->closeStream();
    delete audio;
  }
  else
  {
    if (playChans>0)
    {
      playaudio->closeStream();
      delete playaudio;
    }
    if (recChans>0)
    {
      recaudio->closeStream();
      delete recaudio;
    }
  }

    delete data;
}

void eplSound::clear()
{
  data->playBuf->clear();
  data->recBuf->clear();
  return;
}

int eplSound::recstart()
{

  // first clear (is there any case where we don't want to do this?)
  data->recBuf->clear();

  // set to recording
  data->recording = 1;

  if (streaming == 0)
  {
    // must start streaming
    startstream();
  }

  return 0;
}

int eplSound::recstop()
{
  // stop recording
  data->recording = 0;

  return 0;
}

int eplSound::startstream()
{
  if (streaming == 0)
  {
    if (isDuplex)
    {
      try {
	audio->startStream();
      }
      catch (RtError &error) {
	error.printMessage();
	exit(EXIT_FAILURE);
      }
    }
    else
    {
      if (playChans>0)
      {
	try {
	  playaudio->startStream();
	}
	catch (RtError &error) {
	  error.printMessage();
	  exit(EXIT_FAILURE);
	}
      }
      if (recChans>0)
      {
	try {
	  recaudio->startStream();
	}
	catch (RtError &error) {
	  error.printMessage();
	  exit(EXIT_FAILURE);
	}
      }
    }
    streaming = 1;
  }

  return 0; 
}

int eplSound::stopstream()
{
  if (streaming == 1)
  {
    if (isDuplex)
    {
      try {
	audio->stopStream();
      }
      catch (RtError &error) {
	error.printMessage();
	exit(EXIT_FAILURE);
      }
    }
    else
    {
      if (playChans>0)
      {
	try {
	  playaudio->stopStream();
	}
	catch (RtError &error) {
	  error.printMessage();
	  exit(EXIT_FAILURE);
	}
      }
      if (recChans>0)
      {
	try {
	  recaudio->stopStream();
	}
	catch (RtError &error) {
	  error.printMessage();
	  exit(EXIT_FAILURE);
	}
      }
    }
    streaming = 0;
    data->recording = 0;
  }

  return 0; 
}

long eplSound::append(MY_TYPE *newdata, long length, int overwrite, float ampFactor)
{
  long i;

  // apply amplification if necessary
  if (ampFactor != 1.0)
  {
    // Apply the factor
    for (i=0;i<length;i++)
    {
      newdata[i]= (MY_TYPE)(newdata[i]*ampFactor);
    }
  }
  // append to the play buffer
  return data->playBuf->append(newdata,length,overwrite);
}

long eplSound::consume(MY_TYPE *newdata, long length)
{
  // consume from the recBuf
  return data->recBuf->consume(newdata,length);
}

int eplSound::getBufferSize()
{
  return bufferSize;
}

long eplSound::getSamplesPlayed()
{
  return data->samplesPlayed;
}

void eplSound::resetSamplesPlayed()
{
  data->samplesPlayed = 0;
}

int eplSound::getRecChans()
{
  return recChans;
}

int eplSound::getPlayChans()
{
  return playChans;
}

int eplSound::getSampleRate()
{
  return sampleRate;
}



int inout(char *buffer, int buffer_size, void *data)
{
  audioBuffer *mydata = (audioBuffer *)data;
  MY_TYPE *mybuffer = (MY_TYPE *) buffer;
  int written = 0;
 
  if (mydata->recording)
  {
    // read in from buffer to recBuf
    mydata->recBuf->append(mybuffer,buffer_size*mydata->recChans);
  }

  // write from playBuf to buffer
  written =  mydata->playBuf->consume(mybuffer,buffer_size*mydata->playChans);
  
  // Append samples played
  mydata->samplesPlayed += written/(mydata->playChans);

  // if we wrote less than buffer, append silence
  if (written < buffer_size * mydata->playChans)
  {
    // append zeros
    memset((void *)&mybuffer[written],0,sizeof(MY_TYPE)*((buffer_size*mydata->playChans)-written));
  }
  
  return 0;
}

int playcall(char *buffer, int buffer_size, void *data)
{
  audioBuffer *mydata = (audioBuffer *)data;
  MY_TYPE *mybuffer = (MY_TYPE *) buffer;
  int written = 0;

  // write from playBuf to buffer
  written =  mydata->playBuf->consume(mybuffer,buffer_size*mydata->playChans);

  // Append samples played
  mydata->samplesPlayed += written/(mydata->playChans);

  // if we wrote less than buffer, append silence
  if (written < buffer_size * mydata->playChans)
  {
    // append zeros
    memset((void *)&mybuffer[written],0,sizeof(MY_TYPE)*((buffer_size*mydata->playChans)-written));
  }
  
  return 0;
}

int reccall(char *buffer, int buffer_size, void *data)
{
  audioBuffer *mydata = (audioBuffer *)data;
  MY_TYPE *mybuffer = (MY_TYPE *) buffer;
 
  if (mydata->recording)
  {
    // read in from buffer to recBuf
    mydata->recBuf->append(mybuffer,buffer_size*mydata->recChans);
  }

  
  return 0;
}


audioBuffer::audioBuffer(long reclen, long playlen, int inrecChans, int inplayChans, int samprate)
{
  // Allocate space for the two buffers
  recBuf = new fifo(reclen*inrecChans*samprate);
  playBuf = new fifo(playlen*inplayChans*samprate);
  
  //chans = numchans;
  recChans = inrecChans;
  playChans = inplayChans;
  rate = samprate;

  recording = 0;
  samplesPlayed = 0;
}

audioBuffer::~audioBuffer()
{
  // clear the buffer memory
  delete recBuf;
  delete playBuf;
}

