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

    Exidy 6502 hardware

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

#include "emu.h"
#include "cpu/z80/z80.h"
#include "machine/rescap.h"
#include "cpu/m6502/m6502.h"
#include "machine/6821pia.h"
#include "machine/6532riot.h"
#include "sound/hc55516.h"
#include "sound/tms5220.h"
#include "audio/exidy.h"



/*************************************
 *
 *  Constants
 *
 *************************************/

#define CRYSTAL_OSC				(XTAL_3_579545MHz)
#define SH8253_CLOCK			(CRYSTAL_OSC / 2)
#define SH6840_CLOCK			(CRYSTAL_OSC / 4)
#define SH6532_CLOCK			(CRYSTAL_OSC / 4)
#define CVSD_CLOCK				(1.0 / (0.693 * (RES_K(2.4) + 2.0 * RES_K(20)) * CAP_P(2200)))
#define CVSD_Z80_CLOCK			(CRYSTAL_OSC / 2)
#define BASE_VOLUME				(32767 / 6)

enum
{
	RIOT_IDLE,
	RIOT_COUNT,
	RIOT_POST_COUNT
};



/*************************************
 *
 *  Local variables
 *
 *************************************/

/* 6840 variables */
struct sh6840_timer_channel
{
	UINT8	cr;
	UINT8	state;
	UINT8	leftovers;
	UINT16	timer;
	UINT32	clocks;
	union
	{
#ifdef LSB_FIRST
		struct { UINT8 l, h; } b;
#else
		struct { UINT8 h, l; } b;
#endif
		UINT16 w;
	} counter;
};

struct sh8253_timer_channel
{
	UINT8	clstate;
	UINT8	enable;
	UINT16	count;
	UINT32	step;
	UINT32	fraction;
};

typedef struct _exidy_sound_state exidy_sound_state;
struct _exidy_sound_state
{
	device_t *m_maincpu;

	/* IRQ variable */
	UINT8 m_riot_irq_state;

	/* 6532 variables */
	device_t *m_riot;

	struct sh6840_timer_channel m_sh6840_timer[3];
	INT16 m_sh6840_volume[3];
	UINT8 m_sh6840_MSB_latch;
	UINT8 m_sh6840_LSB_latch;
	UINT8 m_sh6840_LFSR_oldxor;
	UINT32 m_sh6840_LFSR_0;
	UINT32 m_sh6840_LFSR_1;
	UINT32 m_sh6840_LFSR_2;
	UINT32 m_sh6840_LFSR_3;
	UINT32 m_sh6840_clocks_per_sample;
	UINT32 m_sh6840_clock_count;

	UINT8 m_sfxctrl;

	/* 8253 variables */
	struct sh8253_timer_channel m_sh8253_timer[3];
	int m_has_sh8253;

	/* 5220/CVSD variables */
	device_t *m_cvsd;
	device_t *m_tms;
	pia6821_device *m_pia1;

	/* sound streaming variables */
	sound_stream *m_stream;
	double m_freq_to_step;

	UINT8 m_victory_sound_response_ack_clk;	/* 7474 @ F4 */
};


INLINE exidy_sound_state *get_safe_token(device_t *device)
{
	assert(device != NULL);
	assert(device->type() == EXIDY || device->type() == EXIDY_VENTURE || device->type() == EXIDY_VICTORY);

	return (exidy_sound_state *)downcast<legacy_device_base *>(device)->token();
}

/*************************************
 *
 *  Interrupt generation helper
 *
 *************************************/

static WRITE_LINE_DEVICE_HANDLER( update_irq_state )
{
	exidy_sound_state *sndstate = get_safe_token(device);
	cputag_set_input_line(device->machine(), "audiocpu", M6502_IRQ_LINE, (sndstate->m_pia1->irq_b_state() | sndstate->m_riot_irq_state) ? ASSERT_LINE : CLEAR_LINE);
}



/*************************************
 *
 *  6840 clock counting helper
 *
 *************************************/

INLINE void sh6840_apply_clock(struct sh6840_timer_channel *t, int clocks)
{
	/* dual 8-bit case */
	if (t->cr & 0x04)
	{
		/* handle full decrements */
		while (clocks > t->counter.b.l)
		{
			clocks -= t->counter.b.l + 1;
			t->counter.b.l = t->timer;

			/* decrement MSB */
			if (!t->counter.b.h--)
			{
				t->state = 0;
				t->counter.w = t->timer;
			}

			/* state goes high when MSB is 0 */
			else if (!t->counter.b.h)
			{
				t->state = 1;
				t->clocks++;
			}
		}

		/* subtract off the remainder */
		t->counter.b.l -= clocks;
	}

	/* 16-bit case */
	else
	{
		/* handle full decrements */
		while (clocks > t->counter.w)
		{
			clocks -= t->counter.w + 1;
			t->state ^= 1;
			t->clocks += t->state;
			t->counter.w = t->timer;
		}

		/* subtract off the remainder */
		t->counter.w -= clocks;
	}
}



