#include <stdio.h>
#include <stdint.h>
#include <math.h>

#define RATE 16000.0
#define SEMITONE 1.0594631

float sinetab[2048];

typedef struct voice {
	uint32_t phasor;
	uint32_t step;
	float pan, pan_step;
	float left, right;
	uint32_t ticks, ticks_reset;
	uint8_t on;
} voice_t;

void init_voice(voice_t* v, float freq, float pan, float travel,
		uint32_t initial_ticks, uint32_t ticks_reset) {
	v->step = (uint32_t) ((freq / RATE) * UINT32_MAX);
	v->pan = pan;
	v->pan_step = 1/(travel*RATE);
	v->ticks = initial_ticks*RATE;
	v->ticks_reset = ticks_reset*RATE;
	v->on = 0;
}

void update_voice(voice_t* v) {
	uint16_t index;
	float lfo, out;
	switch(v->on) {
		case 0:
			v->left = 0;
			v->right = 0;
			v->ticks--;
			if(v->ticks == 0) {
				v->on = 1;
			}
			break;
		case 1:
			// Update pan position
			v->pan += v->pan_step;
			if((v->pan <= 0) || (v->pan >= 1)) {
				// Change direction and reset countdown
				v->pan_step *= -1;
				v->on = 0;
				v->ticks = v->ticks_reset;
				// Make sure we didn't overstep bounds
				if(v->pan <= 0) {
					v->pan = 0;
				} else {
					v->pan = 1;
				}
			}
			// Derive sinusoidal "LFO" value from pan position
			index = (uint16_t) (v->pan*(1024-1));
			lfo = sinetab[index];
			// Update oscillator
			v->phasor += v->step;
			index = v->phasor >> (32-11);
			out = tanh((1+3*lfo)*sinetab[index]);
			// Apply sinusoidal volume envelope
			out *= lfo;
			// Pan left and right
			v->left = (1 - v->pan)*out;
			v->right = v->pan*out;
	}
}

int main() {
	voice_t voice[5];
	uint16_t out;
	float left, right;

	for(int i=0; i<2048; i++) {
		sinetab[i] = (float) sin(i*2*M_PI/2048);
	}

	init_voice(&voice[0], 155.56, 0, 5, 1, 5);
	init_voice(&voice[1], 155.56*powf(SEMITONE, 2), 1, -6, 3, 5);
	init_voice(&voice[2], 155.56*powf(SEMITONE, 4), 0, 7, 5, 5);
	init_voice(&voice[3], 155.56*powf(SEMITONE, 6), 1, -8, 7, 5);
	init_voice(&voice[4], 155.56/2, 0, 10, 9, 5);

	while(1) {
		left = 0;
		right = 0;
		for(int i=0; i<5; i++) {
			update_voice(&voice[i]);
			left += (1.0/5.0)*voice[i].left;
			right += (1.0/5.0)*voice[i].right;
		}
		out = (int16_t) (INT16_MAX*left);
		fwrite(&out, 2, 1, stdout);
		out = (int16_t) (INT16_MAX*right);
		fwrite(&out, 2, 1, stdout);
	}
	return 1;
}
