// license:BSD-3-Clause
// copyright-holders:Acho A. Tang, Nicola Salmoria
/***************************************************************************
Functions to emulate the Alpha Denshi "59MC07" audio board
|----------------------|
| Fully boxed = socket |
|----------------------|
| separation = solder
-----------------------------------------------------------------------|
|ALPHA DENSHI CO, LTD. SOUND BOARD NO.59MC07 |
| |
| |--------------------| |
| P | \/ | JAPAN 84250C | SN74LS232J | SN74LS74AN | DISSIPATOR |
| | /\ | M5l8085AP | 8131BJ | 8314A | |
| |--------------------| |
| |
| N | SN74LS74A |
| | 8122AG |
| |
| |------------| |
| M | HV1VR OKI | SN74LS138J | SN74LS74AJ | LM324N | LM324N |
| | 2764-45 | 8044AG | F8048 | J423A2 | 98509 |- (+12V)
| |------------| |
| |- (+5V)
| |------------| |
| L | HV2VR OKI | T74LS14B1 | SN74LS08N |- (OUT)
| | 2764-45 | 88442 | K8208 |
| |------------| |- (
| | (GND)
| |------------| |- (
| K | HV3VR OKI | SN74LS08J | SN74LS04N | LM324N |
| | 2764-45 | K8208 | I8313 | 98509 |
| |------------| |
| |
| J | SN74LS74A | SN74LS232J | | LM324N |
| | 8122AG | 8131BG | | 436A |
| |
| |------------| |------------| |
| H | HV4VR OKI | SN74LS393N | TBP18S030N | | LM324N |
| | 2764-45 | K8208 | J419X | | 436A |
| |------------| |------------| |
| |
| F | SN74LS138J | HCF40174BE | | CD4066BCN |
| | 8044AG | MSM40174 | | MM5666BN |
| |
| E | M74LS123P | SN74LS138J | CD4066BCN | CD4066BCN |
| | 84A6C1 | 8044AG | MM5666BN | MM5666BN |
| MUSIC |
| |-------------------------| |
| D | SN74LS373N | JAPAN 841903 | | CD4066BCN |
| | 2764-45 | M5L8155P | | MM5666BN |
| |-------------------------| VOICE |
| |
|C |-------------------------| |
|O C | 74LS173 PC | MSM5232RS | |
|N | 8247 | OKI 4342 | |
|N |-------------------------| |
| |
|T B |
|O |
| |-------------------------| |
|M A | 74LS173 PC | SOUND AY-3-8910 | CD4066BCN | CD4066BCN |
|A | 8247 | 8046 CDA | MM5666BN | MM5666BN |
|I |-------------------------| |
|N |
| | 1 | 2 | 3 | 4 | 5 |
-----------------------------------------------------------------------|
***************************************************************************/
#include "emu.h"
#include "audio/ad_sound.h"
#include "cpu/z80/z80.h"
#include "sound/ay8910.h"
#include "sound/volt_reg.h"
#include "speaker.h"
//**************************************************************************
// GLOBAL VARIABLES
//**************************************************************************
DEFINE_DEVICE_TYPE(AD_59MC07, ad_59mc07_device, "ad_59mc07", "Alpha Denshi 59MC07 sound board")
//**************************************************************************
// MEMORY MAPS
//**************************************************************************
void ad_59mc07_device::sound_map(address_map &map)
{
map(0x0000, 0xbfff).rom();
map(0xc000, 0xc000).r(m_soundlatch, FUNC(generic_latch_8_device::read));
map(0xc080, 0xc08d).w(m_msm, FUNC(msm5232_device::write));
map(0xc0a0, 0xc0a1).w("aysnd", FUNC(ay8910_device::data_address_w));
map(0xc0b0, 0xc0b0).nopw(); // n.c.
map(0xc0c0, 0xc0c0).w(FUNC(ad_59mc07_device::cymbal_ctrl_w));
map(0xc0d0, 0xc0d0).w(FUNC(ad_59mc07_device::dac_latch_w)); // followed by 1 (and usually 0) on 8155 port B
map(0xc0e0, 0xc0e0).w(FUNC(ad_59mc07_device::dac_latch_w)); // followed by 2 (and usually 0) on 8155 port B
map(0xc0f8, 0xc0ff).w(FUNC(ad_59mc07_device::c0f8_w));
map(0xe000, 0xe0ff).rw(m_audio8155, FUNC(i8155_device::memory_r), FUNC(i8155_device::memory_w));
}
void ad_59mc07_device::sound_portmap(address_map &map)
{
map(0x00e0, 0x00e7).rw(m_audio8155, FUNC(i8155_device::io_r), FUNC(i8155_device::io_w));
}
//**************************************************************************
// SPECIFIC IMPLEMENTATION
//**************************************************************************
//-------------------------------------------------
// ad_59mc07_device: Constructor
//-------------------------------------------------
ad_59mc07_device::ad_59mc07_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: device_t(mconfig, AD_59MC07, tag, owner, clock),
m_audiocpu(*this, "audiocpu"),
m_audio8155(*this, "audio8155"),
m_samples(*this, "samples"),
m_msm(*this, "msm"),
m_dac_1(*this, "dac1"),
m_dac_2(*this, "dac2"),
m_soundlatch(*this, "soundlatch"),
m_frq_adjuster(*this, ":FRQ") // TODO: this should be moved here, but I don't think it's possible to set the default per-game value with a variable
{}
// MSM5232 clock is generated by a transistor oscillator circuit, not by the pcb xtal
// The circuit is controlled by a pot to allow adjusting the frequency. All games
// have been hand-tuned to a recording claiming to be an original recording from the
// boards. You can adjust this in the Slider Controls while using -cheat if needed.
#define MSM5232_MAX_CLOCK 6144000
#define MSM5232_MIN_CLOCK 214000 // unstable
/******************************************************************************/
// Sound
WRITE_LINE_MEMBER(ad_59mc07_device::i8155_timer_pulse)
{
if (!state)
m_audiocpu->set_input_line(I8085_TRAP_LINE, ASSERT_LINE);
}
TIMER_CALLBACK_MEMBER(ad_59mc07_device::frq_adjuster_callback)
{
uint8_t frq = m_frq_adjuster->read();
m_msm->set_clock(MSM5232_MIN_CLOCK + frq * (MSM5232_MAX_CLOCK - MSM5232_MIN_CLOCK) / 100);
//popmessage("8155: C %02x A %02x AY: A %02x B %02x Unk:%x", m_8155_port_c, m_8155_port_a, m_ay_port_a, m_ay_port_b, m_cymbal_ctrl & 15);
m_cymvol *= 0.94f;
m_hihatvol *= 0.94f;
m_msm->set_output_gain(10, m_hihatvol + m_cymvol * (m_ay_port_b & 3) * 0.33f); /* NO from msm5232 */
}
void ad_59mc07_device::c0f8_w(offs_t offset, uint8_t data)
{
switch (offset)
{
case 0: // c0f8: NMI ack (written by NMI handler)
m_audiocpu->set_input_line(I8085_TRAP_LINE, CLEAR_LINE);
break;
case 1: // c0f9: RST75 trigger (written by NMI handler)
// Note: solder pad CP3 on the pcb would allow to disable this
m_audiocpu->pulse_input_line(I8085_RST75_LINE, attotime::zero);
break;
case 2: // c0fa: INTR trigger (written by NMI handler)
// verified on PCB:
m_audiocpu->set_input_line(I8085_INTR_LINE, HOLD_LINE);
break;
case 3: // c0fb: n.c.
// Note: this is written at end of the INTR handler, however LS138@E3 pin 12 is definitely unconnected
break;
case 4: // c0fc: increment PROM address (written by NMI handler)
m_sound_prom_address = (m_sound_prom_address + 1) & 0x1f;
// at this point, the 5-bit value
// goes to an op-amp and to the base of a transistor. The transistor is part
// of a resonator that is used to generate the M5232 clock. The PROM doesn't
// actually seem to be important, since even removing it the M5232 clock
// continues to come out normally.
break;
case 5: // c0fd: n.c.
// Note: this is written at end of the RST75 handler, however LS138@E3 pin 10 is definitely unconnected
break;
case 6: // c0fe: 4-bit answer for main CPU (unused)
// Note: this would go to the LS173@B1, which is however unpopulated on the pcb
break;
case 7: // c0ff: sound command latch clear
// Note: solder pad CP1 on the pcb would allow to disable this
m_soundlatch->clear_w();
break;
}
}
void ad_59mc07_device::ay8910_porta_w(uint8_t data)
{
// bongo 1
m_samples->set_volume(0, ((data & 0x30) >> 4) * 0.33);
if (data & ~m_ay_port_a & 0x80)
m_samples->start(0, 0);
// bongo 2
m_samples->set_volume(1, (data & 0x03) * 0.33);
if (data & ~m_ay_port_a & 0x08)
m_samples->start(1, 1);
m_ay_port_a = data;
}
void ad_59mc07_device::ay8910_portb_w(uint8_t data)
{
// bongo 3
m_samples->set_volume(2, ((data & 0x30)>>4) * 0.33);
if (data & ~m_ay_port_b & 0x80)
m_samples->start(2, 2);
// FIXME I'm just enabling the MSM5232 Noise Output for now. Proper emulation
// of the analog circuitry should be done instead.
// if (data & ~m_ay_port_b & 0x08) cymbal hit trigger
// if (data & ~m_ay_port_b & 0x04) hi-hat hit trigger
// data & 3 cymbal volume
// data & 0x40 hi-hat enable
if (data & ~m_ay_port_b & 0x08)
m_cymvol = 1.0f;
if (data & ~m_ay_port_b & 0x04)
m_hihatvol = 0.8f;
if (~data & 0x40)
m_hihatvol = 0.0f;
m_ay_port_b = data;
}
void ad_59mc07_device::cymbal_ctrl_w(uint8_t data)
{
m_cymbal_ctrl++;
}
void ad_59mc07_device::update_dac()
{
// there is only one latch, which is used to drive two DAC channels.
// When the channel is enabled in the 4066, it goes to a series of
// low-pass filters. The channel is kept enabled only for a short time,
// then it's disabled again.
// Note that PB0 goes through three filters while PB1 only goes through one.
if (m_8155_port_b & 1)
m_dac_1->write(m_dac_latch);
if (m_8155_port_b & 2)
m_dac_2->write(m_dac_latch);
}
void ad_59mc07_device::dac_latch_w(uint8_t data)
{
m_dac_latch = data;
update_dac();
}
void ad_59mc07_device::i8155_porta_w(uint8_t data)
{
m_8155_port_a = data;
m_msm->set_output_gain(0, (data >> 4) / 15.0); // group1 from msm5232
m_msm->set_output_gain(1, (data >> 4) / 15.0); // group1 from msm5232
m_msm->set_output_gain(2, (data >> 4) / 15.0); // group1 from msm5232
m_msm->set_output_gain(3, (data >> 4) / 15.0); // group1 from msm5232
m_msm->set_output_gain(4, (data & 0x0f) / 15.0); // group2 from msm5232
m_msm->set_output_gain(5, (data & 0x0f) / 15.0); // group2 from msm5232
m_msm->set_output_gain(6, (data & 0x0f) / 15.0); // group2 from msm5232
m_msm->set_output_gain(7, (data & 0x0f) / 15.0); // group2 from msm5232
}
void ad_59mc07_device::i8155_portb_w(uint8_t data)
{
m_8155_port_b = data;
update_dac();
}
void ad_59mc07_device::i8155_portc_w(uint8_t data)
{
m_8155_port_c = data;
m_msm->set_output_gain(8, (data & 0x0f) / 15.0); // SOLO 8' from msm5232
if (data & 0x20)
m_msm->set_output_gain(9, (data & 0x0f) / 15.0); // SOLO 16' from msm5232
else
m_msm->set_output_gain(9, 0); // SOLO 16' from msm5232
}
WRITE_LINE_MEMBER(ad_59mc07_device::msm5232_gate)
{
}
static const char *const alphamc07_sample_names[] =
{
"*equites",
"bongo1",
"bongo2",
"bongo3",
nullptr
};
#define MSM5232_BASE_VOLUME 1.0
//-------------------------------------------------
// device_add_mconfig - add device configuration
//-------------------------------------------------
void ad_59mc07_device::device_add_mconfig(machine_config &config)
{
I8085A(config, m_audiocpu, 6.144_MHz_XTAL); // verified on pcb
m_audiocpu->set_addrmap(AS_PROGRAM, &ad_59mc07_device::sound_map);
m_audiocpu->set_addrmap(AS_IO, &ad_59mc07_device::sound_portmap);
m_audiocpu->set_clk_out(m_audio8155, FUNC(i8155_device::set_unscaled_clock_int));
I8155(config, m_audio8155, 0);
m_audio8155->out_pa_callback().set(FUNC(ad_59mc07_device::i8155_porta_w));
m_audio8155->out_pb_callback().set(FUNC(ad_59mc07_device::i8155_portb_w));
m_audio8155->out_pc_callback().set(FUNC(ad_59mc07_device::i8155_portc_w));
m_audio8155->out_to_callback().set(FUNC(ad_59mc07_device::i8155_timer_pulse));
// sound hardware
SPEAKER(config, "speaker").front_center();
GENERIC_LATCH_8(config, m_soundlatch);
MSM5232(config, m_msm, MSM5232_MAX_CLOCK); // will be adjusted at runtime through PORT_ADJUSTER
m_msm->set_capacitors(0.47e-6, 0.47e-6, 0.47e-6, 0.47e-6, 0.47e-6, 0.47e-6, 0.47e-6, 0.47e-6); // verified
m_msm->gate().set(FUNC(ad_59mc07_device::msm5232_gate));
m_msm->add_route(0, "speaker", MSM5232_BASE_VOLUME/2.2); // pin 28 2'-1 : 22k resistor
m_msm->add_route(1, "speaker", MSM5232_BASE_VOLUME/1.5); // pin 29 4'-1 : 15k resistor
m_msm->add_route(2, "speaker", MSM5232_BASE_VOLUME); // pin 30 8'-1 : 10k resistor
m_msm->add_route(3, "speaker", MSM5232_BASE_VOLUME); // pin 31 16'-1 : 10k resistor
m_msm->add_route(4, "speaker", MSM5232_BASE_VOLUME/2.2); // pin 36 2'-2 : 22k resistor
m_msm->add_route(5, "speaker", MSM5232_BASE_VOLUME/1.5); // pin 35 4'-2 : 15k resistor
m_msm->add_route(6, "speaker", MSM5232_BASE_VOLUME); // pin 34 8'-2 : 10k resistor
m_msm->add_route(7, "speaker", MSM5232_BASE_VOLUME); // pin 33 16'-2 : 10k resistor
m_msm->add_route(8, "speaker", 1.0); // pin 1 SOLO 8' (this actually feeds an analog section)
m_msm->add_route(9, "speaker", 1.0); // pin 2 SOLO 16' (this actually feeds an analog section)
m_msm->add_route(10,"speaker", 0.12); // pin 22 Noise Output (this actually feeds an analog section)
ay8910_device &aysnd(AY8910(config, "aysnd", 6.144_MHz_XTAL/4)); // verified on pcb
aysnd.port_a_write_callback().set(FUNC(ad_59mc07_device::ay8910_porta_w));
aysnd.port_b_write_callback().set(FUNC(ad_59mc07_device::ay8910_portb_w));
aysnd.add_route(ALL_OUTPUTS, "speaker", 0.15);
DAC_6BIT_R2R(config, m_dac_1, 0).add_route(ALL_OUTPUTS, "speaker", 0.5); // unknown DAC
DAC_6BIT_R2R(config, m_dac_2, 0).add_route(ALL_OUTPUTS, "speaker", 0.5); // unknown DAC
voltage_regulator_device &vref(VOLTAGE_REGULATOR(config, "vref", 0));
vref.add_route(0, "dac1", 1.0, DAC_VREF_POS_INPUT);
vref.add_route(0, "dac1", -1.0, DAC_VREF_NEG_INPUT);
vref.add_route(0, "dac2", 1.0, DAC_VREF_POS_INPUT);
vref.add_route(0, "dac2", -1.0, DAC_VREF_NEG_INPUT);
SAMPLES(config, m_samples);
m_samples->set_channels(3);
m_samples->set_samples_names(alphamc07_sample_names);
m_samples->add_route(ALL_OUTPUTS, "speaker", 0.3);
}
//-------------------------------------------------
// device_start: Start up the device
//-------------------------------------------------
void ad_59mc07_device::device_start()
{
// zerofill
m_sound_prom_address = 0;
m_dac_latch = 0;
m_8155_port_b = 0;
m_8155_port_a = 0;
m_8155_port_c = 0;
m_ay_port_a = 0;
m_ay_port_b = 0;
m_cymbal_ctrl = 0;
m_cymvol = 0.0;
m_hihatvol = 0.0;
// register for savestates
save_item(NAME(m_sound_prom_address));
save_item(NAME(m_dac_latch));
save_item(NAME(m_8155_port_b));
save_item(NAME(m_8155_port_a));
save_item(NAME(m_8155_port_c));
save_item(NAME(m_ay_port_a));
save_item(NAME(m_ay_port_b));
save_item(NAME(m_cymbal_ctrl));
save_item(NAME(m_cymvol));
save_item(NAME(m_hihatvol));
m_adjuster_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(ad_59mc07_device::frq_adjuster_callback), this));
m_adjuster_timer->adjust(attotime::from_hz(60), 0, attotime::from_hz(60));
}
//-------------------------------------------------
// device_reset: Reset the device
//-------------------------------------------------
void ad_59mc07_device::device_reset()
{
m_audiocpu->set_input_line(I8085_INTR_LINE, CLEAR_LINE);
m_audiocpu->set_input_line(I8085_TRAP_LINE, CLEAR_LINE);
}
/*
Functions to emulate the Alpha Denshi "60MC01" audio board
CPU : Z80A
Sound: AY-3-8910A (unpopulated: another 8910 and a YM2203)
OSC : 16.000MHz
TODO: This bears a lot of similarities with the Super Stingray audio board. Verify if PCB codes match and if so merge implementations;
fix interrupts;
is there really no music?
*/
//**************************************************************************
// GLOBAL VARIABLES
//**************************************************************************
DEFINE_DEVICE_TYPE(AD_60MC01, ad_60mc01_device, "ad_60mc01", "Alpha Denshi 60MC01 sound board")
//**************************************************************************
// MEMORY MAPS
//**************************************************************************
void ad_60mc01_device::sound_map(address_map &map)
{
map(0x0000, 0x1fff).rom();
map(0x8000, 0x87ff).ram();
map(0xc100, 0xc100).r(m_soundlatch, FUNC(generic_latch_8_device::read));
map(0xc102, 0xc102).w(m_soundlatch, FUNC(generic_latch_8_device::clear_w));
map(0xc104, 0xc104).nopw(); // written at start up, would be DAC if it were populated
map(0xc106, 0xc10e).nopw(); // written continuously, it's audio board I/O according to Super Stingray's emulation
}
void ad_60mc01_device::sound_portmap(address_map &map)
{
map.global_mask(0xff);
map(0x10, 0x11).nopw(); // written at start up, would be the YM2203 if it were populated
map(0x80, 0x81).w("aysnd", FUNC(ay8910_device::data_address_w));
}
//**************************************************************************
// SPECIFIC IMPLEMENTATION
//**************************************************************************
//-------------------------------------------------
// ad_60mc01_device: Constructor
//-------------------------------------------------
ad_60mc01_device::ad_60mc01_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: device_t(mconfig, AD_60MC01, tag, owner, clock),
m_audiocpu(*this, "audiocpu"),
m_soundlatch(*this, "soundlatch")
{}
//-------------------------------------------------
// device_add_mconfig - add device configuration
//-------------------------------------------------
void ad_60mc01_device::device_add_mconfig(machine_config &config)
{
Z80(config, m_audiocpu, 16_MHz_XTAL / 4); // divider not verified
m_audiocpu->set_addrmap(AS_PROGRAM, &ad_60mc01_device::sound_map);
m_audiocpu->set_addrmap(AS_IO, &ad_60mc01_device::sound_portmap);
m_audiocpu->set_periodic_int(FUNC(ad_60mc01_device::sound_irq), attotime::from_hz(128));
// sound hardware
SPEAKER(config, "speaker").front_center();
GENERIC_LATCH_8(config, m_soundlatch);
ay8910_device &aysnd(AY8910(config, "aysnd", 16_MHz_XTAL / 8)); // divider not verified
aysnd.add_route(ALL_OUTPUTS, "speaker", 0.50);
}
//-------------------------------------------------
// device_start: Start up the device
//-------------------------------------------------
void ad_60mc01_device::device_start()
{
}
//-------------------------------------------------
// device_reset: Reset the device
//-------------------------------------------------
void ad_60mc01_device::device_reset()
{
}