// --------------------------------------------------------------
// --------------------------------------------------------------


#include <string.h>
#include <stdlib.h>
#include <math.h>

#include "noise.h"
#include "synth_defs.h"
#include "blit.h"
#include "fir/fir2.h"
#include "synth_types.h"
#include "vector_utils.h"

// --------------------------------------------------------------
// internal funcs
// --------------------------------------------------------------
INT32 smin(INT32 x, INT32 y)
{
	if (x < y)
		return x;

	return y;
}

INT32 smax(INT32 x, INT32 y)
{
	if (x > y)
		return x;

	return y;
}

UINT32 umin(UINT32 x, UINT32 y)
{
	if (x < y)
		return x;

	return y;
}

UINT32 umax(UINT32 x, UINT32 y)
{
	if (x > y)
		return x;

	return y;
}

void IP_ModInit(blit_common_t *pCom)
{
	INT32 n, k;
	synth_float_t *pN_K = pCom->N_K;
	synth_float_t *pK = pCom->K;

	for (n=0; n <= BLIT_INTEROLATION_ORDER; n++)
	{
		for (k=0; k <= BLIT_INTEROLATION_ORDER; k++)
		{
			if (k == n)
				continue;

			*(pN_K++) =  (synth_float_t)1 / (n-k);
			*(pK++) =  (synth_float_t)k;
		}
	}
}

synth_float_t IP_Process(blit_t *pObj, synth_float_t *pLUT, synth_float_t m)
{
	INT32 mi, j;
	INT32 n, k;
	synth_float_t hn, y = 0, delay;
	synth_float_t *pN_K = pObj->pCom->N_K;
	synth_float_t *pK = pObj->pCom->K;

	mi = (INT32)m;
	delay = m - (synth_float_t)mi;

	j = (INT32)BLIT_INTEROLATION_ORDER;
	for (n=0; n <= BLIT_INTEROLATION_ORDER; n++)
	{
		hn = (synth_float_t)1;
		for (k=0; k < BLIT_INTEROLATION_ORDER; k++)
		{
			hn *= (*(pN_K++) * (delay - *(pK++)));
		}
		y += hn*pLUT[smax(0, mi-j)];
		j--;
	}
	return y;
}

void BLIT_process_misc(blit_t *pObj)
{
	if (pObj->misc_process_downsammple_counter)
	{
		pObj->misc_process_downsammple_counter--;
		return;
	}

	pObj->misc_process_downsammple_counter = BLIT_MISC_PROCESS_DOWN_SAMPLE;

	const synth_float_t alpha = BLIT_JITTER_ERROR*BLIT_MISC_PROCESS_DOWN_SAMPLE;
	synth_float_t pm = Noise_Uniform(&pObj->pCom->noise, 1, 0);

	if (pm > 0)
	{
		pObj->jitter += alpha;
	}
	else 
	{
		pObj->jitter -= alpha;
	}

}

void BLIT_freq_update(blit_t *pObj, synth_float_t scale)
{
	synth_float_t p;

	pObj->freq_update_req = 0;

//	p = pObj->fs/(pObj->f*scale*pObj->fm);
	p = pObj->fs/(scale * pObj->fm * (1 + pObj->jitter));

	pObj->dx  = (synth_float_t)1.0/p;
	pObj->m = umin(BLIT_NUM_HARM_MAX-1, (UINT32)((synth_float_t)SYNTH_BANDWIDTH*2.0*(int)((p/2)) + 0));
}