/*************************************
 *
 *  Noise generation helper
 *
 *************************************/

INLINE int sh6840_update_noise(exidy_sound_state *state, int clocks)
{
	UINT32 newxor;
	int noise_clocks = 0;
	int i;

	/* loop over clocks */
	for (i = 0; i < clocks; i++)
	{
		/* shift the LFSR. its a LOOOONG LFSR, so we need
        * four longs to hold it all!
        * first we grab new sample, then shift the high bits,
        * then the low ones; finally or in the result and see if we've
        * had a 0->1 transition */
		newxor = (state->m_sh6840_LFSR_3 ^ state->m_sh6840_LFSR_2) >> 31; /* high bits of 3 and 2 xored is new xor */
		state->m_sh6840_LFSR_3 <<= 1;
		state->m_sh6840_LFSR_3 |= state->m_sh6840_LFSR_2 >> 31;
		state->m_sh6840_LFSR_2 <<= 1;
		state->m_sh6840_LFSR_2 |= state->m_sh6840_LFSR_1 >> 31;
		state->m_sh6840_LFSR_1 <<= 1;
		state->m_sh6840_LFSR_1 |= state->m_sh6840_LFSR_0 >> 31;
		state->m_sh6840_LFSR_0 <<= 1;
		state->m_sh6840_LFSR_0 |= newxor ^ state->m_sh6840_LFSR_oldxor;
		state->m_sh6840_LFSR_oldxor = newxor;
		/*printf("LFSR: %4x, %4x, %4x, %4x\n", sh6840_LFSR_3, sh6840_LFSR_2, sh6840_LFSR_1, sh6840_LFSR_0);*/
		/* if we clocked 0->1, that will serve as an external clock */
		if ((state->m_sh6840_LFSR_2 & 0x03) == 0x01) /* tap is at 96th bit */
		{
			noise_clocks++;
		}
	}
	return noise_clocks;
}



/*************************************
 *
 *  6840 state saving
 *
 *************************************/

static void sh6840_register_state_globals(device_t *device)
{
	exidy_sound_state *state = get_safe_token(device);

	device->save_item(NAME(state->m_sh6840_volume));
	device->save_item(NAME(state->m_sh6840_MSB_latch));
	device->save_item(NAME(state->m_sh6840_LSB_latch));
	device->save_item(NAME(state->m_sh6840_LFSR_oldxor));
	device->save_item(NAME(state->m_sh6840_LFSR_0));
	device->save_item(NAME(state->m_sh6840_LFSR_1));
	device->save_item(NAME(state->m_sh6840_LFSR_2));
	device->save_item(NAME(state->m_sh6840_LFSR_3));
	device->save_item(NAME(state->m_sh6840_clock_count));
	device->save_item(NAME(state->m_sfxctrl));
	device->save_item(NAME(state->m_sh6840_timer[0].cr));
	device->save_item(NAME(state->m_sh6840_timer[0].state));
	device->save_item(NAME(state->m_sh6840_timer[0].leftovers));
	device->save_item(NAME(state->m_sh6840_timer[0].timer));
	device->save_item(NAME(state->m_sh6840_timer[0].clocks));
	device->save_item(NAME(state->m_sh6840_timer[0].counter.w));
	device->save_item(NAME(state->m_sh6840_timer[1].cr));
	device->save_item(NAME(state->m_sh6840_timer[1].state));
	device->save_item(NAME(state->m_sh6840_timer[1].leftovers));
	device->save_item(NAME(state->m_sh6840_timer[1].timer));
	device->save_item(NAME(state->m_sh6840_timer[1].clocks));
	device->save_item(NAME(state->m_sh6840_timer[1].counter.w));
	device->save_item(NAME(state->m_sh6840_timer[2].cr));
	device->save_item(NAME(state->m_sh6840_timer[2].state));
	device->save_item(NAME(state->m_sh6840_timer[2].leftovers));
	device->save_item(NAME(state->m_sh6840_timer[2].timer));
	device->save_item(NAME(state->m_sh6840_timer[2].clocks));
	device->save_item(NAME(state->m_sh6840_timer[2].counter.w));
}



/*************************************
 *
 *  Core sound generation
 *
 *************************************/

