diff options
Diffstat (limited to 'trunk/src/mame/audio/gottlieb.c')
-rw-r--r-- | trunk/src/mame/audio/gottlieb.c | 622 |
1 files changed, 622 insertions, 0 deletions
diff --git a/trunk/src/mame/audio/gottlieb.c b/trunk/src/mame/audio/gottlieb.c new file mode 100644 index 00000000000..06747c4716f --- /dev/null +++ b/trunk/src/mame/audio/gottlieb.c @@ -0,0 +1,622 @@ +/*************************************************************************** + + Gottlieb hardware + dedicated to Warren Davis, Jeff Lee, Tim Skelly & David Thiel + +***************************************************************************/ + +#include "emu.h" +#include "debugger.h" +#include "cpu/m6502/m6502.h" +#include "machine/6532riot.h" +#include "sound/samples.h" +#include "sound/dac.h" +#include "sound/ay8910.h" +//#include "sound/votrax.h" +#include "sound/sp0250.h" +#include "includes/gottlieb.h" + + +#define SOUND1_CLOCK XTAL_3_579545MHz +#define SOUND2_CLOCK XTAL_4MHz +#define SOUND2_SPEECH_CLOCK XTAL_3_12MHz + + + + + + + + + +static void gottlieb1_sh_w(device_t *riot, UINT8 data); +static void gottlieb2_sh_w(address_space *space, UINT8 data); +static void trigger_sample(device_t *samples, UINT8 data); + + + +/************************************* + * + * Generic interfaces + * + *************************************/ + +WRITE8_HANDLER( gottlieb_sh_w ) +{ + device_t *riot = space->machine().device("riot"); + + /* identify rev1 boards by the presence of a 6532 RIOT device */ + if (riot != NULL) + gottlieb1_sh_w(riot, data); + else + gottlieb2_sh_w(space, data); +} + + + +/************************************* + * + * Rev. 1 handlers + * + *************************************/ + +static void gottlieb1_sh_w(device_t *riot, UINT8 data) +{ + device_t *samples = riot->machine().device("samples"); + int pa7 = (data & 0x0f) != 0xf; + int pa0_5 = ~data & 0x3f; + + /* snoop the data looking for commands that need samples */ + if (pa7 && samples != NULL) + trigger_sample(samples, pa0_5); + + /* write the command data to the low 6 bits, and the trigger to the upper bit */ + riot6532_porta_in_set(riot, pa0_5 | (pa7 << 7), 0xbf); +} + + + +/************************************* + * + * Rev. 1 RIOT interfaces + * + *************************************/ + +static WRITE_LINE_DEVICE_HANDLER( snd_interrupt ) +{ + cputag_set_input_line(device->machine(), "audiocpu", M6502_IRQ_LINE, state); +} + + +static WRITE8_DEVICE_HANDLER( r6532_portb_w ) +{ + /* unsure if this is ever used, but the NMI is connected to the RIOT's PB7 */ + cputag_set_input_line(device->machine(), "audiocpu", INPUT_LINE_NMI, (data & 0x80) ? CLEAR_LINE : ASSERT_LINE); +} + + +static const riot6532_interface gottlieb_riot6532_intf = +{ + DEVCB_NULL, + DEVCB_INPUT_PORT("SB1"), + DEVCB_NULL, + DEVCB_HANDLER(r6532_portb_w), + DEVCB_LINE(snd_interrupt) +}; + + + +/************************************* + * + * Rev. 1 sample players + * + *************************************/ + +static void play_sample(device_t *samples, const char *phonemes) +{ + if (strcmp(phonemes, "[0] HEH3LOOW AH1EH3I3YMTERI2NDAHN") == 0) /* Q-Bert - Hello, I am turned on */ + sample_start(samples, 0, 42, 0); + else if (strcmp(phonemes, "[0]BAH1EH1Y") == 0) /* Q-Bert - Bye, bye */ + sample_start(samples, 0, 43, 0); + else if (strcmp(phonemes, "[0]A2YHT LEH2FTTH") == 0) /* Reactor - Eight left */ + sample_start(samples, 0, 0, 0); + else if (strcmp(phonemes, "[0]SI3KS DTYN LEH2FTTH") == 0) /* Reactor - Sixteen left */ + sample_start(samples, 0, 1, 0); + else if (strcmp(phonemes, "[0]WO2RNYNG KO2R UH1NSDTABUH1L") == 0) /* Reactor - Warning core unstable */ + sample_start(samples, 0, 5, 0); + else if (strcmp(phonemes, "[0]CHAMBERR AE1EH2KTI1VA1I3DTEH1DT ") == 0) /* Reactor - Chamber activated */ + sample_start(samples, 0, 7, 0); +} + + +static void trigger_sample(device_t *samples, UINT8 data) +{ + gottlieb_state *state = samples->machine().driver_data<gottlieb_state>(); + /* Reactor samples */ + if (strcmp(samples->machine().system().name, "reactor") == 0) + { + switch (data) + { + case 55: + case 56: + case 57: + case 59: + sample_start(samples, 0, data - 53, 0); + break; + + case 31: + state->m_score_sample = 7; + break; + + case 39: + state->m_score_sample++; + if (state->m_score_sample < 20) + sample_start(samples, 0, state->m_score_sample, 0); + break; + } + } + + /* Q*Bert samples */ + else + { + switch (data) + { + case 17: + case 18: + case 19: + case 20: + case 21: + sample_start(samples, 0, (data - 17) * 8 + state->m_random_offset, 0); + state->m_random_offset = (state->m_random_offset + 1) & 7; + break; + + case 22: + sample_start(samples, 0,40,0); + break; + + case 23: + sample_start(samples, 0,41,0); + break; + } + } +} + + +#ifdef UNUSED_FUNCTION +void gottlieb_knocker(running_machine &machine) +{ + device_t *samples = space->machine().device("samples"); + if (!strcmp(machine.system().name,"reactor")) /* reactor */ + { + } + else if (samples != NULL) /* qbert */ + sample_start(samples, 0,44,0); +} +#endif + + + +/************************************* + * + * Rev. 1 speech interface + * + *************************************/ + +/* callback for the timer */ +static TIMER_CALLBACK( gottlieb_nmi_generate ) +{ + cputag_set_input_line(machine, "audiocpu", INPUT_LINE_NMI, PULSE_LINE); +} + + +static WRITE8_HANDLER( vortrax_data_w ) +{ + gottlieb_state *state = space->machine().driver_data<gottlieb_state>(); + static const char *const PhonemeTable[0x40] = + { + "EH3", "EH2", "EH1", "PA0", "DT" , "A1" , "A2" , "ZH", + "AH2", "I3" , "I2" , "I1" , "M" , "N" , "B" , "V", + "CH" , "SH" , "Z" , "AW1", "NG" , "AH1", "OO1", "OO", + "L" , "K" , "J" , "H" , "G" , "F" , "D" , "S", + "A" , "AY" , "Y1" , "UH3", "AH" , "P" , "O" , "I", + "U" , "Y" , "T" , "R" , "E" , "W" , "AE" , "AE1", + "AW2", "UH2", "UH1", "UH" , "O2" , "O1" , "IU" , "U1", + "THV", "TH" , "ER" , "EH" , "E1" , "AW" , "PA1", "STOP" + }; + + data ^= 0xff; + +logerror("Votrax: intonation %d, phoneme %02x %s\n",data >> 6,data & 0x3f,PhonemeTable[data & 0x3f]); + + state->m_votrax_queue[state->m_votrax_queuepos++] = data; + + if ((data & 0x3f) == 0x3f) + { + if (state->m_votrax_queuepos > 1) + { + device_t *samples = space->machine().device("samples"); + int last = -1; + int i; + char phonemes[200]; + + phonemes[0] = 0; + for (i = 0;i < state->m_votrax_queuepos-1;i++) + { + static const char *const inf[4] = { "[0]", "[1]", "[2]", "[3]" }; + int phoneme = state->m_votrax_queue[i] & 0x3f; + int inflection = state->m_votrax_queue[i] >> 6; + if (inflection != last) strcat(phonemes, inf[inflection]); + last = inflection; + if (phoneme == 0x03 || phoneme == 0x3e) strcat(phonemes," "); + else strcat(phonemes,PhonemeTable[phoneme]); + } + + mame_printf_debug("Votrax played '%s'\n", phonemes); + play_sample(samples, phonemes); +#if 0 + popmessage("%s", phonemes); +#endif + } + + state->m_votrax_queuepos = 0; + } + + /* generate a NMI after a while to make the CPU continue to send data */ + space->machine().scheduler().timer_set(attotime::from_usec(50), FUNC(gottlieb_nmi_generate)); +} + +static WRITE8_HANDLER( speech_clock_dac_w ) +{ + gottlieb_state *state = space->machine().driver_data<gottlieb_state>(); +if (data != state->m_last) + mame_printf_debug("clock = %02X\n", data); +state->m_last = data; +} + + +/************************************* + * + * Rev 1. initialization + * + *************************************/ + +static SOUND_START( gottlieb1 ) +{ + gottlieb_state *state = machine.driver_data<gottlieb_state>(); + state->m_score_sample = 7; + state->m_random_offset = 0; + + state_save_register_global_array(machine, state->m_votrax_queue); + state_save_register_global(machine, state->m_votrax_queuepos); +} + + + +/************************************* + * + * Rev 1. address map + * + *************************************/ + +static ADDRESS_MAP_START( gottlieb_sound1_map, AS_PROGRAM, 8 ) + /* A15 not decoded except in expansion socket */ + ADDRESS_MAP_GLOBAL_MASK(0x7fff) + AM_RANGE(0x0000, 0x007f) AM_MIRROR(0x0d80) AM_RAM + AM_RANGE(0x0200, 0x021f) AM_MIRROR(0x0de0) AM_DEVREADWRITE("riot", riot6532_r, riot6532_w) + AM_RANGE(0x1000, 0x1000) AM_MIRROR(0x0fff) AM_DEVWRITE("dac", dac_w) + AM_RANGE(0x2000, 0x2000) AM_MIRROR(0x0fff) AM_WRITE(vortrax_data_w) + AM_RANGE(0x3000, 0x3000) AM_MIRROR(0x0fff) AM_WRITE(speech_clock_dac_w) + AM_RANGE(0x6000, 0x7fff) AM_ROM +ADDRESS_MAP_END + + + +/************************************* + * + * Rev. 1 machine driver + * + *************************************/ + +MACHINE_CONFIG_FRAGMENT( gottlieb_soundrev1 ) + MCFG_SOUND_START(gottlieb1) + + MCFG_RIOT6532_ADD("riot", SOUND1_CLOCK/4, gottlieb_riot6532_intf) + + MCFG_CPU_ADD("audiocpu", M6502, SOUND1_CLOCK/4) /* the board can be set to /2 as well */ + MCFG_CPU_PROGRAM_MAP(gottlieb_sound1_map) + + /* sound hardware */ + MCFG_SOUND_ADD("dac", DAC, 0) + MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.50) +MACHINE_CONFIG_END + + + +/************************************* + * + * Rev. 1 input ports + * + *************************************/ + +INPUT_PORTS_START( gottlieb1_sound ) + PORT_START("SB1") + PORT_DIPUNKNOWN_DIPLOC( 0x01, 0x01, "SB1:7" ) + PORT_DIPUNKNOWN_DIPLOC( 0x02, 0x02, "SB1:6" ) + PORT_DIPUNKNOWN_DIPLOC( 0x04, 0x04, "SB1:5" ) + PORT_DIPUNKNOWN_DIPLOC( 0x08, 0x08, "SB1:1" ) + PORT_DIPUNKNOWN_DIPLOC( 0x10, 0x10, "SB1:4" ) + PORT_DIPUNKNOWN_DIPLOC( 0x20, 0x20, "SB1:3" ) + PORT_DIPNAME( 0x40, 0x40, "Sound Test" ) + PORT_DIPSETTING( 0x40, DEF_STR( Off ) ) + PORT_DIPSETTING( 0x00, DEF_STR( On ) ) + PORT_BIT( 0x80, 0x80, IPT_UNKNOWN ) /* To U3-6 on QBert */ +INPUT_PORTS_END + + + +/************************************* + * + * Rev. 2 communication handlers + * + *************************************/ + +static void gottlieb2_sh_w(address_space *space, UINT8 data) +{ + gottlieb_state *state = space->machine().driver_data<gottlieb_state>(); + /* when data is not 0xff, the transparent latch at A3 allows it to pass through unmolested */ + if (data != 0xff) + { + /* each CPU has its own latch */ + soundlatch_w(space, 0, data); + soundlatch2_w(space, 0, data); + + /* if the previous data was 0xff, clock an IRQ on each */ + if (state->m_last_command == 0xff) + { + cputag_set_input_line(space->machine(), "audiocpu", M6502_IRQ_LINE, ASSERT_LINE); + cputag_set_input_line(space->machine(), "speech", M6502_IRQ_LINE, ASSERT_LINE); + } + } + state->m_last_command = data; +} + + +static READ8_HANDLER( speech_data_r ) +{ + cputag_set_input_line(space->machine(), "speech", M6502_IRQ_LINE, CLEAR_LINE); + return soundlatch_r(space, offset); +} + + +static READ8_HANDLER( audio_data_r ) +{ + cputag_set_input_line(space->machine(), "audiocpu", M6502_IRQ_LINE, CLEAR_LINE); + return soundlatch2_r(space, offset); +} + + +static WRITE8_HANDLER( signal_audio_nmi_w ) +{ + cputag_set_input_line(space->machine(), "audiocpu", INPUT_LINE_NMI, ASSERT_LINE); + cputag_set_input_line(space->machine(), "audiocpu", INPUT_LINE_NMI, CLEAR_LINE); +} + + + +/************************************* + * + * Rev. 2 NMI timer + * + *************************************/ + +INLINE void nmi_timer_adjust(running_machine &machine) +{ + gottlieb_state *state = machine.driver_data<gottlieb_state>(); + /* adjust timer to go off in the future based on the current rate */ + state->m_nmi_timer->adjust(attotime::from_hz(SOUND2_CLOCK/16) * (256 * (256 - state->m_nmi_rate))); +} + + +INLINE void nmi_state_update(running_machine &machine) +{ + gottlieb_state *state = machine.driver_data<gottlieb_state>(); + /* update the NMI line state based on the enable and state */ + cputag_set_input_line(machine, "speech", INPUT_LINE_NMI, (state->m_nmi_state && (state->m_speech_control & 1)) ? ASSERT_LINE : CLEAR_LINE); +} + + +static TIMER_CALLBACK( nmi_clear ) +{ + gottlieb_state *state = machine.driver_data<gottlieb_state>(); + /* clear the NMI state and update it */ + state->m_nmi_state = 0; + nmi_state_update(machine); +} + + +static TIMER_CALLBACK( nmi_callback ) +{ + gottlieb_state *state = machine.driver_data<gottlieb_state>(); + /* assert the NMI if it is not disabled */ + state->m_nmi_state = 1; + nmi_state_update(machine); + + /* set a timer to turn it off again on hte next SOUND_CLOCK/16 */ + machine.scheduler().timer_set(attotime::from_hz(SOUND2_CLOCK/16), FUNC(nmi_clear)); + + /* adjust the NMI timer for the next time */ + nmi_timer_adjust(machine); +} + + +static WRITE8_HANDLER( nmi_rate_w ) +{ + gottlieb_state *state = space->machine().driver_data<gottlieb_state>(); + /* the new rate is picked up when the previous timer expires */ + state->m_nmi_rate = data; +} + + + +/************************************* + * + * Rev. 2 sound chip access + * + *************************************/ + +static CUSTOM_INPUT( speech_drq_custom_r ) +{ + return sp0250_drq_r(field.machine().device("spsnd")); +} + + +static WRITE8_DEVICE_HANDLER( gottlieb_dac_w ) +{ + gottlieb_state *state = device->machine().driver_data<gottlieb_state>(); + /* dual DAC; the first DAC serves as the reference voltage for the + second, effectively scaling the output */ + state->m_dac_data[offset] = data; + dac_data_16_w(device, state->m_dac_data[0] * state->m_dac_data[1]); +} + + +static WRITE8_HANDLER( speech_control_w ) +{ + gottlieb_state *state = space->machine().driver_data<gottlieb_state>(); + UINT8 previous = state->m_speech_control; + state->m_speech_control = data; + + /* bit 0 enables/disables the NMI line */ + nmi_state_update(space->machine()); + + /* bit 1 controls a LED on the sound board */ + + /* bit 2 goes to 8913 BDIR pin */ + if ((previous & 0x04) != 0 && (data & 0x04) == 0) + { + /* bit 3 selects which of the two 8913 to enable */ + /* bit 4 goes to the 8913 BC1 pin */ + device_t *ay = space->machine().device((data & 0x08) ? "ay1" : "ay2"); + ay8910_data_address_w(ay, data >> 4, *state->m_psg_latch); + } + + /* bit 5 goes to the speech chip DIRECT DATA TEST pin */ + + /* bit 6 = speech chip DATA PRESENT pin; high then low to make the chip read data */ + if ((previous & 0x40) == 0 && (data & 0x40) != 0) + { + device_t *sp = space->machine().device("spsnd"); + sp0250_w(sp, 0, *state->m_sp0250_latch); + } + + /* bit 7 goes to the speech chip RESET pin */ + if ((previous ^ data) & 0x80) + space->machine().device("spsnd")->reset(); +} + + + +/************************************* + * + * Rev. 2 initialization + * + *************************************/ + +static SOUND_START( gottlieb2 ) +{ + gottlieb_state *state = machine.driver_data<gottlieb_state>(); + /* set up the NMI timer */ + state->m_nmi_timer = machine.scheduler().timer_alloc(FUNC(nmi_callback)); + state->m_nmi_rate = 0; + nmi_timer_adjust(machine); + + state->m_dac_data[0] = state->m_dac_data[1] = 0xff; + + /* register for save states */ + state_save_register_global(machine, state->m_nmi_rate); + state_save_register_global(machine, state->m_nmi_state); + state_save_register_global(machine, state->m_speech_control); + state_save_register_global(machine, state->m_last_command); +} + + + +/************************************* + * + * Rev. 2 address map + * + *************************************/ + +static ADDRESS_MAP_START( gottlieb_speech2_map, AS_PROGRAM, 8 ) + AM_RANGE(0x0000, 0x03ff) AM_MIRROR(0x1c00) AM_RAM + AM_RANGE(0x2000, 0x2000) AM_MIRROR(0x1fff) AM_WRITEONLY AM_BASE_MEMBER(gottlieb_state, m_sp0250_latch) + AM_RANGE(0x4000, 0x4000) AM_MIRROR(0x1fff) AM_WRITE(speech_control_w) + AM_RANGE(0x6000, 0x6000) AM_MIRROR(0x1fff) AM_READ_PORT("GOTTLIEB2") + AM_RANGE(0x8000, 0x8000) AM_MIRROR(0x1fff) AM_WRITEONLY AM_BASE_MEMBER(gottlieb_state, m_psg_latch) + AM_RANGE(0xa000, 0xa000) AM_MIRROR(0x07ff) AM_WRITE(nmi_rate_w) + AM_RANGE(0xa800, 0xa800) AM_MIRROR(0x07ff) AM_READ(speech_data_r) + AM_RANGE(0xb000, 0xb000) AM_MIRROR(0x07ff) AM_WRITE(signal_audio_nmi_w) + AM_RANGE(0xc000, 0xffff) AM_ROM +ADDRESS_MAP_END + + +static ADDRESS_MAP_START( gottlieb_audio2_map, AS_PROGRAM, 8 ) + AM_RANGE(0x0000, 0x03ff) AM_MIRROR(0x3c00) AM_RAM + AM_RANGE(0x4000, 0x4001) AM_MIRROR(0x3ffe) AM_DEVWRITE("dac1", gottlieb_dac_w) AM_BASE_MEMBER(gottlieb_state, m_dac_data) + AM_RANGE(0x8000, 0x8000) AM_MIRROR(0x3fff) AM_READ(audio_data_r) + AM_RANGE(0xe000, 0xffff) AM_MIRROR(0x2000) AM_ROM +ADDRESS_MAP_END + + + +/************************************* + * + * Rev. 2 machine driver + * + *************************************/ + +MACHINE_CONFIG_FRAGMENT( gottlieb_soundrev2 ) + /* audio CPUs */ + MCFG_CPU_ADD("audiocpu", M6502, SOUND2_CLOCK/4) + MCFG_CPU_PROGRAM_MAP(gottlieb_audio2_map) + + MCFG_CPU_ADD("speech", M6502, SOUND2_CLOCK/4) + MCFG_CPU_PROGRAM_MAP(gottlieb_speech2_map) + + /* sound hardware */ + MCFG_SOUND_START( gottlieb2 ) + + MCFG_SOUND_ADD("dac1", DAC, 0) + MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.15) + + MCFG_SOUND_ADD("dac2", DAC, 0) + MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.15) + + MCFG_SOUND_ADD("ay1", AY8913, SOUND2_CLOCK/2) + MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.15) + + MCFG_SOUND_ADD("ay2", AY8913, SOUND2_CLOCK/2) + MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.15) + + MCFG_SOUND_ADD("spsnd", SP0250, SOUND2_SPEECH_CLOCK) + MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 1.0) +MACHINE_CONFIG_END + + + +/************************************* + * + * Rev. 2 input ports + * + *************************************/ + +INPUT_PORTS_START( gottlieb2_sound ) + PORT_START("GOTTLIEB2") + PORT_BIT( 0x0f, IP_ACTIVE_HIGH, IPT_UNKNOWN ) + PORT_DIPUNKNOWN( 0x10, 0x10 ) + PORT_DIPUNKNOWN( 0x20, 0x20 ) + PORT_DIPNAME( 0x40, 0x40, "Sound Test" ) + PORT_DIPSETTING( 0x40, DEF_STR( Off ) ) + PORT_DIPSETTING( 0x00, DEF_STR( On ) ) + PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_SPECIAL ) PORT_CUSTOM(speech_drq_custom_r, NULL) +INPUT_PORTS_END |