// --------------------------------------------------------------
// Exported functions
// --------------------------------------------------------------
void BLIT_ModInit(blit_common_t *pCom)
{
	int m, n;
	synth_float_t **ppKaiser;
	synth_float_t x, dx, a, b, blit, blep;

	// Calculate table sizes
	pCom->pTableSizes = (UINT32*)malloc(BLIT_NUM_HARM_MAX*sizeof(UINT32));
	for (m=0; m < BLIT_NUM_HARM_MAX; m++)
	{
		pCom->pTableSizes[m] = m*BLIT_TABLE_OVERSAMPLING;
		if (pCom->pTableSizes[m] < BLIT_TABLE_SIZE_MIN)
			pCom->pTableSizes[m] = BLIT_TABLE_SIZE_MIN;
	}

	// Generate Kaiser window
	ppKaiser = (synth_float_t**)malloc(BLIT_NUM_HARM_MAX*sizeof(synth_float_t*));
	for (m=0; m < BLIT_NUM_HARM_MAX; m++)
	{
		ppKaiser[m] = (synth_float_t*)malloc(pCom->pTableSizes[m]*sizeof(synth_float_t));
		CalcKaiser(NULL, ppKaiser[m], 8.f, pCom->pTableSizes[m]);
	}

	// Allocate BLIT table
	pCom->ppBLEP = (synth_float_t**)malloc(BLIT_NUM_HARM_MAX*sizeof(synth_float_t*));
	for (m=0; m < BLIT_NUM_HARM_MAX; m++)
	{
		pCom->ppBLEP[m] = (synth_float_t*)malloc(pCom->pTableSizes[m]*sizeof(synth_float_t));
	}

	// Generate BLIT
	for (m=0; m < BLIT_NUM_HARM_MAX; m++)
	{
		dx = (synth_float_t)1/(pCom->pTableSizes[m]);
		x = -0.5;
		blep = 0;
		for (n=0; n < pCom->pTableSizes[m]; n++)
		{
			a = sin(x*jsy_pi);
			b = sin(m*x*jsy_pi);
			if (a == 0)
				blit = (synth_float_t)m;
			else
				blit = b/a * ppKaiser[m][n];

			blep += blit;
			pCom->ppBLEP[m][n] = blep/(pCom->pTableSizes[m]);
			x += dx;
		}
	}

/*
	m = BLIT_NUM_HARM_MAX/2;
	for (n=0; n < BLIT_TABLE_SIZE; n++)
	{
		SynthDebug("BLEP[%d][%d] = %.14f\n", m, n, pCom->ppBLEP[m][n]);
	}

	for (n=0; n < BLIT_TABLE_SIZE; n++)
	{
		SynthDebug("Kaiser[%d] = %.14f\n", n, pKaiser[n]);
	}
*/
	for (m=0; m < BLIT_NUM_HARM_MAX; m++)
	{
		free(ppKaiser[m]);
	}
	free(ppKaiser);

	// Precalculate Interpolation constants
	IP_ModInit(pCom);
}

void BLIT_ModFree(blit_common_t *pCom)
{
	int m;

	if (pCom->ppBLEP)
	{
		for (m=0; m < BLIT_NUM_HARM_MAX; m++)
		{
			free(pCom->ppBLEP[m]);
		}
		free(pCom->ppBLEP);
	}
	free(pCom->pTableSizes);
}

void BLIT_Init(blit_t *pObj, blit_common_t *pCom, synth_float_t fs)
{
	pObj->pCom = pCom;
	Noise_Init(&pCom->noise, 0x31101970);

	pObj->fs = fs;
	pObj->dutycycle = 0.5;
	BLIT_Reset(pObj, 0);
	pObj->bufsize = 0;
	BLIT_SetBufsize(pObj, SYNTH_MAX_BUFSIZE);
	BLIT_Prepare(pObj, SYNTH_MAX_BUFSIZE);
}

void BLIT_Free(blit_t *pObj)
{
	BLIT_SetBufsize(pObj, 0);
}

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

	pObj->bufsize = size;

	if (!size)
		return;

}

void BLIT_SetFS(blit_t *pObj, synth_float_t fs)
{
	pObj->fs = fs;
	pObj->freq_update_req = 1;
}

