/*-----------------------------------------------------------------------------

� 1999, Steinberg Soft und Hardware GmbH, All Rights Reserved

-----------------------------------------------------------------------------*/

#ifdef WIN32
#include <windows.h>
#endif


#include <stdio.h>
#include <math.h>
#include "iir.h"
#include "Vitalisierer2.hpp"
#include "paramutils.h"

#define	MAX_FILTERTYPES		2
#define MAX_FILTERORDER		6
#define MIN_FILTERORDER		2
#define	MAX_FILTERQ			10

#define NUM_PARMS			8
#define NUM_FILTERMODE		3
#define MAX_FILTERMODE		(NUM_FILTERMODE-1)

#define ALPHA				0.4f
#define BETA				(1.0f-ALPHA)

#define ALPHA_DC			0.0002f
#define BETA_DC				(1.0f-ALPHA_DC)

#define	PARAM_CALC_TOL_FG			1E-3
#define	PARAM_CALC_TOL_HARMONICS	1E-3

const char * const pFTypeString[8] =
{
	"Unknown",
	"Lowpass",
	"Highpass",
    "Bandpass",
	"Bandstop",
	"Peaking",
	"Low-Shelving",
	"High-Shelving"
};

enum eFilterMode
{
	eFilterModeBypass, eFilterModePre, eFilterModePost
};

char *gFilterModeStr[NUM_FILTERMODE] = 
{
	"Bypass", "Pre", "Post"
};

char *gSwitchStr[] = 
{
	"Off", "On"
};

enum eParams
{
	PARM_FILTER_TYPE, PARM_FILTER_ORDER, PARM_FILTERMODE, PARM_MIDHITUNE, PARM_HARMONICS_EVEN, PARM_HARMONICS_ODD, PARM_PROCESS, PARM_AUDITION
};

char *pParmStr[NUM_PARMS] = 
{
	"Filter-Type", "Order", "Filter-Mode", "Mid-Hi-Tune","Even Harmonics","Odd Harmonics", "Process","Audition"
};

//-----------------------------------------------------------------------------
Filter::Filter(audioMasterCallback audioMaster)
	: AudioEffectX(audioMaster, 1, NUM_PARMS)	// 1 program, 1 parameter only
{
	// Defaults
	m_fg = 4000.0f;					// Mid-Hi Tune
	m_filterType = IIR_FILTERTYPE_HIGHPASS;	
	m_filterMode = eFilterModePre;
	m_filterOrder = 2;
	m_filterQ = 1.0f;
	m_process = 0.2f;
	m_harmonicsOdd = 0.18f;
	m_harmonicsEven = 0.0f;
	m_knormEven = 0.0;
	m_knormOdd = 0.0;
	m_keven = 0.0;
	m_kodd = 0.0;
	
	m_audition = 0;
	
	// Initialze smoothed params
	m_smFg = m_fg;
	m_smProcess = m_process;
	m_smHarmonicsOdd = m_harmonicsOdd;
	m_smHarmonicsEven = m_harmonicsEven;

	m_smFilterQ = m_filterQ;

	m_nInputs = 2;
	m_nOutputs = 2;

	m_ppOdd = 0;
	m_ppEven = 0;

	setNumInputs(m_nInputs);		// stereo in
	setNumOutputs(m_nOutputs);		// stereo out
	setUniqueID('Vita');	// identify
	canMono();				// makes sense to feed both inputs with the same signal
	canProcessReplacing();	// supports both accumulating and replacing output
	strcpy(programName, "Default");	// default program name
	m_pDC = new float[m_nInputs];

	IIRInit(m_filterCoeffOdd[0], MAX_FILTERORDER);
	IIRInit(m_filterCoeffOdd[1], MAX_FILTERORDER);
	IIRInit(m_filterCoeffEven[0], MAX_FILTERORDER);
	IIRInit(m_filterCoeffEven[1], MAX_FILTERORDER);

	isFilterTypedChanged = true;
	isFilterOrderChanged = true;
	isSampleRateChanged = true;
	isFilterFgChanged = true;
	isHarmonicsEvenChanged = true;
	isHarmonicsOddChanged = true;

}