static STREAM_UPDATE( exidy_stream_update )
{
	exidy_sound_state *state = get_safe_token(device);
	struct sh6840_timer_channel *sh6840_timer = state->m_sh6840_timer;

	/* hack to skip the expensive lfsr noise generation unless at least one of the 3 channels actually depends on it being generated */
	int noisy = ((sh6840_timer[0].cr & sh6840_timer[1].cr & sh6840_timer[2].cr & 0x02) == 0);
	stream_sample_t *buffer = outputs[0];

	/* loop over samples */
	while (samples--)
	{
		struct sh6840_timer_channel *t;
		struct sh8253_timer_channel *c;
		int clocks_this_sample;
		int clocks;
		INT16 sample = 0;

		/* determine how many 6840 clocks this sample */
		state->m_sh6840_clock_count += state->m_sh6840_clocks_per_sample;
		clocks_this_sample = state->m_sh6840_clock_count >> 24;
		state->m_sh6840_clock_count &= (1 << 24) - 1;

		/* skip if nothing enabled */
		if ((sh6840_timer[0].cr & 0x01) == 0)
		{
			int noise_clocks_this_sample = 0;
			UINT32 chan0_clocks;

			/* generate E-clocked noise if configured to do so */
			if (noisy && !(state->m_sfxctrl & 0x01))
				noise_clocks_this_sample = sh6840_update_noise(state, clocks_this_sample);

			/* handle timer 0 if enabled */
			t = &sh6840_timer[0];
			chan0_clocks = t->clocks;
			clocks = (t->cr & 0x02) ? clocks_this_sample : noise_clocks_this_sample;
			sh6840_apply_clock(t, clocks);
			if (t->state && !(state->m_sfxctrl & 0x02) && (t->cr & 0x80))
				sample += state->m_sh6840_volume[0];

			/* generate channel 0-clocked noise if configured to do so */
			if (noisy && (state->m_sfxctrl & 0x01))
				noise_clocks_this_sample = sh6840_update_noise(state, t->clocks - chan0_clocks);

			/* handle timer 1 if enabled */
			t = &sh6840_timer[1];
			clocks = (t->cr & 0x02) ? clocks_this_sample : noise_clocks_this_sample;
			sh6840_apply_clock(t, clocks);
			if (t->state && (t->cr & 0x80))
				sample += state->m_sh6840_volume[1];

			/* handle timer 2 if enabled */
			t = &sh6840_timer[2];
			clocks = (t->cr & 0x02) ? clocks_this_sample : noise_clocks_this_sample;
			/* prescale */
			if (t->cr & 0x01)
			{
				clocks += t->leftovers;
				t->leftovers = clocks % 8;
				clocks /= 8;
			}
			sh6840_apply_clock(t, clocks);
			if (t->state && (t->cr & 0x80))
				sample += state->m_sh6840_volume[2];
		}

		/* music (if present) */
		if (state->m_has_sh8253)
		{
			/* music channel 0 */
			c = &state->m_sh8253_timer[0];
			if (c->enable)
			{
				c->fraction += c->step;
				if (c->fraction & 0x0800000)
					sample += BASE_VOLUME;
			}

			/* music channel 1 */
			c = &state->m_sh8253_timer[1];
			if (c->enable)
			{
				c->fraction += c->step;
				if (c->fraction & 0x0800000)
					sample += BASE_VOLUME;
			}

			/* music channel 2 */
			c = &state->m_sh8253_timer[2];
			if (c->enable)
			{
				c->fraction += c->step;
				if (c->fraction & 0x0800000)
					sample += BASE_VOLUME;
			}
		}

		/* stash */
		*buffer++ = sample;
	}
}



/*************************************
 *
 *  Audio startup routines
 *
 *************************************/

static DEVICE_START( common_sh_start )
{
	exidy_sound_state *state = get_safe_token(device);
	int sample_rate = SH8253_CLOCK;

	state->m_sh6840_clocks_per_sample = (int)((double)SH6840_CLOCK / (double)sample_rate * (double)(1 << 24));

	/* allocate the stream */
	state->m_stream = device->machine().sound().stream_alloc(*device, 0, 1, sample_rate, NULL, exidy_stream_update);
	state->m_maincpu = device->machine().device("maincpu");

	sh6840_register_state_globals(device);
}

static DEVICE_START( exidy_sound )
{
	exidy_sound_state *state = get_safe_token(device);

	/* indicate no additional hardware */
	state->m_has_sh8253  = FALSE;
	state->m_tms = NULL;
	state->m_cvsd = NULL;

	DEVICE_START_CALL(common_sh_start);
}

DEFINE_LEGACY_SOUND_DEVICE(EXIDY, exidy_sound);

/*************************************
 *
 *  Audio reset routines
 *
 *************************************/

static DEVICE_RESET( common_sh_reset )
{
	exidy_sound_state *state = get_safe_token(device);

	/* 6840 */
	memset(state->m_sh6840_timer, 0, sizeof(state->m_sh6840_timer));
	state->m_sh6840_MSB_latch = 0;
	state->m_sh6840_LSB_latch = 0;
	state->m_sh6840_volume[0] = 0;
	state->m_sh6840_volume[1] = 0;
	state->m_sh6840_volume[2] = 0;
	state->m_sh6840_clock_count = 0;
	state->m_sfxctrl = 0;

	/* LFSR */
	state->m_sh6840_LFSR_oldxor = 0;
	state->m_sh6840_LFSR_0 = 0xffffffff;
	state->m_sh6840_LFSR_1 = 0xffffffff;
	state->m_sh6840_LFSR_2 = 0xffffffff;
	state->m_sh6840_LFSR_3 = 0xffffffff;
}

static DEVICE_RESET( exidy_sound )
{
	DEVICE_RESET_CALL(common_sh_reset);
}


