summaryrefslogblamecommitdiffstatshomepage
path: root/src/mame/audio/segausb.cpp
blob: dc87b73ae8c730ab17551b516b55598d5e86362b (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14













                                                                            























































































                                                                                                                                
                                                      































                                                            















                                                                            






















































                                                                                                                

                                               




















































































































































































































































                                                                                                                                     
                                                

                                                   
                                                                                                                                                      
 
                                

                               
                                                                        

































































































































                                                                                                                                             
                                                                          


































































































































































































































































                                                                                                                          
// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************

    segausb.cpp

    Sega Universal Sound Board.

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

#include "emu.h"
#include "segausb.h"
#include "nl_segausb.h"

#include <cmath>


#define VERBOSE 0
#include "logmacro.h"


/***************************************************************************
    CONSTANTS
***************************************************************************/

#define USB_MASTER_CLOCK    6000000
#define USB_2MHZ_CLOCK      (USB_MASTER_CLOCK/3)
#define USB_PCS_CLOCK       (USB_2MHZ_CLOCK/2)
#define USB_GOS_CLOCK       (USB_2MHZ_CLOCK/16/4)
#define MM5837_CLOCK        100000


/***************************************************************************
    UNIVERSAL SOUND BOARD
***************************************************************************/

DEFINE_DEVICE_TYPE(SEGAUSB, usb_sound_device, "segausb", "Sega Universal Sound Board")

usb_sound_device::usb_sound_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock)
	: device_t(mconfig, type, tag, owner, clock),
		device_mixer_interface(mconfig, *this),
		m_in_latch(0),
		m_out_latch(0),
		m_last_p2_value(0),
		m_program_ram(*this, "pgmram"),
		m_work_ram(*this, "workram"),
		m_work_ram_bank(0),
		m_t1_clock(0),
		m_t1_clock_mask(0),
		m_ourcpu(*this, "ourcpu"),
		m_maincpu(*this, finder_base::DUMMY_TAG),
#if (ENABLE_SEGAUSB_NETLIST)
		m_pit(*this, "pit_%u", 0),
		m_nl_dac0(*this, "sound_nl:dac0_%u", 0),
		m_nl_sel0(*this, "sound_nl:sel0"),
		m_nl_pit0_out(*this, "sound_nl:pit0_out%u", 0),
		m_nl_dac1(*this, "sound_nl:dac1_%u", 0),
		m_nl_sel1(*this, "sound_nl:sel1"),
		m_nl_pit1_out(*this, "sound_nl:pit1_out%u", 0),
		m_nl_dac2(*this, "sound_nl:dac2_%u", 0),
		m_nl_sel2(*this, "sound_nl:sel2"),
		m_nl_pit2_out(*this, "sound_nl:pit2_out%u", 0),
		m_gos_clock(0)
#else
		m_stream(nullptr),
		m_noise_shift(0),
		m_noise_state(0),
		m_noise_subcount(0)
#endif
{
}

usb_sound_device::usb_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
	: usb_sound_device(mconfig, SEGAUSB, tag, owner, clock)
{
}

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

