//	TonesSounder.cpp - The game's Sounder.
//  ----------------------------------------------------------------------------
//	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 "TonesSounder.h"
#include "PitchFactory.h"
#include "MiscHelper.h"
#include "Random.h"

#include "DebugHeaders.h"

#include <cassert>

//------------------------------------------------------------------------------
TonesSounder::TonesSounder():
Sounder(),
globalPitch(1.0f),
lowMode(1.0f)
{
	int i;

	for(i=0;i<NumVoices;++i)
	{
		voices[i].fadeOut = false;
		voices[i].active = false;
	}

	pitches = PitchFactory::getInstance().getPitches(5);
}

//------------------------------------------------------------------------------
TonesSounder::~TonesSounder()
{

}

//------------------------------------------------------------------------------
void TonesSounder::getAudio(float *input, float *output, int numSamples)
{
	int i, j;
	float amp;
	float coeff;

	for(i=0;i<NumVoices;++i)
	{
		if(voices[i].active)
		{
			for(j=0;j<numSamples;++j)
			{
				//Calculate the amplitude envelope.
				if(!voices[i].fadeOut && (voices[i].amplitude < 1.0f))
				{
					voices[i].amplitude += 0.00001f;
					if(voices[i].amplitude > 1.0f)
						voices[i].amplitude = 1.0f;

					amp = fakeLog(voices[i].amplitude);
				}
				else if(voices[i].fadeOut && (voices[i].amplitude >= 0.0f))
				{
					voices[i].amplitude -= 0.000025f;
					if(voices[i].amplitude <= 0.0f)
					{
						voices[i].amplitude = 0.0f;
						voices[i].active = false;
						break;
					}

					amp = fakeLog(voices[i].amplitude);
				}
				else
					amp = 1.0f;

				//Calculate any change in pitch.
				if(voices[i].newCoeff != voices[i].coeff)
				{
					coeff = (voices[i].newCoeff * voices[i].glissando);
					coeff += (voices[i].coeff * (1.0f-voices[i].glissando));

					voices[i].glissando += 0.00005f;
					if(voices[i].glissando >= 1.0f)
						voices[i].coeff = voices[i].newCoeff;
				}
				else
					coeff = voices[i].coeff;

				//Generate the sine tone.
				voices[i].x0 -= (coeff*voices[i].x1);
				voices[i].x1 += (coeff*voices[i].x0);

				//Write to the buffer.
				output[j*2] += voices[i].x1 * amp * 0.06125f;
				output[(j*2)+1] += voices[i].x1 * amp * 0.0625f;
			}
		}
	}
}

//------------------------------------------------------------------------------
int TonesSounder::noteOn(int rail, float octave)
{
	int i;
	float offset;
	int retval = -1;

	for(i=0;i<NumVoices;++i)
	{
		if(!voices[i].active)
		{
			voices[i].pitchIndex = rail;
			voices[i].octave = octave;

			offset = (Random::getGlobal().getRandomFloat() * 2.0f) - 1.0f;
			voices[i].frequency = (pitches[rail] * octave) + offset;
			//DebugStatement(L"noteOn(rail, octave): freq = " << voices[i].frequency);

			assert((voices[i].frequency > 0.0f) && (voices[i].frequency < 11025.0f));

			voices[i].x0 = 1.0f;
			voices[i].x1 = 0.0f;
			voices[i].coeff = 2.0f*sinf(3.1415926535897932384626433832795f*voices[i].frequency*globalPitch*lowMode*2.0f/44100.0f);
			voices[i].newCoeff = voices[i].coeff;
			voices[i].amplitude = 0.0f;
			voices[i].fadeOut = false;

			voices[i].active = true;

			retval = i;

			break;
		}
	}

	return retval;
}

//------------------------------------------------------------------------------
int TonesSounder::noteOn(float freq)
{
	int i;
	float offset;
	int retval = -1;

	for(i=0;i<NumVoices;++i)
	{
		if(!voices[i].active)
		{
			voices[i].pitchIndex = 0;
			voices[i].octave = 1.0f;

			offset = (Random::getGlobal().getRandomFloat() * 2.0f) - 1.0f;
			voices[i].frequency = freq + offset;
			//DebugStatement(L"noteOn(freq): freq = " << voices[i].frequency);

			voices[i].x0 = 1.0f;
			voices[i].x1 = 0.0f;
			voices[i].coeff = 2.0f*sinf(3.1415926535897932384626433832795f*voices[i].frequency*2.0f/44100.0f);
			voices[i].newCoeff = voices[i].coeff;
			voices[i].amplitude = 0.0f;
			voices[i].fadeOut = false;

			voices[i].active = true;

			retval = i;

			break;
		}
	}

	return retval;
}

