//	EXCOSCSounder.cpp - A Sounder which plays StreamingSounds.
//  ----------------------------------------------------------------------------
//	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 "sound/SoundFileLoader.h"
#include "EXCOSCSounder.h"
#include "DebugHeaders.h"

#include <cassert>
#include <cmath>

using std::wstring;
using std::vector;
using std::map;

//------------------------------------------------------------------------------
EXCOSCSounder::EXCOSCSounder():
Thread("EXCOSCSounder"),
music(-1),
delayToggle(false),
granulatorToggle(false),
distGain(4.0f),
distToggle(false),
timeToStop(false)
{
	granulator.setMix(0.5f);
	granulator.setMaxBlockSize(44100*2);

	startThread(this);
}

//------------------------------------------------------------------------------
EXCOSCSounder::~EXCOSCSounder()
{
	map<wstring, PreLoadedBuffer>::iterator it;

	timeToStop = true;
	Thread::wait(300);

	for(it=preLoadedBuffers.begin();
		it!=preLoadedBuffers.end();
		++it)
	{
		delete [] it->second.buffer;
	}
}

//------------------------------------------------------------------------------
void EXCOSCSounder::getAudio(float *input, float *output, int numSamples)
{
	int i;
	float distLevel(1.0f/distGain);

	//Clear the buffer, just in case.
	for(i=0;i<(numSamples*2);++i)
		output[i] = 0.0f;
	
	for(i=0;i<NumSounds;++i)
	{
		if(sounds[i].getActive())
			sounds[i].getAudio(output, numSamples);
	}

	//Process the granulator.
	if(granulatorToggle)
		granulator.processBlock(output, numSamples);

	//Apply delay if necessary.
	if(delayToggle)
		delay.getAudio(output, numSamples);

	if(distToggle)
	{
		if(distLevel < 0.4f)
			distLevel = 0.4f;
		for(i=0;i<(numSamples*2);++i)
			output[i] = tanh(output[i] * distGain)/distGain;
	}
}

//------------------------------------------------------------------------------
void EXCOSCSounder::loadSoundFileResource(SoundFileLoaderPtr soundFileLoader)
{
	int i, j;
	const int64_t fileLength = soundFileLoader->getFileLength();
	const int64_t shortFileMaxSize = (2 * 44100);

	soundFiles.insert(make_pair(soundFileLoader->getName(), soundFileLoader));

	if(fileLength < shortFileMaxSize)
	{
		int64_t bufPos;
		PreLoadedBuffer buffer;

		buffer.buffer = new float[(uint32_t)fileLength * 2];
		buffer.size = (int)fileLength * 2;

		bufPos = 0;
		while(bufPos < fileLength)
		{
			bufPos = soundFileLoader->loadChunk(buffer.buffer,
												bufPos,
												buffer.size,
												0);
		}

		//Convert mono files to stereo.
		if(soundFileLoader->getNumChannels() < 2)
		{
			for(j=(buffer.size-2);j>=0;j-=2)
			{
				buffer.buffer[j] = buffer.buffer[j/2];
				buffer.buffer[j+1] = buffer.buffer[j];
			}
		}

		preLoadedBuffers.insert(make_pair(soundFileLoader->getName(), buffer));
	}
	else
	{
		int bufPos;
		int64_t tempPos;
		vector<vector<float> >& initialBuffers = soundFileLoader->getInitialBuffers();
		float tempBuf[StreamingSound::NumBuffers][StreamingSound::BufferSize*2];

		for(i=0;i<StreamingSound::NumBuffers;++i)
		{
			bufPos = 0;
			while(bufPos < StreamingSound::BufferSize)
			{
				tempPos = (StreamingSound::BufferSize * i) + bufPos;

				bufPos = soundFileLoader->loadChunk(tempBuf[i],
													tempPos,
													(StreamingSound::BufferSize-bufPos) * 2,
													bufPos);
			}
		}

		//Convert mono files to stereo.
		if(soundFileLoader->getNumChannels() < 2)
		{
			for(i=0;i<StreamingSound::NumBuffers;++i)
			{
				for(j=(StreamingSound::BufferSize * 2)-2;j>=0;j-=2)
				{
					tempBuf[i][j] = tempBuf[i][j/2];
					tempBuf[i][j+1] = tempBuf[i][j];
				}
			}
		}

		//Just in case.
		for(i=0;i<(int)initialBuffers.size();++i)
			initialBuffers[i].clear();
		initialBuffers.clear();

		//Copy into soundFileLoader's initialBuffers.
		for(i=0;i<StreamingSound::NumBuffers;++i)
		{
			initialBuffers.push_back(vector<float>());

			for(j=0;j<(StreamingSound::BufferSize * 2);++j)
				initialBuffers[i].push_back(tempBuf[i][j]);
		}
	}
}

//------------------------------------------------------------------------------
bool EXCOSCSounder::run()
{
	int i;

	while(!timeToStop)
	{
		for(i=0;i<NumSounds;++i)
		{
			if(sounds[i].getActive())
				sounds[i].threadUpdate();
		}

		//Wait a bit before looping.
		Thread::wait(62);
	}

	return true;
}

