//	StreamingSound.cpp - Class which streams a sound file from disk.
//  ----------------------------------------------------------------------------
//	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 "StreamingSound.h"
#include "SoundFileLoader.h"

#include <cassert>

using std::vector;

//------------------------------------------------------------------------------
StreamingSound::StreamingSound():
currentBuffer(0),
streamingPos(0),
preLoadedBuffer(0),
preLoadedSize(0),
position(0),
readPos(0),
loop(false),
level(1.0f),
newLevel(1.0f),
pan(0.5f),
samplerate(44100.0f),
callbackTime(0.0f),
fade(false),
fadeVal(0.0f),
fadeDir(false)
{
	callbackIterator = callbacks.end();
}

//------------------------------------------------------------------------------
StreamingSound::~StreamingSound()
{

}

//------------------------------------------------------------------------------
void StreamingSound::getAudio(float *buffer, int numSamples)
{
	int i, j;
	float panLeft, panRight;
	const float sampleTime = 1.0f/samplerate;

	if(pan < 0.5f)
	{
		panLeft = 1.0f;
		panRight = pan * 2.0f;
	}
	else if(pan >= 0.5f)
	{
		panLeft = (0.5f-(pan-0.5f)) * 2.0f;
		panRight = 1.0f;
	}

	//If we're playing a short file from a pre-loaded buffer.-------------------
	if(preLoadedBuffer)
	{
		for(i=0,j=0;i<numSamples;++i,j+=2)
		{
			buffer[j] += preLoadedBuffer[(readPos*2)] * fadeVal * level * panLeft * 0.5f;
			buffer[j+1] += preLoadedBuffer[(readPos*2)+1] * fadeVal * level * panRight * 0.5f;
			++readPos;

			if(!fadeDir && (fadeVal < 1.0f))
				fadeVal += 0.00001f;
			else if(fadeDir && (fadeVal > 0.0f))
				fadeVal -= 0.00001f;

			if(level < newLevel)
			{
				level += 0.001f;
				if(level > newLevel)
					level = newLevel;
			}
			else if(level > newLevel)
			{
				level -= 0.001f;
				if(level < newLevel)
					level = newLevel;
			}

			callbackTime += sampleTime;
			if(callbackIterator != callbacks.end())
			{
				if(callbackTime > callbackIterator->first)
					callTime(preLoadedName);
			}

			if(readPos >= (preLoadedSize/2))
			{
				/*if(callback)
					callback->soundFinished(preLoadedName);*/
				callFinished(preLoadedName);

				if(loop)
					readPos = 0;
				else
				{
					fadeVal = 0.0f;
					break;
				}
			}
		}
	}
	//We're streaming a larger file from disk.----------------------------------
	else
	{
		//If we've not reached the end of this buffer.
		if((BufferSize-streamingPos) >= numSamples)
		{
			for(i=0,j=0;i<numSamples;++i,j+=2)
			{
				buffer[j] += streamingBuffers[currentBuffer][(streamingPos*2)] * fadeVal * level * panLeft * 0.5f;
				buffer[j+1] += streamingBuffers[currentBuffer][(streamingPos*2)+1] * fadeVal * level * panRight * 0.5f;
				++streamingPos;
				++readPos;

				if(!fadeDir && (fadeVal < 1.0f))
					fadeVal += 0.00001f;
				else if(fadeDir && (fadeVal > 0.0f))
					fadeVal -= 0.00001f;

				if(level < newLevel)
				{
					level += 0.001f;
					if(level > newLevel)
						level = newLevel;
				}
				else if(level > newLevel)
				{
					level -= 0.001f;
					if(level < newLevel)
						level = newLevel;
				}

				callbackTime += sampleTime;
				if(callbackIterator != callbacks.end())
				{
					if(callbackTime > callbackIterator->first)
						callTime(soundFileLoader->getName());
				}

				if(readPos >= soundFileLoader->getFileLength())
				{
					callFinished(soundFileLoader->getName());

					if(loop)
					{
						readPos = 0;
						streamingPos = 0;
						clearedBuffers[currentBuffer] = true;
						currentBuffer = (currentBuffer+1)%NumBuffers;
						assert(!clearedBuffers[currentBuffer]);
					}
					else
					{
						fadeVal = 0.0f;
						break;
					}
				}
			}
		}
		//If we need to read from the next buffer halfway through this audio block.
		else
		{
			int tempint = BufferSize-streamingPos;

			//Read the rest of the samples from the current buffer.
			for(i=0;i<tempint;++i)
			{
				buffer[(i*2)] += streamingBuffers[currentBuffer][(streamingPos*2)] * fadeVal * level * panLeft * 0.5f;
				buffer[(i*2)+1] += streamingBuffers[currentBuffer][(streamingPos*2)+1] * fadeVal * level * panRight * 0.5f;
				++streamingPos;
				++readPos;

				if(!fadeDir && (fadeVal < 1.0f))
					fadeVal += 0.00001f;
				else if(fadeDir && (fadeVal > 0.0f))
					fadeVal -= 0.00001f;

				if(level < newLevel)
				{
					level += 0.001f;
					if(level > newLevel)
						level = newLevel;
				}
				else if(level > newLevel)
				{
					level -= 0.001f;
					if(level < newLevel)
						level = newLevel;
				}

				callbackTime += sampleTime;
				if(callbackIterator != callbacks.end())
				{
					if(callbackTime > callbackIterator->first)
						callTime(soundFileLoader->getName());
				}

				if(readPos >= soundFileLoader->getFileLength())
				{
					callFinished(soundFileLoader->getName());

					if(loop)
					{
						readPos = 0;
						streamingPos = 0;
						clearedBuffers[currentBuffer] = true;
						currentBuffer = (currentBuffer+1)%NumBuffers;
						assert(!clearedBuffers[currentBuffer]);
					}
					else
					{
						fadeVal = 0.0f;
						break;
					}
				}
			}

			//Move to the next buffer.
			streamingPos = 0;
			clearedBuffers[currentBuffer] = true;
			currentBuffer = (currentBuffer+1)%NumBuffers;

			if(readPos < soundFileLoader->getFileLength())
			{
				//Read the remaining samples from the new buffer.
				for(i=tempint;i<numSamples;++i)
				{
					buffer[(i*2)] += streamingBuffers[currentBuffer][(streamingPos*2)] * fadeVal * level * panLeft * 0.5f;
					buffer[(i*2)+1] += streamingBuffers[currentBuffer][(streamingPos*2)+1] * fadeVal * level * panRight * 0.5f;
					++streamingPos;
					++readPos;

					if(!fadeDir && (fadeVal < 1.0f))
						fadeVal += 0.00001f;
					else if(fadeDir && (fadeVal > 0.0f))
						fadeVal -= 0.00001f;

					if(level < newLevel)
					{
						level += 0.001f;
						if(level > newLevel)
							level = newLevel;
					}
					else if(level > newLevel)
					{
						level -= 0.001f;
						if(level < newLevel)
							level = newLevel;
					}

					callbackTime += sampleTime;
					if(callbackIterator != callbacks.end())
					{
						if(callbackTime > callbackIterator->first)
							callTime(soundFileLoader->getName());
					}

					if(readPos >= soundFileLoader->getFileLength())
					{
						callFinished(soundFileLoader->getName());

						if(loop)
						{
							readPos = 0;
							streamingPos = 0;
							clearedBuffers[currentBuffer] = true;
							currentBuffer = (currentBuffer+1)%NumBuffers;
							assert(!clearedBuffers[currentBuffer]);
						}
						else
						{
							fadeVal = 0.0f;
							break;
						}
					}
				}
			}
		}
	}
}