void usb_sound_device::device_start()
{
	// register for save states
	save_item(NAME(m_in_latch));
	save_item(NAME(m_out_latch));
	save_item(NAME(m_last_p2_value));
	save_item(NAME(m_work_ram_bank));
	save_item(NAME(m_t1_clock));

#if (ENABLE_SEGAUSB_NETLIST)

	for (int index = 0; index < 3; index++)
	{
		m_pit[index]->write_gate0(1);
		m_pit[index]->write_gate1(1);
		m_pit[index]->write_gate2(1);
	}
	save_item(NAME(m_gos_clock));

#else

	m_stream = stream_alloc(0, 1, USB_2MHZ_CLOCK);

	m_noise_shift = 0x15555;

	for (timer8253 &group : m_timer_group)
	{
		group.chan_filter[0].configure(10e3, 1e-6);
		group.chan_filter[1].configure(10e3, 1e-6);
		group.gate1.configure(100e3, 0.01e-6);
		group.gate2.configure(2 * 100e3, 0.01e-6);
	}

	g80_filter_state temp;
	temp.configure(100e3, 0.01e-6);
	m_gate_rc1_exp[0] = temp.exponent;
	temp.configure(1e3, 0.01e-6);
	m_gate_rc1_exp[1] = temp.exponent;
	temp.configure(2 * 100e3, 0.01e-6);
	m_gate_rc2_exp[0] = temp.exponent;
	temp.configure(2 * 1e3, 0.01e-6);
	m_gate_rc2_exp[1] = temp.exponent;

	m_noise_filters[0].configure(2.7e3 + 2.7e3, 1.0e-6);
	m_noise_filters[1].configure(2.7e3 + 1e3, 0.30e-6);
	m_noise_filters[2].configure(2.7e3 + 270, 0.15e-6);
	m_noise_filters[3].configure(2.7e3 + 0, 0.082e-6);
	m_noise_filters[4].configure(33e3, 0.1e-6);

	m_final_filter.configure(100e3, 4.7e-6);

	for (int tgroup = 0; tgroup < 3; tgroup++)
	{
		timer8253 &group = m_timer_group[tgroup];
		save_item(STRUCT_MEMBER(group.chan, holding), tgroup);
		save_item(STRUCT_MEMBER(group.chan, latchmode), tgroup);
		save_item(STRUCT_MEMBER(group.chan, latchtoggle), tgroup);
		save_item(STRUCT_MEMBER(group.chan, clockmode), tgroup);
		save_item(STRUCT_MEMBER(group.chan, bcdmode), tgroup);
		save_item(STRUCT_MEMBER(group.chan, output), tgroup);
		save_item(STRUCT_MEMBER(group.chan, lastgate), tgroup);
		save_item(STRUCT_MEMBER(group.chan, gate), tgroup);
		save_item(STRUCT_MEMBER(group.chan, subcount), tgroup);
		save_item(STRUCT_MEMBER(group.chan, count), tgroup);
		save_item(STRUCT_MEMBER(group.chan, remain), tgroup);
		save_item(NAME(group.env), tgroup);
		save_item(STRUCT_MEMBER(group.chan_filter, capval), tgroup);
		save_item(NAME(group.gate1.capval), tgroup);
		save_item(NAME(group.gate2.capval), tgroup);
		save_item(NAME(group.config), tgroup);
	}

	save_item(NAME(m_timer_mode));
	save_item(NAME(m_noise_shift));
	save_item(NAME(m_noise_state));
	save_item(NAME(m_noise_subcount));
	save_item(NAME(m_final_filter.capval));
	save_item(NAME(m_noise_filters[0].capval));
	save_item(NAME(m_noise_filters[1].capval));
	save_item(NAME(m_noise_filters[2].capval));
	save_item(NAME(m_noise_filters[3].capval));
	save_item(NAME(m_noise_filters[4].capval));

#endif
}


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

void usb_sound_device::device_reset()
{
	// halt the USB CPU at reset time
	m_ourcpu->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);

	// start the clock timer
	m_t1_clock_mask = 0x10;
}


/*************************************
 *
 *  Initialization/reset
 *
 *************************************/

TIMER_DEVICE_CALLBACK_MEMBER( usb_sound_device::increment_t1_clock_timer_cb )
{
	// only increment if it is not being forced clear
	if (!(m_last_p2_value & 0x80))
		m_t1_clock++;
}


/*************************************
 *
 *  External access
 *
 *************************************/

u8 usb_sound_device::status_r()
{
	LOG("%s:usb_data_r = %02X\n", machine().describe_context(), (m_out_latch & 0x81) | (m_in_latch & 0x7e));

	if (!machine().side_effects_disabled())
		m_maincpu->adjust_icount(-200);

	// only bits 0 and 7 are controlled by the I8035; the remaining
	// bits 1-6 reflect the current input latch values
	return (m_out_latch & 0x81) | (m_in_latch & 0x7e);
}


TIMER_CALLBACK_MEMBER( usb_sound_device::delayed_usb_data_w )
{
	// look for rising/falling edges of bit 7 to control the RESET line
	int data = param;
	m_ourcpu->set_input_line(INPUT_LINE_RESET, (data & 0x80) ? ASSERT_LINE : CLEAR_LINE);

	// if the CLEAR line is set, the low 7 bits of the input are ignored
	if ((m_last_p2_value & 0x40) == 0)
		data &= ~0x7f;

	// update the effective input latch
	m_in_latch = data;
}


