/* from Andrew Scott (ascott@utkux.utcc.utk.edu) */

/*
  updated by BUT
  - corrected music tempo (not confirmed Satan of Saturn and clone)
  - adjusted music freq (except Satan of Saturn and clone)
  - adjusted music waveform
  - support playing flag for music channel 0
  - support HD38880 speech by samples
*/


#include "driver.h"
#include "streams.h"
#include "sound/sn76477.h"
#include "sound/samples.h"
#include "rockola.h"
#include "sound/discrete.h"


#ifndef M_LN2
#define M_LN2		0.69314718055994530942
#endif

#define TONE_VOLUME	50
#define CHANNELS	3

#define SAMPLE_RATE	(48000)
#define FRAC_BITS	16
#define FRAC_ONE	(1 << FRAC_BITS)
#define FRAC_MASK	(FRAC_ONE - 1)

typedef struct tone
{
	int	mute;
	int	offset;
	int	base;
	int	mask;
	INT32	sample_rate;
	INT32	sample_step;
	INT32	sample_cur;
	INT16	form[16];
} TONE;

static TONE tone_channels[CHANNELS];
static INT32 tone_clock_expire;
static INT32 tone_clock;
static sound_stream * tone_stream;

static int Sound0StopOnRollover;
static UINT8 LastPort1;


static const char *const sasuke_sample_names[] =
{
	"*sasuke",

	// SN76477 and discrete
	"hit.wav",
	"boss_start.wav",
	"shot.wav",
	"boss_attack.wav",

	0
};

const samples_interface sasuke_samples_interface =
{
	4,	/* 4 channels */
	sasuke_sample_names
};

static const char *const vanguard_sample_names[] =
{
	"*vanguard",

	// SN76477 and discrete
	"fire.wav",
	"explsion.wav",

	// HD38880 speech
	"vg_voi-0.wav",
	"vg_voi-1.wav",
	"vg_voi-2.wav",
	"vg_voi-3.wav",
	"vg_voi-4.wav",
	"vg_voi-5.wav",
	"vg_voi-6.wav",
	"vg_voi-7.wav",
	"vg_voi-8.wav",
	"vg_voi-9.wav",
	"vg_voi-a.wav",
	"vg_voi-b.wav",
	"vg_voi-c.wav",
	"vg_voi-d.wav",
	"vg_voi-e.wav",
	"vg_voi-f.wav",

	0
};

const samples_interface vanguard_samples_interface =
{
	3,	/* 3 channel */
	vanguard_sample_names
};

static const char *const fantasy_sample_names[] =
{
	"*fantasy",

	// HD38880 speech
	"ft_voi-0.wav",
	"ft_voi-1.wav",
	"ft_voi-2.wav",
	"ft_voi-3.wav",
	"ft_voi-4.wav",
	"ft_voi-5.wav",
	"ft_voi-6.wav",
	"ft_voi-7.wav",
	"ft_voi-8.wav",
	"ft_voi-9.wav",
	"ft_voi-a.wav",
	"ft_voi-b.wav",

	0
};

const samples_interface fantasy_samples_interface =
{
	1,	/* 1 channel */
	fantasy_sample_names
};


const sn76477_interface sasuke_sn76477_intf_1 =
{
	RES_K(470),		/*  4  noise_res     */
	RES_K(150),		/*  5  filter_res    */
	CAP_P(4700),	/*  6  filter_cap    */
	RES_K(22),		/*  7  decay_res     */
	CAP_U(10),		/*  8  attack_decay_cap  */
	RES_K(10),		/* 10  attack_res    */
	RES_K(100),		/* 11  amplitude_res     */
	RES_K(47),		/* 12  feedback_res      */
	0 /* NC */,		/* 16  vco_voltage   */
	0 /* NC */,		/* 17  vco_cap       */
	0 /* NC */,		/* 18  vco_res       */
	0 /* NC */,		/* 19  pitch_voltage     */
	RES_K(10),		/* 20  slf_res       */
	0 /* NC */,		/* 21  slf_cap       */
	CAP_U(2.2),		/* 23  oneshot_cap   */
	RES_K(100),		/* 24  oneshot_res   */
	0,			    /* 22  vco                    */
	0,			    /* 26  mixer A           */
	1,			    /* 25  mixer B           */
	0,			    /* 27  mixer C           */
	1,			    /* 1   envelope 1        */
	0,			    /* 28  envelope 2        */
	1			    /* 9   enable (variable)      */

	// ic48     GND: 2,22,26,27,28  +5V: 1,15,25
};

