/*************************************************************************/
/* vcf.c */
/*************************************************************************/
#include <string.h>
#include <stdlib.h>
#include <math.h>

#include "synth_defs.h"
#include "vcf.h"
#include "fast_trig.h"

/*************************************************************************/
/* Global Variables */
/*************************************************************************/

/******************************************************************************/
void VCF_Init(vcf_t *pObj, UINT32 id, vcf_common_t *pCom, synth_float_t fs)
{
	pObj->pCom = pCom;
	pObj->id = id;
	pObj->fs = fs;

	if (id == 0)
	{
		VCF_ModInit(pObj);
	}

	pObj->pOut = NULL;
	pObj->pCoef = NULL;
	pObj->bufsize = 0;
	VCF_SetType(pObj, VCF_FILTERTYPE_LOWPASS);
	VCF_SetOrder(pObj, 4);
	pObj->req_filter_update = 0;


	VCF_clear_state(pObj);
	VCF_SetBufsize(pObj, SYNTH_MAX_BUFSIZE);
	VCF_FilterParamUpdate(pObj);
	VCF_SetF(pObj, 20000);
	VCF_SetQ(pObj, 1);
}

void VCF_Free(vcf_t *pObj)
{
	VCF_SetBufsize(pObj, 0);

	if (pObj->id == 0)
	{
		VCF_ModFree(pObj);
	}

}

void VCF_ModInit(vcf_t *pObj)
{
	vcf_common_t *pCom = pObj->pCom;

	pCom->pLUT_cos = (synth_float_t*)malloc(FILTER_TABLE_SIZE*sizeof(synth_float_t));
	pCom->pLUT_sin = (synth_float_t*)malloc(FILTER_TABLE_SIZE*sizeof(synth_float_t));
}

void VCF_ModFree(vcf_t *pObj)
{
	vcf_common_t *pCom = pObj->pCom;
	
	free(pCom->pLUT_cos);
	free(pCom->pLUT_sin);
}

void VCF_LUT_gen(vcf_t *pObj)
{
	int i;
	synth_float_t cv, omega;
	vcf_common_t *pCom = pObj->pCom;
	
	for (i=0; i < FILTER_TABLE_SIZE; i++)
	{
		cv = (synth_float_t)i/(FILTER_TABLE_SIZE-1);
		omega = VCF_cv2omega(pObj, cv);
		pCom->pLUT_cos[i] = cos(jsy_pi*omega);
		pCom->pLUT_sin[i] = sin(jsy_pi*omega);
	}
}

void VCF_FilterParamUpdate(vcf_t *pObj)
{
	pObj->omega_max = 2*FILTER_F_MAX/pObj->fs;
	pObj->omega_min = 2*FILTER_F_MIN/pObj->fs;
	pObj->num_octaves = log(FILTER_F_MAX/FILTER_F_MIN)/log(FILTER_SWEEP_BASE);

	if (pObj->id == 0)
	{
		VCF_LUT_gen(pObj);
	}
}

synth_float_t VCF_LUT_get_cos(vcf_t *pObj, synth_float_t cv)
{
	int ni, ni_next;
	synth_float_t n, nf;
	vcf_common_t *pCom = pObj->pCom;

	n = jsy_min(1, jsy_max(0, cv))*(FILTER_TABLE_SIZE-1);
	ni = (int)n;
	nf = n - (synth_float_t)ni;
	ni_next = jsy_min(FILTER_TABLE_SIZE-1, ni+1);

	return (pCom->pLUT_cos[ni_next] - pCom->pLUT_cos[ni])*nf + pCom->pLUT_cos[ni];
}

synth_float_t VCF_LUT_get_sin(vcf_t *pObj, synth_float_t cv)
{
	int ni, ni_next;
	synth_float_t n, nf;
	vcf_common_t *pCom = pObj->pCom;

	n = jsy_min(1, jsy_max(0, cv))*(FILTER_TABLE_SIZE-1);
	ni = (int)n;
	nf = n - (synth_float_t)ni;
	ni_next = jsy_min(FILTER_TABLE_SIZE-1, ni+1);

	return (pCom->pLUT_sin[ni_next] - pCom->pLUT_sin[ni])*nf + pCom->pLUT_sin[ni];
}

