/*
	==============================================================================
	This file is part of Tal-Reverb by Patrick Kunz.

	Copyright(c) 2005-2009 Patrick Kunz, TAL
	Togu Audio Line, Inc.
	http://kunz.corrupt.ch

	This file may be licensed under the terms of of the
	GNU General Public License Version 2 (the ``GPL'').

	Software distributed under the License is distributed
	on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
	express or implied. See the GPL for the specific language
	governing rights and limitations.

	You should have received a copy of the GPL along with this
	program. If not, go to http://www.gnu.org/licenses/gpl.html
	or write to the Free Software Foundation, Inc.,  
	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
	==============================================================================
 */

#if !defined(__TalReverb_h)
#define __TalReverb_h

#include "AllPassFilter.h"
#include "CombFilter.h"
#include "NoiseGenerator.h"
#include "Filter.h"
#include "math.h"
#include "AudioUtils.h"
#include "TalEq.h"

class TalReverb
{
private:
	static const int DELAY_LINES_COMB = 4;
	static const int DELAY_LINES_ALLPASS = 5;

	static const int MAX_PRE_DELAY_MS = 1000;
	float* reflectionGains;
	float* reflectionDelays;

	CombFilter *combFiltersPreDelayL;
	CombFilter *combFiltersPreDelayR;

	CombFilter **combFiltersL;
	CombFilter **combFiltersR;
	NoiseGenerator **noiseGeneratorAllPassL;
	NoiseGenerator **noiseGeneratorAllPassR;
	NoiseGenerator **noiseGeneratorDelayL;
	NoiseGenerator **noiseGeneratorDelayR;
	NoiseGenerator **diffusionL;
	NoiseGenerator **diffusionR;

	AllPassFilter **allPassFiltersL;
	AllPassFilter **allPassFiltersR;

	AllPassFilter *preAllPassFilterL;
	AllPassFilter *preAllPassFilterR;

	AllPassFilter *postAllPassFilterL;
	AllPassFilter *postAllPassFilterR;

	TalEq* talEqL;
	TalEq* talEqR;

    float feedbackValueL;
    float feedbackValueR;

	float decayTime;
	float preDelayTime;
	bool stereoMode;
	float modulationIntensity;

	float outL;
	float outR;

    float feedbackSampleRateFactor;

	AudioUtils audioUtils;

public:
	TalReverb(long sampleRate)
	{
        // Ignore sample rates smaller then 44100 Hz
        this->feedbackSampleRateFactor = sampleRate / 44100.0f;
        if (this->feedbackSampleRateFactor < 1.0f) this->feedbackSampleRateFactor = 1.0f;

		this->createDelaysAndCoefficients(DELAY_LINES_ALLPASS, 100.0f);

		this->combFiltersPreDelayL = new CombFilter((float)MAX_PRE_DELAY_MS, 0.0f, sampleRate);
		this->combFiltersPreDelayR = new CombFilter((float)MAX_PRE_DELAY_MS, 0.0f, sampleRate);

		this->combFiltersL = new CombFilter *[DELAY_LINES_COMB];
		this->combFiltersR = new CombFilter *[DELAY_LINES_COMB];
		this->noiseGeneratorAllPassL = new NoiseGenerator *[DELAY_LINES_COMB];
		this->noiseGeneratorAllPassR = new NoiseGenerator *[DELAY_LINES_COMB];
		this->noiseGeneratorDelayL = new NoiseGenerator *[DELAY_LINES_COMB];
		this->noiseGeneratorDelayR = new NoiseGenerator *[DELAY_LINES_COMB];
	    this->diffusionL = new NoiseGenerator *[DELAY_LINES_COMB];
	    this->diffusionR = new NoiseGenerator *[DELAY_LINES_COMB];

		float stereoSpreadValue = 0.008f;
		float stereoSpreadSign = 1.0f;
		for (int i = 0; i < DELAY_LINES_COMB; i++)
		{
			float stereoSpreadFactor = 1.0f + stereoSpreadValue;
			if (stereoSpreadSign > 0.0f)
			{
				combFiltersL[i] = new CombFilter(reflectionDelays[i] * stereoSpreadFactor, 1.0f, sampleRate);
				combFiltersR[i] = new CombFilter(reflectionDelays[i], 1.0f, sampleRate);
			}
			else
			{
				combFiltersL[i] = new CombFilter(reflectionDelays[i], 1.0f, sampleRate);
				combFiltersR[i] = new CombFilter(reflectionDelays[i] * stereoSpreadFactor, 1.0f, sampleRate);
			}
			stereoSpreadSign *= -1.0f;

			this->noiseGeneratorAllPassL[i] = new NoiseGenerator((float)sampleRate, (float)i);
			this->noiseGeneratorAllPassR[i] = new NoiseGenerator((float)sampleRate, i + 163.0f);
			this->noiseGeneratorDelayL[i] = new NoiseGenerator((float)sampleRate, i + 257.0f);
			this->noiseGeneratorDelayR[i] = new NoiseGenerator((float)sampleRate, i + 353.0f);
			this->diffusionL[i] = new NoiseGenerator((float)sampleRate, i + 455.0f);
			this->diffusionR[i] = new NoiseGenerator((float)sampleRate, i + 633.0f);
		}

		this->preAllPassFilterL = new AllPassFilter(90.0f,  0.68f, sampleRate);
		this->preAllPassFilterR = new AllPassFilter(90.0f,  0.68f, sampleRate);

		this->postAllPassFilterL = new AllPassFilter(91.0f,  0.68f, sampleRate);
		this->postAllPassFilterR = new AllPassFilter(91.0f,  0.68f, sampleRate);
 
		this->allPassFiltersL = new AllPassFilter *[DELAY_LINES_ALLPASS];
		this->allPassFiltersR = new AllPassFilter *[DELAY_LINES_ALLPASS];
		for (int i = 0; i < DELAY_LINES_ALLPASS; i++)
		{
			this->allPassFiltersL[i] = new AllPassFilter(reflectionDelays[i] * 0.2f,  0.68f, sampleRate);
			this->allPassFiltersR[i] = new AllPassFilter(reflectionDelays[i] * 0.2f,  0.68f, sampleRate);
        }

		this->talEqL = new TalEq((float)sampleRate);
		this->talEqR = new TalEq((float)sampleRate);

		this->decayTime = 0.5f;
		this->preDelayTime = 0.0f;
		this->modulationIntensity = 0.12f;
		this->stereoMode = false;

		this->outL = 0.0f;
		this->outR = 0.0f;

        this->feedbackValueL = 0.0f;
        this->feedbackValueR = 0.0f;
	}

