//	SDL2.0AudioDevice.cpp - An SDL 2.0 subclass of AudioDevice.
//  ----------------------------------------------------------------------------
//	This file is part of 'NiallsAVLib', base code for any kind of audiovisual
//	apps.
//	Copyright (C) 2012  Niall Moody
//	
//	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 3 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, see <http://www.gnu.org/licenses/>.
//	----------------------------------------------------------------------------

#include "SDL2.0AudioDevice.h"
#include "DebugHeaders.h"
#include "UTF8File.h"

#include <cassert>

using std::wstring;
using std::string;

//------------------------------------------------------------------------------
float *SDL2_0AudioDevice::floatBuffer = 0;

//------------------------------------------------------------------------------
SDL2_0AudioDevice::SDL2_0AudioDevice():
audioSpec(0),
callback(0),
numInputs(0),
numOutputs(2),
samplerate(44100.0),
isRunning(false)
{
	apiList.push_back(L"SDL 2.0");

	inputList.push_back(L"SDL Input");
	outputList.push_back(L"SDL Output");
}

//------------------------------------------------------------------------------
SDL2_0AudioDevice::~SDL2_0AudioDevice()
{
	if(isRunning)
	{
		SDL_PauseAudio(1);

		SDL_CloseAudio();

		delete audioSpec;
		delete [] floatBuffer;
	}
}

//------------------------------------------------------------------------------
void SDL2_0AudioDevice::setCallback(AudioDeviceCallback *val)
{
	callback = val;
}

//------------------------------------------------------------------------------
void SDL2_0AudioDevice::setNumChannels(int inputs, int outputs)
{
	numInputs = inputs;
	numOutputs = outputs;
}

//------------------------------------------------------------------------------
void SDL2_0AudioDevice::setSamplerate(double rate)
{
	samplerate = rate;
}

//------------------------------------------------------------------------------
bool SDL2_0AudioDevice::start()
{
	bool retval = false;
	SDL_AudioSpec *obtainedAudioSpec;

	assert(callback);

	if(!isRunning)
	{
		//Just in case.
		if(audioSpec)
		{
			SDL_CloseAudio();
			delete audioSpec;
			delete [] floatBuffer;
			audioSpec = 0;
			floatBuffer = 0;
		}

		//Fill out our desired audioSpec.
		audioSpec = new SDL_AudioSpec;

		audioSpec->freq = (int)samplerate;
		audioSpec->format = AUDIO_S16SYS;
		audioSpec->channels = (numInputs > numOutputs) ? numInputs : numOutputs;
		audioSpec->samples = 4096; //Is this a sensible value?
		audioSpec->callback = sdlAudioCallback;
		audioSpec->userdata = (void *)this;

		DebugStatement(L"SDL2.0AudioDevice: Desired audioSpec: " << numInputs << L"(in) " << numOutputs << L"(out); samplerate: " << samplerate);

		//Open the soundcard.
		obtainedAudioSpec = new SDL_AudioSpec;
		if(SDL_OpenAudio(audioSpec, obtainedAudioSpec) == -1)
		{
			wstring tempstr;

			UTF8File::charToWstring(string(SDL_GetError()), tempstr);
			DebugStatement(L"SDL2.0AudioDevice: Could not open audio device:" << tempstr);
		}
		else
		{
			//If the audio spec we obtained is different to the one we requested.
			if(obtainedAudioSpec)
			{
				delete audioSpec;
				audioSpec = obtainedAudioSpec;

				numInputs = audioSpec->channels;
				numOutputs = audioSpec->channels;
				samplerate = (double)audioSpec->freq;

				DebugStatement(L"SDL2.0AudioDevice: Obtained audioSpec different to requested.");
			}

			//Create the float buffer.
			floatBuffer = new float[audioSpec->channels * audioSpec->size];

			//Start the audio going.
			SDL_PauseAudio(0);
			isRunning = true;
			retval = true;
		}
	}

	return retval;
}

//------------------------------------------------------------------------------
void SDL2_0AudioDevice::stop()
{
	if(isRunning)
	{
		SDL_PauseAudio(1);
		SDL_CloseAudio();

		delete audioSpec;
		delete [] floatBuffer;

		audioSpec = 0;
		floatBuffer = 0;
		isRunning = false;
	}
}

//------------------------------------------------------------------------------
void SDL2_0AudioDevice::sdlAudioCallback(void *user,
										 uint8_t *buffer,
										 int numBytes)
{
	int i;
	int numChannels;
	int16_t tempint;
	int numSamples;
	SDL2_0AudioDevice *audioDevice = (SDL2_0AudioDevice *)user;

	numChannels = audioDevice->getNumOutputs();
	numSamples = (numBytes/2)/numChannels;

	//Convert the integer audio data to float...
	for(i=0;i<(numSamples * numChannels);++i)
	{
		tempint = buffer[(i*2)];
		tempint += buffer[(i*2)+1] << 8;

		floatBuffer[i] = (float)tempint/32767.0f;
	}

	//...call the actual callback...
	audioDevice->getCallback()->getAudio(floatBuffer, floatBuffer, numSamples);

	//...and convert the float audio back.
	for(i=0;i<(numSamples * numChannels);++i)
	{
		if(floatBuffer[i] > 1.0f)
			floatBuffer[i] = 1.0f;
		else if(floatBuffer[i] < -1.0f)
			floatBuffer[i] = -1.0f;

		tempint = (int16_t)(floatBuffer[i] * 32767.0f);

		buffer[(i*2)] = tempint & 0xFF;
		buffer[(i*2)+1] = tempint >> 8;
	}
}