void VCF_SetBufsize(vcf_t *pObj, UINT32 size)
{
	if (pObj->bufsize == size)
		return;

	pObj->bufsize = size;

	if (pObj->pOut)
		free(pObj->pOut);

	if (pObj->pCoef)
		free(pObj->pCoef);

	pObj->pOut = NULL;
	pObj->pCoef = NULL;

	if (!size)
		return;

	pObj->pOut = (synth_float_t*)malloc(pObj->bufsize*sizeof(synth_float_t));
	pObj->pCoef = (vcf_coef_t*)malloc(pObj->bufsize*sizeof(vcf_coef_t));
	pObj->pCoeff_last = pObj->pCoef;
	VCF_Coeff_update(pObj);

}

void VCF_SetFS(vcf_t *pObj, synth_float_t fs)
{
	pObj->fs = fs;
	VCF_FilterParamUpdate(pObj);
	pObj->req_filter_update = 1;
}

void VCF_SetF(vcf_t *pObj, synth_float_t f)
{
	if (f > pObj->fs/2)
		f = FILTER_F_MAX;

	if (f < FILTER_F_MIN)
		f = FILTER_F_MIN;

	if (f < 0)
		f = FILTER_F_MIN;

	pObj->cv_f_bias = VCF_omega2cv(pObj, 2*f/pObj->fs);
	pObj->req_filter_update = 1;
}

void VCF_SetQ(vcf_t *pObj, synth_float_t q)
{
	pObj->cv_q_bias = jsy_max(FILTER_Q_MIN, q);
	pObj->req_filter_update = 1;
}

void VCF_SetType(vcf_t *pObj, UINT32 type)
{
	pObj->type = type;
	VCF_SetQ(pObj, pObj->cv_q_bias);

	VCF_Coeff_update(pObj);
}

synth_float_t IIRCalcQp(unsigned p, unsigned N)
{
	return 1.0f/(synth_float_t)(2*sin(jsy_pi*(2*p-1)/(2*N)));
}

void VCF_SetOrder(vcf_t *pObj, UINT32 order)
{
	UINT32 p;
	
	if (pObj->order == order)
		return;

	if (order % 2)
		return;

	if (order > FILTER_MAX_ORDER)
		return;

	pObj->order = order;
	pObj->num_sections = pObj->order/2;

	for(p=0; p < pObj->num_sections; p++)
	{
		pObj->pQP_inv[p] = (synth_float_t)1.0/IIRCalcQp(p+1, pObj->order);
	}
	VCF_clear_state(pObj);
	VCF_SetQ(pObj, pObj->cv_q_bias);
	VCF_Coeff_update(pObj);
}

void VCF_Coeff_update(vcf_t *pObj)
{
	pObj->req_filter_update = 1;
}

void VCF_clear_state(vcf_t *pObj)
{
	UINT32 p;

	for(p=0; p < pObj->num_sections; p++)
	{
		pObj->pState[p].xn1 = 0;
		pObj->pState[p].xn2 = 0;
		pObj->pState[p].yn1 = 0;
		pObj->pState[p].yn2 = 0;
	}
	VCF_Coeff_update(pObj);
}

synth_float_t VCF_omega2cv(vcf_t *pObj, synth_float_t omega)
{
	synth_float_t cv;

	cv = log(omega/pObj->omega_min)/(pObj->num_octaves * log(FILTER_SWEEP_BASE));
	return jsy_min(1, jsy_max(0, cv));
}

synth_float_t VCF_cv2omega(vcf_t *pObj, synth_float_t cv)
{
	synth_float_t omega;

	omega = pObj->omega_min*pow(FILTER_SWEEP_BASE, pObj->num_octaves*jsy_min(1, jsy_max(0, cv))); 
	return omega;
}