DEVICE_GET_INFO( exidy_sound )
{
	switch (state)
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_INT_TOKEN_BYTES:					info->i = sizeof(exidy_sound_state);			break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:							info->start = DEVICE_START_NAME(exidy_sound);	break;
		case DEVINFO_FCT_RESET:							info->reset = DEVICE_RESET_NAME(exidy_sound);	break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_NAME:							strcpy(info->s, "Exidy SFX");					break;
		case DEVINFO_STR_SOURCE_FILE:						strcpy(info->s, __FILE__);						break;
	}
}


/*************************************
 *
 *  6532 interface
 *
 *************************************/

static void r6532_irq(device_t *device, int state)
{
	exidy_sound_state *sndstate = get_safe_token(device);
	sndstate->m_riot_irq_state = (state == ASSERT_LINE) ? 1 : 0;
	update_irq_state(device, 0);
}


static WRITE8_DEVICE_HANDLER( r6532_porta_w )
{
	exidy_sound_state *state = get_safe_token(device);
	if (state->m_cvsd != NULL)
		cputag_set_input_line(device->machine(), "cvsdcpu", INPUT_LINE_RESET, (data & 0x10) ? CLEAR_LINE : ASSERT_LINE);

	if (state->m_tms != NULL)
	{
		logerror("(%f)%s:TMS5220 data write = %02X\n", device->machine().time().as_double(), device->machine().describe_context(), riot6532_porta_out_get(state->m_riot));
		tms5220_data_w(state->m_tms, 0, data);
	}
}

static READ8_DEVICE_HANDLER( r6532_porta_r )
{
	exidy_sound_state *state = get_safe_token(device);
	if (state->m_tms != NULL)
	{
		logerror("(%f)%s:TMS5220 status read = %02X\n", device->machine().time().as_double(), device->machine().describe_context(), tms5220_status_r(state->m_tms, 0));
		return tms5220_status_r(state->m_tms, 0);
	}
	else
		return 0xff;
}

static WRITE8_DEVICE_HANDLER( r6532_portb_w )
{
	exidy_sound_state *state = get_safe_token(device);
	if (state->m_tms != NULL)
	{
		tms5220_rsq_w(state->m_tms, data & 0x01);
		tms5220_wsq_w(state->m_tms, (data >> 1) & 0x01);
	}
}


static READ8_DEVICE_HANDLER( r6532_portb_r )
{
	exidy_sound_state *state = get_safe_token(device);
	UINT8 newdata = riot6532_portb_in_get(state->m_riot);
	if (state->m_tms != NULL)
	{
		newdata &= ~0x0c;
		if (tms5220_readyq_r(state->m_tms)) newdata |= 0x04;
		if (tms5220_intq_r(state->m_tms)) newdata |= 0x08;
	}
	return newdata;
}


static const riot6532_interface r6532_interface =
{
	DEVCB_DEVICE_HANDLER("custom", r6532_porta_r),	/* port A read handler */
	DEVCB_DEVICE_HANDLER("custom", r6532_portb_r),	/* port B read handler */
	DEVCB_DEVICE_HANDLER("custom", r6532_porta_w),	/* port A write handler */
	DEVCB_DEVICE_HANDLER("custom", r6532_portb_w),	/* port B write handler */
	DEVCB_DEVICE_LINE("custom", r6532_irq)			/* IRQ callback */
};



/*************************************
 *
 *  8253 state saving
 *
 *************************************/


static void sh8253_register_state_globals(device_t *device)
{
	exidy_sound_state *state = get_safe_token(device);

	device->save_item(NAME(state->m_sh8253_timer[0].clstate));
	device->save_item(NAME(state->m_sh8253_timer[0].enable));
	device->save_item(NAME(state->m_sh8253_timer[0].count));
	device->save_item(NAME(state->m_sh8253_timer[0].step));
	device->save_item(NAME(state->m_sh8253_timer[0].fraction));
	device->save_item(NAME(state->m_sh8253_timer[1].clstate));
	device->save_item(NAME(state->m_sh8253_timer[1].enable));
	device->save_item(NAME(state->m_sh8253_timer[1].count));
	device->save_item(NAME(state->m_sh8253_timer[1].step));
	device->save_item(NAME(state->m_sh8253_timer[1].fraction));
	device->save_item(NAME(state->m_sh8253_timer[2].clstate));
	device->save_item(NAME(state->m_sh8253_timer[2].enable));
	device->save_item(NAME(state->m_sh8253_timer[2].count));
	device->save_item(NAME(state->m_sh8253_timer[2].step));
	device->save_item(NAME(state->m_sh8253_timer[2].fraction));
}

/*************************************
 *
 *  8253 timer handlers
 *
 *************************************/