void usb_sound_device::data_w(u8 data)
{
	LOG("%s:usb_data_w = %02X\n", machine().describe_context(), data);
	machine().scheduler().synchronize(timer_expired_delegate(FUNC(usb_sound_device::delayed_usb_data_w), this), data);

	// boost the interleave so that sequences can be sent
	machine().scheduler().boost_interleave(attotime::zero, attotime::from_usec(250));
}


u8 usb_sound_device::ram_r(offs_t offset)
{
	return m_program_ram[offset];
}


void usb_sound_device::ram_w(offs_t offset, u8 data)
{
	if (m_in_latch & 0x80)
		m_program_ram[offset] = data;
	else
		LOG("%s:sega_usb_ram_w(%03X) = %02X while /LOAD disabled\n", machine().describe_context(), offset, data);
}



/*************************************
 *
 *  I8035 port accesses
 *
 *************************************/

u8 usb_sound_device::p1_r()
{
	// bits 0-6 are inputs and map to bits 0-6 of the input latch
	if ((m_in_latch & 0x7f) != 0)
		LOG("%s: P1 read = %02X\n", machine().describe_context(), m_in_latch & 0x7f);
	return m_in_latch & 0x7f;
}


void usb_sound_device::p1_w(u8 data)
{
	// bit 7 maps to bit 0 on the output latch
	m_out_latch = (m_out_latch & 0xfe) | (data >> 7);
	LOG("%s: P1 write = %02X\n", machine().describe_context(), data);
}


void usb_sound_device::p2_w(u8 data)
{
	u8 old = m_last_p2_value;
	m_last_p2_value = data;

	// low 2 bits control the bank of work RAM we are addressing
	m_work_ram_bank = data & 3;

	// bit 6 controls the "ready" bit output to the host
	// it also clears the input latch from the host (active low)
	m_out_latch = ((data & 0x40) << 1) | (m_out_latch & 0x7f);
	if ((data & 0x40) == 0)
		m_in_latch = 0;

	// bit 7 controls the reset on the upper counter at U33
	if ((old & 0x80) && !(data & 0x80))
		m_t1_clock = 0;

	LOG("%s: P2 write -> bank=%d ready=%d clock=%d\n", machine().describe_context(), data & 3, (data >> 6) & 1, (data >> 7) & 1);
}


READ_LINE_MEMBER( usb_sound_device::t1_r )
{
	// T1 returns 1 based on the value of the T1 clock; the exact
	// pattern is determined by one or more jumpers on the board.
	return (m_t1_clock & m_t1_clock_mask) != 0;
}



/*************************************
 *
 *  Sound generation
 *
 *************************************/

#if (!ENABLE_SEGAUSB_NETLIST)

inline void usb_sound_device::g80_filter_state::configure(double r, double c)
{
	capval = 0.0;
	exponent = 1.0 - std::exp(-1.0 / (r * c * USB_2MHZ_CLOCK));
}

inline void usb_sound_device::timer8253::channel::clock()
{
	u8 const old_lastgate = lastgate;

	// update the gate
	lastgate = gate;

	// if we're holding, skip
	if (holding)
		return;

	// switch off the clock mode
	switch (clockmode)
	{
		// oneshot; waits for trigger to restart
		case 1:
			if (!old_lastgate && gate)
			{
				output = 0;
				remain = count;
			}
			else
			{
				if (--remain == 0)
					output = 1;
			}
			break;

		// square wave: counts down by 2 and toggles output
		case 3:
			remain = (remain - 1) & ~1;
			if (remain == 0)
			{
				output ^= 1;
				remain = count;
			}
			break;
	}
}


/*************************************
 *
 *  USB timer and envelope controls
 *
 *************************************/