const sn76477_interface sasuke_sn76477_intf_2 =
{
	RES_K(340),		/*  4  noise_res     */
	RES_K(47),		/*  5  filter_res    */
	CAP_P(100),		/*  6  filter_cap    */
	RES_K(470),		/*  7  decay_res     */
	CAP_U(4.7),		/*  8  attack_decay_cap  */
	RES_K(10),		/* 10  attack_res    */
	RES_K(100),		/* 11  amplitude_res     */
	RES_K(47),		/* 12  feedback_res      */
	0 /* NC */,		/* 16  vco_voltage   */
	CAP_P(220),		/* 17  vco_cap       */
	RES_K(1000),	/* 18  vco_res       */
	0 /* NC */,		/* 19  pitch_voltage     */
	RES_K(220),		/* 20  slf_res       */
	0 /* NC */,		/* 21  slf_cap       */
	CAP_U(22),		/* 23  oneshot_cap   */
	RES_K(47),		/* 24  oneshot_res   */
	1,			    /* 22  vco                    */
	0,			    /* 26  mixer A           */
	1,			    /* 25  mixer B           */
	0,			    /* 27  mixer C           */
	1,			    /* 1   envelope 1        */
	1,			    /* 28  envelope 2        */
	1			    /* 9   enable (variable)      */

	// ic51     GND: 2,26,27        +5V: 1,15,22,25,28
};

const sn76477_interface sasuke_sn76477_intf_3 =
{
	RES_K(330),		/*  4  noise_res     */
	RES_K(47),		/*  5  filter_res    */
	CAP_P(100),		/*  6  filter_cap    */
	RES_K(1),		/*  7  decay_res     */
	0 /* NC */,		/*  8  attack_decay_cap  */
	RES_K(1),		/* 10  attack_res    */
	RES_K(100),		/* 11  amplitude_res     */
	RES_K(47),		/* 12  feedback_res      */
	0 /* NC */,		/* 16  vco_voltage   */
	CAP_P(1000),	/* 17  vco_cap       */
	RES_K(1000),	/* 18  vco_res       */
	0 /* NC */,		/* 19  pitch_voltage     */
	RES_K(10),		/* 20  slf_res       */
	CAP_U(1),		/* 21  slf_cap       */
	CAP_U(2.2),		/* 23  oneshot_cap   */
	RES_K(150),		/* 24  oneshot_res   */
	0,			    /* 22  vco                    */
	1,			    /* 26  mixer A           */
	1,			    /* 25  mixer B           */
	0,			    /* 27  mixer C           */
	1,			    /* 1   envelope 1        */
	0,			    /* 28  envelope 2        */
	1			    /* 9   enable (variable)      */

	// ic52     GND: 2,22,27,28     +5V: 1,15,25,26
};

const sn76477_interface satansat_sn76477_intf =
{
	RES_K(470),		/*  4  noise_res     */
	RES_M(1.5),		/*  5  filter_res    */
	CAP_P(220),		/*  6  filter_cap    */
	0,				/*  7  decay_res     */
	0,				/*  8  attack_decay_cap  */
	0,				/* 10  attack_res    */
	RES_K(47),		/* 11  amplitude_res     */
	RES_K(47),		/* 12  feedback_res      */
	0,				/* 16  vco_voltage   */
	0,				/* 17  vco_cap       */
	0,				/* 18  vco_res       */
	0,				/* 19  pitch_voltage     */
	0,				/* 20  slf_res       */
	0,				/* 21  slf_cap       */
	0,				/* 23  oneshot_cap   */
	0,				/* 24  oneshot_res   */
	0,			    /* 22  vco                    */
	0,			    /* 26  mixer A           */
	1,			    /* 25  mixer B           */
	0,			    /* 27  mixer C           */
	1,			    /* 1   envelope 1        */
	1,			    /* 28  envelope 2        */
	1			    /* 9   enable (variable)      */

	// ???      GND: 2,26,27        +5V: 15,25
};