static WRITE8_DEVICE_HANDLER( exidy_sh8253_w )
{
	exidy_sound_state *state = get_safe_token(device);
	int chan;

	state->m_stream->update();

	switch (offset)
	{
		case 0:
		case 1:
		case 2:
			chan = offset;
			if (!state->m_sh8253_timer[chan].clstate)
			{
				state->m_sh8253_timer[chan].clstate = 1;
				state->m_sh8253_timer[chan].count = (state->m_sh8253_timer[chan].count & 0xff00) | (data & 0x00ff);
			}
			else
			{
				state->m_sh8253_timer[chan].clstate = 0;
				state->m_sh8253_timer[chan].count = (state->m_sh8253_timer[chan].count & 0x00ff) | ((data << 8) & 0xff00);
				if (state->m_sh8253_timer[chan].count)
					state->m_sh8253_timer[chan].step = state->m_freq_to_step * (double)SH8253_CLOCK / (double)state->m_sh8253_timer[chan].count;
				else
					state->m_sh8253_timer[chan].step = 0;
			}
			break;

		case 3:
			chan = (data & 0xc0) >> 6;
			state->m_sh8253_timer[chan].enable = ((data & 0x0e) != 0);
			break;
	}
}


static READ8_DEVICE_HANDLER( exidy_sh8253_r )
{
	logerror("8253(R): %x\n",offset);

	return 0;
}



/*************************************
 *
 *  6840 timer handlers
 *
 *************************************/

READ8_DEVICE_HANDLER( exidy_sh6840_r )
{
	exidy_sound_state *state = get_safe_token(device);

	/* force an update of the stream */
	state->m_stream->update();

	switch (offset)
	{
		/* offset 0: Motorola datasheet says it isn't used, Hitachi datasheet says it reads as 0s always*/
		case 0:
		return 0;
		/* offset 1 reads the status register: bits 2 1 0 correspond to ints on channels 2,1,0, and bit 7 is an 'OR' of bits 2,1,0 */
		case 1:
		logerror("%04X:exidy_sh6840_r - unexpected read, status register is TODO!\n", cpu_get_pc(state->m_maincpu));
		return 0;
		/* offsets 2,4,6 read channel 0,1,2 MSBs and latch the LSB*/
		case 2: case 4: case 6:
		state->m_sh6840_LSB_latch = state->m_sh6840_timer[((offset<<1)-1)].counter.b.l;
		return state->m_sh6840_timer[((offset<<1)-1)].counter.b.h;
		/* offsets 3,5,7 read the LSB latch*/
		default: /* case 3,5,7 */
		return state->m_sh6840_LSB_latch;
	}
}


WRITE8_DEVICE_HANDLER( exidy_sh6840_w )
{
	exidy_sound_state *state = get_safe_token(device);
	struct sh6840_timer_channel *sh6840_timer = state->m_sh6840_timer;

	/* force an update of the stream */
	state->m_stream->update();

	switch (offset)
	{
		/* offset 0 writes to either channel 0 control or channel 2 control */
		case 0:
			if (sh6840_timer[1].cr & 0x01)
				sh6840_timer[0].cr = data;
			else
				sh6840_timer[2].cr = data;

			/* only support mode 0 and 2 */
			if (((data >> 3) & 5) != 0)
				fatalerror("exidy_sh6840_w - channel %d configured for mode %d", (sh6840_timer[1].cr & 0x01) ? 0 : 2, (data >> 3) & 7);
			break;

		/* offset 1 writes to channel 1 control */
		case 1:
			sh6840_timer[1].cr = data;

			/* only support mode 0 and 2 */
			if (((data >> 3) & 5) != 0)
				fatalerror("exidy_sh6840_w - channel 1 configured for mode %d", (data >> 3) & 7);
			break;

		/* offsets 2/4/6 write to the common MSB latch */
		case 2:
		case 4:
		case 6:
			state->m_sh6840_MSB_latch = data;
			break;

		/* offsets 3/5/7 write to the LSB controls */
		case 3:
		case 5:
		case 7:
		{
			/* latch the timer value */
			int ch = (offset - 3) / 2;
			sh6840_timer[ch].timer = (state->m_sh6840_MSB_latch << 8) | (data & 0xff);

			/* if CR4 is clear, the value is loaded immediately */
			if (!(sh6840_timer[ch].cr & 0x10))
				sh6840_timer[ch].counter.w = sh6840_timer[ch].timer;
			break;
		}
	}
}



/*************************************
 *
 *  External sound effect controls
 *
 *************************************/

WRITE8_DEVICE_HANDLER( exidy_sfxctrl_w )
{
	exidy_sound_state *state = get_safe_token(device);

	state->m_stream->update();

	switch (offset)
	{
		case 0:
			state->m_sfxctrl = data;
			break;

		case 1:
		case 2:
		case 3:
			state->m_sh6840_volume[offset - 1] = ((data & 7) * BASE_VOLUME) / 7;
			break;
	}
}



/*************************************
 *
 *  Sound filter control
 *
 *************************************/

static WRITE8_DEVICE_HANDLER( exidy_sound_filter_w )
{
	logerror("exidy_sound_filter_w = %02X\n", data);
}



/*************************************
 *
 *  Venture, etc.
 *
 *************************************/