void usb_sound_device::timer_w(int which, u8 offset, u8 data)
{
	timer8253 &group = m_timer_group[which];

	m_stream->update();

	// switch off the offset
	switch (offset)
	{
		case 0:
		case 1:
		case 2:
		{
			timer8253::channel &ch = group.chan[offset];
			bool was_holding = ch.holding;

			// based on the latching mode
			switch (ch.latchmode)
			{
				case 1: // low word only
					ch.count = data;
					ch.holding = false;
					break;

				case 2: // high word only
					ch.count = data << 8;
					ch.holding = false;
					break;

				case 3: // low word followed by high word
					if (ch.latchtoggle == 0)
					{
						ch.count = (ch.count & 0xff00) | (data & 0x00ff);
						ch.latchtoggle = 1;
					}
					else
					{
						ch.count = (ch.count & 0x00ff) | (data << 8);
						ch.holding = false;
						ch.latchtoggle = 0;
					}
					break;
			}

			// if we're not holding, load the initial count for some modes
			if (was_holding && !ch.holding)
				ch.remain = 1;
			break;
		}

		case 3:
			// break out the components
			if (((data & 0xc0) >> 6) < 3)
			{
				timer8253::channel &ch = group.chan[(data & 0xc0) >> 6];

				// extract the bits
				ch.holding = true;
				ch.latchmode = (data >> 4) & 3;
				ch.clockmode = (data >> 1) & 7;
				ch.bcdmode = (data >> 0) & 1;
				ch.latchtoggle = 0;
				ch.output = (ch.clockmode == 1);
			}
			break;
	}
}


void usb_sound_device::env_w(int which, u8 offset, u8 data)
{
	timer8253 &group = m_timer_group[which];

	m_stream->update();

	if (offset < 3)
		group.env[offset] = double(data);
	else
		group.config = data & 1;
}

//-------------------------------------------------
//  sound_stream_update - handle a stream update
//-------------------------------------------------

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

	// iterate over samples
	for (int sampindex = 0; sampindex < dest.samples(); sampindex++)
	{
		/*----------------
		    Noise Source
		  ----------------

		                 RC
		   MM5837 ---> FILTER ---> CR FILTER ---> 3.2x AMP ---> NOISE
		               LADDER
		*/

		// update the noise source
		if (m_noise_subcount-- == 0)
		{
			m_noise_shift = (m_noise_shift << 1) | (((m_noise_shift >> 13) ^ (m_noise_shift >> 16)) & 1);
			m_noise_state = (m_noise_shift >> 16) & 1;
			m_noise_subcount += USB_2MHZ_CLOCK / MM5837_CLOCK;
		}

		// update the filtered noise value -- this is just an approximation to the pink noise filter
		// being applied on the PCB, but it sounds pretty close
		m_noise_filters[0].capval = 0.99765 * m_noise_filters[0].capval + m_noise_state * 0.0990460;
		m_noise_filters[1].capval = 0.96300 * m_noise_filters[1].capval + m_noise_state * 0.2965164;
		m_noise_filters[2].capval = 0.57000 * m_noise_filters[2].capval + m_noise_state * 1.0526913;
		double noiseval = m_noise_filters[0].capval + m_noise_filters[1].capval + m_noise_filters[2].capval + m_noise_state * 0.1848;

		// final output goes through a CR filter; the scaling factor is arbitrary to get the noise to the
		// correct relative volume
		noiseval = m_noise_filters[4].step_cr(noiseval);
		noiseval *= 0.075;

		// there are 3 identical groups of circuits, each with its own 8253
		double sample = 0;
		for (int groupnum = 0; groupnum < 3; groupnum++)
		{
			timer8253 &group = m_timer_group[groupnum];

			/*-------------
			    Channel 0
			  -------------

			    8253        CR                   AD7524
			    OUT0 ---> FILTER ---> BUFFER--->  VRef  ---> 100k ---> mix
			*/

			// channel 0 clocks with the PCS clock
			if (group.chan[0].subcount-- == 0)
			{
				group.chan[0].subcount += USB_2MHZ_CLOCK / USB_PCS_CLOCK;
				group.chan[0].gate = 1;
				group.chan[0].clock();
			}

			// channel 0 is mixed in with a resistance of 100k
			double chan0 = group.chan_filter[0].step_cr(group.chan[0].output) * group.env[0] * (1.0/100.0);

			/*-------------
			    Channel 1
			  -------------

			    8253        CR                   AD7524
			    OUT1 ---> FILTER ---> BUFFER--->  VRef  ---> 100k ---> mix
			*/

			// channel 1 clocks with the PCS clock
			if (group.chan[1].subcount-- == 0)
			{
				group.chan[1].subcount += USB_2MHZ_CLOCK / USB_PCS_CLOCK;
				group.chan[1].gate = 1;
				group.chan[1].clock();
			}

			// channel 1 is mixed in with a resistance of 100k
			double chan1 = group.chan_filter[1].step_cr(group.chan[1].output) * group.env[1] * (1.0/100.0);

			/*-------------
			    Channel 2
			  -------------

			  If timer_mode == 0:

			               SWITCHED                                  AD7524
			    NOISE --->    RC   ---> 1.56x AMP ---> INVERTER --->  VRef ---> 33k ---> mix
			                FILTERS

			  If timer mode == 1:

			                             AD7524                                    SWITCHED
			    NOISE ---> INVERTER --->  VRef ---> 33k ---> mix ---> INVERTER --->   RC   ---> 1.56x AMP ---> finalmix
			                                                                        FILTERS
			*/

			// channel 2 clocks with the 2MHZ clock and triggers with the GOS clock
			if (group.chan[2].subcount-- == 0)
			{
				group.chan[2].subcount += USB_2MHZ_CLOCK / USB_GOS_CLOCK / 2;
				group.chan[2].gate = !group.chan[2].gate;
			}
			group.chan[2].clock();

			// the exponents for the gate filters are determined by channel 2's output
			group.gate1.exponent = m_gate_rc1_exp[group.chan[2].output];
			group.gate2.exponent = m_gate_rc2_exp[group.chan[2].output];

			// based on the envelope mode, we do one of two things with source 2
			double chan2, mix;
			if (group.config == 0)
			{
				chan2 = group.gate2.step_rc(group.gate1.step_rc(noiseval)) * -1.56 * group.env[2] * (1.0/33.0);
				mix = chan0 + chan1 + chan2;
			}
			else
			{
				chan2 = -noiseval * group.env[2] * (1.0/33.0);
				mix = chan0 + chan1 + chan2;
				mix = group.gate2.step_rc(group.gate1.step_rc(-mix)) * 1.56;
			}

			// accumulate the sample
			sample += mix;
		}

		/*-------------
		    Final mix
		  -------------

		  INPUTS
		  EQUAL ---> 1.2x INVERTER ---> CR FILTER ---> out
		  WEIGHT

		*/
		dest.put(sampindex, 0.1 * m_final_filter.step_cr(sample));
	}
}