//------------------------------------------------------------------------------
void EXCOSCSounder::playMusic(const std::wstring& sound)
{
	int i;

	//If it's a short sound, set it up accordingly.
	if(preLoadedBuffers.find(sound) != preLoadedBuffers.end())
	{
		if(music > -1)
			sounds[music].stopPlaying();

		for(i=0;i<NumSounds;++i)
		{
			if(!sounds[i].getActive())
			{
				music = i;
				sounds[i].setFade(true);
				sounds[i].setLoop(true);
				sounds[i].startPlaying(preLoadedBuffers[sound].buffer,
									   preLoadedBuffers[sound].size,
									   sound);

				break;
			}
		}
	}
	//Ditto if it's a longer file.
	else if(soundFiles.find(sound) != soundFiles.end())
	{
		if(music > -1)
			sounds[music].stopPlaying();

		for(i=0;i<NumSounds;++i)
		{
			if(!sounds[i].getActive())
			{
				music = i;
				sounds[i].setFade(true);
				sounds[i].setLoop(true);
				sounds[i].startPlaying(soundFiles[sound]);

				break;
			}
		}
	}
	else
		DebugStatement(L"EXCOSCSounder: Could not find sound " << sound << " to play as music.");
}

//------------------------------------------------------------------------------
void EXCOSCSounder::stopMusic()
{
	if(music > -1)
	{
		sounds[music].stopPlaying();
		music = -1;
	}
}

//------------------------------------------------------------------------------
void EXCOSCSounder::triggerSound(const std::wstring& sound,
									  float level,
									  float pan)
{
	int i;

	//If it's a short sound, set it up accordingly.
	if(preLoadedBuffers.find(sound) != preLoadedBuffers.end())
	{
		for(i=0;i<NumSounds;++i)
		{
			if(!sounds[i].getActive())
			{
				sounds[i].setFade(false);
				sounds[i].setLoop(false);
				sounds[i].setLevel(level, false);
				sounds[i].setPan(pan);
				sounds[i].startPlaying(preLoadedBuffers[sound].buffer,
									   preLoadedBuffers[sound].size,
									   sound);

				break;
			}
		}
	}
	//Ditto if it's a longer file.
	else if(soundFiles.find(sound) != soundFiles.end())
	{
		for(i=0;i<NumSounds;++i)
		{
			if(!sounds[i].getActive())
			{
				sounds[i].setFade(false);
				sounds[i].setLoop(false);
				sounds[i].setLevel(level, false);
				sounds[i].setPan(pan);
				sounds[i].startPlaying(soundFiles[sound]);

				break;
			}
		}
	}
	else
		DebugStatement(L"EXCOSCSounder: Could not find sound " << sound << " to play as a one-shot.");
}

//------------------------------------------------------------------------------
int EXCOSCSounder::triggerLoop(const std::wstring& sound,
									float level,
									float pan,
									bool fade)
{
	int i;
	int retval = -1;

	//If it's a short sound, set it up accordingly.
	if(preLoadedBuffers.find(sound) != preLoadedBuffers.end())
	{
		for(i=0;i<NumSounds;++i)
		{
			if(!sounds[i].getActive())
			{
				sounds[i].setFade(fade);
				sounds[i].setLoop(true);
				sounds[i].setLevel(level, false);
				sounds[i].setPan(pan);
				sounds[i].startPlaying(preLoadedBuffers[sound].buffer,
									   preLoadedBuffers[sound].size,
									   sound);
				retval = i;

				break;
			}
		}
	}
	//Ditto if it's a longer file.
	else if(soundFiles.find(sound) != soundFiles.end())
	{
		for(i=0;i<NumSounds;++i)
		{
			if(!sounds[i].getActive())
			{
				sounds[i].setFade(fade);
				sounds[i].setLoop(true);
				sounds[i].setLevel(level, false);
				sounds[i].setPan(pan);
				sounds[i].startPlaying(soundFiles[sound]);
				retval = i;

				break;
			}
		}
	}
	else
		DebugStatement(L"EXCOSCSounder: Could not find sound " << sound << " to play as a loop.");

	return retval;
}

//------------------------------------------------------------------------------
void EXCOSCSounder::stopLoop(int index)
{
	if(sounds[index].getActive())
		sounds[index].stopPlaying();
}

//------------------------------------------------------------------------------
void EXCOSCSounder::stopAll()
{
	int i;

	for(i=0;i<NumSounds;++i)
	{
		if(sounds[i].getActive())
			sounds[i].stopPlaying();
	}
}

//------------------------------------------------------------------------------
void EXCOSCSounder::toggleGranulator(bool val)
{
	granulatorToggle = val;
}

//------------------------------------------------------------------------------
void EXCOSCSounder::setGranulatorParams(float density,
										float duration,
										float pitch,
										float feedback,
										float mix,
										float level)
{
	granulator.setDensity(density);
	granulator.setDuration(duration);
	granulator.setPitch(pitch);
	granulator.setFeedback(feedback);
	granulator.setMix(mix);
	granulator.setLevel(level);
}

//------------------------------------------------------------------------------
void EXCOSCSounder::toggleDelay(bool val)
{
	delayToggle = val;
}

//------------------------------------------------------------------------------
void EXCOSCSounder::setDelayParams(float delayTime, float feedback, float mix)
{
	delay.setDelay(delayTime);
	delay.setFeedback(feedback);
	delay.setMix(mix);
}

//------------------------------------------------------------------------------
void EXCOSCSounder::toggleDistortion(bool val)
{
	distToggle = val;
}

//------------------------------------------------------------------------------
void EXCOSCSounder::setDistortionGain(float val)
{
	distGain = val;
}
