/***************************************************************************

	NAMCO sound driver.

	This driver handles the four known types of NAMCO wavetable sounds:

		- 3-voice mono (Pac-Man, Pengo, Dig Dug, etc)
		- 8-voice mono (Mappy, Dig Dug 2, etc)
		- 8-voice stereo (System 1)
		- 6-voice stereo (Pole Position 1, Pole Position 2)

***************************************************************************/

#include "driver.h"


/* 8 voices max */
#define MAX_VOICES 8

#define MAX_VOLUME 16

/* quality parameter */
#define VIF_RESOLUTION		3
#define VIF_MAX_MULTIPLE	4	/* 1, 2, 4 or more */

/* decimal fraction bits (22:10) */
#define F_FRACBITS	10

/* 16 bits:	sample bits of the stream buffer	*/
/* 4 bits:	volume					*/
/* 4 bits:	prom sample bits			*/ 
#define MIXLEVEL	(1 << (16 - 4 - 4))

/* stream output level */
#define OUTPUT_LEVEL(n)		((n) * MIXLEVEL / num_voices)

/* a position of waveform sample */
#define WAVEFORM_OFFSET(n)	(((n) >> (15 + F_FRACBITS)) & 0x1f)

typedef INT16 (*vif_func) (const INT16 *, int, const int *);

/* this structure defines the parameters for a channel */
typedef struct
{
	int frequency;
	int counter;
	int delta;
	int volume[2];
	int noise_sw;
	int noise_state;
	int noise_seed;
	int noise_counter;
	int waveform_select;
	vif_func vif_function;
	int vif_delta[VIF_MAX_MULTIPLE];
} sound_channel;


/* globals available to everyone */
unsigned char *namco_soundregs;
unsigned char *namco_wavedata;

/* data about the sound system */
static sound_channel channel_list[MAX_VOICES];
static sound_channel *last_channel;

/* global sound parameters */
static int num_voices;
static int sound_enable;
static int stream;
static int namco_clock;
static int sample_rate;
static float fbase;

/* decoded waveform table */
static INT16 *waveform[MAX_VOLUME];


/* update the decoded waveform data */
static void update_namcos1_waveform (int offset, int data)
{
	int wdata;
	int v;

	/* use full byte, first 4 high bits, then low 4 bits */
	for (v = 0; v < MAX_VOLUME; v++)
	{
		wdata = ((data >> 4) & 0x0f) - 8;
		waveform[v][offset * 2] = OUTPUT_LEVEL (wdata * v);
		wdata = (data & 0x0f) - 8;
		waveform[v][offset * 2 + 1] = OUTPUT_LEVEL (wdata * v);
	}
}

/* build the decoded waveform table */
static int build_decoded_waveform (int region)
{
	INT16 *p;
	int size;
	int offset;
	int v;

	if (region == -1)
		size = 32 * 16;		/* 32 samples, 16 waveforms */
	else
		size = 32 * 8;		/* 32 samples, 8 waveforms */

	if ((p = (INT16 *)malloc (size * MAX_VOLUME * sizeof (INT16))) == NULL)
		return 1;

	for (v = 0; v < MAX_VOLUME; v++)
	{
		waveform[v] = p;
		p += size;
	}

	if (region == -1)
	{

		/* use full byte, first 4 high bits, then low 4 bits, 256 bytes */
		for (offset = 0; offset < 256; offset++)
			update_namcos1_waveform (offset, namco_wavedata[offset]);
	}
	else
	{
		const unsigned char *sound_prom = memory_region (region);

		/* use only low 4 bits, 256 bytes */
		for (offset = 0; offset < 256; offset++)
			for (v = 0; v < MAX_VOLUME; v++)
				waveform[v][offset] = OUTPUT_LEVEL (((sound_prom[offset] & 0x0f) - 8) * v);
	}

	return 0;
}


/* calculates wave data by the Variable Internal Frequency */
/* optimize for speed, we need each level functions */
static INT16 vif_multiple1 (const INT16 *w, int offset, const int *delta)
{
	/* delta[0] always equals 0. */
	return w[WAVEFORM_OFFSET (offset)];
}

static INT16 vif_multiple2 (const INT16 *w, int offset, const int *delta)
{
	int data;

	/* delta[0] always equals 0. */
	data  = w[WAVEFORM_OFFSET (offset)];
	data += w[WAVEFORM_OFFSET (offset + delta[1])];

	return data >> 1;
}