const sn76477_interface vanguard_sn76477_intf_1 =
{
	RES_K(470),		/*  4  noise_res     */
	RES_M(1.5),		/*  5  filter_res    */
	CAP_P(220),		/*  6  filter_cap    */
	0,				/*  7  decay_res     */
	0,				/*  8  attack_decay_cap  */
	0,				/* 10  attack_res    */
	RES_K(47),		/* 11  amplitude_res     */
	RES_K(4.7),		/* 12  feedback_res      */
	0,				/* 16  vco_voltage   */
	0,				/* 17  vco_cap       */
	0,				/* 18  vco_res       */
	0,				/* 19  pitch_voltage     */
	0,				/* 20  slf_res       */
	0,				/* 21  slf_cap       */
	0,				/* 23  oneshot_cap   */
	0,				/* 24  oneshot_res   */
	0,			    /* 22  vco                    */
	0,				/* 26  mixer A           */
	1,				/* 25  mixer B           */
	0,				/* 27  mixer C           */
	1,				/* 1   envelope 1        */
	1,				/* 28  envelope 2        */
	1			    /* 9   enable (variable)      */

	// SHOT A   GND: 2,9,26,27  +5V: 15,25
};

const sn76477_interface vanguard_sn76477_intf_2 =
{
	RES_K(10),		/*  4  noise_res     */
	RES_K(30),		/*  5  filter_res    */
	0,				/*  6  filter_cap    */
	0,				/*  7  decay_res     */
	0,				/*  8  attack_decay_cap  */
	0,				/* 10  attack_res    */
	RES_K(47),		/* 11  amplitude_res     */
	RES_K(4.7),		/* 12  feedback_res      */
	0,				/* 16  vco_voltage   */
	0,				/* 17  vco_cap       */
	0,				/* 18  vco_res       */
	0,				/* 19  pitch_voltage     */
	0,				/* 20  slf_res       */
	0,				/* 21  slf_cap       */
	0,				/* 23  oneshot_cap   */
	0,				/* 24  oneshot_res   */
	0,			    /* 22  vco                    */
	0,				/* 26  mixer A           */
	1,				/* 25  mixer B           */
	0,				/* 27  mixer C           */
	0,				/* 1   envelope 1        */
	1,				/* 28  envelope 2        */
	1			    /* 9   enable (variable)      */

	// SHOT B   GND: 1,2,26,27  +5V: 15,25,28
};

const sn76477_interface fantasy_sn76477_intf =
{
	RES_K(470),		/*  4  noise_res     */
	RES_M(1.5),		/*  5  filter_res    */
	CAP_P(220),		/*  6  filter_cap    */
	0,				/*  7  decay_res     */
	0,				/*  8  attack_decay_cap  */
	0,				/* 10  attack_res    */
	RES_K(470),		/* 11  amplitude_res     */
	RES_K(4.7),		/* 12  feedback_res      */
	0,				/* 16  vco_voltage   */
	0,				/* 17  vco_cap       */
	0,				/* 18  vco_res       */
	0,				/* 19  pitch_voltage     */
	0,				/* 20  slf_res       */
	0,				/* 21  slf_cap       */
	0,				/* 23  oneshot_cap   */
	0,				/* 24  oneshot_res   */
	0,			    /* 22  vco                    */
	0,			    /* 26  mixer A           */
	1,			    /* 25  mixer B           */
	0,			    /* 27  mixer C           */
	/* schematic does not show pin 1 grounded, but it must be. */
	/* otherwise it is using the VCO for the envelope, but the VCO is not hooked up */
	0,			    /* 1   envelope 1        */
	1,			    /* 28  envelope 2        */
	0			    /* 9   enable (variable)      */

	// BOMB     GND:    2,9,26,27       +5V: 15,25
};


/************************************************************************
 * fantasy Sound System Analog emulation
 * July 2008, D. Renaud
 ************************************************************************/

static const discrete_op_amp_filt_info fantasy_filter =
{
	RES_K(10.5), 0, RES_K(33), 0, RES_K(470), CAP_U(.01), CAP_U(.01), 0, 0, 12, -12
};

#define FANTASY_BOMB_EN				NODE_01
#define FANTASY_NOISE_STREAM_IN		NODE_02
#define FANTASY_NOISE_LOGIC			NODE_03