static const pia6821_interface venture_pia0_intf =
{
	DEVCB_NULL,		/* port A in */
	DEVCB_NULL,		/* port B in */
	DEVCB_NULL,		/* line CA1 in */
	DEVCB_NULL,		/* line CB1 in */
	DEVCB_NULL,		/* line CA2 in */
	DEVCB_NULL,		/* line CB2 in */
	DEVCB_DEVICE_MEMBER("pia1", pia6821_device, portb_w),		/* port A out */
	DEVCB_DEVICE_MEMBER("pia1", pia6821_device, porta_w),		/* port B out */
	DEVCB_DEVICE_LINE_MEMBER("pia1", pia6821_device, cb1_w),		/* line CA2 out */
	DEVCB_DEVICE_LINE_MEMBER("pia1", pia6821_device, ca1_w),		/* port CB2 out */
	DEVCB_NULL,		/* IRQA */
	DEVCB_NULL		/* IRQB */
};


static const pia6821_interface venture_pia1_intf =
{
	DEVCB_NULL,		/* port A in */
	DEVCB_NULL,		/* port B in */
	DEVCB_NULL,		/* line CA1 in */
	DEVCB_NULL,		/* line CB1 in */
	DEVCB_NULL,		/* line CA2 in */
	DEVCB_NULL,		/* line CB2 in */
	DEVCB_DEVICE_MEMBER("pia0", pia6821_device, portb_w),		/* port A out */
	DEVCB_DEVICE_MEMBER("pia0", pia6821_device, porta_w),		/* port B out */
	DEVCB_DEVICE_LINE_MEMBER("pia0", pia6821_device, cb1_w),		/* line CA2 out */
	DEVCB_DEVICE_LINE_MEMBER("pia0", pia6821_device, ca1_w),		/* port CB2 out */
	DEVCB_NULL,		/* IRQA */
	DEVCB_DEVICE_LINE("custom", update_irq_state)		/* IRQB */
};


static DEVICE_START( venture_common_sh_start )
{
	exidy_sound_state *state = get_safe_token(device);
	running_machine &machine = device->machine();

	DEVICE_START_CALL(common_sh_start);

	state->m_riot = machine.device("riot");

	state->m_has_sh8253  = TRUE;
	state->m_tms = NULL;
	state->m_pia1 = device->machine().device<pia6821_device>("pia1");

	/* determine which sound hardware is installed */
	state->m_cvsd = device->machine().device("cvsd");

	/* 8253 */
	state->m_freq_to_step = (double)(1 << 24) / (double)SH8253_CLOCK;

	device->save_item(NAME(state->m_riot_irq_state));
	sh8253_register_state_globals(device);
}


static DEVICE_START( venture_sound )
{
	DEVICE_START_CALL(venture_common_sh_start);
}


static DEVICE_RESET( venture_sound )
{
	exidy_sound_state *state = get_safe_token(device);

	DEVICE_RESET_CALL(common_sh_reset);

	/* PIA */
	devtag_reset(device->machine(), "pia0");
	devtag_reset(device->machine(), "pia1");

	/* 6532 */
	state->m_riot->reset();

	/* 8253 */
	memset(state->m_sh8253_timer, 0, sizeof(state->m_sh8253_timer));
}


DEVICE_GET_INFO( venture_sound )
{
	switch (state)
	{
		case DEVINFO_INT_TOKEN_BYTES:					info->i = sizeof(exidy_sound_state);			break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:							info->start = DEVICE_START_NAME(venture_sound);	break;
		case DEVINFO_FCT_RESET:							info->reset = DEVICE_RESET_NAME(venture_sound);	break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_NAME:							strcpy(info->s, "Exidy SFX+PSG");				break;
		case DEVINFO_STR_SOURCE_FILE:						strcpy(info->s, __FILE__);						break;
	}
}

DEFINE_LEGACY_SOUND_DEVICE(EXIDY_VENTURE, venture_sound);


static ADDRESS_MAP_START( venture_audio_map, AS_PROGRAM, 8 )
	ADDRESS_MAP_GLOBAL_MASK(0x7fff)
	AM_RANGE(0x0000, 0x007f) AM_MIRROR(0x0780) AM_RAM
	AM_RANGE(0x0800, 0x087f) AM_MIRROR(0x0780) AM_DEVREADWRITE("riot", riot6532_r, riot6532_w)
	AM_RANGE(0x1000, 0x1003) AM_MIRROR(0x07fc) AM_DEVREADWRITE_MODERN("pia1", pia6821_device, read, write)
	AM_RANGE(0x1800, 0x1803) AM_MIRROR(0x07fc) AM_DEVREADWRITE("custom", exidy_sh8253_r, exidy_sh8253_w)
	AM_RANGE(0x2000, 0x27ff) AM_DEVWRITE("custom", exidy_sound_filter_w)
	AM_RANGE(0x2800, 0x2807) AM_MIRROR(0x07f8) AM_DEVREADWRITE("custom", exidy_sh6840_r, exidy_sh6840_w)
	AM_RANGE(0x3000, 0x3003) AM_MIRROR(0x07fc) AM_DEVWRITE("custom", exidy_sfxctrl_w)
	AM_RANGE(0x5800, 0x7fff) AM_ROM