static INT16 vif_multiple4 (const INT16 *w, int offset, const int *delta)
{
	int data;

	/* delta[0] always equals 0. */
	data  = w[WAVEFORM_OFFSET (offset)];
	data += w[WAVEFORM_OFFSET (offset + delta[1])];
	data += w[WAVEFORM_OFFSET (offset + delta[2])];
	data += w[WAVEFORM_OFFSET (offset + delta[3])];

	return data >> 2;
}

/* the multiple is more than 4 times */
static INT16 vif_multipleMAX (const INT16 *w, int offset, const int *delta)
{
	int data;
	int i;

	/* delta[0] always equals 0. */
	data  = w[WAVEFORM_OFFSET (offset)];

	for (i = 1; i < VIF_MAX_MULTIPLE; i++)
		data += w[WAVEFORM_OFFSET (offset + delta[i])];

	return data / VIF_MAX_MULTIPLE;
}

/* update table for the Variable Internal Frequency */
static void update_vif_function (sound_channel *voice)
{
	int level;
	int i;

	level = (voice->delta / (1 << (F_FRACBITS + 15 - VIF_RESOLUTION))) + 1;
	if (level > VIF_MAX_MULTIPLE)
		level = VIF_MAX_MULTIPLE;

	switch (level)
	{
	case 1:
		voice->vif_function = vif_multiple1;
		break;
	case 2:
		voice->vif_function = vif_multiple2;
		break;
	case 3:
	case 4:
		level = 4;
		voice->vif_function = vif_multiple4;
		break;
	default:
		level = VIF_MAX_MULTIPLE;
		voice->vif_function = vif_multipleMAX;
	}

	/* vif_delta[0] always equals 0. */
	for (i = 0; i < level; i++)
		voice->vif_delta[i] = voice->delta * i / level;
}


/* generate sound to the mix buffer in mono */
static void namco_update_mono(int ch, INT16 *buffer, int length)
{
	sound_channel *voice;
	short *mix;
	int i;

	/* zap the contents of the buffer */
	memset(buffer, 0, length * sizeof(INT16));

	/* if no sound, we're done */
	if (sound_enable == 0)
		return;

	/* loop over each voice and add its contribution */
	for (voice = channel_list; voice < last_channel; voice++)
	{
		int d = voice->delta;
		int v = voice->volume[0];

		mix = buffer;

		if (voice->noise_sw)
		{
			/* only update if we have non-zero volume and frequency */
			if (v && d)
			{
				int c = voice->noise_counter;
				INT16 noise_data = OUTPUT_LEVEL (0x07 * (v >> 1));

				/* add our contribution */
				for (i = 0; i < length; i++)
				{
					int cnt;

					if (voice->noise_state)
						*mix++ += noise_data;
					else
						*mix++ -= noise_data;

					c += d;
					cnt = (c >> (8 + F_FRACBITS));
					c &= (1 << (8 + F_FRACBITS)) - 1;
					for( ;cnt > 0; cnt--)
					{
						if ((voice->noise_seed + 1) & 2) voice->noise_state ^= 1;
						if (voice->noise_seed & 1) voice->noise_seed ^= 0x28000;
						voice->noise_seed >>= 1;
					}
				}

				/* update the counter for this voice */
				voice->noise_counter = c;
			}
		}
		else
		{
			/* only update if we have non-zero volume and frequency */
			if (v && d)
			{
				vif_func vif_function = voice->vif_function;
				const int *vif_delta = voice->vif_delta;
				const INT16 *w = waveform[v] + voice->waveform_select * 32;
				int c = voice->counter;

				/* add our contribution */
				for (i = 0; i < length; i++)
				{
					*mix++ += vif_function (w, c, vif_delta);
					c += d;
				}

				/* update the counter for this voice */
				voice->counter = c;
			}
		}
	}
}


