#include "driver.h"


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

  Many games use a master-slave CPU setup. Typically, the main CPU writes
  a command to some register, and then writes to another register to trigger
  an interrupt on the slave CPU (the interrupt might also be triggered by
  the first write). The slave CPU, notified by the interrupt, goes and reads
  the command.

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

static int cleared_value = 0x00;

static int latch,read_debug;


static void soundlatch_callback(int param)
{
	if (read_debug == 0 && latch != param)
		logerror("Warning: sound latch written before being read. Previous: %02x, new: %02x\n",latch,param);
	latch = param;
	read_debug = 0;
}

WRITE_HANDLER( soundlatch_w )
{
	/* make all the CPUs synchronize, and only AFTER that write the new command to the latch */
	timer_set(TIME_NOW,data,soundlatch_callback);
}

WRITE16_HANDLER( soundlatch_word_w )
{
	static data16_t word;
	COMBINE_DATA(&word);

	/* make all the CPUs synchronize, and only AFTER that write the new command to the latch */
	timer_set(TIME_NOW,word,soundlatch_callback);
}

READ_HANDLER( soundlatch_r )
{
	read_debug = 1;
	return latch;
}

READ16_HANDLER( soundlatch_word_r )
{
	read_debug = 1;
	return latch;
}

WRITE_HANDLER( soundlatch_clear_w )
{
	latch = cleared_value;
}


static int latch2,read_debug2;

static void soundlatch2_callback(int param)
{
	if (read_debug2 == 0 && latch2 != param)
		logerror("Warning: sound latch 2 written before being read. Previous: %02x, new: %02x\n",latch2,param);
	latch2 = param;
	read_debug2 = 0;
}

WRITE_HANDLER( soundlatch2_w )
{
	/* make all the CPUs synchronize, and only AFTER that write the new command to the latch */
	timer_set(TIME_NOW,data,soundlatch2_callback);
}

WRITE16_HANDLER( soundlatch2_word_w )
{
	static data16_t word;
	COMBINE_DATA(&word);

	/* make all the CPUs synchronize, and only AFTER that write the new command to the latch */
	timer_set(TIME_NOW,word,soundlatch2_callback);
}

READ_HANDLER( soundlatch2_r )
{
	read_debug2 = 1;
	return latch2;
}

READ16_HANDLER( soundlatch2_word_r )
{
	read_debug2 = 1;
	return latch2;
}

WRITE_HANDLER( soundlatch2_clear_w )
{
	latch2 = cleared_value;
}


static int latch3,read_debug3;

static void soundlatch3_callback(int param)
{
	if (read_debug3 == 0 && latch3 != param)
		logerror("Warning: sound latch 3 written before being read. Previous: %02x, new: %02x\n",latch3,param);
	latch3 = param;
	read_debug3 = 0;
}

WRITE_HANDLER( soundlatch3_w )
{
	/* make all the CPUs synchronize, and only AFTER that write the new command to the latch */
	timer_set(TIME_NOW,data,soundlatch3_callback);
}

WRITE16_HANDLER( soundlatch3_word_w )
{
	static data16_t word;
	COMBINE_DATA(&word);

	/* make all the CPUs synchronize, and only AFTER that write the new command to the latch */
	timer_set(TIME_NOW,word,soundlatch3_callback);
}

READ_HANDLER( soundlatch3_r )
{
	read_debug3 = 1;
	return latch3;
}

READ16_HANDLER( soundlatch3_word_r )
{
	read_debug3 = 1;
	return latch3;
}

WRITE_HANDLER( soundlatch3_clear_w )
{
	latch3 = cleared_value;
}


static int latch4,read_debug4;

static void soundlatch4_callback(int param)
{
	if (read_debug4 == 0 && latch4 != param)
		logerror("Warning: sound latch 4 written before being read. Previous: %02x, new: %02x\n",latch2,param);
	latch4 = param;
	read_debug4 = 0;
}

WRITE_HANDLER( soundlatch4_w )
{
	/* make all the CPUs synchronize, and only AFTER that write the new command to the latch */
	timer_set(TIME_NOW,data,soundlatch4_callback);
}

WRITE16_HANDLER( soundlatch4_word_w )
{
	static data16_t word;
	COMBINE_DATA(&word);

	/* make all the CPUs synchronize, and only AFTER that write the new command to the latch */
	timer_set(TIME_NOW,word,soundlatch4_callback);
}

READ_HANDLER( soundlatch4_r )
{
	read_debug4 = 1;
	return latch4;
}

READ16_HANDLER( soundlatch4_word_r )
{
	read_debug4 = 1;
	return latch4;
}

WRITE_HANDLER( soundlatch4_clear_w )
{
	latch4 = cleared_value;
}


void soundlatch_setclearedvalue(int value)
{
	cleared_value = value;
}





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



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

static void *sound_update_timer;
static double refresh_period;
static double refresh_period_inv;


struct snd_interface
{
	unsigned sound_num;										/* ID */
	const char *name;										/* description */
	int (*chips_num)(const struct MachineSound *msound);	/* returns number of chips if applicable */
	int (*chips_clock)(const struct MachineSound *msound);	/* returns chips clock if applicable */
	int (*start)(const struct MachineSound *msound);		/* starts sound emulation */
	void (*stop)(void);										/* stops sound emulation */
	void (*update)(void);									/* updates emulation once per frame if necessary */
	void (*reset)(void);									/* resets sound emulation */
};