#endif



/*************************************
 *
 *  USB work RAM access
 *
 *************************************/

u8 usb_sound_device::workram_r(offs_t offset)
{
	offset += 256 * m_work_ram_bank;
	return m_work_ram[offset];
}


void usb_sound_device::workram_w(offs_t offset, u8 data)
{
	offset += 256 * m_work_ram_bank;
	m_work_ram[offset] = data;

	// writes to the low 32 bytes go to various controls
#if (ENABLE_SEGAUSB_NETLIST)

	switch (offset)
	{
		case 0x00:  // 8253 U41
		case 0x01:  // 8253 U41
		case 0x02:  // 8253 U41
		case 0x03:  // 8253 U41
			if ((offset & 3) != 3)
				printf("%s: 2.%d count=%d\n", machine().scheduler().time().as_string(), offset & 3, data);
			m_pit[0]->write(offset & 3, data);
			break;

		case 0x04:  // ENV0 U26
		case 0x05:  // ENV0 U25
		case 0x06:  // ENV0 U24
			m_nl_dac0[offset & 3]->write(double(data) / 255.0);
			break;

		case 0x07:  // ENV0 U38B
			m_nl_sel0->write(data & 1);
			break;

		case 0x08:  // 8253 U42
		case 0x09:  // 8253 U42
		case 0x0a:  // 8253 U42
		case 0x0b:  // 8253 U42
			if ((offset & 3) != 3)
				printf("%s: 2.%d count=%d\n", machine().scheduler().time().as_string(), offset & 3, data);
			m_pit[1]->write(offset & 3, data);
			break;

		case 0x0c:  // ENV1 U12
		case 0x0d:  // ENV1 U13
		case 0x0e:  // ENV1 U14
			m_nl_dac1[offset & 3]->write(double(data) / 255.0);
			break;

		case 0x0f:  // ENV1 U2B
			m_nl_sel1->write(data & 1);
			break;

		case 0x10:  // 8253 U43
		case 0x11:  // 8253 U43
		case 0x12:  // 8253 U43
		case 0x13:  // 8253 U43
			if ((offset & 3) != 3)
				printf("%s: 2.%d count=%d\n", machine().scheduler().time().as_string(), offset & 3, data);
			m_pit[2]->write(offset & 3, data);
			break;

		case 0x14:  // ENV2 U27
		case 0x15:  // ENV2 U28
		case 0x16:  // ENV2 U29
			m_nl_dac2[offset & 3]->write(double(data) / 255.0);
			break;

		case 0x17:  // ENV2 U38B
			m_nl_sel2->write(data & 1);
			break;
	}

#else

	switch (offset & ~3)
	{
		case 0x00:  // CTC0
			timer_w(0, offset & 3, data);
			break;

		case 0x04:  // ENV0
			env_w(0, offset & 3, data);
			break;

		case 0x08:  // CTC1
			timer_w(1, offset & 3, data);
			break;

		case 0x0c:  // ENV1
			env_w(1, offset & 3, data);
			break;

		case 0x10:  // CTC2
			timer_w(2, offset & 3, data);
			break;

		case 0x14:  // ENV2
			env_w(2, offset & 3, data);
			break;
	}

#endif
}



