From 9f8e1b32bc5e24483f75b47af7805716b0dc6cc9 Mon Sep 17 00:00:00 2001 From: m1macrophage <168948267+m1macrophage@users.noreply.github.com> Date: Fri, 3 Jan 2025 08:08:12 -0800 Subject: oberheim/dmx.cpp: Driver for Oberheim DMX. Includes layout. No sound. (#13165) --- src/mame/layout/oberheim_dmx.lay | 494 +++++++++++++++++++++++++++++++++ src/mame/mame.lst | 3 + src/mame/oberheim/dmx.cpp | 582 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 1079 insertions(+) create mode 100644 src/mame/layout/oberheim_dmx.lay create mode 100644 src/mame/oberheim/dmx.cpp diff --git a/src/mame/layout/oberheim_dmx.lay b/src/mame/layout/oberheim_dmx.lay new file mode 100644 index 00000000000..a317a6e2aec --- /dev/null +++ b/src/mame/layout/oberheim_dmx.lay @@ -0,0 +1,494 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mame/mame.lst b/src/mame/mame.lst index 9946ce86875..8ef035873d6 100644 --- a/src/mame/mame.lst +++ b/src/mame/mame.lst @@ -35517,6 +35517,9 @@ sbasssta // @source:novation/drumsta.cpp drumsta // +@source:oberheim/dmx.cpp +obdmx // Oberheim DMX + @source:olivetti/m20.cpp m20 // m40 // diff --git a/src/mame/oberheim/dmx.cpp b/src/mame/oberheim/dmx.cpp new file mode 100644 index 00000000000..e0923737ff0 --- /dev/null +++ b/src/mame/oberheim/dmx.cpp @@ -0,0 +1,582 @@ +// license:BSD-3-Clause +// copyright-holders:m1macrophage + +/* +The Oberheim DMX is a digital drum machine. + +The firmware, running on a Z80, implements the drum machine functionality (e.g. +sequencer), controls the UI (scans for button presses, drives the display), +triggers voices, and controls voice variations. All harware functionality is +mapped to I/O ports. Only ROM and RAM are mapped to memory. + +There are 8 voice cards (though the CYMBAL voice consists of 2 physical cards), +each of which can produce 3 different variations, for a total of 24 sounds. The +8 voice cards can play back simultaneously, but only 1 variation per card can be +active at a time, for a total of 8 voices of polyphony. + +The drum sounds are samples stored in ROMs. During playback, they are +post-processed by an analog VCA, which is modulated by a Release envelope +genereator. Pitch and volume variations for each voice are also controlled by +analog circuitry. + +Currently, this driver emulates the early version of the DMX. A later hardware +revision added additional memory, while the final hardware revision added MIDI. +These 2 subsequent revisions are not yet emulated. + +The driver is based on the DMX's service manual and schematics. It is intended +as an educational tool. + +PCBoards: +- Processor Board (includes the power supply and connectors) +- Switch Board (buttons, faders and display) +- Voice Card - Bass +- Voice Card - Snare +- Voice Card - Hi-hat +- Voice Card - Tom 1 +- Voice Card - Tom 2 +- Voice Card - Perc 1 +- Voice Card - Perc 2 +- Voice Card - Cymbal #1 +- Voice Card - Cymbal #2 +Cymbal #1 and #2 implement a single voice, but occupy two voice card slots. + +Usage notes: +- No audio support (coming very soon). +- An interactive, clickable layout is included. +- This driver is marked as "not working", due to the lack of audio support. But + the UI is otherwise functional. +- The volume faders can be controlled from the "Sliders" menu. +- The drum keys are mapped to the keyboard in the same. for example: + Q - Bass 1, W - Snare 1, ... + A - Bass 2, S - Snare 2, ... + Z - Bass 3, X - Snare 3, ... +- The number buttons on the layout are mapped to the numeric keypad. +*/ + +#include "emu.h" + +#include "attotime.h" +#include "cpu/z80/z80.h" +#include "machine/nvram.h" +#include "machine/output_latch.h" +#include "machine/timer.h" +#include "video/dl1416.h" + +#include "oberheim_dmx.lh" + +#define LOG_TRIGGERS (1U << 1) +#define LOG_INT_TIMER (1U << 2) + +#define VERBOSE (LOG_GENERAL | LOG_TRIGGERS | LOG_INT_TIMER) +//#define LOG_OUTPUT_FUNC osd_printf_info + +#include "logmacro.h" + +namespace { + +constexpr const char MAINCPU_TAG[] = "z80"; +constexpr const char NVRAM_TAG[] = "nvram"; + +class dmx_state : public driver_device +{ +public: + dmx_state(const machine_config &mconfig, device_type type, const char *tag) ATTR_COLD + : driver_device(mconfig, type, tag) + , m_maincpu(*this, MAINCPU_TAG) + , m_digit_device(*this, "dl1414_%d", 0) + , m_digit_output(*this, "digit_%d", 0U) + , m_metronome_timer(*this, "metronome_timer") + , m_buttons(*this, "buttons_%d", 0) + , m_switches(*this, "switches") + , m_external_triggers(*this, "external_triggers") + , m_clk_in(*this, "clk_in") + , m_clk_out_tip(*this, "CLK_OUT_tip") + , m_metronome_mix(*this, "METRONOME_MIX") + , m_metronome(*this, "METRONOME") + { + } + + void dmx(machine_config &config) ATTR_COLD; + + DECLARE_INPUT_CHANGED_MEMBER(clk_in_changed); + +protected: + void machine_start() override ATTR_COLD; + +private: + void refresh_int_flipflop(); + void int_timer_preset_w(u8 data); + void int_timer_enable_w(int state); + TIMER_DEVICE_CALLBACK_MEMBER(int_timer_tick); + + void metronome_trigger_w(u8 data); + void metronome_mix_w(u8 data); + TIMER_DEVICE_CALLBACK_MEMBER(metronome_timer_tick); + + void display_w(offs_t offset, u8 data); + template void display_output_w(offs_t offset, u16 data); + + u8 buttons_r(offs_t offset); + u8 external_triggers_r(); + u8 cassette_r(); + template void gen_trigger_w(u8 data); + + void memory_map(address_map &map) ATTR_COLD; + void io_map(address_map &map) ATTR_COLD; + + required_device m_maincpu; + required_device_array m_digit_device; + output_finder<16> m_digit_output; + required_device m_metronome_timer; + required_ioport_array<6> m_buttons; + required_ioport m_switches; // Includes foot switches. + required_ioport m_external_triggers; + required_ioport m_clk_in; + output_finder<> m_clk_out_tip; // Tip conncetion of "CLK OUT" TRS jack. + output_finder<> m_metronome_mix; + output_finder<> m_metronome; + + // 40103 timer (U11 in Processor Board) + u8 m_int_timer_preset = 0xff; + bool m_int_timer_enabled = true; + s16 m_int_timer_value = 0xff; + u8 m_int_timer_out = 1; + u8 m_int_flipflop_clock = 0; + + // The service manual states that the crystal is 4.912MHz, in the section + // that describes the clock circuitry. However, the parts list specifies + // this as a 4.9152 MHz crystal. The parts list value is likely the correct + // one. + // Divided by U35, 74LS74. + static constexpr const XTAL CPU_CLOCK = 4.9152_MHz_XTAL / 2; + // Divided by U34, 74LS393. + static constexpr const XTAL INT_TIMER_CLOCK = CPU_CLOCK / 256; +}; + +void dmx_state::refresh_int_flipflop() +{ + // All component designations refer to the Processor Board. + + // Interrups are used for maintaining tempo. They are generated by an SR + // flipflop (U23B, 4013), with /Q connected to /INT. D is always 1. + // If CLK IN is not connected, the flipflop is clocked by the timer's output + // (U11, 40103). + // If CLK IN is connected, it will clock the flipflop, and the timer's + // output will be ignored. + // The Z80's interrupt acknowledge cycle clears the flipfop (flipflop + // "clear" connected to NOR(/M1, /IORQ) (U32, 4001)). + // CSYN (cassette sync) will also trigger interrupts, via the flipflop's + // "set" input. + // The circuit ensures that either the timer is enabled, or CSYN signals + // are generated, but not both. This prevents potentially conflicting + // interrupts. + + u8 new_int_flipflop_clock = 0; + if (m_clk_in->read() & 0x01) // Is CLK IN plugged? + { + new_int_flipflop_clock = (m_clk_in->read() & 0x02) ? 1 : 0; + } + else + { + // 40103 (U11) timer output is inverted by Q1, R20 and R43. + new_int_flipflop_clock = m_int_timer_out ? 0 : 1; + } + + if (!m_int_flipflop_clock && new_int_flipflop_clock) // 0->1 transition. + m_maincpu->set_input_line(INPUT_LINE_IRQ0, HOLD_LINE); + + m_int_flipflop_clock = new_int_flipflop_clock; + m_clk_out_tip = m_int_flipflop_clock; +} + +void dmx_state::int_timer_preset_w(u8 data) +{ + if (m_int_timer_preset == data) + return; + m_int_timer_preset = data; + LOGMASKED(LOG_INT_TIMER, "INT timer preset: %02x\n", m_int_timer_preset); +} + +void dmx_state::int_timer_enable_w(int state) +{ + // When the INT timer is enabled, the cassette sync INT generator is + // disabled, and vice versa. Cassette functionality (including sync) is + // not currently emulated. + if (m_int_timer_enabled == bool(state)) + return; + + // The CLEN signal is inverted by U28 (4011 NAND), but it is active low + // since it is connected on the /CE input of a 40103. So, logically, + // "1" == "enabled". + m_int_timer_enabled = bool(state); + LOGMASKED(LOG_INT_TIMER, "INT timer enabled: %d\n", m_int_timer_enabled); +} + +TIMER_DEVICE_CALLBACK_MEMBER(dmx_state::int_timer_tick) +{ + if (!m_int_timer_enabled) + return; + + if (m_int_timer_value <= 0) // We reached 0 in the previous clock cycle. + { + // The 40103 timer is configured for synchronous loading of the preset + // value. So it starts over in the next clock cycle after reaching 0. + // TODO: Actually verify that a preset of 0 means 256. Not clear in + // the datasheet. + if (m_int_timer_preset == 0) + m_int_timer_value = 256; + else + m_int_timer_value = m_int_timer_preset; + + // The timer's output transitions to 1 in the clock cycle after reaching + // 0 (the one we are handling here). + m_int_timer_out = 1; + refresh_int_flipflop(); + return; + } + + --m_int_timer_value; + + if (m_int_timer_value <= 0) + { + // The timer's output goes low when its counter reaches 0. + m_int_timer_out = 0; + refresh_int_flipflop(); + } +} + +void dmx_state::metronome_trigger_w(u8 /*data*/) +{ + // Writing to this port clocks U33A (D-flipflop). R62 and C32 form an RC + // network that resets the flipflop after ~1ms. This becomes a 1ms, 10-12V + // pulse (via Q8, R55, R54) on the "metronome out" connection. + // (component designations refer to the Processor Board) + m_metronome_timer->adjust(attotime::from_msec(1)); + m_metronome = 1; +} + +void dmx_state::metronome_mix_w(u8 data) +{ + // D0 connected to D of U35 74LS74 (D-flipflop), which is clocked by + // CLICK* from the address decoder (U7, 74LS42). + + // The service manual states that this bit enables mixing the metronome into + // the main mix, and that the LVI signal controls the volume. But that seems + // to describe how the firmware uses these. Looking at the schematic, it + // seems that both this signal and LVI would enable mixing on their own, and + // setting both to 1 would increase the volume. + m_metronome_mix = BIT(data, 0); +} + +TIMER_DEVICE_CALLBACK_MEMBER(dmx_state::metronome_timer_tick) +{ + m_metronome = 0; +} + +void dmx_state::display_w(offs_t offset, u8 data) +{ + // Decoding between different displays is done by U2 (74LS42). The + // m_digit_device array is ordered from right to left (U6-U3). All + // components are on the Switch Board. + m_digit_device[offset >> 2]->bus_w(offset & 0x03, data); +} + +template void dmx_state::display_output_w(offs_t offset, u16 data) +{ + // The displays and the digits within each display are addressed from right + // to left with increassing offset. Reversing that order in m_digit_output, + // since left-to-right is more convenient for building the layout. + static_assert(DISPLAY >= 0 && DISPLAY < 4); + const int display_in_layout = 3 - DISPLAY; + const int digit_in_layout = 3 - (offset & 0x03); + m_digit_output[4 * display_in_layout + digit_in_layout] = data; +} + +u8 dmx_state::buttons_r(offs_t offset) +{ + // Switches are inverted and buffered by 2 x 80C98 (inverting 3-state + // buffers), U7 and U8 on Switch Board. + return ~m_buttons[offset]->read(); +} + +u8 dmx_state::external_triggers_r() +{ + // External triggers are inverted by 2 X CA3086 NPN arrays (U4 for D0-D3 and + // U5 for D4-D7) and buffered by U6 (74LS244). All components are on the + // Processor Board. + return ~m_external_triggers->read(); +} + +u8 dmx_state::cassette_r() +{ + // All components are on the Processor Board. + // Inputs buffered by U19 (75LS244). This is a mix of cassette-related + // inputs and the states of foot-switches and toggle-switches. + + const u8 data = m_switches->read(); + + const u8 d0 = 0; // CD0: not yet implemented. + const u8 d1 = 0; // CD1: not yet implemented. + const u8 d2 = 0; // CD2: not yet implemented. + + const u8 d3 = BIT(data, 3); // CASEN* (cassette enable switch. Active low). + + // START footswitch input is pulled high (R41) and ANDed with 1 (U22, + // 74LS08, with the other input tied high via R42). So U22 is logically a + // no-op. However, the NEXT footswitch is tied directly to U19. + const u8 d4 = BIT(data, 4); // START* (output of U22). + const u8 d5 = BIT(data, 5); // NEXT (pulled high by R58). + const u8 d6 = BIT(data, 6); // TSYN* (pulled high by R23). + const u8 d7 = BIT(data, 7); // PROT* (memory protect switch. Active low). + + return (d7 << 7) | (d6 << 6) | (d5 << 5) | (d4 << 4) | + (d3 << 3) | (d2 << 2) | (d1 << 1) | d0; +} + +template void dmx_state::gen_trigger_w(u8 data) +{ + // Triggers are active-high and only last for the duration of the write. + // They are implemented by enabling an 74LS244 (U8 and U9) whose outputs are + // normally pulled low. + + static_assert(GROUP >= 0 && GROUP <= 1); + static constexpr const int NUM_VOICE_CARDS = 8; + static constexpr const int TRIGGER_MAPPINGS[2][NUM_VOICE_CARDS] = + { + {0, 2, 4, 6, 1, 3, 5, 7}, + {8, 14, 10, 12, 9, 15, 11, 13}, + }; + + if (data == 0) + return; + + std::vector triggers(2 * NUM_VOICE_CARDS, false); + for (int i = 0; i < NUM_VOICE_CARDS; ++i) + if (BIT(data, i)) + triggers[TRIGGER_MAPPINGS[GROUP][i]] = true; + + for (int i = 0; i < NUM_VOICE_CARDS; ++i) + { + const bool tr0 = triggers[2 * i]; + const bool tr1 = triggers[2 * i + 1]; + if (tr0 || tr1) + { + LOGMASKED(LOG_TRIGGERS, "Triggered Voice: %d, tr0: %d, tr1: %d\n", + i, tr0, tr1); + } + } +} + +void dmx_state::memory_map(address_map &map) +{ + // Component designations refer to the Processor Board. + // Memory decoding done by U2 (74LS42) and U22 (74LS08). + map.global_mask(0x3fff); // A14 and A15 not used. + map(0x0000, 0x1fff).rom(); // 2 x 2732 (4K x 8bit) ROMs, U18-U17. + // 4 x 6116 (2K x 8bit) RAMs, U16-U13. U3 (4071, OR-gate) will only allow + // RAM select signals to go through if fully powered up. + map(0x2000, 0x3fff).ram().share(NVRAM_TAG); +} + +void dmx_state::io_map(address_map &map) +{ + // Signal names (e.g. GEN0*) match those in the schematics. + + map.global_mask(0x7f); // A7-A15 not used for port decoding or I/O. + + // Write decoding done by U7 (74LS42) on the Processor Board. + map(0x00, 0x0f).w(FUNC(dmx_state::display_w)); // ID*. + map(0x10, 0x10).mirror(0x0f).w(FUNC(dmx_state::gen_trigger_w<0>)); // GEN0* + map(0x20, 0x20).mirror(0x0f).w(FUNC(dmx_state::gen_trigger_w<1>)); // GEN1* + map(0x30, 0x30).mirror(0x0f).w(FUNC(dmx_state::int_timer_preset_w)); // STIM* + map(0x40, 0x40).mirror(0x0f).w(FUNC(dmx_state::metronome_trigger_w)); // METRONOME OUT + map(0x50, 0x50).mirror(0x0f).w("cas_latch", FUNC(output_latch_device::write)); // CAS0* + map(0x60, 0x60).mirror(0x0f).w(FUNC(dmx_state::metronome_mix_w)); // CLICK* + + // Read decoding done by U9 (74LS42) on the Switch Board. + map(0x00, 0x05).mirror(0x78).r(FUNC(dmx_state::buttons_r)); // IORD* + map(0x06, 0x06).mirror(0x78).r(FUNC(dmx_state::external_triggers_r)); // SW6* + map(0x07, 0x07).mirror(0x78).r(FUNC(dmx_state::cassette_r)); // CASI* +} + +void dmx_state::machine_start() +{ + m_clk_out_tip.resolve(); + m_metronome_mix.resolve(); + m_metronome.resolve(); + m_digit_output.resolve(); + + save_item(NAME(m_int_timer_preset)); + save_item(NAME(m_int_timer_enabled)); + save_item(NAME(m_int_timer_value)); + save_item(NAME(m_int_timer_out)); + save_item(NAME(m_int_flipflop_clock)); +} + +void dmx_state::dmx(machine_config &config) +{ + Z80(config, m_maincpu, CPU_CLOCK); + m_maincpu->set_addrmap(AS_PROGRAM, &dmx_state::memory_map); + m_maincpu->set_addrmap(AS_IO, &dmx_state::io_map); + + NVRAM(config, NVRAM_TAG, nvram_device::DEFAULT_ALL_0); + + // 40103 timer (U11 on Processor Board). See refresh_int_flipflop() and + // int_timer_tick(). + TIMER(config, "int_timer_u11").configure_periodic( + FUNC(dmx_state::int_timer_tick), attotime::from_hz(INT_TIMER_CLOCK)); + + // A one-shot timer based on an RC-cicruit. See metronome_trigger_w(). + TIMER(config, m_metronome_timer).configure_generic( + FUNC(dmx_state::metronome_timer_tick)); + + DL1414T(config, m_digit_device[0], 0U).update().set(FUNC(dmx_state::display_output_w<0>)); + DL1414T(config, m_digit_device[1], 0U).update().set(FUNC(dmx_state::display_output_w<1>)); + DL1414T(config, m_digit_device[2], 0U).update().set(FUNC(dmx_state::display_output_w<2>)); + DL1414T(config, m_digit_device[3], 0U).update().set(FUNC(dmx_state::display_output_w<3>)); + + config.set_default_layout(layout_oberheim_dmx); + + // 74C174 (U20, on Processor Board), 6-bit latch. + output_latch_device &cas_latch(OUTPUT_LATCH(config, "cas_latch")); + cas_latch.bit_handler<0>().set_output("CDATO"); + cas_latch.bit_handler<1>().set_output("ARM"); + cas_latch.bit_handler<2>().set(FUNC(dmx_state::int_timer_enable_w)); + // Bit 3 is an open-collector connection to the Ring of the "CLK OUT" TRS jack. + cas_latch.bit_handler<3>().set_output("CLK_OUT_ring").invert(); + cas_latch.bit_handler<4>().set_output("LVI"); + // Bit 5 not connected. +} + +DECLARE_INPUT_CHANGED_MEMBER(dmx_state::clk_in_changed) +{ + refresh_int_flipflop(); +} + +INPUT_PORTS_START(dmx) + PORT_START("buttons_0") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("2") PORT_CODE(KEYCODE_2_PAD) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("3") PORT_CODE(KEYCODE_3_PAD) + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("4") PORT_CODE(KEYCODE_4_PAD) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("5") PORT_CODE(KEYCODE_5_PAD) + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("6") PORT_CODE(KEYCODE_6_PAD) + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("7") PORT_CODE(KEYCODE_7_PAD) + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("8") PORT_CODE(KEYCODE_8_PAD) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("9") PORT_CODE(KEYCODE_9_PAD) + + PORT_START("buttons_1") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("0") PORT_CODE(KEYCODE_0_PAD) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("1") PORT_CODE(KEYCODE_1_PAD) + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("<") PORT_CODE(KEYCODE_LEFT) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME(">") PORT_CODE(KEYCODE_RIGHT) + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SWING") PORT_CODE(KEYCODE_BACKSPACE) + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SONG") PORT_CODE(KEYCODE_INSERT) + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("EDIT") PORT_CODE(KEYCODE_HOME) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("STEP") PORT_CODE(KEYCODE_PGUP) + + PORT_START("buttons_2") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("RECORD") PORT_CODE(KEYCODE_ENTER) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("ERASE") PORT_CODE(KEYCODE_DEL) + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("COPY") PORT_CODE(KEYCODE_END) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("LENGTH") PORT_CODE(KEYCODE_L) + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("PLAY") PORT_CODE(KEYCODE_SPACE) + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TEMPO") PORT_CODE(KEYCODE_OPENBRACE) + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("QUANT") PORT_CODE(KEYCODE_BACKSLASH) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SIGN") PORT_CODE(KEYCODE_CLOSEBRACE) + + PORT_START("buttons_3") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("BASS 3") PORT_CODE(KEYCODE_Z) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SNARE 3") PORT_CODE(KEYCODE_X) + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("HI-HAT OPEN") PORT_CODE(KEYCODE_C) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TOM 3") PORT_CODE(KEYCODE_V) + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TOM 6") PORT_CODE(KEYCODE_B) + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("CYMB 3") PORT_CODE(KEYCODE_N) + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("RIMSHOT") PORT_CODE(KEYCODE_M) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("CLAPS") PORT_CODE(KEYCODE_COMMA) + + PORT_START("buttons_4") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("BASS 2") PORT_CODE(KEYCODE_A) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SNARE 2") PORT_CODE(KEYCODE_S) + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("HI-HAT ACC") PORT_CODE(KEYCODE_D) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TOM 2") PORT_CODE(KEYCODE_F) + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TOM 5") PORT_CODE(KEYCODE_G) + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("CYMB 2") PORT_CODE(KEYCODE_H) + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TAMP 2") PORT_CODE(KEYCODE_J) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SHAKE 2") PORT_CODE(KEYCODE_K) + + PORT_START("buttons_5") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("BASS 1") PORT_CODE(KEYCODE_Q) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SNARE 1") PORT_CODE(KEYCODE_W) + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("HI-HAT CLOSED") PORT_CODE(KEYCODE_E) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TOM 1") PORT_CODE(KEYCODE_R) + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TOM 4") PORT_CODE(KEYCODE_T) + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("CYMB 1") PORT_CODE(KEYCODE_Y) + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TAMB 1") PORT_CODE(KEYCODE_U) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SHAKE 1") PORT_CODE(KEYCODE_I) + + PORT_START("switches") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("CASSETTE ENABLE") PORT_TOGGLE + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("START") // Footswitch. + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("NEXT") // Footswitch. + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SYNC IN middle") // Plug input. + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("MEMORY PROTECT") PORT_TOGGLE + + PORT_START("external_triggers") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("BASS TRIG") + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("PERC1 TRIG") + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("CYMBAL TRIG") + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("TOM2 TRIG") + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("TOM1 TRIG") + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("HI-HAT TRIG") + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("SNARE TRIG") + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("PERC2 TRIG") + + PORT_START("clk_in") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("CLK IN plugged") PORT_CODE(KEYCODE_QUOTE) PORT_TOGGLE + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("CLK IN") PORT_CODE(KEYCODE_SLASH) + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(dmx_state::clk_in_changed), 0) + + // Fader potentiometers. P1-P10 on the Switch Board. + PORT_START("fader_p1") + PORT_ADJUSTER(100, "BASS") + PORT_START("fader_p2") + PORT_ADJUSTER(100, "SNARE") + PORT_START("fader_p3") + PORT_ADJUSTER(100, "HI-HAT") + PORT_START("fader_p4") + PORT_ADJUSTER(100, "TOM1") + PORT_START("fader_p5") + PORT_ADJUSTER(100, "TOM2") + PORT_START("fader_p6") + PORT_ADJUSTER(100, "CYMBAL") + PORT_START("fader_p7") + PORT_ADJUSTER(100, "PERC1") + PORT_START("fader_p8") + PORT_ADJUSTER(100, "PERC2") + PORT_START("fader_p9") + PORT_ADJUSTER(100, "MET") + PORT_START("fader_p10") + PORT_ADJUSTER(100, "VOLUME") +INPUT_PORTS_END + +ROM_START(obdmx) + ROM_REGION(0x2000, MAINCPU_TAG, 0) // 2 x 2732 (4K x 8bit) ROMs, U18 and U17. + ROM_DEFAULT_BIOS("rev_f") + + ROM_SYSTEM_BIOS(0, "debug", "DMX debug firmware") + ROMX_LOAD("dmx_dbg.u18", 0x000000, 0x001000, CRC(b3a24d64) SHA1(99a64b92f1520aaf2e7117b2fc75bdbd847f3a7f), ROM_BIOS(0)) + ROMX_FILL(0x001000, 0x001000, 0xff, ROM_BIOS(0)) // No ROM needed in U17. + + ROM_SYSTEM_BIOS(1, "rev_b", "DMX 1.00 (Revision B), 1981") + ROMX_LOAD("dmx_b0.u18", 0x000000, 0x001000, CRC(69116c5b) SHA1(29448b60d7f864b0a9df2a786b877feb7b019c05), ROM_BIOS(1)) + ROMX_LOAD("dmx_b1.u17", 0x001000, 0x001000, CRC(49084dee) SHA1(fffe4e6592dc586c0a0f03bed18308df38b991da), ROM_BIOS(1)) + + ROM_SYSTEM_BIOS(2, "rev_f", "DMX 2.21 (Revision F), 1982") + ROMX_LOAD("dmx_f0.u18", 0x000000, 0x001000, CRC(a01e5e7d) SHA1(74faf9c056c7e3eb89ed7657d8b64b1d3fe0ec22), ROM_BIOS(2)) + ROMX_LOAD("dmx_f1.u17", 0x001000, 0x001000, CRC(b24d4040) SHA1(74e7ae87cdcf9442cf76b696970c0efe52a30262), ROM_BIOS(2)) +ROM_END + +} // anonymous namespace + +SYST(1980, obdmx, 0, 0, dmx, dmx, dmx_state, empty_init, "Oberheim", "DMX", MACHINE_SUPPORTS_SAVE | MACHINE_NOT_WORKING | MACHINE_NO_SOUND) + -- cgit v1.2.3