DISCRETE_SOUND_START( fantasy )

	DISCRETE_INPUT_LOGIC (FANTASY_BOMB_EN)
	DISCRETE_INPUT_STREAM(FANTASY_NOISE_STREAM_IN, 0)

	/* This is not the perfect way to discharge, but it is good enough for now */
	/* it does not take into acount that there is no discharge when noise is low */
	DISCRETE_RCDISC2(NODE_10, FANTASY_BOMB_EN, 0, RES_K(10) + RES_K(33), DEFAULT_TTL_V_LOGIC_1 - 0.5, RES_K(1), CAP_U(1))
	DISCRETE_CLAMP(FANTASY_NOISE_LOGIC, 1, FANTASY_NOISE_STREAM_IN, 0, 1, 0)
	DISCRETE_SWITCH(NODE_11, 1, FANTASY_NOISE_LOGIC, 0, NODE_10)

	DISCRETE_OP_AMP_FILTER(NODE_20, 1, NODE_11, 0, DISC_OP_AMP_FILTER_IS_BAND_PASS_1M, &fantasy_filter)
	DISCRETE_RCFILTER(NODE_21, 1, NODE_20, RES_K(22), CAP_U(.01))
	DISCRETE_RCFILTER(NODE_22, 1, NODE_21, RES_K(22) +  RES_K(22), CAP_P(2200))
	DISCRETE_RCFILTER(NODE_23, 1, NODE_22, RES_K(22) + RES_K(22) +  RES_K(22), CAP_U(.001))

	DISCRETE_OUTPUT(NODE_23, 32760.0/12)
DISCRETE_SOUND_END


INLINE void validate_tone_channel(running_machine *machine, int channel)
{
	if (!tone_channels[channel].mute)
	{
		UINT8 *ROM = memory_region(machine, "rockola");
		UINT8 romdata = ROM[tone_channels[channel].base + tone_channels[channel].offset];

		if (romdata != 0xff)
			tone_channels[channel].sample_step = tone_channels[channel].sample_rate / (256 - romdata);
		else
			tone_channels[channel].sample_step = 0;
	}
}

static STREAM_UPDATE( rockola_tone_update )
{
	stream_sample_t *buffer = outputs[0];
	int i;

	for (i = 0; i < CHANNELS; i++)
		validate_tone_channel(device->machine, i);

	while (samples-- > 0)
	{
		INT32 data = 0;

		for (i = 0; i < CHANNELS; i++)
		{
			TONE *voice = &tone_channels[i];
			INT16 *form = voice->form;

			if (!voice->mute && voice->sample_step)
			{
				int cur_pos = voice->sample_cur + voice->sample_step;
				int prev = form[(voice->sample_cur >> FRAC_BITS) & 15];
				int cur = form[(cur_pos >> FRAC_BITS) & 15];

				/* interpolate */
				data += ((INT32)prev * (FRAC_ONE - (cur_pos & FRAC_MASK))
				        + (INT32)cur * (cur_pos & FRAC_MASK)) >> FRAC_BITS;

				voice->sample_cur = cur_pos;
			}
		}

		*buffer++ = data;

		tone_clock += FRAC_ONE;
		if (tone_clock >= tone_clock_expire)
		{
			for (i = 0; i < CHANNELS; i++)
			{
				tone_channels[i].offset++;
				tone_channels[i].offset &= tone_channels[i].mask;

				validate_tone_channel(device->machine, i);
			}

			if (tone_channels[0].offset == 0 && Sound0StopOnRollover)
				tone_channels[0].mute = 1;

			tone_clock -= tone_clock_expire;
		}

	}
}


static void sasuke_build_waveform(int mask)
{
	int bit0, bit1, bit2, bit3;
	int base;
	int i;

	mask &= 7;

	//logerror("0: wave form = %d\n", mask);
	bit0 = bit1 = bit3 = 0;
	bit2 = 1;

	if (mask & 1)
		bit0 = 1;
	if (mask & 2)
		bit1 = 1;
	if (mask & 4)
		bit3 = 1;

	base = (bit0 + bit1 + bit2 + bit3 + 1) / 2;

	for (i = 0; i < 16; i++)
	{
		int data = 0;

		if (i & 1)
			data += bit0;
		if (i & 2)
			data += bit1;
		if (i & 4)
			data += bit2;
		if (i & 8)
			data += bit3;

		//logerror(" %3d\n", data);
		tone_channels[0].form[i] = data - base;
	}

	for (i = 0; i < 16; i++)
		tone_channels[0].form[i] *= 65535 / 16;
}