	~TalReverb()
	{
		delete[] reflectionGains;
		delete[] reflectionDelays;

		delete combFiltersPreDelayL;
		delete combFiltersPreDelayR;

        for (int i = 0; i < DELAY_LINES_COMB; i++) delete combFiltersL[i];
		delete[] combFiltersL;
        for (int i = 0; i < DELAY_LINES_COMB; i++) delete combFiltersR[i];
		delete[] combFiltersR;

        for (int i = 0; i < DELAY_LINES_ALLPASS; i++) delete allPassFiltersL[i];
		delete[] allPassFiltersL;
        for (int i = 0; i < DELAY_LINES_ALLPASS; i++) delete allPassFiltersR[i];
		delete[] allPassFiltersR;

        for (int i = 0; i < DELAY_LINES_COMB; i++) delete noiseGeneratorAllPassL[i];
		delete[] noiseGeneratorAllPassL;
        for (int i = 0; i < DELAY_LINES_COMB; i++) delete noiseGeneratorAllPassR[i];
		delete[] noiseGeneratorAllPassR;
        for (int i = 0; i < DELAY_LINES_COMB; i++) delete noiseGeneratorDelayL[i];
		delete[] noiseGeneratorDelayL;
        for (int i = 0; i < DELAY_LINES_COMB; i++) delete noiseGeneratorDelayR[i];
		delete[] noiseGeneratorDelayR;

        for (int i = 0; i < DELAY_LINES_COMB; i++) delete diffusionL[i];
	    delete[] diffusionL;
        for (int i = 0; i < DELAY_LINES_COMB; i++) delete diffusionR[i];
	    delete[] diffusionR;

		delete preAllPassFilterL;
		delete preAllPassFilterR;

		delete postAllPassFilterL;
		delete postAllPassFilterR;

		delete talEqL;
		delete talEqR;
	}

	void setDecayTime(float decayTime)
	{
		this->decayTime = audioUtils.getLogScaledValueInverted(decayTime) * 0.9f + 0.1f;
	}

	void setPreDelay(float preDelayTime)
	{
		this->preDelayTime = audioUtils.getLogScaledValue(preDelayTime);
	}

	void setStereoMode(bool stereoMode)
	{
		this->stereoMode = stereoMode;
	}

