// license:BSD-3-Clause
// copyright-holders:R. Belmont, Olivier Galibert
/***************************************************************************

  Sega Z80 Digital Sound Board

  used for Model 1/2/3

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


#include "emu.h"
#include "dsbz80.h"
#include "machine/clock.h"

#define Z80_TAG "mpegcpu"

void dsbz80_device::dsbz80_map(address_map &map)
{
	map(0x0000, 0x7fff).rom().region(":mpegcpu", 0);
	map(0x8000, 0xffff).ram();
}

void dsbz80_device::dsbz80io_map(address_map &map)
{
	map.global_mask(0xff);
	map(0xe0, 0xe0).w(FUNC(dsbz80_device::mpeg_trigger_w));
	map(0xe2, 0xe4).rw(FUNC(dsbz80_device::mpeg_pos_r), FUNC(dsbz80_device::mpeg_start_w));
	map(0xe5, 0xe7).w(FUNC(dsbz80_device::mpeg_end_w));
	map(0xe8, 0xe8).w(FUNC(dsbz80_device::mpeg_volume_w));
	map(0xe9, 0xe9).w(FUNC(dsbz80_device::mpeg_stereo_w));
	map(0xf0, 0xf1).rw("uart", FUNC(i8251_device::read), FUNC(i8251_device::write));
}


//**************************************************************************
//  GLOBAL VARIABLES
//**************************************************************************

DEFINE_DEVICE_TYPE(DSBZ80, dsbz80_device, "dsbz80_device", "Sega Z80-based Digital Sound Board")


//-------------------------------------------------
//  device_add_mconfig - add device configuration
//-------------------------------------------------

void dsbz80_device::device_add_mconfig(machine_config &config)
{
	Z80(config, m_ourcpu, 4000000);     // unknown clock, but probably pretty slow considering the z80 does like nothing
	m_ourcpu->set_addrmap(AS_PROGRAM, &dsbz80_device::dsbz80_map);
	m_ourcpu->set_addrmap(AS_IO, &dsbz80_device::dsbz80io_map);

	I8251(config, m_uart, 4000000);
	m_uart->rxrdy_handler().set_inputline(m_ourcpu, INPUT_LINE_IRQ0);
	m_uart->txd_handler().set(FUNC(dsbz80_device::output_txd));

	clock_device &uart_clock(CLOCK(config, "uart_clock", 500000)); // 16 times 31.25MHz (standard Sega/MIDI sound data rate)
	uart_clock.signal_handler().set("uart", FUNC(i8251_device::write_rxc));
	uart_clock.signal_handler().append("uart", FUNC(i8251_device::write_txc));
}

//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

//-------------------------------------------------
//  dsbz80_device - constructor
//-------------------------------------------------

dsbz80_device::dsbz80_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	device_t(mconfig, DSBZ80, tag, owner, clock),
	device_sound_interface(mconfig, *this),
	m_ourcpu(*this, Z80_TAG),
	m_uart(*this, "uart"),
	m_rxd_handler(*this)
{
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void dsbz80_device::device_start()
{
	m_rxd_handler.resolve_safe();
	uint8_t *rom_base = machine().root_device().memregion("mpeg")->base();
	decoder = new mpeg_audio(rom_base, mpeg_audio::L2, false, 0);
	stream_alloc(0, 2, 32000);
}

//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void dsbz80_device::device_reset()
{
	start = end = 0;
	audio_pos = audio_avail = 0;
	memset(audio_buf, 0, sizeof(audio_buf));
	mp_state = 0;

	m_uart->write_cts(0);
}

WRITE_LINE_MEMBER(dsbz80_device::write_txd)
{
	m_uart->write_rxd(state);
}

WRITE_LINE_MEMBER(dsbz80_device::output_txd)
{
	// not used by swa
	m_rxd_handler(state);
}

void dsbz80_device::mpeg_trigger_w(uint8_t data)
{
	mp_state = data;

	if (data == 0)  // stop
	{
		mp_state = 0;
		audio_pos = audio_avail = 0;
	}
	else if (data == 1) // play without loop
	{
		mp_pos = mp_start*8;
	}
	else if (data == 2) // play with loop
	{
		mp_pos = mp_start*8;
	}
}

uint8_t dsbz80_device::mpeg_pos_r(offs_t offset)
{
	int mp_prg = mp_pos >> 3;

	switch (offset)
	{
		case 0:
			return (mp_prg>>16)&0xff;
		case 1:
			return (mp_prg>>8)&0xff;
		case 2:
			return mp_prg&0xff;
	}

	return 0;
}

/* NOTE: writes to the start and end while playback is already in progress
   get latched.  When the current stream ends, the MPEG hardware starts playing
   immediately from the latched start and end position.  In this way, the Z80
   enforces looping where appropriate and multi-part songs in other cases
   (song #16 is a good example)
*/

