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

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

#include "synth_defs.h"
#include "lfo.h"

// --------------------------------------------------------------
// internal funcs
// --------------------------------------------------------------
void LFO_freq_update(lfo_t *pObj)
{
	synth_float_t f;
	
	f = pObj->param[LFO_PARAM2_FREQ];
	pObj->b = 2.0 * sin(f*pi / pObj->fs);
	pObj->dx = f/pObj->fs;

	if (pObj->param[LFO_PARAM2_DELAY] > 0)
	{
		pObj->delay_dx =  1.0/(pObj->fs*pObj->param[LFO_PARAM2_DELAY]);
	}
	else
	{
		pObj->delay_dx =  1.0;
	}

	pObj->attack_a =  1.0/(pObj->fs*pObj->param[LFO_PARAM2_ATTACK]*LFO_KE);

}

void LFO_smmother_update(lfo_t *pObj)
{
	pObj->smooth_is_negative = (pObj->param[LFO_PARAM2_SMOOTH] < 0);
	
	if (fabs(pObj->param[LFO_PARAM2_SMOOTH]) < 0.001)
	{
		pObj->smooth_b = 1;
		pObj->smooth_is_negative = 0;
	}
	else
	{
		pObj->smooth_b = fabs((synth_float_t)5/(pObj->param[LFO_PARAM2_SMOOTH]*pObj->fs));
	}

	pObj->smooth_a = 1 - pObj->smooth_b;

}

// --------------------------------------------------------------
// Exported functions
// --------------------------------------------------------------
void LFO_Init(lfo_t *pObj, synth_float_t fs)
{
	pObj->fs = fs;
	pObj->pOut = NULL;
	pObj->bufsize = 0;

	pObj->param[LFO_PARAM2_WAVEFORM] = LFO_WAVEFORM_SINE;
	pObj->param[LFO_PARAM2_FREQ] = 1;
	pObj->param[LFO_PARAM2_SMOOTH] = 0.001;		// seconds
	pObj->param[LFO_PARAM2_DELAY] = 0;		// seconds
	pObj->param[LFO_PARAM2_ATTACK] = 0;		// seconds
	
	pObj->sh_sample = 0;
	pObj->out = 0;
	pObj->sqr = 0;
	pObj->tri = 0;
	pObj->gain = 1;
	pObj->offset = 0.5;

	LFO_Reset(pObj, 0);

	pObj->freq_update_req = 0;
	LFO_SetBufsize(pObj, SYNTH_MAX_BUFSIZE);

	Noise_Init(&pObj->noise, 1+(UINT32)clock() * (UINT32)clock());
}

void LFO_Free(lfo_t *pObj)
{
	LFO_SetBufsize(pObj, 0);
}

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

	pObj->bufsize = size;

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

	pObj->pOut = NULL;

	if (!size)
		return;

	pObj->pOut = (synth_float_t*)malloc(pObj->bufsize*sizeof(synth_float_t));
}

void LFO_SetFS(lfo_t *pObj, synth_float_t fs)
{
	pObj->fs = fs;
	LFO_freq_update(pObj);
}

void LFO_Reset(lfo_t *pObj, synth_float_t initial_phase)
{
	pObj->a = 0.5;
	pObj->y[0] = pObj->a*cos(2*pi*initial_phase);
	pObj->y[1] = pObj->a*sin(2*pi*initial_phase);
	pObj->x = initial_phase;
	pObj->out = 0;
	pObj->sqr = 0;
	pObj->tri = 0;
	pObj->delay_x = 0;
	pObj->attack_y = 0;
	LFO_freq_update(pObj);
	LFO_smmother_update(pObj);

}

void LFO_Param2Set(lfo_t *pObj, UINT32 type, synth_float_t value)
{
	switch(type)
	{
		case LFO_PARAM2_WAVEFORM:
			if (pObj->param[type] == (UINT32)value)
				break;
			pObj->param[type] = (UINT32)value;
			pObj->freq_update_req = 1;
			break;

		case LFO_PARAM2_FREQ:
			pObj->param[type] = value;
			pObj->freq_update_req = 1;
			break;

		case LFO_PARAM2_SMOOTH:
			pObj->param[type] = value;
			LFO_smmother_update(pObj);
			break;

		case LFO_PARAM2_DELAY:
			pObj->param[type] = value;
			pObj->freq_update_req = 1;
			break;

		case LFO_PARAM2_ATTACK:
			pObj->param[type] = value;
			pObj->freq_update_req = 1;
			break;

		case LFO_PARAM2_SYMMETRY:
			pObj->param[type] = value;
			pObj->gain = 1;
			pObj->offset = 0.5 *(1 + value) - 1;
			break;

		default:
			break;
	}
}

