#include "Synth.h"

#include "sample.i"

double waveform_table[9][4096];

INITBLOCK {
	for(int i = 0; i < 4096; i++) {
		double arg = M_2PI * i / 4096;
		waveform_table[WAVEFORM_SIN][i] = sin(arg);
		waveform_table[WAVEFORM_SQUARE][i] = sgn(waveform_table[WAVEFORM_SIN][i]);
		waveform_table[WAVEFORM_TRIANGLE][i] = abs(2048 - i) / 1024.0 - 1;
		waveform_table[WAVEFORM_SAWTOOTH][i] = 2 * i / 4096.0 - 1;
	}
	
	for(int i = 0; i < 4096; i++) {
		waveform_table[4][i] = wave_saxophone[i];
		waveform_table[5][i] = wave_violin[i];
		waveform_table[6][i] = wave_doublebass[i];
		waveform_table[7][i] = wave_banjo[i];
		waveform_table[8][i] = wave_trumpet[i];
	}
}

double Rate(double x)
{
	return pow(10, (6 * x / 100 - 6));
}

void SoundGen::FMOPGen::Comp()
{
	attack = 1 + Rate(attack);
	decay = 1 - Rate(decay);
	release = 1 - Rate(release);
	duration = 44100 * duration / 1000;
	fdrift = exp2(fdrift / 12 / 44100);
	if(volume > 0)
		volume = pow(10, (volume - 100) / 40);
	if(sustain > 0)
		sustain = pow(10, (sustain - 100) / 40);
	wave_tab = waveform_table[clamp(waveform, 0, (int)WAVEFORM_LASTSAMPLE)];
}

String SoundGen::FMOPGen::ToString() const
{
	return String() << "Volume: " << volume << ", phase: " << p << ", ADSR volume: " << v;
}

force_inline
double SoundGen::FMOPGen::Evaluate(int t, double mf, double mod, double& cv)
{
	if(volume == 0)
		return 0;
	if(t >= duration) {
		v *= release;
		cv = v;
	}
	else
	switch(p) {
	case 0:
		v *= attack;
		if(v >= 1.0) {
			v = 1.0;
			p = 1;
		}
		cv = v > 0;
		break;
	case 1:
		v *= decay;
		if(v < sustain) {
			v = sustain;
			p = 2;
		}
		cv = v;
		break;
	default:
		cv = v = sustain;
	}
	cv *= volume;
	f *= fdrift;
	double fn;
	if(findarg(waveform, WAVEFORM_BROWN, WAVEFORM_WHITE) >= 0) {
		static dword state = 1;
		state ^= state << 13;
		state ^= state >> 17;
		state ^= state << 5;
		fn = 2.0 / 4294967295.0 * state - 1;
		if(waveform == WAVEFORM_BROWN)
			fn = n = clamp(n + 0.06 * fn, -1.0, 1.0);
	}
	else {
#if 0
		double arg = mf * f * t + mod;
		if(waveform == WAVEFORM_SIN)
			fn = sin(arg);
		else {
			arg = fmod(arg, M_2PI);
			fn = waveform == WAVEFORM_SQUARE ? sgn(abs(M_PI - arg) / (M_PI / 2) - 1) :
			     waveform == WAVEFORM_TRIANGLE ? abs(M_PI - arg) / (M_PI / 2) - 1 :
			     waveform == WAVEFORM_SAWTOOTH ? arg / M_PI - 1 :
			     0;
		}
#else
		double arg = mf * f * t + mod;
		fn = wave_tab[dword((4096 / M_2PI) * arg) & 4095];
#endif
	}
	
	return volume * v * fn;
}

void SoundGen::Start(const Sound& s)
{
	for(int i = 0; i < OPCOUNT; i++) {
		(FMOP&)op[i] = s.op[i];
		op[i].Comp();
		op[i].Start();
	}
	f = s.f / 22100 * M_PI;
	lpan = (float)s.pan;
	rpan = (float)(1 - s.pan);
	for(int i = 3; i <= 4; i++) // vibrato, tremolo
		op[i].f = op[i].f / 22100 * M_PI;
	t = 0;
	memset(feedback, 0, sizeof(feedback));
	current_volume = 1;
	lfo_mod = Randomf() * M_2PI;
}

float SoundGen::Get()
{
#if 0
	float r = op[0].Evaluate(t, f, 0, current_volume);
#else
	if(current_volume < 0.001)
		return 0;
	double dummy;
	double lfo1 = op[3].Evaluate(t, 1, lfo_mod, dummy);
	double lfo2 = op[4].Evaluate(t, 1, lfo_mod, dummy);
	double v = (1 - op[4].volume + lfo2);
	float r =
		(float)(v *
			op[0].Evaluate(t, f, op[1].Evaluate(t, f, lfo1, dummy) + op[2].Evaluate(t, f, lfo1, dummy) + lfo1, current_volume)
	     );
#endif
	t++;
	return r;
}

String SoundGen::ToString() const
{
	String r;
	r << "----- Current volume: " << current_volume << ", time: " << t << '\n';
	for(int i = 0; i < OPCOUNT; i++)
		r << i << ": " << op[i] << '\n';
	return r;
}