void VCF_CalcCoeff_LPF(vcf_t *pObj, vcf_coef_t *pCoeff, synth_float_t *pCV_f, synth_float_t *pCV_q, UINT32 len)
{
	synth_float_t a0_inv;
	synth_float_t alpha, ks, kc, Q_inv, q, cv;
	unsigned p, n, filter_changed;

	ks = 0;
	kc = 0;
	Q_inv = 0;
	for(n=0; n < len; n++)
	{
		filter_changed = pObj->req_filter_update;

		if (fabs(pObj->cv_f_last - pCV_f[n]) > FILTER_RECALC_F_THRESH)
			filter_changed = 1;

		if (fabs(pObj->cv_q_last - pCV_q[n]) > FILTER_RECALC_Q_THRESH)
			filter_changed = 1;

		if (filter_changed)
		{
			pObj->req_filter_update = 0;
			pObj->cv_f_last = pCV_f[n];
			cv = pObj->cv_f_bias + pCV_f[n];
			ks = VCF_LUT_get_sin(pObj, cv);
			kc = VCF_LUT_get_cos(pObj, cv);
			q = jsy_min(FILTER_Q_MAX, jsy_max(FILTER_Q_MIN, pObj->cv_q_bias + FILTER_Q_MAX*pCV_q[n]));
			pObj->cv_q_last = q;
			if (pObj->order == 2)
				q *= q;

			Q_inv = (synth_float_t)1.0/q;
			pObj->pCoeff_last = pCoeff;
		}

		for(p=0; p < pObj->num_sections; p++)
		{
			if (filter_changed)
			{
				alpha = 0.5f*ks * Q_inv * pObj->pQP_inv[p];
				a0_inv = (synth_float_t)1.0/(1 + alpha);

				(*pCoeff).ak1 = -2.0f*kc *a0_inv;
				(*pCoeff).ak2 = (1 - alpha) *a0_inv;

				(*pCoeff).bk1 = (1 - kc) *a0_inv;
				(*pCoeff).bk0 = 0.5f*(1 - kc) *a0_inv;
				(*pCoeff).bk2 = (*pCoeff).bk0;
			}
			else
			{
				*pCoeff = pObj->pCoeff_last[p];
			}
			pCoeff++;
		}
	}
}


void VCF_CalcCoeff_HPF(vcf_t *pObj, vcf_coef_t *pCoeff, synth_float_t *pCV_f, synth_float_t *pCV_q, UINT32 len)
{
	synth_float_t a0_inv;
	synth_float_t alpha, ks, kc, Q_inv, q, cv;
	unsigned p, n, filter_changed;

	ks = 0;
	kc = 0;
	Q_inv = 0;
	for(n=0; n < len; n++)
	{
		filter_changed = pObj->req_filter_update;

		if (fabs(pObj->cv_f_last - pCV_f[n]) > FILTER_RECALC_F_THRESH)
			filter_changed = 1;

		if (fabs(pObj->cv_q_last - pCV_q[n]) > FILTER_RECALC_Q_THRESH)
			filter_changed = 1;

		if (filter_changed)
		{
			pObj->req_filter_update = 0;
			pObj->cv_f_last = pCV_f[n];
			cv = pObj->cv_f_bias + pCV_f[n];
			ks = VCF_LUT_get_sin(pObj, cv);
			kc = VCF_LUT_get_cos(pObj, cv);
			q = jsy_min(FILTER_Q_MAX, jsy_max(FILTER_Q_MIN, pObj->cv_q_bias + FILTER_Q_MAX*pCV_q[n]));
			pObj->cv_q_last = q;
			if (pObj->order == 2)
				q *= q;

			Q_inv = (synth_float_t)1.0/q;
			pObj->pCoeff_last = pCoeff;
		}

		for(p=0; p < pObj->num_sections; p++)
		{
			if (filter_changed)
			{
				alpha = 0.5f*ks * Q_inv * pObj->pQP_inv[p];
				a0_inv = (synth_float_t)1.0/(1 + alpha);

				(*pCoeff).ak1 = -2.0f*kc *a0_inv;
				(*pCoeff).ak2 = (1 - alpha) *a0_inv;

				(*pCoeff).bk1 = - (1 + kc) *a0_inv;
				(*pCoeff).bk0 = 0.5f*(1 + kc) *a0_inv;
				(*pCoeff).bk2 = (*pCoeff).bk0;
			}
			else
			{
				*pCoeff = pObj->pCoeff_last[p];
			}
			pCoeff++;
		}
	}
}