synth_float_t* LFO_ProcessDataV(lfo_t *pObj, UINT32 len)
{
	synth_float_t *pOut, out, in;
	UINT32 i, i0;
	
	pOut = pObj->pOut;

	// Process Delay
	for (i=0; i< len; i++)
	{	
		pObj->delay_x += pObj->delay_dx;

		if (pObj->delay_x >= 1.0)
			break;
		
		in = (synth_float_t)0;
		pObj->out = pObj->smooth_a*pObj->out + pObj->smooth_b*in;
		out = pObj->out;
		if (pObj->smooth_is_negative)
			out = 2*in - pObj->out;

		*(pOut++) = out;

		if (pObj->freq_update_req)
		{
			pObj->freq_update_req = 0;
			LFO_freq_update(pObj);
		}
	}
	i0 = i;

	if (pObj->param[LFO_PARAM2_WAVEFORM] == LFO_WAVEFORM_SAW)
	{
		// Create phase
		for (i=i0; i< len; i++)
		{
			in = pObj->x + pObj->offset;
			pObj->out = pObj->smooth_a*pObj->out + pObj->smooth_b*in;
			out = pObj->out;
			if (pObj->smooth_is_negative)
				out = 2*in - pObj->out;

			*(pOut++) = out*pObj->attack_y;
			pObj->attack_y += pObj->attack_a*(1-pObj->attack_y);

			pObj->x += pObj->dx;
			if (pObj->x >= 1.f)
			{
				pObj->x -= 1.f;
			}

			if (pObj->freq_update_req)
			{
				pObj->freq_update_req = 0;
				LFO_freq_update(pObj);
			}
		}
	}

	if (pObj->param[LFO_PARAM2_WAVEFORM] == LFO_WAVEFORM_ONE_MINUS_SAW)
	{
		// Create phase
		for (i=i0; i< len; i++)
		{
			in = (1-pObj->x) + pObj->offset;
			pObj->out = pObj->smooth_a*pObj->out + pObj->smooth_b*in;
			out = pObj->out;
			if (pObj->smooth_is_negative)
				out = 2*in - pObj->out;

			*(pOut++) = out*pObj->attack_y;
			pObj->attack_y += pObj->attack_a*(1-pObj->attack_y);

			pObj->x += pObj->dx;
			if (pObj->x >= 1.f)
			{
				pObj->x -= 1.f;
			}

			if (pObj->freq_update_req)
			{
				pObj->freq_update_req = 0;
				LFO_freq_update(pObj);
			}
		}
	}

	if (pObj->param[LFO_PARAM2_WAVEFORM] == LFO_WAVEFORM_SH_UNI)
	{
		// Create phase for S/H
		for (i=i0; i< len; i++)
		{
			if (pObj->x >= 1.f)
			{
				pObj->x -= 1.f;
				pObj->sh_sample = Noise_Uniform(&pObj->noise, 1, 0.5);
			}
			in = pObj->sh_sample + pObj->offset;
			pObj->out = pObj->smooth_a*pObj->out + pObj->smooth_b*in;
			out = pObj->out;
			if (pObj->smooth_is_negative)
				out = 2*in - pObj->out;

			*(pOut++) = out*pObj->attack_y;
			pObj->attack_y += pObj->attack_a*(1-pObj->attack_y);

			pObj->x += pObj->dx;

			if (pObj->freq_update_req)
			{
				pObj->freq_update_req = 0;
				LFO_freq_update(pObj);
			}
		}
	}

	if (pObj->param[LFO_PARAM2_WAVEFORM] == LFO_WAVEFORM_SH_GAUSS)
	{
		// Create phase for S/H
		for (i=i0; i< len; i++)
		{
			if (pObj->x >= 1.f)
			{
				pObj->x -= 1.f;
				do
				{
					pObj->sh_sample = Noise_Gaussian(&pObj->noise, sqrt(1.f/36), 0.5);

				} while ((pObj->sh_sample < 0) || (pObj->sh_sample > 1.f));
			}
			in = pObj->sh_sample + pObj->offset;
			pObj->out = pObj->smooth_a*pObj->out + pObj->smooth_b*in;
			out = pObj->out;
			if (pObj->smooth_is_negative)
				out = 2*in - pObj->out;

			*(pOut++) = out*pObj->attack_y;
			pObj->attack_y += pObj->attack_a*(1-pObj->attack_y);

			pObj->x += pObj->dx;

			if (pObj->freq_update_req)
			{
				pObj->freq_update_req = 0;
				LFO_freq_update(pObj);
			}
		}
	}

	// Create output
	if (pObj->param[LFO_PARAM2_WAVEFORM] == LFO_WAVEFORM_SINE)
	{
		for (i=i0; i< len; i++)
		{
			in = pObj->y[1]+pObj->a + pObj->offset;
			pObj->out = pObj->smooth_a*pObj->out + pObj->smooth_b*in;
			out = pObj->out;
			if (pObj->smooth_is_negative)
				out = 2*in - pObj->out;

			*(pOut++) = out*pObj->attack_y;
			pObj->attack_y += pObj->attack_a*(1-pObj->attack_y);

			pObj->y[0] = pObj->y[0] - pObj->b*pObj->y[1];
			pObj->y[1] = pObj->y[1] + pObj->b*pObj->y[0];

			if (pObj->freq_update_req)
			{
				pObj->freq_update_req = 0;
				LFO_freq_update(pObj);
			}
		}
	}

	if (pObj->param[LFO_PARAM2_WAVEFORM] == LFO_WAVEFORM_ONE_MINUS_SINE)
	{
		for (i=i0; i< len; i++)
		{
			in = (1-(pObj->y[1]+pObj->a)) + pObj->offset;
			pObj->out = pObj->smooth_a*pObj->out + pObj->smooth_b*in;
			out = pObj->out;
			if (pObj->smooth_is_negative)
				out = 2*in - pObj->out;

			*(pOut++) = out*pObj->attack_y;
			pObj->attack_y += pObj->attack_a*(1-pObj->attack_y);

			pObj->y[0] = pObj->y[0] - pObj->b*pObj->y[1];
			pObj->y[1] = pObj->y[1] + pObj->b*pObj->y[0];

			if (pObj->freq_update_req)
			{
				pObj->freq_update_req = 0;
				LFO_freq_update(pObj);
			}
		}
	}

	if (pObj->param[LFO_PARAM2_WAVEFORM] == LFO_WAVEFORM_COSINE)
	{
		for (i=i0; i< len; i++)
		{
			in = pObj->y[0]+pObj->a + pObj->offset;
			pObj->out = pObj->smooth_a*pObj->out + pObj->smooth_b*in;
			out = pObj->out;
			if (pObj->smooth_is_negative)
				out = 2*in - pObj->out;

			*(pOut++) = out*pObj->attack_y;
			pObj->attack_y += pObj->attack_a*(1-pObj->attack_y);

			pObj->y[0] = pObj->y[0] - pObj->b*pObj->y[1];
			pObj->y[1] = pObj->y[1] + pObj->b*pObj->y[0];

			if (pObj->freq_update_req)
			{
				pObj->freq_update_req = 0;
				LFO_freq_update(pObj);
			}
		}
	}

	if (pObj->param[LFO_PARAM2_WAVEFORM] == LFO_WAVEFORM_ONE_MINUS_COSINE)
	{
		for (i=i0; i< len; i++)
		{
			in = (1-(pObj->y[0]+pObj->a)) + pObj->offset;
			pObj->out = pObj->smooth_a*pObj->out + pObj->smooth_b*in;
			out = pObj->out;
			if (pObj->smooth_is_negative)
				out = 2*in - pObj->out;

			*(pOut++) = out*pObj->attack_y;
			pObj->attack_y += pObj->attack_a*(1-pObj->attack_y);

			pObj->y[0] = pObj->y[0] - pObj->b*pObj->y[1];
			pObj->y[1] = pObj->y[1] + pObj->b*pObj->y[0];

			if (pObj->freq_update_req)
			{
				pObj->freq_update_req = 0;
				LFO_freq_update(pObj);
			}
		}
	}

	if (pObj->param[LFO_PARAM2_WAVEFORM] == LFO_WAVEFORM_SQUARE)
	{
		// Create phase
		for (i=i0; i< len; i++)
		{
			in = (synth_float_t)pObj->sqr + pObj->offset;
			pObj->out = pObj->smooth_a*pObj->out + pObj->smooth_b*in;
			out = pObj->out;
			if (pObj->smooth_is_negative)
				out = 2*in - pObj->out;

			*(pOut++) = out*pObj->attack_y;
			pObj->attack_y += pObj->attack_a*(1-pObj->attack_y);

			pObj->x += 2*pObj->dx;
			if (pObj->x >= 1.f)
			{
				pObj->x -= 1.f;
				pObj->sqr = !pObj->sqr;
			}

			if (pObj->freq_update_req)
			{
				pObj->freq_update_req = 0;
				LFO_freq_update(pObj);
			}
		}
	}

	if (pObj->param[LFO_PARAM2_WAVEFORM] == LFO_WAVEFORM_ONE_MINUS_SQUARE)
	{
		// Create phase
		for (i=i0; i< len; i++)
		{
			in = (1-((synth_float_t)pObj->sqr)) + pObj->offset;
			pObj->out = pObj->smooth_a*pObj->out + pObj->smooth_b*in;
			out = pObj->out;
			if (pObj->smooth_is_negative)
				out = 2*in - pObj->out;

			*(pOut++) = out*pObj->attack_y;
			pObj->attack_y += pObj->attack_a*(1-pObj->attack_y);

			pObj->x += 2*pObj->dx;
			if (pObj->x >= 1.f)
			{
				pObj->x -= 1.f;
				pObj->sqr = !pObj->sqr;
			}

			if (pObj->freq_update_req)
			{
				pObj->freq_update_req = 0;
				LFO_freq_update(pObj);
			}
		}
	}

	if (pObj->param[LFO_PARAM2_WAVEFORM] == LFO_WAVEFORM_TRIANGLE)
	{
		// Create phase
		for (i=i0; i< len; i++)
		{
			in = (synth_float_t)pObj->tri + pObj->offset;
			pObj->out = pObj->smooth_a*pObj->out + pObj->smooth_b*in;
			out = pObj->out;
			if (pObj->smooth_is_negative)
				out = 2*in - pObj->out;

			*(pOut++) = out*pObj->attack_y;
			pObj->attack_y += pObj->attack_a*(1-pObj->attack_y);

			if (pObj->x < 0.5)
				pObj->tri = 2*pObj->x;
			else
				pObj->tri = 2*(1-pObj->x);

			pObj->x += pObj->dx;
			if (pObj->x >= 1.f)
			{
				pObj->x -= 1.f;
			}

			if (pObj->freq_update_req)
			{
				pObj->freq_update_req = 0;
				LFO_freq_update(pObj);
			}
		}
	}
	
	if (pObj->param[LFO_PARAM2_WAVEFORM] == LFO_WAVEFORM_ONE_MINUS_TRIANGLE)
	{
		// Create phase
		for (i=i0; i< len; i++)
		{
			in = (1-((synth_float_t)pObj->tri)) + pObj->offset;
			pObj->out = pObj->smooth_a*pObj->out + pObj->smooth_b*in;
			out = pObj->out;
			if (pObj->smooth_is_negative)
				out = 2*in - pObj->out;

			*(pOut++) = out*pObj->attack_y;
			pObj->attack_y += pObj->attack_a*(1-pObj->attack_y);

			if (pObj->x < 0.5)
				pObj->tri = 2*pObj->x;
			else
				pObj->tri = 2*(1-pObj->x);

			pObj->x += pObj->dx;
			if (pObj->x >= 1.f)
			{
				pObj->x -= 1.f;
			}

			if (pObj->freq_update_req)
			{
				pObj->freq_update_req = 0;
				LFO_freq_update(pObj);
			}
		}
	}

	return pObj->pOut;
}