//------------------------------------------------------------------------------
void TonesSounder::noteOff(int index, bool pitchDrop)
{
	if(pitchDrop)
	{
		float offset;
	
		voices[index].glissando = 0.0f;
		offset = (Random::getGlobal().getRandomFloat() * 2.0f) - 1.0f;
		voices[index].frequency = 50.0f + offset;
		//DebugStatement(L"noteOff: freq = " << voices[index].frequency);
		voices[index].newCoeff = 2.0f*sinf(3.1415926535897932384626433832795f*voices[index].frequency*globalPitch*lowMode*2.0f/44100.0f);
	}

	voices[index].fadeOut = true;
}

//------------------------------------------------------------------------------
void TonesSounder::changePitch(int index, int newRail, float octave)
{
	float offset;

	//****Temp fix.****
	if(newRail >= (int)pitches.size())
		newRail = pitches.size()-1;

	voices[index].pitchIndex = newRail;

	voices[index].glissando = 0.0f;
	offset = (Random::getGlobal().getRandomFloat() * 2.0f) - 1.0f;
	voices[index].frequency = (pitches[newRail]*octave) + offset;
	//DebugStatement(L"changePitch: freq = " << voices[index].frequency);
	voices[index].newCoeff = 2.0f*sinf(3.1415926535897932384626433832795f*voices[index].frequency*globalPitch*lowMode*2.0f/44100.0f);
}

//------------------------------------------------------------------------------
void TonesSounder::changeAmplitude(int index, float val)
{
	//voices[index].amplitude = val;
}

//------------------------------------------------------------------------------
int TonesSounder::createCopyNote(int index)
{
	int i;
	float offset;
	int retval = -1;

	for(i=0;i<NumVoices;++i)
	{
		if(!voices[i].active)
		{
			offset = (Random::getGlobal().getRandomFloat() * 2.0f) - 1.0f;
			voices[i].frequency = voices[index].frequency + offset;
			//DebugStatement(L"createCopyNote: freq = " << voices[index].frequency);

			voices[i].x0 = 1.0f;
			voices[i].x1 = 0.0f;
			voices[i].coeff = 2.0f*sinf(3.1415926535897932384626433832795f*voices[i].frequency*globalPitch*lowMode*2.0f/44100.0f);
			voices[i].newCoeff = voices[i].coeff;
			voices[i].amplitude = 0.0f;
			voices[i].fadeOut = false;

			voices[i].active = true;

			retval = i;

			break;
		}
	}

	return retval;
}

//------------------------------------------------------------------------------
void TonesSounder::setPitches(const std::vector<float>& p)
{
	int i;

	pitches.clear();
	pitches = p;

	//Glissando any active voices to the new pitches.
	for(i=0;i<NumVoices;++i)
	{
		if(voices[i].active)
			changePitch(i, voices[i].pitchIndex, voices[i].octave);
	}
}

//------------------------------------------------------------------------------
void TonesSounder::setLowMode(bool val)
{
	int i;

	if(val)
		lowMode = 0.5f;
	else
		lowMode = 1.0f;

	for(i=0;i<NumVoices;++i)
	{
		if(voices[i].active)
		{
			voices[i].glissando = 0.0f;
			voices[i].newCoeff = 2.0f*sinf(3.1415926535897932384626433832795f*voices[i].frequency*globalPitch*lowMode*2.0f/44100.0f);
		}
	}
}

//------------------------------------------------------------------------------
void TonesSounder::setGlobalPitch(float val)
{
	int i;

	if(val != globalPitch)
	{
		globalPitch = val;
		for(i=0;i<NumVoices;++i)
		{
			if(voices[i].active)
			{
				voices[i].glissando = 0.0f;
				voices[i].newCoeff = 2.0f*sinf(3.1415926535897932384626433832795f*voices[i].frequency*globalPitch*lowMode*2.0f/44100.0f);
			}
		}
	}
}