//-----------------------------------------------------------------------------------------
Filter::~Filter()
{
	int i;
	if (m_ppOdd)
	{
		for(i=0; i < m_nOutputs; i++)
			delete [] m_ppOdd[i];

		delete [] m_ppOdd;
		m_ppOdd = 0;
	}

	if (m_ppEven)
	{
		for(i=0; i < m_nOutputs; i++)
			delete [] m_ppEven[i];

		delete [] m_ppEven;
		m_ppEven = 0;
	}
	delete [] m_pDC;
}

//-----------------------------------------------------------------------------------------
void Filter::setProgramName(char *name)
{
	strcpy(programName, name);
}

//-----------------------------------------------------------------------------------------
void Filter::getProgramName(char *name)
{
	strcpy(name, programName);
}

//-----------------------------------------------------------------------------------------
void Filter::setParameter(int32_t index, float value)
{
	switch(index)
	{
	case PARM_FILTER_TYPE:
		m_paramFilterType = value;
		m_filterType = (long)norm2quant(1, MAX_FILTERTYPES, 1, m_paramFilterType);
		isFilterTypedChanged = true;
		break;

	case PARM_FILTER_ORDER:
		m_paramFilterOrder = value;
		m_filterOrder = (long)norm2quant(MIN_FILTERORDER, MAX_FILTERORDER, 2, m_paramFilterOrder);
		isFilterOrderChanged = true;
		break;
		
	case PARM_FILTERMODE:
		m_paramFilterMode = value;
		m_filterMode = (int)norm2quant(eFilterModeBypass, eFilterModePost, 1, value);
		break;

	case PARM_MIDHITUNE:
		m_paramFg = value;
		m_fg = 0.5f*m_paramFg*sampleRate;
		isFilterFgChanged = true;
		break;

	case PARM_HARMONICS_ODD:
		m_harmonicsOdd = value;
		isHarmonicsOddChanged = true;
		break;

	case PARM_HARMONICS_EVEN:
		m_harmonicsEven = value;
		isHarmonicsEvenChanged = true;
		break;

	case PARM_PROCESS:
		m_process = value;
		break;

	case PARM_AUDITION:
		m_audition = (int)norm2quant(0,1,1,value);
		break;
	
	
	}
	
}

//-----------------------------------------------------------------------------------------
float Filter::getParameter(int32_t index)
{
	float value = 0;
	
	switch(index)
	{
	case PARM_FILTER_TYPE:
		m_paramFilterType = (float)quant2norm(1, MAX_FILTERTYPES, (float)m_filterType);
		value = m_paramFilterType;
		break;

	case PARM_FILTER_ORDER:
		m_paramFilterOrder = (float)quant2norm(2, MAX_FILTERORDER, (float)m_filterOrder);
		value = m_paramFilterOrder;
		break;
		
	case PARM_FILTERMODE:
		value = m_paramFilterMode = quant2norm(0,MAX_FILTERMODE,(float)m_filterMode);
		break;

	case PARM_MIDHITUNE:
		value = m_paramFg = 2.0f * m_fg /sampleRate;
		break;

	case PARM_HARMONICS_ODD:
		value = m_harmonicsOdd;
		break;

	case PARM_HARMONICS_EVEN:
		value = m_harmonicsEven;
		break;

	case PARM_PROCESS:
		value = m_process;
		break;

	case PARM_AUDITION:
		value = (float)quant2norm(0,1,(float)m_audition);
		break;

	}
	return value;
}

//-----------------------------------------------------------------------------------------
void Filter::getParameterName(int32_t index, char *label)
{
	strcpy(label,pParmStr[index]);
}

//-----------------------------------------------------------------------------------------
void Filter::getParameterDisplay(int32_t index, char *text)
{
	switch(index)
	{
	case PARM_FILTER_TYPE:
		strcpy(text,pFTypeString[m_filterType]);
		break;

	case PARM_FILTER_ORDER:
		sprintf(text, "%ld", m_filterOrder);
//		long2string(m_filterOrder, text);
		break;
		
	case PARM_FILTERMODE:
		strcpy(text,gFilterModeStr[m_filterMode]);
		break;

	case PARM_MIDHITUNE:
		sprintf(text, "%f", m_fg);
		break;

	case PARM_HARMONICS_ODD:
		sprintf(text, "%f", 100.0f*m_harmonicsOdd);
		break;
	
	case PARM_HARMONICS_EVEN:
		sprintf(text, "%f", 100.0f*m_harmonicsEven);
		break;

	case PARM_PROCESS:
		sprintf(text, "%f", 100.0f*m_process);
		break;
	
	case PARM_AUDITION:
		strcpy(text,gSwitchStr[m_audition]);
		break;
	}
}