//------------------------------------------------------------------------------
void StreamingSound::startPlaying(SoundFileLoaderPtr loader)
{
	unsigned int i, j;
	const vector<vector<float> >& initialBuffers = loader->getInitialBuffers();

	assert(initialBuffers.size() >= NumBuffers);
	assert(initialBuffers[0].size() >= (BufferSize*2));

	callbackTime = 0.0f;
	callbackIterator = callbacks.begin();
	if(callbackIterator != callbacks.end())
	{
		while(callbackIterator->first < 0.0f)
		{
			++callbackIterator;
			if(callbackIterator == callbacks.end())
				break;
		}
	}

	preLoadedBuffer = 0;
	preLoadedSize = 0;

	soundFileLoader = loader;
	currentBuffer = 0;


	for(i=0;i<initialBuffers.size();++i)
	{
		assert(i < NumBuffers);

		clearedBuffers[i] = false;

		for(j=0;j<initialBuffers[i].size();++j)
			streamingBuffers[i][j] = initialBuffers[i][j];
	}
	
	streamingPos = 0;
	position = NumBuffers * BufferSize;
	readPos = 0;

	if(fade)
	{
		fadeDir = false;
		fadeVal = 0.00001f;
	}
	else
		fadeVal = 1.0f;
}

//------------------------------------------------------------------------------
void StreamingSound::startPlaying(float *buffer,
								  int32_t size,
								  const std::wstring& soundName)
{
	soundFileLoader.reset();

	callbackTime = 0.0f;
	callbackIterator = callbacks.begin();
	if(callbackIterator != callbacks.end())
	{
		while(callbackIterator->first < 0.0f)
			++callbackIterator;
	}

	preLoadedBuffer = buffer;
	preLoadedSize = size;
	preLoadedName = soundName;

	position = 0;
	readPos = 0;

	if(fade)
	{
		fadeDir = false;
		fadeVal = fadeVal + 0.00001f;
	}
	else
		fadeVal = 1.0f;
}

