/*
 * Sound emulation (version 2)
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include "Main.h"
#include "SoundEmul.h"

#define fragnum 4
#define fragsize 8 /* 1<<8 == 256 bytes */

SoundEmul::SoundEmul (void)
{
	int stereo = 0;
	int afmt = AFMT_S16_LE;
	int speed = 22050;
	int frag = (fragnum << 16) | fragsize;
	
	devfd = open ("/dev/dsp",O_WRONLY);
	
	ioctl (devfd, SNDCTL_DSP_RESET);
	ioctl (devfd, SNDCTL_DSP_SETFMT, &afmt);
	ioctl (devfd, SNDCTL_DSP_SPEED, &speed);
	ioctl (devfd, SNDCTL_DSP_STEREO, &stereo);
	
	ioctl (devfd, SNDCTL_DSP_SYNC);
	
	ioctl (devfd, SNDCTL_DSP_SETFRAGMENT, &frag);
}

SoundEmul::~SoundEmul (void)
{
	close (devfd);
}

void SoundEmul::Reset (void)
{
	chanactive[0] = chanactive[1] = chanactive[2] = chanactive[3] = 0;
	chanfreq[0] = chanfreq[1] = chanfreq[2] = chanfreq[3] = 0;
	chanvol[0] = chanvol[1] = chanvol[2] = chanvol[3] = 0;
	
	chan_lastflip[1] = chan_lastflip[2] = chan_lastflip[3] = 0;
	chan_lastflip[0] = 0;
	
	last_chan = 1;
	noise_type = 0;
	chan0_followfreq1 = 0;
	
	chan_fi[1] = 1005; chan_fi[2] = 1020; chan_fi[3] = 1023;
	SetFreq (1); SetFreq (2); SetFreq (3);
	
	/*SetFreq (1, 1005);
	SetFreq (2, 1020);
	SetFreq (3, 1023);*/
	
	SetVol (1, 15);
	SetVol (2, 15);
	SetVol (3, 15);
}

// The actual frequency to play is 125000/freqindex
// This is a square wave for chan 1-3
// 22050/(125000/freqindex) samples per wave
// [22050/(125000/freqindex)]/2 samples per flip
// freqindex*(22050/250000) samples per flip

#define freqindex chan_fi[chan]
void SoundEmul::SetFreq (int chan)
{
	if (freqindex == 0)
		chanfreq[0] = 0;
	else
	{
		chanfreq[chan] = (int)(((double)freqindex)*(22050.0/250000.0)*256.0);
		if (!chanfreq[chan])
			chanfreq[chan] = 1;
	}
	if (chanvol[chan] && chanfreq[chan])
		chanactive[chan] = 1;
	else
		chanactive[chan] = 0;
	if (chan0_followfreq1 && chan == 1)
	{
		chanfreq[0] = chanfreq[1];
		if (chanvol[0] && chanfreq[0])
			chanactive[0] = 1;
		else
			chanactive[0] = 0;
	}
}
#undef freqindex

void SoundEmul::SetVol (int chan, int volindex)
{
	// Range is 15 bits +/-
	// volindex is 4 bits
	// We make it quieter by a factor of 4 (so no division to do)
	chanvol[chan] = volindex << 9;
	if (chanvol[chan] && chanfreq[chan])
		chanactive[chan] = 1;
	else
		chanactive[chan] = 0;
}

// This is loosely based on BeebEm's PlayUpTil system
// NB: We call the sound updates synchronously from the emulation loop
void SoundEmul::RenderSound (int samples_to_render)
{
	int i, j;
	static sword soundbuf[441];
	int chan_nactive;
	
	// Render the buffer (samples_to_render will be <= 441)
	for (i = 0; i < samples_to_render; i++)
	{
		soundbuf[i] = 0;
		chan_nactive = 0;
		for (j = 1; j < 4; j++)
		{
			if (!chanactive[j])
			{
				chan_nactive++;
				continue;
			}
			if (chan_lastflip[j] >= chanfreq[j]*2)
			{
				chan_lastflip[j] -= chanfreq[j]*2;
				soundbuf[i] += chanvol[j];
			}
			else if (chan_lastflip[j] >= chanfreq[j])
				soundbuf[i] -= chanvol[j];
			else
				soundbuf[i] += chanvol[j];
			// 1 sample distance
			chan_lastflip[j] += 1<<8;
		}
		if (chan_nactive == 3)
			return;
	}
	write (devfd, soundbuf, samples_to_render*2);
}

void SoundEmul::DoRenderSound (void)
{
	// This is called from the emulloop
	// We render the sound upto the last cycle emulated (translated into
	// samples of course)
	int cycles_to_render, samples_to_render;
	
	if (devfd <= 0)
		return;		// Sound not enabled
	
	// Map emulated cycles to samples
	// Emulated cycles run at 2MHz samples run at 22.05kHz.
	// Thus there are 2000000/22050 emulated cycles per sample
	
	// NB cycles_to_render should be at MOST 20000+19999 since
	// 20000 is the frequency called from the emullloop
	
	// Thus cycles_to_render*22050 will fit in an int
	if (emul_cycles < last_cycle)		// Roll-over or reset
		emul_cycles = last_cycle;
	cycles_to_render = (emul_cycles - last_cycle) + cycles_extraneous;
	last_cycle = emul_cycles;
	samples_to_render = cycles_to_render*22050;
	cycles_extraneous = samples_to_render % 2000000;
	cycles_extraneous /= 22050;
	samples_to_render /= 2000000;
	
	if (samples_to_render <= 0)
		return;
	RenderSound (samples_to_render);
}

void SoundEmul::Write (byte val)
{
	DoRenderSound ();
	if (!(val & 0x80))
	{
		chan_fi[last_chan] = (chan_fi[last_chan] & 0xf) | ((val & 0x3f)<<4);
		SetFreq (last_chan);
	}
	else
		switch ((val >> 4) & 0x7)
		{
			case 0:		// Channel 3 frequency
			chan_fi[3] &= 0x3f0;
			chan_fi[3] |= val & 0xf;
			SetFreq (3);
			last_chan = 3;
			break;
			
			case 1:		// Channel 3 volume
			SetVol (3, (val & 0xf) ^ 0xf);
			last_chan = 3;
			break;
			
			case 2:		// Channel 2 frequency
			chan_fi[2] &= 0x3f0;
			chan_fi[2] |= val & 0xf;
			SetFreq (2);
			last_chan = 2;
			break;
			
			case 3:		// Channel 2 volume
			SetVol (2, (val & 0xf) ^ 0xf);
			last_chan = 2;
			break;
			
			case 4:		// Channel 1 frequency
			chan_fi[1] &= 0x3f0;
			chan_fi[1] |= val & 0xf;
			SetFreq (1);
			last_chan = 1;
			break;
			
			case 5:		// Channel 1 volume
			SetVol (1, (val & 0xf) ^ 0xf);
			last_chan = 1;
			break;
			
			case 6:		// Noise control
			/*noisefreq = val & 0x3;
			noisetype = val & 0x4;*/
			last_chan = 0;
			break;
			
			case 7:		// Noise volume
			//vol[0] = (val & 0xf) ^ 0xf;
			last_chan = 0;
			break;
		}
}

/* End of file. */