//-----------------------------------------------------------------------------------------
void Filter::getParameterLabel(int32_t index, char *label)
{
	switch(index)
	{
	case PARM_FILTER_TYPE:
		strcpy(label, "           ");
		break;

	case PARM_FILTER_ORDER:
		strcpy(label, "           ");
		break;
		
	case PARM_FILTERMODE:
		strcpy(label, "           ");
		break;

	case PARM_MIDHITUNE:
		strcpy(label, "     Hz    ");
		break;

	case PARM_HARMONICS_ODD:
		strcpy(label, "     %     ");
		break;

	case PARM_HARMONICS_EVEN:
		strcpy(label, "     %     ");
		break;

	case PARM_PROCESS:
		strcpy(label, "     %     ");
		break;

	case PARM_AUDITION:
		strcpy(label, "           ");
		break;
	}
}

//-----------------------------------------------------------------------------------------
void Filter::process(float **inputs, float **outputs, int32_t sampleFrames)
{
    float *in1  =  inputs[0];
    float *in2  =  inputs[1];
    float *out1 = outputs[0];
    float *out2 = outputs[1];

	while(--sampleFrames >= 0)
    {
        (*out1++) += (*in1++);    // accumulating
        (*out2++) += (*in2++);
    }
}

//-----------------------------------------------------------------------------------------
void Filter::processReplacing(float **inputs, float **outputs, int32_t sampleFrames)
{
	float absXodd, absXeven, Xodd, Xeven;
	int i, n;

    float *in1  =  inputs[0];
    float *in2  =  inputs[1];
    float *out1 = outputs[0];
    float *out2 = outputs[1];
	float **ppOdd = m_ppOdd;
	float **ppEven= m_ppEven;

	

	// Parameter calculation if changed
	if (isHarmonicsEvenChanged)
	{
		// Paramter smoothing
		m_smHarmonicsEven = BETA*m_smHarmonicsEven+ALPHA*m_harmonicsEven;
		if (fabsf(m_smHarmonicsEven-m_harmonicsEven) <= PARAM_CALC_TOL_HARMONICS)
			isHarmonicsEvenChanged = false;

		m_keven = 0.0001f + 10*m_smHarmonicsEven;
		m_knormEven = 1.0f/(1-expf(-m_keven));
		sprintf(dbgStr, "HARMONICS EVEN Calc");
//		OutputDebugString(dbgStr);
	}

	if (isHarmonicsOddChanged)
	{
		// Paramter smoothing
		m_smHarmonicsOdd = BETA*m_smHarmonicsOdd+ALPHA*m_harmonicsOdd;
		if (fabsf(m_smHarmonicsOdd-m_harmonicsOdd) <= PARAM_CALC_TOL_HARMONICS)
			isHarmonicsOddChanged = false;
		
		m_kodd = 0.0001f + 10*m_smHarmonicsOdd;
		m_knormOdd = 1.0f/(1-expf(-m_kodd));
		sprintf(dbgStr, "HARMONICS ODD Calc");
//		OutputDebugString(dbgStr);
	}

	if (isFilterFgChanged || isSampleRateChanged || isFilterTypedChanged || isFilterOrderChanged)
	{
		// Paramter smoothing
		m_smFg = BETA*m_smFg+ALPHA*m_fg;
		if (fabsf(m_smFg-m_fg) > PARAM_CALC_TOL_FG)
			isFilterFgChanged = false;

		IIRCalcFilterCoeff(m_filterCoeffEven[0], sampleRate, m_smFg, m_filterQ, m_filterOrder, m_filterType);
		IIRCalcFilterCoeff(m_filterCoeffOdd[0], sampleRate, m_smFg, m_filterQ, m_filterOrder, m_filterType);
		IIRCalcFilterCoeff(m_filterCoeffEven[1], sampleRate, m_smFg, m_filterQ, m_filterOrder, m_filterType);
		IIRCalcFilterCoeff(m_filterCoeffOdd[1], sampleRate, m_smFg, m_filterQ, m_filterOrder, m_filterType);

		isFilterTypedChanged = false;
		isFilterOrderChanged = false;
		isSampleRateChanged = false;

		sprintf(dbgStr, "Filter calc");
//		OutputDebugString(dbgStr);
	}
	
	// Paramter smoothing
	m_smProcess = BETA*m_smProcess+ALPHA*m_process;

	if (sampleFrames > blockSize)
	{
		sprintf(dbgStr, "Scheisse");
//		OutputDebugString(dbgStr);
		return;
	}

	// Pre-Filtering
	if (m_filterMode == eFilterModePre)
	{
		for (i=0; i < m_nInputs; i++)
		{
			IIR(m_filterCoeffOdd[i], inputs[i], ppOdd[i], m_filterOrder, sampleFrames);
			IIR(m_filterCoeffEven[i], inputs[i], ppEven[i], m_filterOrder, sampleFrames);
		}
	}
	else
	{
		for (i=0; i < m_nInputs; i++)
		{
			memcpy(ppOdd[i], inputs[i], sampleFrames*sizeof(float));
			memcpy(ppEven[i], inputs[i], sampleFrames*sizeof(float));
		}
	}

	for (i=0; i < m_nInputs; i++)
	{
		for (n=0; n < sampleFrames; n++)
		{
			Xodd = ppOdd[i][n];
			Xeven = ppEven[i][n];
			absXodd = fabsf(Xodd);
			absXeven = fabsf(Xeven);

			if (Xodd > 0.0f)
				ppOdd[i][n] =  m_knormOdd * (1 - expf(-m_kodd*absXodd)) - Xodd;
			else
				ppOdd[i][n] = -m_knormOdd * (1 - expf(-m_kodd*absXodd)) - Xodd;

			ppEven[i][n] =  m_knormEven * (1 - expf(-m_keven*absXeven)) - absXeven;

			m_pDC[i] = BETA_DC*m_pDC[i] + ALPHA_DC*ppEven[i][n];
			ppEven[i][n] -=  m_pDC[i];
		}
	}

	// Post-Filtering
	if (m_filterMode == eFilterModePost)
	{
		for (i=0; i < m_nInputs; i++)
		{
			IIR(m_filterCoeffOdd[i], ppOdd[i], ppOdd[i], m_filterOrder, sampleFrames);
			IIR(m_filterCoeffEven[i], ppEven[i], ppEven[i], m_filterOrder, sampleFrames);
		}
	}
	// Mixing
	if (m_audition)
	{
		for (i=0; i < m_nInputs; i++)
		{
			for (n=0; n < sampleFrames; n++)
			{
				outputs[i][n] = m_smProcess * (ppOdd[i][n] + ppEven[i][n]);
			}
		}
	}
	else
	{
		for (i=0; i < m_nInputs; i++)
		{
			for (n=0; n < sampleFrames; n++)
			{
				outputs[i][n] = inputs[i][n] - m_smProcess * (ppOdd[i][n] + ppEven[i][n]);
			}
		}

	}	
}

//-----------------------------------------------------------------------------------------
void Filter::setSampleRate(float sampleRate)
{
	this->sampleRate = sampleRate;
	isSampleRateChanged = true;
	sprintf(dbgStr, "Sample Rate = %f",sampleRate);
//	OutputDebugString(dbgStr);
}

//-----------------------------------------------------------------------------------------
void Filter::setBlockSize(int32_t blockSize)
{
	int i;
	this->blockSize = blockSize;
	if (m_ppOdd)
	{
		for(i=0; i < m_nOutputs; i++)
			delete [] m_ppOdd[i];

		delete [] m_ppOdd;
		m_ppOdd = 0;
	}
	m_ppOdd = new float*[m_nOutputs];

	for(i=0; i < m_nOutputs; i++)
		m_ppOdd[i] = new float[2*blockSize];

	if (m_ppEven)
	{
		for(i=0; i < m_nOutputs; i++)
			delete [] m_ppEven[i];

		delete [] m_ppEven;
		m_ppEven = 0;
	}
	m_ppEven = new float*[m_nOutputs];

	for(i=0; i < m_nOutputs; i++)
		m_ppEven[i] = new float[2*blockSize];
}
