/* Simple NES-like sound chip emulator that plays back file of logged writes */

#include "blip_buf.h"
#include "wave_writer.h"
#include <stdlib.h>
#include <stdio.h>

static const int   sample_rate =   44100;     /* 44.1 kHz sample rate*/
static const double clock_rate = 1789772.727; /* 1.78 MHz clock rate */

static blip_buffer_t* blip;

enum { period, volume, timbre }; /* indicies into Chan's regs [] */

typedef struct Chan Chan;
struct Chan
{
	void (*run)( Chan*, int end_time );
	int gain;       /* overall volume of channel */
	int regs [3];   /* period (clocks between deltas), volume, timbre */
	int time;       /* clock time of next delta */
	int phase;      /* position within waveform */
	int amp;        /* current amplitude in delta buffer */
};

/* Updates amplitude of waveform in delta buffer */
static void update_amp( Chan* m, int new_amp )
{
	int delta = new_amp * m->gain - m->amp;
	m->amp += delta;
	blip_add_delta( blip, m->time, delta );
}

/* Runs square wave to end_time */
static void run_square( Chan* m, int end_time )
{
	for ( ; m->time < end_time; m->time += m->regs [period] )
	{
		m->phase = (m->phase + 1) % 8;
		update_amp( m, (m->phase < m->regs [timbre]) ? 0 : m->regs [volume] );
	}
}

/* Runs triangle wave to end_time */
static void run_triangle( Chan* m, int end_time )
{
	for ( ; m->time < end_time; m->time += m->regs [period] )
	{
		/* phase only increments when volume is non-zero (volume is otherwise
		ignored)*/
		if ( m->regs [volume] != 0 )
		{
			m->phase = (m->phase + 1) % 32;
			update_amp( m, (m->phase < 16 ? m->phase : 31 - m->phase) );
		}
	}
}

/* Runs noise to end_time */
static void run_noise( Chan* m, int end_time )
{
	/* phase is noise LFSR, which must never be zero */
	if ( m->phase == 0 )
		m->phase = 1;
	
	for ( ; m->time < end_time; m->time += m->regs [period] )
	{
		m->phase = ((m->phase & 1) * m->regs [timbre]) ^ (m->phase >> 1);
		update_amp( m, (m->phase & 1) * m->regs [volume] );
	}
}

enum { master_vol = 65536 / 15 };
enum { chan_count = 4 };
static Chan chans [chan_count] =
{
	{ run_square,   master_vol * 26 / 100, { 10, 0, 0 }, 0, 0, 0 },
	{ run_square,   master_vol * 26 / 100, { 10, 0, 0 }, 0, 0, 0 },
	{ run_triangle, master_vol * 30 / 100, { 10, 0, 0 }, 0, 0, 0 },
	{ run_noise,    master_vol * 18 / 100, { 10, 0, 0 }, 0, 0, 0 }
};

/* Runs channel to specified time, then writes data to channel's register */
static void write_chan( int time, int chan, int addr, int data )
{
	Chan* c = &chans [chan];
	c->run( c, time );
	c->regs [addr] = data;
}

/* Ends time frame and flushes samples */
static void end_frame( int end_time )
{
	int i;
	for ( i = 0; i < chan_count; ++i )
	{
		chans [i].run( &chans [i], end_time );
		chans [i].time -= end_time;
	}
	
	blip_end_frame( blip, end_time );
	
	while ( blip_samples_avail( blip ) > 0 )
	{
		enum { temp_size = 1024 };
		short temp [temp_size];
		
		/* count is number of samples actually read (in case there
		were fewer than temp_size samples actually available) */
		int count = blip_read_samples( blip, temp, temp_size, 0 );
		wave_write( temp, count );
	}
}

static void init_sound( void )
{
	blip = blip_new( sample_rate / 10 );
	if ( blip == NULL )
		exit( EXIT_FAILURE ); /* out of memory */
	
	blip_set_rates( blip, clock_rate, sample_rate );
}

int main( void )
{
	/* Open log of writes to sound hardware */
	FILE* in = fopen( "demo_log.txt", "r" );
	if ( in == NULL )
		return EXIT_FAILURE;
	
	init_sound();
	
	/* Play back logged writes and record to wave sound file */
	wave_open( sample_rate, "out.wav" );
	while ( wave_sample_count() < 120 * sample_rate )
	{
		/* In an emulator these writes would be generated by the emulated CPU */
		int time, chan, addr, data;
		if ( fscanf( in, "%d %d %d %d\n", &time, &chan, &addr, &data ) < 4 )
			break;
		
		if ( chan < chan_count )
			write_chan( time, chan, addr, data );
		else
			end_frame( time );
	}
	wave_close();
	blip_delete( blip );
	
	return 0;
}