static void satansat_build_waveform(int mask)
{
	int bit0, bit1, bit2, bit3;
	int base;
	int i;

	mask &= 7;

	//logerror("1: wave form = %d\n", mask);
	bit0 = bit1 = bit2 = 1;
	bit3 = 0;

	if (mask & 1)
		bit3 = 1;

	base = (bit0 + bit1 + bit2 + bit3 + 1) / 2;

	for (i = 0; i < 16; i++)
	{
		int data = 0;

		if (i & 1)
			data += bit0;
		if (i & 2)
			data += bit1;
		if (i & 4)
			data += bit2;
		if (i & 8)
			data += bit3;

		//logerror(" %3d\n", data);
		tone_channels[1].form[i] = data - base;
	}

	for (i = 0; i < 16; i++)
		tone_channels[1].form[i] *= 65535 / 16;
}

static void build_waveform(int channel, int mask)
{
	int bit0, bit1, bit2, bit3;
	int base;
	int i;

	mask &= 15;

	//logerror("%d: wave form = %d\n", channel, mask);
	bit0 = bit1 = bit2 = bit3 = 0;

	// bit 3
	if (mask & (1 | 2))
		bit3 = 8;
	else if (mask & 4)
		bit3 = 4;
	else if (mask & 8)
		bit3 = 2;

	// bit 2
	if (mask & 4)
		bit2 = 8;
	else if (mask & (2 | 8))
		bit2 = 4;

	// bit 1
	if (mask & 8)
		bit1 = 8;
	else if (mask & 4)
		bit1 = 4;
	else if (mask & 2)
		bit1 = 2;

	// bit 0
	bit0 = bit1 / 2;

	if (bit0 + bit1 + bit2 + bit3 < 16)
	{
		bit0 *= 2;
		bit1 *= 2;
		bit2 *= 2;
		bit3 *= 2;
	}

	base = (bit0 + bit1 + bit2 + bit3 + 1) / 2;

	for (i = 0; i < 16; i++)
	{
		/* special channel for fantasy */
		if (channel == 2)
		{
			tone_channels[channel].form[i] = (i & 8) ? 7 : -8;
		}
		else
		{
			int data = 0;

			if (i & 1)
				data += bit0;
			if (i & 2)
				data += bit1;
			if (i & 4)
				data += bit2;
			if (i & 8)
				data += bit3;

			//logerror(" %3d\n", data);
			tone_channels[channel].form[i] = data - base;
		}
	}

	for (i = 0; i < 16; i++)
		tone_channels[channel].form[i] *= 65535 / 160;
}

void rockola_set_music_freq(int freq)
{
	int i;

	for (i = 0; i < CHANNELS; i++)
	{
		tone_channels[i].mute = 1;
		tone_channels[i].offset = 0;
		tone_channels[i].base = i * 0x800;
		tone_channels[i].mask = 0xff;
		tone_channels[i].sample_step = 0;
		tone_channels[i].sample_cur = 0;
		tone_channels[i].sample_rate = (double)(freq * 8) / SAMPLE_RATE * FRAC_ONE;

		build_waveform(i, 1);
	}
}

void rockola_set_music_clock(double clock_time)
{
	tone_clock_expire = clock_time * SAMPLE_RATE * FRAC_ONE;
	tone_clock = 0;
}

static DEVICE_START( rockola_sound )
{
	// adjusted
	rockola_set_music_freq(43000);

	// 38.99 Hz update (according to schematic)
	rockola_set_music_clock(M_LN2 * (RES_K(18) * 2 + RES_K(1)) * CAP_U(1));

	tone_stream = stream_create(device, 0, 1, SAMPLE_RATE, NULL, rockola_tone_update);
}

