/*
* audio.cc
* DIN Is Noise is copyright (c) 2006-2022 Jagannathan Sampath
* DIN Is Noise is released under GNU Public License 2.0
* For more information, please visit https://dinisnoise.org/
*/


#include "main.h"
#include "audio.h"
#include "chrono.h"
#include "command.h"
#include <math.h>
#include <fstream>
#include <cstring>
#include <limits.h>
#include <stdlib.h>
#include <string>
#include "utils.h"
#include "RtError.h"
#include "log.h"
#include "console.h"
using namespace std;

extern string user_data_dir;
extern string APP_NAME;

#if defined __UNIX_JACK__
	#define SOUND_API RtAudio::UNIX_JACK
#elif defined __LINUX_ALSA__
  #define SOUND_API RtAudio::LINUX_ALSA
#elif defined __MACOSX_CORE__
  #define SOUND_API RtAudio::MACOSX_CORE
#elif defined __WINDOWS_DS__
  #define SOUND_API RtAudio::WINDOWS_DS
#endif

audio_out::audio_out () : dac (SOUND_API) {

  samples_buffers = 0;
	available = 0;
  result = ams = fms = vol = gatr = mix = mixa = bufL = bufR = fdr1 = fdr2 = 0;

  prefs_name = user_data_dir + "audio_prefs";
  load_prefs ();

  probe ();
	open ();

}

audio_out::~audio_out () {

  save_prefs ();

  int num_ptrs = 11;
  sample_t* ptr [] = {samples_buffers, result, ams, fms, vol, gatr, mix, bufL, bufR, fdr1, fdr2, mixa};
  for (int i = 0; i < num_ptrs; ++i) if (ptr[i]) delete[] ptr[i];

  if (available) delete[] available;

  dlog << "--- destroyed audio ---" << endl;

}

void audio_out::alloc () {

  // alloc memory for various buffers
  //

  samples_per_buffer = num_channels * samples_per_channel;
  samples_buffer_size = sizeof (sample_t) * samples_per_buffer;
  samples_channel_size = sizeof (sample_t) * samples_per_channel;

  if (samples_buffers) delete[] samples_buffers;
  samples_buffers = new sample_t [num_samples_buffers * samples_per_buffer];

  if (available) delete[] available;
  available = new int [num_samples_buffers];
	memset (available, 0, num_samples_buffers * sizeof (int));

  readi = writei = 0;
  readp = writep = samples_buffers;

  int num_ptrs = 11;
  sample_t** ptr [] = {&result, &ams, &fms, &vol, &gatr, &mix, &bufL, &bufR, &fdr1, &fdr2, &mixa};
  for (int i = 0; i < num_ptrs; ++i) {
    if (ptr[i]) delete[] *ptr[i];
    *ptr[i] = new sample_t [samples_per_channel];
		memset (*ptr[i], 0, samples_channel_size);
  }

}

int audio_out::open () {
  return open (current_device, sample_rate, samples_per_channel);
}

int audio_out::open (int id, unsigned int sr, unsigned int spc) {

  RtAudio::StreamParameters parameters;
  if (id > -1 && id < num_devices) {
		parameters.deviceId = id;
  } else {
    id = default_device;
    parameters.deviceId = id;
	}

  current_device = id;

  // current sample rate exists?
  find_sample_rate_id (sr);
  sr = infos[id].sampleRates[i_sample_rate];

  parameters.nChannels = num_channels;

  RtAudio::StreamOptions options;
  options.flags = RTAUDIO_NONINTERLEAVED;

  try {
    dac.openStream (&parameters, 0, RTAUDIO_FLOAT32, sr, &spc, audio_wanted, 0, &options);
		set_samples_per_channel (spc);
    alloc ();
    set_sample_rate (sr);
    dlog << "+++ opened audio stream +++" << endl;
		return 1;
  } catch (RtError& e) {
    dlog << "!!! couldnt open audio stream !!!" << endl;
  }
  return 0;

}

int audio_out::start () {
  try {
    dac.startStream ();
    dlog << "+++ " << APP_NAME << " is now streaming audio +++" << endl;
  } catch (RtError& e) {
    dlog << "!!! couldnt start the audio stream !!!" << endl;
  }
  return 0;
}

int audio_out::close () {
  if (dac.isStreamOpen ()) {
    dac.stopStream ();
    dac.closeStream ();
    dlog << "+++ stopped and closed audio stream +++" << endl;
    return 1;
  }
  return 0;
}

void audio_out::load_prefs () {
  //
  // load audio preferences from disk
  //

  ifstream file (prefs_name.c_str(), ios::in);
  string ignore;

  if (!file) {
		dlog << "!!! bad audio_prefs file. sorry, have to abort !!!" << endl;
		exit (1);
	} else {
		file >> ignore >> current_device;
		file >> ignore >> num_channels;
		int spc; file >> ignore >> spc; set_samples_per_channel (spc);
		file >> ignore >> sample_rate;
		file >> ignore >> num_samples_buffers;
  }
}