void BLIT_Prepare(blit_t *pObj, UINT32 len)
{
	synth_float_t pitch_buf[SYNTH_MAX_BUFSIZE];
	synth_float_t out_buf[SYNTH_MAX_BUFSIZE];

	len = jsy_min(len, SYNTH_MAX_BUFSIZE);

	// Let Oscillators run with different pitches to randomize phase
	Add_inplace_VS(pitch_buf, 0, 8*440.0*(1+0.01*(synth_float_t)rand()/RAND_MAX), len);
	BLIT_Process_SAW_Vector(pObj, pitch_buf, NULL, NULL, NULL, out_buf, len);
	BLIT_Process_SQR_Vector(pObj, pitch_buf, NULL, NULL, NULL, NULL, out_buf, len);
	BLIT_Process_TRI_Vector(pObj, pitch_buf, NULL, NULL, NULL, out_buf, len);
}

void BLIT_Reset(blit_t *pObj, synth_float_t phase)
{
	pObj->saw_x = fmod(0.5f + phase, 1);
	pObj->sqr_x1 = fmod(0.5f + phase, 1);
	pObj->sqr_x2 = fmod(0.5f + phase, 1);
	pObj->sqr_pol1 = 1;				// ToDo: Adjust according to phase
	pObj->sqr_pol2 = 1;				// ToDo: Adjust according to phase
	pObj->tri_x = fmod(0.5f + phase, 1);
	pObj->tri_sum = 0;				// ToDo: Adjust according to phase
	pObj->tri_pol = 1;				// ToDo: Adjust according to phase
	pObj->fm = 1;
	pObj->pwm = 0;
	pObj->pulse_offset = 0;		// ToDo: Adjust according to phase
	pObj->jitter = 0;
	pObj->misc_process_downsammple_counter = BLIT_MISC_PROCESS_DOWN_SAMPLE;
}

void BLIT_Start(blit_t *pObj)
{
	pObj->freq_update_req = 1;
}

void BLIT_SetDutyCycle(blit_t *pObj, synth_float_t duty_cycle)
{
	pObj->dutycycle = duty_cycle;
}

void BLIT_Process_SAW_Vector(blit_t *pObj, synth_float_t *pPitch, synth_float_t *pCV_fm, UINT32 *pSyncIn, UINT32 *pSyncOut, synth_float_t *pOut, UINT32 len)
{
	UINT32 i, is_slave, m;
	synth_float_t out;
	synth_float_t blep, n;
	blit_common_t *pCom = pObj->pCom;

	is_slave = (pSyncIn != NULL) && (pSyncOut == NULL);

	if (pSyncOut)
		memset(pSyncOut, 0, len*sizeof(synth_float_t));

	// Create output
	for (i=0; i< len; i++)
	{
		BLIT_process_misc(pObj);
		if (pObj->freq_update_req)
		{
			BLIT_freq_update(pObj, pPitch[i]);
		}

		if (pObj->saw_x >= (synth_float_t)0.5)
		{
			pObj->saw_x -= 1;
			if (pCV_fm)
			{
				pObj->fm = (synth_float_t)pow((synth_float_t)2, pCV_fm[i]);
			}
			BLIT_freq_update(pObj, pPitch[i]);
		}

		m = pObj->m;

		// Lagrange interpolation
		n = (pObj->saw_x+0.5)*((pCom->pTableSizes[m])-1);
		blep = IP_Process(pObj, &pCom->ppBLEP[m][0], n);

		out = pObj->saw_x - blep;
		pObj->saw_x += pObj->dx;

		*(pOut++) = 2*(out+0.5);
	}
}