//------------------------------------------------------------------------------
void StreamingSound::stopPlaying()
{
	if(fade)
		fadeDir = true;
	else
		fadeVal = 0.0f;
}

//------------------------------------------------------------------------------
void StreamingSound::setFade(bool val)
{
	fade = val;
	if(!fade)
	{
		fadeDir = false;
		//fadeVal = 1.0f;
	}
}

//------------------------------------------------------------------------------
void StreamingSound::setLevel(float val, bool ramp)
{
	newLevel = val;
	if(!ramp)
		level = newLevel;
}

//------------------------------------------------------------------------------
void StreamingSound::setPan(float val)
{
	pan = val;
}

//------------------------------------------------------------------------------
void StreamingSound::setLoop(bool val)
{
	loop = val;
}

//------------------------------------------------------------------------------
void StreamingSound::setSamplerate(float val)
{
	samplerate = val;
}

//------------------------------------------------------------------------------
void StreamingSound::threadUpdate()
{
	int i;
	int numLoaded = 0;

	if(preLoadedSize <= 0)
	{
		for(i=0;i<NumBuffers;++i)
		{
			if(clearedBuffers[i])
			{
				numLoaded = soundFileLoader->loadChunk(streamingBuffers[i],
													   position,
													   (BufferSize*2),
													   0);
				//If we've gone past the end of the file.
				if(numLoaded < BufferSize)
				{
					if(loop)
					{
						position = 0;
						numLoaded = soundFileLoader->loadChunk(streamingBuffers[i],
															   position,
															   (BufferSize*2),
															   numLoaded);
					}
					else if(fade)
						fadeDir = true; //Note: this will fade us out before the end of the file.
				}

				clearedBuffers[i] = false;
			}
		}
	}
}

//------------------------------------------------------------------------------
void StreamingSound::registerCallback(float time, Callback *callback)
{
	std::multimap<float, Callback *>::iterator it;

	callbacks.insert(std::make_pair(time, callback));

	for(it=callbacks.begin();
		it!=callbacks.end();
		++it)
	{
		if(it->first != -1.0f)
		{
			callbackIterator = it;
			break;
		}
	}
}

//------------------------------------------------------------------------------
void StreamingSound::unregisterCallback(Callback *callback)
{
	std::multimap<float, Callback *>::iterator it;
	std::multimap<float, Callback *>::iterator it2;

	for(it=callbacks.begin();it!=callbacks.end();)
	{
		it2 = it;
		++it;

		if(it2->second == callback)
			callbacks.erase(it2);
	}
}

//------------------------------------------------------------------------------
void StreamingSound::clearCallbacks()
{
	callbacks.clear();
}