/* generate sound to the mix buffer in stereo */
static void namco_update_stereo(int ch, INT16 **buffer, int length)
{
	sound_channel *voice;
	short *lmix, *rmix;
	int i;

	/* zap the contents of the buffers */
	memset(buffer[0], 0, length * sizeof(INT16));
	memset(buffer[1], 0, length * sizeof(INT16));

	/* if no sound, we're done */
	if (sound_enable == 0)
		return;

	/* loop over each voice and add its contribution */
	for (voice = channel_list; voice < last_channel; voice++)
	{
		int d = voice->delta;
		int lv = voice->volume[0];
		int rv = voice->volume[1];

		lmix = buffer[0];
		rmix = buffer[1];

		if (voice->noise_sw)
		{
			/* only update if we have non-zero volume and frequency */
			if ((lv || rv) && d)
			{
				int c = voice->noise_counter;
				INT16 l_noise_data = OUTPUT_LEVEL (0x07 * (lv >> 1));
				INT16 r_noise_data = OUTPUT_LEVEL (0x07 * (rv >> 1));

				/* add our contribution */
				for (i = 0; i < length; i++)
				{
					int cnt;

					if (voice->noise_state)
					{
						*lmix++ += l_noise_data;
						*rmix++ += r_noise_data;
					}
					else
					{
						*lmix++ -= l_noise_data;
						*rmix++ -= r_noise_data;
					}

					c += d;
					cnt = (c >> (8 + F_FRACBITS));
					c &= (1 << (8 + F_FRACBITS)) - 1;
					for( ;cnt > 0; cnt--)
					{
						if ((voice->noise_seed + 1) & 2) voice->noise_state ^= 1;
						if (voice->noise_seed & 1) voice->noise_seed ^= 0x28000;
						voice->noise_seed >>= 1;
					}
				}

				/* update the counter for this voice */
				voice->noise_counter = c;
			}
		}
		else
		{
			/* only update if we have non-zero volume and frequency */
			if ((lv || rv) && d)
			{
				vif_func vif_function = voice->vif_function;
				const int *vif_delta = voice->vif_delta;
				const INT16 *lw = waveform[lv] + voice->waveform_select * 32;
				const INT16 *rw = waveform[rv] + voice->waveform_select * 32;
				int c = voice->counter;

				/* add our contribution */
				for (i = 0; i < length; i++)
				{
					*lmix++ += vif_function (lw, c, vif_delta);
					*rmix++ += vif_function (rw, c, vif_delta);
					c += d;
				}

				/* update the counter for this voice */
				voice->counter = c;
			}
		}
	}
}


int namco_sh_start(const struct MachineSound *msound)
{
	const char *mono_name = "NAMCO sound";
	const char *stereo_names[] =
	{
		"NAMCO sound left",
		"NAMCO sound right"
	};
	sound_channel *voice;
	const struct namco_interface *intf = msound->sound_interface;

	/* extract globals from the interface */
	namco_clock = intf->samplerate;
	sample_rate = Machine->sample_rate;
	num_voices = intf->voices;
	last_channel = channel_list + num_voices;
	fbase = (float)namco_clock / (float)sample_rate;

	/* build the waveform table */
	if (build_decoded_waveform (intf->region))
		return 1;

	/* get stream channels */
	if (intf->stereo)
	{
		int vol[2];

		vol[0] = MIXER(intf->volume,MIXER_PAN_LEFT);
		vol[1] = MIXER(intf->volume,MIXER_PAN_RIGHT);
		stream = stream_init_multi(2, stereo_names, vol, sample_rate, 0, namco_update_stereo);
	}
	else
	{
		stream = stream_init(mono_name, intf->volume, sample_rate, 0, namco_update_mono);
	}


	/* start with sound enabled, many games don't have a sound enable register */
	sound_enable = 1;

	/* reset all the voices */
	for (voice = channel_list; voice < last_channel; voice++)
	{
		voice->frequency = 0;
		voice->volume[0] = voice->volume[1] = 0;
		voice->waveform_select = 0;
		voice->counter = 0;
		voice->delta = 0;
		voice->noise_sw = 0;
		voice->noise_state = 0;
		voice->noise_seed = 1;
		voice->noise_counter = 0;
	}

	return 0;
}


void namco_sh_stop(void)
{
	free (waveform[0]);
}


/********************************************************************************/

/* pengo register map
	0x05:		ch 0	waveform select
	0x0a:		ch 1	waveform select
	0x0f:		ch 2	waveform select

	0x10:		ch 0	the first voice has extra frequency bits 
	0x11-0x14:	ch 0	frequency
	0x15:		ch 0	volume

	0x16-0x19:	ch 1	frequency
	0x1a:		ch 1	volume

	0x1b-0x1e:	ch 2	frequency
	0x1f:		ch 2	volume
*/

WRITE_HANDLER( pengo_sound_enable_w )
{
	sound_enable = data;
}