void VCF_CalcCoeff_BPF(vcf_t *pObj, vcf_coef_t *pCoeff, synth_float_t *pCV_f, synth_float_t *pCV_q, UINT32 len)
{
	synth_float_t a0_inv;
	synth_float_t alpha, ks, kc, Q_inv, q, cv;
	unsigned p, n, filter_changed;

	ks = 0;
	kc = 0;
	Q_inv = 0;
	for(n=0; n < len; n++)
	{
		filter_changed = pObj->req_filter_update;

		if (fabs(pObj->cv_f_last - pCV_f[n]) > FILTER_RECALC_F_THRESH)
			filter_changed = 1;

		if (fabs(pObj->cv_q_last - pCV_q[n]) > FILTER_RECALC_Q_THRESH)
			filter_changed = 1;

		if (filter_changed)
		{
			pObj->req_filter_update = 0;
			pObj->cv_f_last = pCV_f[n];
			cv = pObj->cv_f_bias + pCV_f[n];
			ks = VCF_LUT_get_sin(pObj, cv);
			kc = VCF_LUT_get_cos(pObj, cv);
			q = jsy_min(FILTER_Q_MAX, jsy_max(FILTER_Q_MIN, pObj->cv_q_bias + FILTER_Q_MAX*pCV_q[n]));
			pObj->cv_q_last = q;
			if (pObj->order == 2)
				q *= q;

			Q_inv = (synth_float_t)1.0/q;
			pObj->pCoeff_last = pCoeff;
		}

		for(p=0; p < pObj->num_sections; p++)
		{
			if (filter_changed)
			{
				alpha = 0.5f*ks * Q_inv * pObj->pQP_inv[p];
				a0_inv = (synth_float_t)1.0/(1 + alpha);

				(*pCoeff).ak1 = -2.0f*kc *a0_inv;
				(*pCoeff).ak2 = (1 - alpha) *a0_inv;

				(*pCoeff).bk1 = 0;
				(*pCoeff).bk0 = 0.5*ks *a0_inv;
				(*pCoeff).bk2 = -(*pCoeff).bk0;
			}
			else
			{
				*pCoeff = pObj->pCoeff_last[p];
			}
			pCoeff++;
		}
	}
}

void VCF_CalcCoeff(vcf_t *pObj, vcf_coef_t *pCoeff, synth_float_t *pFG, synth_float_t *pQ, UINT32 len)
{
	switch(pObj->type)
	{
		case VCF_FILTERTYPE_LOWPASS:
		VCF_CalcCoeff_LPF(pObj, pCoeff, pFG, pQ, len);
			break;

		case VCF_FILTERTYPE_HIGHPASS:
		VCF_CalcCoeff_HPF(pObj, pCoeff, pFG, pQ, len);
			break;

		case VCF_FILTERTYPE_BANDPASS:
		VCF_CalcCoeff_BPF(pObj, pCoeff, pFG, pQ, len);
			break;

		default:
		VCF_CalcCoeff_LPF(pObj, pCoeff, pFG, pQ, len);
			break;
	}

}

synth_float_t* VCF_ProcessDataV(vcf_t *pObj, synth_float_t *pCV_f, synth_float_t *pCV_q, synth_float_t *pX, UINT32 len)
{
	synth_float_t xp, yp, *pY;
	unsigned i, p;
	vcf_state_t *pState = pObj->pState;
	vcf_coef_t *pCoeff;

	VCF_CalcCoeff(pObj, pObj->pCoef, pCV_f, pCV_q, len);

	pCoeff = pObj->pCoef;

	yp = 0;
	pY = pObj->pOut;
	for (i=0; i<len; i++)
	{
		xp = pX[i];
		for (p=0; p < pObj->num_sections; p++)
		{
			yp = (*pCoeff).bk0*xp 
			   + (*pCoeff).bk1*pState[p].xn1
			   + (*pCoeff).bk2*pState[p].xn2
			   - (*pCoeff).ak1*pState[p].yn1
			   - (*pCoeff).ak2*pState[p].yn2;
				pCoeff++;
			
			pState[p].yn2 = pState[p].yn1;
			pState[p].yn1 = yp;
			pState[p].xn2 = pState[p].xn1;
			pState[p].xn1 = xp;
			xp	  = yp;
		}
		pY[i] = yp;	
	}
	return pObj->pOut;
}