void audio_out::save_prefs () {
  ofstream file (prefs_name.c_str(), ios::out);
  if (file) {
    if (current_device == default_device) current_device = -1;
    file << "current_device " << current_device << endl;
    file << "num_channels " << num_channels << endl;
    file << "samples_per_channel " << samples_per_channel << endl;
    file << "sample_rate " << sample_rate << endl;
    file << "num_samples_buffers " << num_samples_buffers << endl;
    dlog << "+++ saved audio prefrences in " << prefs_name << " +++" << endl;
  } else dlog << "!!! couldnt write audio preferences file: " << prefs_name << " !!!" << endl;
}

void audio_out::set_samples_per_channel (int spc) {
	samples_per_channel = spc;
	last_sample = samples_per_channel - 1;
}

void audio_out::set_sample_rate (int s) {
	SAMPLE_RATE = sample_rate = s;
	SAMPLE_DURATION = 1.0f / SAMPLE_RATE;
	clk.delta_ticks = samples_per_channel;
	clk.delta_secs = samples_per_channel * SAMPLE_DURATION;
}

int audio_out::audio_wanted (void *ob, void *ib, unsigned int spc, double t, RtAudioStreamStatus status, void *data) {

  // stream audio buffer to audio card
	//

  if (aout.available[aout.readi]) { // samples buffer written by main thread ready for streaming
    memcpy (ob, aout.readp, aout.samples_buffer_size); // copy written buffer into audio card

    // update samples buffer status to let main thread write
    aout.available [aout.readi] = 0;
    if (++aout.readi >= aout.num_samples_buffers) {
      aout.readi = 0;
      aout.readp = aout.samples_buffers;
    } else aout.readp += aout.samples_per_buffer;

    ++clk; // update audio clock
  }
  return 0;

}

void audio_out::probe () {

  names.clear ();
  num_devices = dac.getDeviceCount ();
  if (num_devices == 0) {
    dlog << "!!! no audio devices found !!!" << endl;
#ifdef __UNIX_JACK__
		cout << "!!! Please start JACK before running DIN Is Noise !!!" << endl;
		cout << "!!! DIN Is Noise aborted !!!" << endl;
		exit (1);
#endif
  } else {
		last_device = num_devices - 1;
    dlog << "+++ found " << num_devices << " audio devices +++" << endl;
		infos.resize (num_devices);
    default_device = dac.getDefaultOutputDevice ();
		dlog << "+++ default output device = " << default_device << " +++" << endl;
		if (current_device == INVALID) current_device = default_device;
		next_device = current_device;
    for (int i = 0; i < num_devices; ++i) {
			RtAudio::DeviceInfo& info = infos[i];
      info = dac.getDeviceInfo (i);
      names.push_back (info.name);
			char br = '[';
			dlog << "name = " << info.name << " input [default?] = " << info.inputChannels << br << info.isDefaultInput << "], output[default?] = " << info.outputChannels << br << info.isDefaultOutput << "] duplex = " << info.duplexChannels << spc << endl;
			dlog << "sample rates: ";
			for (int i = 0, j = info.sampleRates.size (); i < j; ++i) {
				dlog << info.sampleRates[i] << spc;
			}
			dlog << endl;
    }
  }

}

int audio_out::goto_next_device (int i) {
	next_device = current_device + i;
	wrap (0, next_device, last_device);
	if (infos[next_device].outputChannels == 0) {
		cons << RED << names[next_device] << " has no output channels" << eol;
		return 0;
	}
	current_device = next_device;
	find_sample_rate_id (sample_rate);
	return 1;
}

void audio_out::find_sample_rate_id (unsigned int sr) {
  i_sample_rate = 0;
  std::vector<unsigned int> srates = infos[current_device].sampleRates;
  for (int i = 0, j = srates.size (); i < j; ++i) {
    if (sr == srates[i]) {
      i_sample_rate = i;
      break;
    }
  }
}

int audio_out::goto_next_sample_rate_id (int i) {
  i_sample_rate += i;
  vector<unsigned int> srates = infos[current_device].sampleRates;
  int nrates = srates.size ();
  if (i_sample_rate < 0) i_sample_rate = nrates - 1; else if (i_sample_rate >= nrates) i_sample_rate = 0;
  return srates[i_sample_rate];
}

void audio_out::list () {
	default_device = dac.getDefaultOutputDevice ();
	for (int i = 0; i < num_devices; ++i) {
		RtAudio::DeviceInfo& info = infos[i];
		cons << GREEN << "name = " << info.name << " input = " << info.inputChannels <<  " output = " << info.outputChannels \
				 << " duplex = " << info.duplexChannels << eol;
		cons << CYAN << "  sample rates: ";
		for (int i = 0, j = info.sampleRates.size (); i < j; ++i) cons << int (info.sampleRates[i]) << spc;
		cons << eol;
	}
	cons << YELLOW << "number of devices = " << num_devices << eol;
	cons << "final device = " << last_device << ", next device = " << next_device << eol;
	cons << "default device = " << default_device << ", current device = " << current_device << eol;
}