void dsbz80_device::mpeg_start_w(offs_t offset, uint8_t data)
{
	switch (offset)
	{
		case 0:
			start &= 0x00ffff;
			start |= (int)data<<16;
			break;
		case 1:
			start &= 0xff00ff;
			start |= (int)data<<8;
			break;
		case 2:
			start &= 0xffff00;
			start |= data;

			if (mp_state == 0)
			{
				mp_start = start;
			}
			else
			{
				lp_start = start;
				// SWA: if loop end is zero, it means "keep previous end marker"
				if (lp_end == 0)
				{
//                  MPEG_Set_Loop(ROM + lp_start, mp_end-lp_start);
				}
				else
				{
//                  MPEG_Set_Loop(ROM + lp_start, lp_end-lp_start);
				}
			}
			break;
	}
}

void dsbz80_device::mpeg_end_w(offs_t offset, uint8_t data)
{
	switch (offset)
	{
		case 0:
			end &= 0x00ffff;
			end |= (int)data<<16;
			break;
		case 1:
			end &= 0xff00ff;
			end |= (int)data<<8;
			break;
		case 2:
			end &= 0xffff00;
			end |= data;

			if (mp_state == 0)
			{
				mp_end = end;
			}
			else
			{
				lp_end = end;
//              MPEG_Set_Loop(ROM + lp_start, lp_end-lp_start);
			}
			break;
	}
}

void dsbz80_device::mpeg_volume_w(uint8_t data)
{
	mp_vol = 0x7f - data;
}

void dsbz80_device::mpeg_stereo_w(uint8_t data)
{
	mp_pan = data & 3;  // 0 = stereo, 1 = left on both channels, 2 = right on both channels
}

void dsbz80_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
{
	auto &out_l = outputs[0];
	auto &out_r = outputs[1];

	int samples = out_l.samples();
	int sampindex = 0;
	for(;;)
	{
		while(samples && audio_pos < audio_avail)
		{
			switch (mp_pan)
			{
				case 0: // stereo
					out_l.put_int(sampindex, audio_buf[audio_pos*2], 32768);
					out_r.put_int(sampindex, audio_buf[audio_pos*2+1], 32768);
					sampindex++;
					break;

				case 1: // left only
					out_l.put_int(sampindex, audio_buf[audio_pos*2], 32768);
					out_r.put_int(sampindex, audio_buf[audio_pos*2], 32768);
					sampindex++;
					break;

				case 2: // right only
					out_l.put_int(sampindex, audio_buf[audio_pos*2+1], 32768);
					out_r.put_int(sampindex, audio_buf[audio_pos*2+1], 32768);
					sampindex++;
					break;
			}
			audio_pos++;
			samples--;
		}

		if(!samples)
		{
			break;
		}

		if(mp_state == 0)
		{
			out_l.fill(0, sampindex);
			out_r.fill(0, sampindex);
			break;

		}
		else
		{
			int sample_rate, channel_count;
			bool ok = decoder->decode_buffer(mp_pos, mp_end*8, audio_buf, audio_avail, sample_rate, channel_count);

			if (ok)
			{
				audio_pos = 0;
			}
			else
			{
				if(mp_state == 2)
				{
					if (mp_pos == lp_start*8)
					{
						// We're looping on un-decodable crap, abort abort abort
						mp_state = 0;
					}
					mp_pos = lp_start*8;

					if (lp_end)
					{
						mp_end = lp_end;
					}
				}
				else
				{
					mp_state = 0;
				}
			}
		}
	}
}