ADDRESS_MAP_END


MACHINE_CONFIG_FRAGMENT( venture_audio )

	MCFG_CPU_ADD("audiocpu", M6502, 3579545/4)
	MCFG_CPU_PROGRAM_MAP(venture_audio_map)

	MCFG_RIOT6532_ADD("riot", SH6532_CLOCK, r6532_interface)

	MCFG_PIA6821_ADD("pia0", venture_pia0_intf)
	MCFG_PIA6821_ADD("pia1", venture_pia1_intf)

	MCFG_SPEAKER_STANDARD_MONO("mono")

	MCFG_SOUND_ADD("custom", EXIDY_VENTURE, 0)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.50)
MACHINE_CONFIG_END



/*************************************
 *
 *  CVSD sound for Mouse Trap
 *
 *************************************/

static WRITE8_DEVICE_HANDLER( mtrap_voiceio_w )
{
	exidy_sound_state *state = get_safe_token(device);

	if (!(offset & 0x10))
		hc55516_digit_w(state->m_cvsd, data & 1);

	if (!(offset & 0x20))
		riot6532_portb_in_set(state->m_riot, data & 1, 0xff);
}


static READ8_DEVICE_HANDLER( mtrap_voiceio_r )
{
	exidy_sound_state *state = get_safe_token(device);

	if (!(offset & 0x80))
	{
		UINT8 porta = riot6532_porta_out_get(state->m_riot);
		UINT8 data = (porta & 0x06) >> 1;
		data |= (porta & 0x01) << 2;
		data |= (porta & 0x08);
		return data;
	}

	if (!(offset & 0x40))
		return hc55516_clock_state_r(state->m_cvsd) << 7;

	return 0;
}


static ADDRESS_MAP_START( cvsd_map, AS_PROGRAM, 8 )
	ADDRESS_MAP_GLOBAL_MASK(0x3fff)
	AM_RANGE(0x0000, 0x3fff) AM_ROM
ADDRESS_MAP_END


static ADDRESS_MAP_START( cvsd_iomap, AS_IO, 8 )
	ADDRESS_MAP_GLOBAL_MASK(0xff)
	AM_RANGE(0x00, 0xff) AM_DEVREADWRITE("custom", mtrap_voiceio_r, mtrap_voiceio_w)
ADDRESS_MAP_END


MACHINE_CONFIG_FRAGMENT( mtrap_cvsd_audio )

	MCFG_CPU_ADD("cvsdcpu", Z80, CVSD_Z80_CLOCK)
	MCFG_CPU_PROGRAM_MAP(cvsd_map)
	MCFG_CPU_IO_MAP(cvsd_iomap)

	/* audio hardware */
	MCFG_SOUND_ADD("cvsd", MC3417, CVSD_CLOCK)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.80)
MACHINE_CONFIG_END



/*************************************
 *
 *  Victory
 *
 *************************************/

#define VICTORY_AUDIO_CPU_CLOCK		(XTAL_3_579545MHz / 4)
#define VICTORY_LOG_SOUND			0



READ8_DEVICE_HANDLER( victory_sound_response_r )
{
	exidy_sound_state *state = get_safe_token(device);
	UINT8 ret = state->m_pia1->b_output();

	if (VICTORY_LOG_SOUND) logerror("%04X:!!!! Sound response read = %02X\n", cpu_get_previouspc(state->m_maincpu), ret);

	state->m_pia1->cb1_w(0);

	return ret;
}


READ8_DEVICE_HANDLER( victory_sound_status_r )
{
	exidy_sound_state *state = get_safe_token(device);
	UINT8 ret = (state->m_pia1->ca1_r() << 7) | (state->m_pia1->cb1_r() << 6);

	if (VICTORY_LOG_SOUND) logerror("%04X:!!!! Sound status read = %02X\n", cpu_get_previouspc(state->m_maincpu), ret);

	return ret;
}


static TIMER_CALLBACK( delayed_command_w )
{
	pia6821_device *pia1 = (pia6821_device *)ptr;
	pia1->set_a_input(param, 0);
	pia1->ca1_w(0);
}

WRITE8_DEVICE_HANDLER( victory_sound_command_w )
{
	exidy_sound_state *state = get_safe_token(device);

	if (VICTORY_LOG_SOUND) logerror("%04X:!!!! Sound command = %02X\n", cpu_get_previouspc(state->m_maincpu), data);

	device->machine().scheduler().synchronize(FUNC(delayed_command_w), data, state->m_pia1);
}


static WRITE8_DEVICE_HANDLER( victory_sound_irq_clear_w )
{
	exidy_sound_state *state = get_safe_token(device);

	if (VICTORY_LOG_SOUND) logerror("%s:!!!! Sound IRQ clear = %02X\n", device->machine().describe_context(), data);

	if (!data) state->m_pia1->ca1_w(1);
}