WRITE_HANDLER( pengo_sound_w )
{
	sound_channel *voice;
	int ch;

	data &= 0x0f;
	if (namco_soundregs[offset] == data)
		return;

	/* update the streams */
	stream_update(stream, 0);

	/* set the register */
	namco_soundregs[offset] = data;

	if (offset < 0x10)
		ch = (offset - 5) / 5;
	else if (offset == 0x10)
		ch = 0;
	else
		ch = (offset - 0x11) / 5;

	if (ch >= num_voices)
		return;

	/* recompute the voice parameters */
	voice = channel_list + ch;
	switch (offset - ch * 5)
	{
	case 0x05:
		voice->waveform_select = data & 7;
		break;

	case 0x10:
	case 0x11:
	case 0x12:
	case 0x13:
	case 0x14:
		/* the frequency has 20 bits */
		/* the first voice has extra frequency bits */
		voice->frequency = (ch == 0) ? namco_soundregs[0x10] : 0;
		voice->frequency += (namco_soundregs[ch * 5 + 0x11] << 4);
		voice->frequency += (namco_soundregs[ch * 5 + 0x12] << 8);
		voice->frequency += (namco_soundregs[ch * 5 + 0x13] << 12);
		voice->frequency += (namco_soundregs[ch * 5 + 0x14] << 16);	/* always 0 */

		voice->delta = (float)(voice->frequency << F_FRACBITS) * fbase;
		if (voice->delta)
			update_vif_function (voice);
		break;

	case 0x15:
		voice->volume[0] = data;
		break;
	}
}


/********************************************************************************/

/* polepos register map
	0x08-0x09	ch 0	frequency
	0x0a		ch 0	front volume
	0x0b		ch 0	rear volume

	0x0c-0x0d	ch 1	frequency
	0x0e		ch 1	front volume
	0x0f		ch 1	rear volume

	.
	.
	.

	0x1c-0x1d	ch 5	frequency
	0x1e		ch 5	front volume
	0x1f		ch 5	rear volume

	0x2b		ch 0	waveform select & rear volume
	0x2f		ch 1	waveform select & rear volume
	0x33		ch 2	waveform select & rear volume
	0x37		ch 3	waveform select & rear volume
	0x3b		ch 4	waveform select & rear volume
	0x3f		ch 5	waveform select & rear volume
*/

WRITE_HANDLER( polepos_sound_w )
{
	sound_channel *voice;
	int ch;

	if (namco_soundregs[offset] == data)
		return;

	/* update the streams */
	stream_update(stream, 0);

	/* set the register */
	namco_soundregs[offset] = data;

	if (offset < 0x2b)
		ch = (offset - 8) / 4;
	else
		ch = (offset - 0x2b) / 4;

	if (ch >= num_voices)
		return;

	/* recompute the voice parameters */
	voice = channel_list + ch;
	switch (offset - ch * 4)
	{
	case 0x08:
	case 0x09:
		/* the frequency has 16 bits */
		voice->frequency = namco_soundregs[ch * 4 + 0x08];
		voice->frequency += namco_soundregs[ch * 4 + 0x09] << 8;

		voice->delta = (float)(voice->frequency << F_FRACBITS) * fbase;
		if (voice->delta)
			update_vif_function (voice);
		break;

	case 0x2b:
		voice->waveform_select = data & 7;
	case 0x0a:
	case 0x0b:
		/* the volume seems to vary between one of these five places */
		/* it's likely that only 3 or 4 are valid; for now, we just */
		/* take the maximum volume and that seems to do the trick */
		/* volume[0] = left speaker ?, volume[1] = right speaker ? */
		voice->volume[0] = voice->volume[1] = 0;
		// front speaker ?
		voice->volume[1] |= namco_soundregs[ch * 4 + 0x0a] & 0x0f;
		voice->volume[0] |= namco_soundregs[ch * 4 + 0x0a] >> 4;
		// rear speaker ?
		voice->volume[1] |= namco_soundregs[ch * 4 + 0x0b] & 0x0f;
		voice->volume[0] |= namco_soundregs[ch * 4 + 0x0b] >> 4;
		voice->volume[1] |= namco_soundregs[ch * 4 + 0x2b] >> 4;
		break;
	}
}


/********************************************************************************/

/* mappy register map
	0x03		ch 0	volume
	0x04-0x05	ch 0	frequency
	0x06		ch 0	waveform select & frequency

	0x0b		ch 1	volume
	0x0c-0x0d	ch 1	frequency
	0x0e		ch 1	waveform select & frequency

	.
	.
	.

	0x3b		ch 7	volume
	0x3c-0x3d	ch 7	frequency
	0x3e		ch 7	waveform select & frequency
*/

WRITE_HANDLER( mappy_sound_enable_w )
{
	sound_enable = offset;
}

