#include "sidfx.h"

enum SIDFXState
{
	SIDFX_IDLE,
	SIDFX_RESET_0,
	SIDFX_RESET_1,
	SIDFX_READY,
	SIDFX_PLAY,
	SIDFX_WAIT
};

__striped static struct SIDFXChannel
{
	const SIDFX	* volatile 		com;
	byte						delay, priority;
	volatile byte				cnt;
	volatile SIDFXState			state;
	unsigned					freq, pwm;

}	channels[3];

void sidfx_init(void)
{
	for(char i=0; i<3; i++)
	{
		channels[i].com = nullptr;
		channels[i].state = SIDFX_IDLE;
		channels[i].priority = 0;
		channels[i].delay = 1;
	}
}

bool sidfx_idle(byte chn)
{
	return channels[chn].state == SIDFX_IDLE;
}

char sidfx_cnt(byte chn)
{
	return channels[chn].cnt;	
}

void sidfx_play(byte chn, const SIDFX * fx, byte cnt)
{
	SIDFXState		ns = channels[chn].state;

	if (ns == SIDFX_IDLE)
		ns = SIDFX_READY;
	else if (channels[chn].priority <= fx->priority)
		ns = SIDFX_RESET_1;
	else
		return;

	channels[chn].state = SIDFX_IDLE;
	channels[chn].delay = 1;

	channels[chn].com = fx;
	channels[chn].cnt = cnt - 1;
	channels[chn].priority = fx->priority;

	channels[chn].state = ns;
}

void sidfx_stop(byte chn)
{
	channels[chn].com = nullptr;
	if (channels[chn].state != SIDFX_IDLE)
	{
		channels[chn].state = SIDFX_RESET_0;
		channels[chn].delay = 1;
	}
}

inline void sidfx_loop_ch(byte ch)
{
	if (channels[ch].state)
	{
		const SIDFX	*	com = channels[ch].com;

		channels[ch].delay--;
		if (channels[ch].delay)
		{
			if (com->dfreq)
			{
				channels[ch].freq += com->dfreq;
				sid.voices[ch].freq = channels[ch].freq;
			}
			if (com->dpwm)
			{
				channels[ch].pwm += com->dpwm;
				sid.voices[ch].pwm = channels[ch].pwm;
			}
		}

		while (!channels[ch].delay)
		{
			switch (channels[ch].state)
			{
			case SIDFX_IDLE:
				channels[ch].delay = 1;
				break;
			case SIDFX_RESET_0:
				sid.voices[ch].ctrl = 0;
				sid.voices[ch].attdec = 0;
				sid.voices[ch].susrel = 0;
				if (com)
					channels[ch].state = SIDFX_READY;
				else
					channels[ch].state = SIDFX_IDLE;
				channels[ch].delay = 1;
				break;
			case SIDFX_RESET_1:
				sid.voices[ch].ctrl = SID_CTRL_TEST;
				sid.voices[ch].ctrl = 0;
				sid.voices[ch].attdec = 0;
				sid.voices[ch].susrel = 0;
				channels[ch].state = SIDFX_READY;
				break;
			case SIDFX_READY:
				channels[ch].freq = com->freq;
				channels[ch].pwm = com->pwm;

				sid.voices[ch].freq = com->freq;
				sid.voices[ch].pwm = com->pwm;
				sid.voices[ch].attdec = com->attdec;
				sid.voices[ch].susrel = com->susrel;
				sid.voices[ch].ctrl = com->ctrl;

				if (com->ctrl & SID_CTRL_GATE)
				{
					channels[ch].delay = com->time1;
					channels[ch].state = SIDFX_PLAY;
				}
				else
				{
					channels[ch].delay = com->time0;
					channels[ch].state = SIDFX_PLAY;
				}
				break;
			case SIDFX_PLAY:
				if (com->time0)
				{
					sid.voices[ch].ctrl = com->ctrl & ~SID_CTRL_GATE;
					channels[ch].delay = com->time0 - 1;
					channels[ch].state = SIDFX_WAIT;
				}
				else if (channels[ch].cnt)
				{
					char sr = com->susrel & 0xf0;
					com++;
					char ctrl = com->ctrl;
					if ((com->attdec & 0xef) == 0 && (ctrl & SID_CTRL_GATE) && (com->susrel & 0xf0) > sr)
					{
						sid.voices[ch].ctrl = ctrl & ~SID_CTRL_GATE;
						sid.voices[ch].ctrl = ctrl |  SID_CTRL_GATE;
					}
					channels[ch].cnt--;
					channels[ch].com = com;
					channels[ch].priority = com->priority;
					channels[ch].state = SIDFX_READY;
				}
				else
				{
					com = nullptr;
					channels[ch].state = SIDFX_RESET_0;					
				}
				break;
			case SIDFX_WAIT:
				if (channels[ch].cnt)
				{
					com++;
					channels[ch].cnt--;
					channels[ch].com = com;
					channels[ch].priority = com->priority;
					if (com->ctrl & SID_CTRL_GATE)
						channels[ch].state = SIDFX_RESET_0;
					else
						channels[ch].state = SIDFX_READY;
				}
				else
				{
					com = nullptr;
					channels[ch].state = SIDFX_RESET_0;					
				}
				break;
			}
		}
	}
}

void sidfx_loop_2(void)
{
	sidfx_loop_ch(2);	
}

void sidfx_loop(void)
{
	for(byte ch=0; ch<3; ch++)
		sidfx_loop_ch(ch);
}	