static WRITE8_DEVICE_HANDLER( victory_main_ack_w )
{
	exidy_sound_state *state = get_safe_token(device);

	if (VICTORY_LOG_SOUND) logerror("%s:!!!! Sound Main ACK W = %02X\n", device->machine().describe_context(), data);

	if (state->m_victory_sound_response_ack_clk && !data)
		state->m_pia1->cb1_w(1);

	state->m_victory_sound_response_ack_clk = data;
}


static const pia6821_interface victory_pia1_intf =
{
	DEVCB_NULL,		/* port A in */
	DEVCB_NULL,		/* port B in */
	DEVCB_NULL,		/* line CA1 in */
	DEVCB_NULL,		/* line CB1 in */
	DEVCB_NULL,		/* line CA2 in */
	DEVCB_NULL,		/* line CB2 in */
	DEVCB_NULL,		/* port A out */
	DEVCB_NULL,		/* port B out */
	DEVCB_DEVICE_HANDLER("custom", victory_sound_irq_clear_w),	/* line CA2 out */
	DEVCB_DEVICE_HANDLER("custom", victory_main_ack_w),			/* port CB2 out */
	DEVCB_NULL,		/* IRQA */
	DEVCB_DEVICE_LINE("custom", update_irq_state)				/* IRQB */
};



static DEVICE_START( victory_sound )
{
	exidy_sound_state *state = get_safe_token(device);

	device->save_item(NAME(state->m_victory_sound_response_ack_clk));

	DEVICE_START_CALL(venture_common_sh_start);
	state->m_tms = device->machine().device("tms");
}


static DEVICE_RESET( victory_sound )
{
	exidy_sound_state *state = get_safe_token(device);
	pia6821_device *pia1 = state->m_pia1;

	DEVICE_RESET_CALL(common_sh_reset);
	pia1->reset();
	state->m_riot->reset();
	memset(state->m_sh8253_timer, 0, sizeof(state->m_sh8253_timer));

	/* the flip-flop @ F4 is reset */
	state->m_victory_sound_response_ack_clk = 0;
	pia1->cb1_w(1);

	/* these two lines shouldn't be needed, but it avoids the log entry
       as the sound CPU checks port A before the main CPU ever writes to it */
	pia1->set_a_input(0, 0);
	pia1->ca1_w(1);
}


DEVICE_GET_INFO( victory_sound )
{
	switch (state)
	{
		case DEVINFO_INT_TOKEN_BYTES:					info->i = sizeof(exidy_sound_state);			break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:							info->start = DEVICE_START_NAME(victory_sound);	break;
		case DEVINFO_FCT_RESET:							info->reset = DEVICE_RESET_NAME(victory_sound);	break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_NAME:							strcpy(info->s, "Exidy SFX+PSG+Speech");		break;
		case DEVINFO_STR_SOURCE_FILE:						strcpy(info->s, __FILE__);						break;
	}
}


DEFINE_LEGACY_SOUND_DEVICE(EXIDY_VICTORY, victory_sound);


static ADDRESS_MAP_START( victory_audio_map, AS_PROGRAM, 8 )
	AM_RANGE(0x0000, 0x00ff) AM_MIRROR(0x0f00) AM_RAM
	AM_RANGE(0x1000, 0x107f) AM_MIRROR(0x0f80) AM_DEVREADWRITE("riot", riot6532_r, riot6532_w)
	AM_RANGE(0x2000, 0x2003) AM_MIRROR(0x0ffc) AM_DEVREADWRITE_MODERN("pia1", pia6821_device, read, write)
	AM_RANGE(0x3000, 0x3003) AM_MIRROR(0x0ffc) AM_DEVREADWRITE("custom", exidy_sh8253_r, exidy_sh8253_w)
	AM_RANGE(0x4000, 0x4fff) AM_NOP
	AM_RANGE(0x5000, 0x5007) AM_MIRROR(0x0ff8) AM_DEVREADWRITE("custom", exidy_sh6840_r, exidy_sh6840_w)
	AM_RANGE(0x6000, 0x6003) AM_MIRROR(0x0ffc) AM_DEVWRITE("custom", exidy_sfxctrl_w)
	AM_RANGE(0x7000, 0xafff) AM_NOP
	AM_RANGE(0xb000, 0xffff) AM_ROM
ADDRESS_MAP_END


MACHINE_CONFIG_FRAGMENT( victory_audio )

	MCFG_CPU_ADD("audiocpu", M6502, VICTORY_AUDIO_CPU_CLOCK)
	MCFG_CPU_PROGRAM_MAP(victory_audio_map)

	MCFG_RIOT6532_ADD("riot", SH6532_CLOCK, r6532_interface)
	MCFG_PIA6821_ADD("pia1", victory_pia1_intf)

	MCFG_SPEAKER_STANDARD_MONO("mono")

	MCFG_SOUND_ADD("custom", EXIDY_VICTORY, 0)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 1.0)

	MCFG_SOUND_ADD("tms", TMS5220, 640000)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 1.0)
MACHINE_CONFIG_END