void BLIT_Process_SQR_Vector(blit_t *pObj, synth_float_t *pPitch, synth_float_t *pCV_fm, synth_float_t *pCV_pwm, UINT32 *pSyncIn, UINT32 *pSyncOut, synth_float_t *pOut, UINT32 len)
{
	UINT32 i, is_slave, m;
	synth_float_t blep1, blep2, n;
	synth_float_t dutycycle;
	blit_common_t *pCom = pObj->pCom;

	is_slave = (pSyncIn != NULL) && (pSyncOut == NULL);

	if (pSyncOut)
		memset(pSyncOut, 0, len*sizeof(synth_float_t));

	// Create output
	for (i=0; i< len; i++)
	{
		BLIT_process_misc(pObj);
		if (pObj->freq_update_req)
		{
			BLIT_freq_update(pObj, pPitch[i]);
		}

		if (pObj->sqr_x1 >= (synth_float_t)0.5)
		{
			pObj->sqr_x1 -= 1;
			pObj->sqr_pol1 = !pObj->sqr_pol1;
			if (pCV_fm)
			{
				pObj->fm = (synth_float_t)pow((synth_float_t)2, pCV_fm[i]);
			}
			if (pCV_pwm)
			{
				pObj->pwm = (synth_float_t)pCV_pwm[i];
			}
			dutycycle = jsy_min(1, jsy_max(0, pObj->dutycycle + pObj->pwm));
			pObj->pulse_offset = 2*(dutycycle-0.5);
			pObj->sqr_x2 = pObj->sqr_x1 + dutycycle - 1;
			pObj->sqr_pol2 = pObj->sqr_pol1;
			BLIT_freq_update(pObj, pPitch[i]);
		}
		while (pObj->sqr_x2 >= (synth_float_t)0.5)
		{
			pObj->sqr_pol2 = !pObj->sqr_pol2;
			pObj->sqr_x2 -= 1;
		}
		while (pObj->sqr_x2 < (synth_float_t)-0.5)
		{
			pObj->sqr_pol2 = !pObj->sqr_pol2;
			pObj->sqr_x2 += 1;
		}
		m = pObj->m;

		// Lagrange interpolation
		n = (pObj->sqr_x1+0.5)*((pCom->pTableSizes[m])-1);
		blep1 = IP_Process(pObj, &pCom->ppBLEP[m][0], n);
		
		if (pObj->sqr_pol1)
			blep1 = 1 - blep1;

		n = (pObj->sqr_x2+0.5)*((pCom->pTableSizes[m])-1);
		blep2 = IP_Process(pObj, &pCom->ppBLEP[m][0], n);
			
		if (pObj->sqr_pol2)
			blep2 = 1 - blep2;

		*(pOut++) = 4*(blep1-0.5)*(blep2-0.5) - pObj->pulse_offset;

		pObj->sqr_x1 += pObj->dx;
		pObj->sqr_x2 += pObj->dx;
	}
}

void BLIT_Process_TRI_Vector(blit_t *pObj, synth_float_t *pPitch, synth_float_t *pCV_fm, UINT32 *pSyncIn, UINT32 *pSyncOut, synth_float_t *pOut, UINT32 len)
{
	UINT32 i, is_slave, m;
	synth_float_t out;
	synth_float_t blep, n;
	blit_common_t *pCom = pObj->pCom;

	is_slave = (pSyncIn != NULL) && (pSyncOut == NULL);

	if (pSyncOut)
		memset(pSyncOut, 0, len*sizeof(synth_float_t));

	for (i=0; i< len; i++)
	{
		BLIT_process_misc(pObj);
		if (pObj->freq_update_req)
		{
			BLIT_freq_update(pObj, pPitch[i]);
		}

		if (pObj->tri_x >= (synth_float_t)0.5)
		{
			pObj->tri_x -= 1;
			pObj->tri_pol = !pObj->tri_pol;
			if (pCV_fm)
			{
				pObj->fm = (synth_float_t)pow((synth_float_t)2, pCV_fm[i]);
			}
			BLIT_freq_update(pObj, 2*pPitch[i]);
		}
		m = pObj->m;

		// Lagrange interpolation
		n = (pObj->tri_x+0.5)*((pCom->pTableSizes[m])-1);
//		blep = IP_Process(&_sg_ppBLEP[m][0], n);

		// No interpolation
		blep = pCom->ppBLEP[m][(UINT32)n];

		if (pObj->tri_pol)
			out = 1 - blep;
		else
			out = blep;

	    pObj->tri_sum += 4*(out-0.5)*pObj->dx;
		*(pOut++) = pObj->tri_sum;

		pObj->tri_x += pObj->dx;

	}
}