	void setLowShelfGain(float lowShelfGain)
	{
		talEqL->setLowShelfGain(lowShelfGain);
		talEqR->setLowShelfGain(lowShelfGain);
	}

	void setHighShelfGain(float highShelfGain)
	{
		talEqL->setHighShelfGain(highShelfGain);
		talEqR->setHighShelfGain(highShelfGain);
	}

	void setLowShelfFrequency(float lowShelfFrequency)
	{
		talEqL->setLowShelfFrequency(lowShelfFrequency);
		talEqR->setLowShelfFrequency(lowShelfFrequency);
	}

	void setHighShelfFrequency(float highShelfFrequency)
	{
		talEqL->setHighShelfFrequency(highShelfFrequency);
		talEqR->setHighShelfFrequency(highShelfFrequency);
	}

	// All input values [0..1]
	inline void process(float* sampleL, float* sampleR)
	{
		float revL = 0.0f;
		float revR = 0.0f;

		if (!stereoMode)
		{
			revL += (*sampleL + *sampleR) * 0.125f; 
			revL += combFiltersPreDelayL->process(revL, 0.0f, 0.0f, preDelayTime);
			talEqL->process(&revL);
			revR = revL;
		}
		else
		{
			revL += combFiltersPreDelayL->process(*sampleL * 0.5f, 0.0f, 0.0f, preDelayTime);
			revR += combFiltersPreDelayR->process(*sampleR * 0.5f, 0.0f, 0.0f, preDelayTime);
			talEqL->process(&revL);
			talEqR->process(&revR);
		}

		// ----------------- Comb Filter --------------------
        float feedbackFactor = 0.15f;
		outL = feedbackValueL * feedbackFactor;
		outR = feedbackValueR * feedbackFactor;

		float scaledRoomSizeDelay = this->decayTime * 0.972f;
		float scaledRoomSizeDamp = this->decayTime * 0.99f;
        float invDecayTime = 0.9f * (1.0f - this->decayTime);
		for (int i = 0; i < DELAY_LINES_COMB; i++)
		{ 
            outL += combFiltersL[i]->processInterpolated(revL, diffusionL[i]->tickFilteredNoiseFast() * invDecayTime, scaledRoomSizeDamp, scaledRoomSizeDelay + 0.028f * noiseGeneratorDelayL[i]->tickFilteredNoise());
			outR += combFiltersR[i]->processInterpolated(revR, diffusionR[i]->tickFilteredNoiseFast() * invDecayTime, scaledRoomSizeDamp, scaledRoomSizeDelay + 0.028f * noiseGeneratorDelayR[i]->tickFilteredNoise());
		}

		// ----------------- Pre AllPass --------------------
		float modL = preAllPassFilterL->processInterpolated(revL, 0.995f + 0.005f * noiseGeneratorAllPassL[0]->tickFilteredNoise(), 0.68f, true);
		float modR = preAllPassFilterR->processInterpolated(revR, 0.995f + 0.005f * noiseGeneratorAllPassR[0]->tickFilteredNoise(), 0.68f, true);

		// ----------------- Post AllPass --------------------
	    outL += 0.4f * postAllPassFilterL->processInterpolated(modL, decayTime, 0.68f, false);
		outR += 0.4f * postAllPassFilterR->processInterpolated(modR, decayTime, 0.68f, false);

		// ----------------- AllPass Filter ------------------
		for (int i = 0; i < DELAY_LINES_ALLPASS; i++)
		{
			outL = allPassFiltersL[i]->process(outL);
			outR = allPassFiltersR[i]->process(outR);
		}

		// ----------------- Write to output / Stereo --------
        //if (outL > 2.0f)  outL = 2.0f;
        //if (outR > 2.0f)  outR = 2.0f;
        //if (outL < -2.0f)  outL = -2.0f;
        //if (outR < -2.0f)  outR = -2.0f;

		*sampleL = outL;
		*sampleR = outR;

        feedbackValueL = outL;
        feedbackValueR = outR;
	}

	void createDelaysAndCoefficients(int numlines, float delayLength)
	{
		this->reflectionDelays = new float[numlines];
		this->reflectionGains = new float[numlines];

		float volumeScale = (float)(-3.0 * delayLength / log10f(0.55f));
		for (int n = numlines - 1; n >= 0; n--)
		{
			reflectionDelays[numlines -1 - n] = delayLength / powf(2.0f, (float)n / numlines); 
			reflectionGains[numlines -1 - n] = powf(10.0f, - (3.0f * reflectionDelays[numlines -1 - n]) / volumeScale);
		}
	}
};
#endif