DEVICE_GET_INFO( rockola_sound )
{
	switch (state)
	{
		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:							info->start = DEVICE_START_NAME(rockola_sound);	break;

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

int rockola_music0_playing(void)
{
	return tone_channels[0].mute;
}


WRITE8_HANDLER( sasuke_sound_w )
{
	const device_config *samples = devtag_get_device(space->machine, "samples");
	switch (offset)
	{
	case 0:
		/*
            bit description

            0   hit (ic52)
            1   boss start (ic51)
            2   shot
            3   boss attack (ic48?)
            4   ??
            5
            6
            7   reset counter
        */

		if ((~data & 0x01) && (LastPort1 & 0x01))
			sample_start(samples, 0, 0, 0);
		if ((~data & 0x02) && (LastPort1 & 0x02))
			sample_start(samples, 1, 1, 0);
		if ((~data & 0x04) && (LastPort1 & 0x04))
			sample_start(samples, 2, 2, 0);
		if ((~data & 0x08) && (LastPort1 & 0x08))
			sample_start(samples, 3, 3, 0);

		if ((data & 0x80) && (~LastPort1 & 0x80))
		{
			tone_channels[0].offset = 0;
			tone_channels[0].mute = 0;
		}

		if ((~data & 0x80) && (LastPort1 & 0x80))
			tone_channels[0].mute = 1;

		LastPort1 = data;
		break;

	case 1:
		/*
            bit description

            0
            1   wave form
            2   wave form
            3   wave form
            4   MUSIC A8
            5   MUSIC A9
            6   MUSIC A10
            7
        */

		/* select tune in ROM based on sound command byte */
		tone_channels[0].base = 0x0000 + ((data & 0x70) << 4);
		tone_channels[0].mask = 0xff;

		Sound0StopOnRollover = 1;

		/* bit 1-3 sound0 waveform control */
		sasuke_build_waveform((data & 0x0e) >> 1);
		break;
	}
}

WRITE8_HANDLER( satansat_sound_w )
{
	const device_config *samples = devtag_get_device(space->machine, "samples");
	switch (offset)
	{
	case 0:
		/*
            bit description

        */

		/* bit 0 = analog sound trigger */

		/* bit 1 = to 76477 */

		/* bit 2 = analog sound trigger */
		if (data & 0x04 && !(LastPort1 & 0x04))
			sample_start(samples, 0, 1, 0);

		if (data & 0x08)
		{
			tone_channels[0].mute = 1;
			tone_channels[0].offset = 0;
		}

		/* bit 4-6 sound0 waveform control */
		sasuke_build_waveform((data & 0x70) >> 4);

		/* bit 7 sound1 waveform control */
		satansat_build_waveform((data & 0x80) >> 7);

		LastPort1 = data;
		break;
	case 1:
		/*
            bit description

        */

		/* select tune in ROM based on sound command byte */
		tone_channels[0].base = 0x0000 + ((data & 0x0e) << 7);
		tone_channels[0].mask = 0xff;
		tone_channels[1].base = 0x0800 + ((data & 0x60) << 4);
		tone_channels[1].mask = 0x1ff;

		Sound0StopOnRollover = 1;

		if (data & 0x01)
			tone_channels[0].mute = 0;

		if (data & 0x10)
			tone_channels[1].mute = 0;
		else
		{
			tone_channels[1].mute = 1;
			tone_channels[1].offset = 0;
		}

		/* bit 7 = ? */
		break;
	}
}

WRITE8_HANDLER( vanguard_sound_w )
{
	const device_config *samples = devtag_get_device(space->machine, "samples");
	switch (offset)
	{
	case 0:
		/*
            bit description

            0   MUSIC A10
            1   MUSIC A9
            2   MUSIC A8
            3   LS05 PORT 1
            4   LS04 PORT 2
            5   SHOT A
            6   SHOT B
            7   BOMB
        */

		/* select musical tune in ROM based on sound command byte */
		tone_channels[0].base = ((data & 0x07) << 8);
		tone_channels[0].mask = 0xff;

		Sound0StopOnRollover = 1;

		/* play noise samples requested by sound command byte */
		/* SHOT A */
		if (data & 0x20 && !(LastPort1 & 0x20))
			sample_start(samples, 1, 0, 0);
		else if (!(data & 0x20) && LastPort1 & 0x20)
			sample_stop(samples, 1);

		/* BOMB */
		if (data & 0x80 && !(LastPort1 & 0x80))
			sample_start(samples, 2, 1, 0);

		if (data & 0x08)
		{
			tone_channels[0].mute = 1;
			tone_channels[0].offset = 0;
		}

		if (data & 0x10)
		{
			tone_channels[0].mute = 0;
		}

		/* SHOT B */
		sn76477_enable_w(devtag_get_device(space->machine, "sn76477.2"), (data & 0x40) ? 0 : 1);

		LastPort1 = data;
		break;
	case 1:
		/*
            bit description

            0   MUSIC A10
            1   MUSIC A9
            2   MUSIC A8
            3   LS04 PORT 3
            4   EXTP A (HD38880 external pitch control A)
            5   EXTP B (HD38880 external pitch control B)
            6
            7
        */

		/* select tune in ROM based on sound command byte */
		tone_channels[1].base = 0x0800 + ((data & 0x07) << 8);
		tone_channels[1].mask = 0xff;

		if (data & 0x08)
			tone_channels[1].mute = 0;
		else
		{
			tone_channels[1].mute = 1;
			tone_channels[1].offset = 0;
		}
		break;
	case 2:
		/*
            bit description

            0   AS 1    (sound0 waveform)
            1   AS 2    (sound0 waveform)
            2   AS 4    (sound0 waveform)
            3   AS 3    (sound0 waveform)
            4   AS 5    (sound1 waveform)
            5   AS 6    (sound1 waveform)
            6   AS 7    (sound1 waveform)
            7   AS 8    (sound1 waveform)
        */

		build_waveform(0, (data & 0x3) | ((data & 4) << 1) | ((data & 8) >> 1));
		build_waveform(1, data >> 4);
	}
}

WRITE8_HANDLER( fantasy_sound_w )
{
	switch (offset)
	{
	case 0:
		/*
            bit description

            0   MUSIC A10
            1   MUSIC A9
            2   MUSIC A8
            3   LS04 PART 1
            4   LS04 PART 2
            5
            6
            7   BOMB
        */

		/* select musical tune in ROM based on sound command byte */
		tone_channels[0].base = 0x0000 + ((data & 0x07) << 8);
		tone_channels[0].mask = 0xff;

		Sound0StopOnRollover = 0;

		if (data & 0x08)
			tone_channels[0].mute = 0;
		else
		{
			tone_channels[0].offset = tone_channels[0].base;
			tone_channels[0].mute = 1;
		}

		if (data & 0x10)
			tone_channels[2].mute = 0;
		else
		{
			tone_channels[2].offset = 0;
			tone_channels[2].mute = 1;
		}

		/* BOMB */
		discrete_sound_w(devtag_get_device(space->machine, "discrete"), FANTASY_BOMB_EN, data & 0x80);

		LastPort1 = data;
		break;
	case 1:
		/*
            bit description

            0   MUSIC A10
            1   MUSIC A9
            2   MUSIC A8
            3   LS04 PART 3
            4   EXT PA (HD38880 external pitch control A)
            5   EXT PB (HD38880 external pitch control B)
            6
            7
        */

		/* select tune in ROM based on sound command byte */
		tone_channels[1].base = 0x0800 + ((data & 0x07) << 8);
		tone_channels[1].mask = 0xff;

		if (data & 0x08)
			tone_channels[1].mute = 0;
		else
		{
			tone_channels[1].mute = 1;
			tone_channels[1].offset = 0;
		}
		break;
	case 2:
		/*
            bit description

            0   AS 1    (sound0 waveform)
            1   AS 3    (sound0 waveform)
            2   AS 2    (sound0 waveform)
            3   AS 4    (sound0 waveform)
            4   AS 5    (sound1 waveform)
            5   AS 6    (sound1 waveform)
            6   AS 7    (sound1 waveform)
            7   AS 8    (sound1 waveform)
        */

		build_waveform(0, (data & 0x9) | ((data & 2) << 1) | ((data & 4) >> 1));
		build_waveform(1, data >> 4);
		break;
	case 3:
		/*
            bit description

            0   BC 1
            1   BC 2
            2   BC 3
            3   MUSIC A10
            4   MUSIC A9
            5   MUSIC A8
            6
            7   INV
        */

		/* select tune in ROM based on sound command byte */
		tone_channels[2].base = 0x1000 + ((data & 0x70) << 4);
		tone_channels[2].mask = 0xff;

		rockola_flipscreen_w(space, 0, data);
		break;
	}
}


/*
  Hitachi HD38880 speech synthesizer chip

    I heard that this chip uses PARCOR coefficients but I don't know ROM data format.
    How do I generate samples?
*/


/* HD38880 command */
#define	HD38880_ADSET	2
#define	HD38880_READ	3
#define	HD38880_INT1	4
#define	HD38880_INT2	6
#define	HD38880_SYSPD	8
#define	HD38880_STOP	10
#define	HD38880_CONDT	11
#define	HD38880_START	12
#define	HD38880_SSTART	14

/* HD38880 control bits */
#define HD38880_CTP	0x10
#define HD38880_CMV	0x20
#define HD68880_SYBS	0x0f

static int	hd38880_cmd;
static UINT32	hd38880_addr;
static int	hd38880_data_bytes;
static double	hd38880_speed;


static void rockola_speech_w(running_machine *machine, UINT8 data, const UINT16 *table, int start)
{
	/*
        bit description
        0   SYBS1
        1   SYBS2
        2   SYBS3
        3   SYBS4
        4   CTP
        5   CMV
        6
        7
    */

	if ((data & HD38880_CTP) && (data & HD38880_CMV))
	{
		const device_config *samples = devtag_get_device(machine, "samples");
		data &= HD68880_SYBS;

		switch (hd38880_cmd)
		{
		case 0:
			switch (data)
			{
			case HD38880_START:
				logerror("speech: START\n");

				if (hd38880_data_bytes == 5 && !sample_playing(samples, 0))
				{
					int i;

					for (i = 0; i < 16; i++)
					{
						if (table[i] && table[i] == hd38880_addr)
						{
							const device_config *samples = devtag_get_device(machine, "samples");
							sample_start(samples, 0, start + i, 0);
							break;
						}
					}
				}
				break;

			case HD38880_SSTART:
				logerror("speech: SSTART\n");
				break;

			case HD38880_STOP:
				sample_stop(samples, 0);
				logerror("speech: STOP\n");
				break;

			case HD38880_SYSPD:
				hd38880_cmd = data;
				break;

			case HD38880_CONDT:
				logerror("speech: CONDT\n");
				break;

			case HD38880_ADSET:
				hd38880_cmd = data;
				hd38880_addr = 0;
				hd38880_data_bytes = 0;
				break;

			case HD38880_READ:
				logerror("speech: READ\n");
				break;

			case HD38880_INT1:
				hd38880_cmd = data;
				break;

			case HD38880_INT2:
				hd38880_cmd = data;
				break;

			case 0:
				// ignore it
				break;

			default:
				logerror("speech: unknown command: 0x%x\n", data);
			}
			break;

		case HD38880_INT1:
			logerror("speech: INT1: 0x%x\n", data);

			if (data & 8)
				logerror("speech:   triangular waveform\n");
			else
				logerror("speech:   impulse waveform\n");

			logerror("speech:   %sable losing effect of vocal tract\n", data & 4 ? "en" : "dis");

			if ((data & 2) && (data & 8))
				logerror("speech:   use external pitch control\n");

			hd38880_cmd = 0;
			break;

		case HD38880_INT2:
			logerror("speech: INT2: 0x%x\n", data);

			logerror("speech:   %d bits / frame\n", data & 8 ? 48 : 96);
			logerror("speech:   %d ms / frame\n", data & 4 ? 20 : 10);
			logerror("speech:   %sable repeat\n", data & 2 ? "en" : "dis");
			logerror("speech:   %d operations\n", ((data & 8) == 0) || (data & 1) ? 10 : 8);

			hd38880_cmd = 0;
			break;

		case HD38880_SYSPD:
			hd38880_speed = ((double)(data + 1)) / 10.0;
			logerror("speech: SYSPD: %1.1f\n", hd38880_speed);
			hd38880_cmd = 0;
			break;

		case HD38880_ADSET:
			hd38880_addr |= (data << (hd38880_data_bytes++ * 4));
			if (hd38880_data_bytes == 5)
			{
				logerror("speech: ADSET: 0x%05x\n", hd38880_addr);
				hd38880_cmd = 0;
			}
			break;
		}
	}
}


/*
 vanguard/fantasy speech

 ROM data format (INT2 = 0xf):
  48 bits / frame
  20 ms / frame
  enable repeat
  10 operations
*/

WRITE8_HANDLER( vanguard_speech_w )
{
	static const UINT16 vanguard_table[16] =
	{
		0x04000,
		0x04325,
		0x044a2,
		0x045b7,
		0x046ee,
		0x04838,
		0x04984,
		0x04b01,
		0x04c38,
		0x04de6,
		0x04f43,
		0x05048,
		0x05160,
		0x05289,
		0x0539e,
		0x054ce
	};

	rockola_speech_w(space->machine, data, vanguard_table, 2);
}

WRITE8_HANDLER( fantasy_speech_w )
{
	static const UINT16 fantasy_table[16] =
	{
		0x04000,
		0x04297,
		0x044b6,
		0x04682,
		0x04927,
		0x04be0,
		0x04cc2,
		0x04e36,
		0x05000,
		0x05163,
		0x052c9,
		0x053fd,
		0,
		0,
		0,
		0
	};

	rockola_speech_w(space->machine, data, fantasy_table, 0);
}