int OKIM6295_num(const struct MachineSound *msound) { return ((struct OKIM6295interface*)msound->sound_interface)->num; }
int OKIM6295_clock(const struct MachineSound *msound) { return ((struct OKIM6295interface*)msound->sound_interface)->frequency[0]; }
int AY8910_clock(const struct MachineSound *msound) { return ((struct AY8910interface*)msound->sound_interface)->baseclock; }
int AY8910_num(const struct MachineSound *msound) { return ((struct AY8910interface*)msound->sound_interface)->num; }
int YM2610_clock(const struct MachineSound *msound) { return ((struct YM2610interface*)msound->sound_interface)->baseclock; }
int YM2610_num(const struct MachineSound *msound) { return ((struct YM2610interface*)msound->sound_interface)->num; }
int YM2151_clock(const struct MachineSound *msound) { return ((struct YM2151interface*)msound->sound_interface)->baseclock; }
int YM2151_num(const struct MachineSound *msound) { return ((struct YM2151interface*)msound->sound_interface)->num; }
int qsound_clock(const struct MachineSound *msound) { return ((struct QSound_interface*)msound->sound_interface)->clock; }

struct snd_interface sndintf[] =
{
    {
		SOUND_DUMMY,
		"",
		0,
		0,
		0,
		0,
		0,
		0
	},
	{
		SOUND_AY8910,
		"AY-3-8910",
		AY8910_num,
		AY8910_clock,
		AY8910_sh_start,
		AY8910_sh_stop,
		0,
		AY8910_sh_reset
	},
    {
		SOUND_YM2151,
		"YM2151",
		YM2151_num,
		YM2151_clock,
		YM2151_sh_start,
		YM2151_sh_stop,
		0,
		YM2151_sh_reset
	},
    {
		SOUND_YM2610,
		"YM2610",
		YM2610_num,
		YM2610_clock,
		YM2610_sh_start,
		YM2610_sh_stop,
		0,
		YM2610_sh_reset
	},
    {
		SOUND_OKIM6295,
		"OKI6295",
		OKIM6295_num,
		OKIM6295_clock,
		OKIM6295_sh_start,
		OKIM6295_sh_stop,
		OKIM6295_sh_update,
		0
	},
	{
		SOUND_QSOUND,
		"QSound",
		0,
		qsound_clock,
		qsound_sh_start,
		qsound_sh_stop,
		0,
		0
	},
};




int sound_start(void)
{
	int totalsound = 0;
	int i;

	/* Verify the order of entries in the sndintf[] array */
	for (i = 0;i < SOUND_COUNT;i++)
	{
		if (sndintf[i].sound_num != i)
		{
            int j;
logerror("Sound #%d wrong ID %d: check enum SOUND_... in src/sndintrf.h!\n",i,sndintf[i].sound_num);
			for (j = 0; j < i; j++)
				logerror("ID %2d: %s\n", j, sndintf[j].name);
            return 1;
		}
	}


	refresh_period = TIME_IN_HZ(Machine->drv->frames_per_second);
	refresh_period_inv = 1.0 / refresh_period;
	sound_update_timer = timer_alloc(NULL);

	if (mixer_sh_start() != 0)
		return 1;

	if (streams_sh_start() != 0)
		return 1;

	while (Machine->drv->sound[totalsound].sound_type != 0 && totalsound < MAX_SOUND)
	{
		if ((*sndintf[Machine->drv->sound[totalsound].sound_type].start)(&Machine->drv->sound[totalsound]) != 0)
			goto getout;

		totalsound++;
	}

	return 0;


getout:
	/* TODO: should also free the resources allocated before */
	return 1;
}



void sound_stop(void)
{
	int totalsound = 0;


	while (Machine->drv->sound[totalsound].sound_type != 0 && totalsound < MAX_SOUND)
	{
		if (sndintf[Machine->drv->sound[totalsound].sound_type].stop)
			(*sndintf[Machine->drv->sound[totalsound].sound_type].stop)();

		totalsound++;
	}

	streams_sh_stop();
	mixer_sh_stop();
}



void sound_update(void)
{
	int totalsound = 0;


	profiler_mark(PROFILER_SOUND);

	while (Machine->drv->sound[totalsound].sound_type != 0 && totalsound < MAX_SOUND)
	{
		if (sndintf[Machine->drv->sound[totalsound].sound_type].update)
			(*sndintf[Machine->drv->sound[totalsound].sound_type].update)();

		totalsound++;
	}

	streams_sh_update();
	mixer_sh_update();

	timer_adjust(sound_update_timer, TIME_NEVER, 0, 0);

	profiler_mark(PROFILER_END);
}


void sound_reset(void)
{
	int totalsound = 0;


	while (Machine->drv->sound[totalsound].sound_type != 0 && totalsound < MAX_SOUND)
	{
		if (sndintf[Machine->drv->sound[totalsound].sound_type].reset)
			(*sndintf[Machine->drv->sound[totalsound].sound_type].reset)();

		totalsound++;
	}
}



const char *soundtype_name(int soundtype)
{
	if (soundtype < SOUND_COUNT)
		return sndintf[soundtype].name;
	else
		return "";
}

const char *sound_name(const struct MachineSound *msound)
{
	return soundtype_name(msound->sound_type);
}

int sound_num(const struct MachineSound *msound)
{
	if (msound->sound_type < SOUND_COUNT && sndintf[msound->sound_type].chips_num)
		return (*sndintf[msound->sound_type].chips_num)(msound);
	else
		return 0;
}

int sound_clock(const struct MachineSound *msound)
{
	if (msound->sound_type < SOUND_COUNT && sndintf[msound->sound_type].chips_clock)
		return (*sndintf[msound->sound_type].chips_clock)(msound);
	else
		return 0;
}


int sound_scalebufferpos(int value)
{
	int result = (int)((double)value * timer_timeelapsed(sound_update_timer) * refresh_period_inv);
	if (value >= 0) return (result < value) ? result : value;
	else return (result > value) ? result : value;
}