WRITE_HANDLER( mappy_sound_w )
{
	sound_channel *voice;
	int ch;

	if (namco_soundregs[offset] == data)
		return;

	/* update the streams */
	stream_update(stream, 0);

	/* set the register */
	namco_soundregs[offset] = data;

	ch = offset / 8;
	if (ch >= num_voices)
		return;

	/* recompute the voice parameters */
	voice = channel_list + ch;
	switch (offset - ch * 8)
	{
	case 0x03:
		voice->volume[0] = data & 0x0f;
		break;

	case 0x06:
		voice->waveform_select = (data >> 4) & 7;
	case 0x04:
	case 0x05:
		/* the frequency has 20 bits */
		voice->frequency = namco_soundregs[ch * 8 + 0x04];
		voice->frequency += namco_soundregs[ch * 8 + 0x05] << 8;
		voice->frequency += (namco_soundregs[ch * 8 + 0x06] & 15) << 16;	/* high bits are from here */

		voice->delta = (float)(voice->frequency << F_FRACBITS) * fbase;
		if (voice->delta)
			update_vif_function (voice);
		break;
	}
}


/********************************************************************************/

/* namcos1 register map
	0x00		ch 0	left volume
	0x01		ch 0	waveform select & frequency
	0x02-0x03	ch 0	frequency
	0x04		ch 0	right volume AND
	0x04		ch 1	noise sw

	0x08		ch 1	left volume
	0x09		ch 1	waveform select & frequency
	0x0a-0x0b	ch 1	frequency
	0x0c		ch 1	right volume AND
	0x0c		ch 2	noise sw

	.
	.
	.

	0x38		ch 7	left volume
	0x39		ch 7	waveform select & frequency
	0x3a-0x3b	ch 7	frequency
	0x3c		ch 7	right volume AND
	0x3c		ch 0	noise sw
*/

WRITE_HANDLER( namcos1_sound_w )
{
	sound_channel *voice;
	int ch;
	int nssw;

	/* verify the offset */
	if (offset > 63)
	{
		logerror("NAMCOS1 sound: Attempting to write past the 64 registers segment\n");
		return;
	}

	if (namco_soundregs[offset] == data)
		return;

	/* update the streams */
	stream_update(stream,0);

	/* set the register */
	namco_soundregs[offset] = data;

	ch = offset / 8;
	if (ch >= num_voices)
		return;

	/* recompute the voice parameters */
	voice = channel_list + ch;
	switch (offset - ch * 8)
	{
	case 0x00:
		voice->volume[0] = data & 0x0f;
		break;

	case 0x01:
		voice->waveform_select = (data >> 4) & 15;
	case 0x02:
	case 0x03:
		/* the frequency has 20 bits */
		voice->frequency = (namco_soundregs[ch * 8 + 0x01] & 15) << 16;	/* high bits are from here */
		voice->frequency += namco_soundregs[ch * 8 + 0x02] << 8;
		voice->frequency += namco_soundregs[ch * 8 + 0x03];

		if (!voice->noise_sw)
		{
			voice->delta = (float)(voice->frequency << F_FRACBITS) * fbase;
			if (voice->delta)
				update_vif_function (voice);
		}
		else
			voice->delta = (float)((voice->frequency & 0xff) << F_FRACBITS) * fbase;
		break;

	case 0x04:
		voice->volume[1] = data & 0x0f;

		nssw = ((data & 0x80) >> 7);
		if ((voice + 1) < last_channel)
			voice++;
		else
			voice = channel_list;
		if (voice->noise_sw == nssw)
			break;

		voice->noise_sw = nssw;
		if (!voice->noise_sw)
		{
			voice->delta = (float)(voice->frequency << F_FRACBITS) * fbase;
			if (voice->delta)
				update_vif_function (voice);
		}
		else
			voice->delta = (float)((voice->frequency & 0xff) << F_FRACBITS) * fbase;
		break;
	}
}

READ_HANDLER( namcos1_sound_r )
{
	return namco_soundregs[offset];
}

WRITE_HANDLER( namcos1_wavedata_w )
{
	/* update the streams */
	stream_update(stream,0);

	namco_wavedata[offset] = data;

	/* update the decoded waveform table */
	update_namcos1_waveform (offset, data);
}

READ_HANDLER( namcos1_wavedata_r )
{
	return namco_wavedata[offset];
}


/********************************************************************************/

WRITE_HANDLER( snkwave_w )
{
	static int freq0 = 0xff;
	sound_channel *voice = channel_list;
	if( offset==0 ) freq0 = data;
	if( offset==1 )
	{
		stream_update(stream, 0);
		if( data==0xff || freq0==0 )
		{
			voice->volume[0] = 0x0;
		}
		else
		{
			voice->volume[0] = 0x8;
			voice->frequency = (data<<16)/freq0;
			voice->delta = (float)(voice->frequency << F_FRACBITS) * fbase;
			if (voice->delta)
				update_vif_function (voice);
		}
	}
}