/*************************************
 *
 *  USB address maps
 *
 *************************************/

void usb_sound_device::usb_map(address_map &map)
{
	map(0x0000, 0x0fff).ram().share("pgmram");
}

void usb_sound_device::usb_portmap(address_map &map)
{
	map(0x00, 0xff).rw(FUNC(usb_sound_device::workram_r), FUNC(usb_sound_device::workram_w)).share("workram");
}


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

#if (ENABLE_SEGAUSB_NETLIST)

TIMER_DEVICE_CALLBACK_MEMBER( usb_sound_device::gos_timer )
{
	// technically we should clock at 2x and toggle between states
	// however, in practice this is just used to trigger a oneshot
	// so we can halve the high frequency rate and just toggle both
	// ways to initiate the countdown
	m_pit[0]->write_gate2(0);
	m_pit[1]->write_gate2(0);
	m_pit[2]->write_gate2(0);
	m_pit[0]->write_gate2(1);
	m_pit[1]->write_gate2(1);
	m_pit[2]->write_gate2(1);
}

#endif


void usb_sound_device::device_add_mconfig(machine_config &config)
{
	// CPU for the usb board
	I8035(config, m_ourcpu, USB_MASTER_CLOCK);     // divide by 15 in CPU
	m_ourcpu->set_addrmap(AS_PROGRAM, &usb_sound_device::usb_map);
	m_ourcpu->set_addrmap(AS_IO, &usb_sound_device::usb_portmap);
	m_ourcpu->p1_in_cb().set(FUNC(usb_sound_device::p1_r));
	m_ourcpu->p1_out_cb().set(FUNC(usb_sound_device::p1_w));
	m_ourcpu->p2_out_cb().set(FUNC(usb_sound_device::p2_w));
	m_ourcpu->t1_in_cb().set(FUNC(usb_sound_device::t1_r));

	TIMER(config, "usb_timer", 0).configure_periodic(
			FUNC(usb_sound_device::increment_t1_clock_timer_cb),
			attotime::from_hz(USB_2MHZ_CLOCK / 256));

#if (ENABLE_SEGAUSB_NETLIST)

	TIMER(config, "gos_timer", 0).configure_periodic(
			FUNC(usb_sound_device::gos_timer), attotime::from_hz(USB_GOS_CLOCK));

	NETLIST_SOUND(config, "sound_nl", 48000)
		.set_source(NETLIST_NAME(segausb))
		.add_route(ALL_OUTPUTS, *this, 1.0);

	// channel 0 inputs
	NETLIST_ANALOG_INPUT(config, m_nl_dac0[0], "I_U26_DAC.IN");
	NETLIST_ANALOG_INPUT(config, m_nl_dac0[1], "I_U25_DAC.IN");
	NETLIST_ANALOG_INPUT(config, m_nl_dac0[2], "I_U24_DAC.IN");
	NETLIST_LOGIC_INPUT(config, m_nl_sel0, "I_U38B_SEL.IN", 0);
	NETLIST_LOGIC_INPUT(config, m_nl_pit0_out[0], "I_U41_OUT0.IN", 0);
	NETLIST_LOGIC_INPUT(config, m_nl_pit0_out[1], "I_U41_OUT1.IN", 0);
	NETLIST_LOGIC_INPUT(config, m_nl_pit0_out[2], "I_U41_OUT2.IN", 0);

	// channel 1 inputs
	NETLIST_ANALOG_INPUT(config, m_nl_dac1[0], "I_U12_DAC.IN");
	NETLIST_ANALOG_INPUT(config, m_nl_dac1[1], "I_U13_DAC.IN");
	NETLIST_ANALOG_INPUT(config, m_nl_dac1[2], "I_U14_DAC.IN");
	NETLIST_LOGIC_INPUT(config, m_nl_sel1, "I_U2B_SEL.IN", 0);
	NETLIST_LOGIC_INPUT(config, m_nl_pit1_out[0], "I_U42_OUT0.IN", 0);
	NETLIST_LOGIC_INPUT(config, m_nl_pit1_out[1], "I_U42_OUT1.IN", 0);
	NETLIST_LOGIC_INPUT(config, m_nl_pit1_out[2], "I_U42_OUT2.IN", 0);

	// channel 2 inputs
	NETLIST_ANALOG_INPUT(config, m_nl_dac2[0], "I_U27_DAC.IN");
	NETLIST_ANALOG_INPUT(config, m_nl_dac2[1], "I_U28_DAC.IN");
	NETLIST_ANALOG_INPUT(config, m_nl_dac2[2], "I_U29_DAC.IN");
	NETLIST_LOGIC_INPUT(config, m_nl_sel2, "I_U2A_SEL.IN", 0);
	NETLIST_LOGIC_INPUT(config, m_nl_pit2_out[0], "I_U43_OUT0.IN", 0);
	NETLIST_LOGIC_INPUT(config, m_nl_pit2_out[1], "I_U43_OUT1.IN", 0);
	NETLIST_LOGIC_INPUT(config, m_nl_pit2_out[2], "I_U43_OUT2.IN", 0);

	// final output
	NETLIST_STREAM_OUTPUT(config, "sound_nl:cout0", 0, "OUTPUT").set_mult_offset(5000.0, 0.0);

	// configure the PIT clocks and gates
	for (int index = 0; index < 3; index++)
	{
		PIT8253(config, m_pit[index], 0);
		m_pit[index]->set_clk<0>(USB_PCS_CLOCK);
		m_pit[index]->set_clk<1>(USB_PCS_CLOCK);
		m_pit[index]->set_clk<2>(USB_2MHZ_CLOCK);
	}

	// connect the PIT outputs to the netlist
	m_pit[0]->out_handler<0>().set(m_nl_pit0_out[0], FUNC(netlist_mame_logic_input_device::write_line));
	m_pit[0]->out_handler<1>().set(m_nl_pit0_out[1], FUNC(netlist_mame_logic_input_device::write_line));
	m_pit[0]->out_handler<2>().set(m_nl_pit0_out[2], FUNC(netlist_mame_logic_input_device::write_line));
	m_pit[1]->out_handler<0>().set(m_nl_pit1_out[0], FUNC(netlist_mame_logic_input_device::write_line));
	m_pit[1]->out_handler<1>().set(m_nl_pit1_out[1], FUNC(netlist_mame_logic_input_device::write_line));
	m_pit[1]->out_handler<2>().set(m_nl_pit1_out[2], FUNC(netlist_mame_logic_input_device::write_line));
	m_pit[2]->out_handler<0>().set(m_nl_pit2_out[0], FUNC(netlist_mame_logic_input_device::write_line));
	m_pit[2]->out_handler<1>().set(m_nl_pit2_out[1], FUNC(netlist_mame_logic_input_device::write_line));
	m_pit[2]->out_handler<2>().set(m_nl_pit2_out[2], FUNC(netlist_mame_logic_input_device::write_line));

#endif
}


DEFINE_DEVICE_TYPE(SEGAUSBROM, usb_rom_sound_device, "segausbrom", "Sega Universal Sound Board with ROM")

usb_rom_sound_device::usb_rom_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
	: usb_sound_device(mconfig, SEGAUSBROM, tag, owner, clock)
{
}

void usb_sound_device::usb_map_rom(address_map &map)
{
	map(0x0000, 0x0fff).rom().region(":usbcpu", 0);
}

void usb_rom_sound_device::device_add_mconfig(machine_config &config)
{
	usb_sound_device::device_add_mconfig(config);

	// CPU for the usb board
	m_ourcpu->set_addrmap(AS_PROGRAM, &usb_rom_sound_device::usb_map_rom);
}