//	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 namespace std;

//------------------------------------------------------------------------------
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),
fade(false),
fadeVal(0.0f),
fadeDir(false)
{
	
}

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

}

//------------------------------------------------------------------------------
void StreamingSound::getAudio(float *buffer, int numSamples)
{
	int i, j;
	float panLeft, panRight;

	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;
			}

			if(readPos >= (preLoadedSize/2))
			{
				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;
				}

				if(readPos >= soundFileLoader->getFileLength())
				{
					if(loop)
					{
						readPos = 0;
						streamingPos = 0;
						clearedBuffers[currentBuffer] = true;
						currentBuffer = (currentBuffer+1)%NumBuffers;
					}
					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;
				}

				if(readPos >= soundFileLoader->getFileLength())
				{
					if(loop)
					{
						readPos = 0;
						streamingPos = 0;
						clearedBuffers[currentBuffer] = true;
						currentBuffer = (currentBuffer+1)%NumBuffers;
					}
					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;
					}

					if(readPos >= soundFileLoader->getFileLength())
					{
						if(loop)
						{
							readPos = 0;
							streamingPos = 0;
							clearedBuffers[currentBuffer] = true;
							currentBuffer = (currentBuffer+1)%NumBuffers;
						}
						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));

	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)
{
	soundFileLoader.reset();

	preLoadedBuffer = buffer;
	preLoadedSize = size;

	position = 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)
{
	newLevel = val;
}

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

//------------------------------------------------------------------------------
void StreamingSound::setLoop(bool val)
{
	loop = 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;
			}
		}
	}
}
