diff options
author | 2025-02-04 09:21:46 -0800 | |
---|---|---|
committer | 2025-02-04 18:21:46 +0100 | |
commit | b29aa542f8862015b1f59b48592b5c474236244f (patch) | |
tree | bf920cf2898d72cae3a021ce5a78ea7d13f4845d | |
parent | 59e516f8ed71f9ab945b5d0310b248e33c7beecf (diff) |
linn/linndrum.cpp: Added driver for LinnDrum. (#13330)
* linn/linndrum.cpp: Added driver for LinnDrum.
* linndrum: Fixed typos.
-rw-r--r-- | src/mame/linn/linndrum.cpp | 495 | ||||
-rw-r--r-- | src/mame/mame.lst | 3 |
2 files changed, 498 insertions, 0 deletions
diff --git a/src/mame/linn/linndrum.cpp b/src/mame/linn/linndrum.cpp new file mode 100644 index 00000000000..2614c3a3e39 --- /dev/null +++ b/src/mame/linn/linndrum.cpp @@ -0,0 +1,495 @@ +// license:BSD-3-Clause +// copyright-holders:m1macrophage + +/* +The LinnDrum (unofficially also known as LM-2) is a digital drum machine. It +stores and plays digital samples (recordings of acoustic dumps), some of which +are processed by analog filters or other analog circuits. + +The firmware runs on a Z80. It controls the UI (reads buttons, drives displays +and LEDs), synchronization with other devices, cassette I/O, and voice +triggering. + +The LinnDrum has 12-voice polyphony, not including the "click" (metronome). +It has a total of 15 voices, along with a "click" sound. Some of the voices have +variations, which brings the total to 24 different sounds. + +The driver is based on the LinnDrum's service manual and schematics, and is +intended as an educational tool. + +Most of the digital functionality is emulated, though it is not very usable due +to the lack of a layout. Audio is not yet emulated. + +PCBoards: +* CPU board. 2 sections in schematics: + * Computer. + * Input/Output. +* DRM board. 4 sections in schematics: + * MUX drums. + * MUX drum output stage. + * Snare/Sidestick. + * Tom/Cga. +* MXR board. +* 5 VSR board (power supply). + +Usage: + +Since there is no layout yet, usability is limited. + +Run the driver with `-log`: +./mame -window linndrum -log + +Tail the log file: +(on linux): tail -f error.log + +Press a few of the assigned buttons (B for bass drum, N for snare, T for toms), +and see the voice triggers in the log. +*/ + +#include "emu.h" +#include "cpu/z80/z80.h" +#include "machine/nvram.h" +#include "machine/output_latch.h" +#include "machine/rescap.h" +#include "machine/timer.h" + +#define LOG_KEYBOARD (1U << 1) +#define LOG_DEBOUNCE (1U << 2) +#define LOG_TEMPO (1U << 3) +#define LOG_TEMPO_CHANGE (1U << 4) +#define LOG_TRIGGERS (1U << 5) +#define LOG_TAPE_SYNC_ENABLE (1U << 6) + +#define VERBOSE (LOG_GENERAL | LOG_TEMPO_CHANGE | LOG_TAPE_SYNC_ENABLE | LOG_TRIGGERS) +//#define LOG_OUTPUT_FUNC osd_printf_info + +#include "logmacro.h" + +namespace { + +constexpr const char MAINCPU_TAG[] = "z80"; +constexpr const char NVRAM_TAG[] = "nvram"; + +class linndrum_state : public driver_device +{ +public: + linndrum_state(const machine_config &mconfig, device_type type, const char *tag) ATTR_COLD + : driver_device(mconfig, type, tag) + , m_maincpu(*this, MAINCPU_TAG) + , m_tempo_timer(*this, "tempo_timer_556_u30a") + , m_debounce_timer(*this, "debounce_timer_556_u30b") + , m_keyboard(*this, "keyboard_col_%d", 0) + , m_playstop(*this, "play_stop") + , m_triggers(*this, "trigger_inputs") + , m_tempo_pot(*this, "pot_tempo") + , m_step_display(*this, "display_step_%d", 0U) + , m_pattern_display(*this, "display_pattern_%d", 0U) + , m_tape_sync_out(*this, "output_tape_sync") + { + } + + void linndrum(machine_config &config) ATTR_COLD; + + DECLARE_INPUT_CHANGED_MEMBER(tempo_pot_adjusted); + +protected: + void machine_start() override ATTR_COLD; + void machine_reset() override ATTR_COLD; + +private: + u8 keyboard_r(offs_t offset); + u8 inport_r(); + template<int DISPLAY> void display_w(u8 data); + + u8 start_debounce_r(); + void start_debounce_w(u8 data); + TIMER_DEVICE_CALLBACK_MEMBER(debounce_timer_elapsed); + + void update_tempo_timer(); + TIMER_DEVICE_CALLBACK_MEMBER(tempo_timer_tick); + + void trigger_w(offs_t offset, u8 data); + void trigger_beep_w(int state); + void data_out_enable_w(int state); + void tape_sync_enable_w(int state); + void update_tape_sync_out(); + + void memory_map(address_map &map) ATTR_COLD; + void io_map(address_map &map) ATTR_COLD; + + required_device<z80_device> m_maincpu; + required_device<timer_device> m_tempo_timer; // 556, U30A. + required_device<timer_device> m_debounce_timer; // 556, U30B. + required_ioport_array<6> m_keyboard; + required_ioport m_playstop; + required_ioport m_triggers; + required_ioport m_tempo_pot; + output_finder<2> m_step_display; // 2 x MAN4710. + output_finder<2> m_pattern_display; // 2 x MAN4710. + output_finder<> m_tape_sync_out; + + bool m_debouncing = false; + bool m_tempo_state = false; + bool m_tape_sync_enabled = false; + bool m_data_out_enabled = false; + + static constexpr const int NUM_VOICE_TRIGGERS = 11; + static constexpr const char *VOICE_TRIGGER_NAMES[NUM_VOICE_TRIGGERS] = + { + "BASS", "SNARE", "HI-HAT", "TOM / CGA", "RIDE", "CRASH", "CABASA", + "TAMB", "COWBELL", "CLAP", "CLICK" + }; + + static constexpr const int DISPLAY_STEP = 0; + static constexpr const int DISPLAY_PATTERN = 1; +}; + +u8 linndrum_state::keyboard_r(offs_t offset) +{ + // Columns in the keyboard matrix are selected by A0-A5. Each of these + // address lines are inverted by U29 (74LS05). A high at Ax enables column + // x. If more than one of A0-A5 are set, multiple columns will be selected. + // Rows are pulled up by a resistor array (RP8), buffered by U28 (74LS244) + // and connected to D0-D5. + u8 selected = 0x3f; // D0-D5. + for (int i = 0; i < 6; ++i) + if (BIT(offset, i)) + selected &= m_keyboard[i]->read(); + + const u8 d6 = m_debouncing ? 1 : 0; + + // The play/stop button is not part of the keyboard matrix. It is always + // read at D7, regardless of the selected column. The play/stop button and + // footswitch are wired such that if any of them is grounded (activated) a + // 0 will be returned in D7. + const bool play_button_pressed = !(m_playstop->read() & 0x01); + const bool play_footswitch_pressed = !(m_playstop->read() & 0x02); + const u8 d7 = (play_button_pressed || play_footswitch_pressed) ? 0 : 1; + + if (selected != 0x3f || d7 == 0) + { + LOGMASKED(LOG_KEYBOARD, + "Offset: %02x, keys: %02x, debounce: %d, play: %d\n", + offset, selected, d6, d7); + } + + return (d7 << 7) | (d6 << 6) | selected; +} + +u8 linndrum_state::inport_r() +{ + const u8 d0 = m_tempo_state ? 1 : 0; // Tempo. + + // The cassette input and sync input circuits are identical. + // TODO: Determine value at rest. + const u8 d1 = 0; // Sync input. TODO: implement. + const u8 d2 = 0; // Cassette input. TODO: implement. + + const u8 triggers = m_triggers->read() & 0x1f; + + return (triggers << 3) | (d2 << 2) | (d1 << 1) | d0; +} + +template<int DISPLAY> void linndrum_state::display_w(u8 data) +{ + static constexpr const u8 PATTERNS[16] = // 4 x 74LS47 (U24-U27). + { + 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7c, 0x07, + 0x7f, 0x67, 0x58, 0x4c, 0x62, 0x69, 0x78, 0x00, + }; + + const u8 ms_digit = PATTERNS[(data >> 4) & 0x0f]; + const u8 ls_digit = PATTERNS[data & 0x0f]; + + static_assert(DISPLAY == DISPLAY_STEP || DISPLAY == DISPLAY_PATTERN); + if (DISPLAY == DISPLAY_STEP) + { + m_step_display[0] = ms_digit; + m_step_display[1] = ls_digit; + } + else + { + m_pattern_display[0] = ms_digit; + m_pattern_display[1] = ls_digit; + } +} + +u8 linndrum_state::start_debounce_r() +{ + if (!machine().side_effects_disabled()) + start_debounce_w(0); + return 0x00; // D0-D7 pulled low by RP10. +} + +void linndrum_state::start_debounce_w(u8 /*data*/) +{ + static constexpr const float R3 = RES_K(30); + static constexpr const float C3 = CAP_U(0.1); + static constexpr attotime INTERVAL = PERIOD_OF_555_MONOSTABLE(R3, C3); + + m_debouncing = true; + m_debounce_timer->adjust(INTERVAL); + LOGMASKED(LOG_DEBOUNCE, "Debounce timer: start\n"); +} + +TIMER_DEVICE_CALLBACK_MEMBER(linndrum_state::debounce_timer_elapsed) +{ + m_debouncing = false; + LOGMASKED(LOG_DEBOUNCE, "Debounce timer: elapsed\n"); +} + +void linndrum_state::update_tempo_timer() +{ + static constexpr const float R1 = RES_K(5.1); + static constexpr const float R2 = RES_K(18); + static constexpr const float C2 = CAP_U(0.1); + static constexpr const float P1_MAX = RES_K(100); + + // Using `100 - pot value` because the higher (the more clockwise) the pot + // is turned, the lower the resistance and the fastest the tempo. + const float tempo_r = (100 - m_tempo_pot->read()) * P1_MAX / 100.0f; + const attotime period = PERIOD_OF_555_ASTABLE(R1, R2 + tempo_r, C2); + m_tempo_timer->adjust(period, 0, period); + LOGMASKED(LOG_TEMPO_CHANGE, "Tempo adjusted: %f\n", period.as_double()); +} + +TIMER_DEVICE_CALLBACK_MEMBER(linndrum_state::tempo_timer_tick) +{ + // The output of the 556 clocks an 74LS74 (U8B) that is wired in a + // divide-by-2 configuration. Each time the timer elapses, m_tempo_state + // will get negated. + m_tempo_state = !m_tempo_state; + update_tape_sync_out(); + LOGMASKED(LOG_TEMPO, "Tempo timer elapsed: %d\n", m_tempo_state); +} + +void linndrum_state::trigger_w(offs_t offset, u8 data) +{ + assert(offset >= 0 && offset < NUM_VOICE_TRIGGERS); + LOGMASKED(LOG_TRIGGERS, "Trigger %s (%02x), data: %02x, data enabled: %d\n", + VOICE_TRIGGER_NAMES[offset], offset, data, m_data_out_enabled); + // TODO: Implement. +} + +void linndrum_state::trigger_beep_w(int state) +{ + LOGMASKED(LOG_TRIGGERS, "Trigger BEEP: %d, data enabled: %d\n", + state, m_data_out_enabled); + // TODO: Implement. +} + +void linndrum_state::data_out_enable_w(int state) +{ + // Controls whether data (D0-D3) is transmitted to the voice circuits. This + // is done by U19 (74LS365 hex buffer. Enable inputs are active-low). + // This is usually disabled to prevent interference from the "noisy" data + // bus to the voice circuits. + m_data_out_enabled = !state; +} + +void linndrum_state::tape_sync_enable_w(int state) +{ + LOGMASKED(LOG_TAPE_SYNC_ENABLE, "Tape sync enable: %d\n", state); + m_tape_sync_enabled = bool(state); + update_tape_sync_out(); +} + +void linndrum_state::update_tape_sync_out() +{ + // tape_sync_enable (U16 latch, O5) is NANDed with the current tempo + // state (U34, 74LS00). The output is then inverted by Q3/R20/R21. + if (m_tape_sync_enabled) + m_tape_sync_out = m_tempo_state ? 1 : 0; + else + m_tape_sync_out = 0; +} + +void linndrum_state::memory_map(address_map &map) +{ + // Signal names (such as "/READ INPORT") are taken from the schematics. + + map.global_mask(0x3fff); // A14 and A15 are not used. + + map(0x0000, 0x1f7f).rom(); // 2 x 2732. U14-U13. + map(0x1f80, 0x1f80).mirror(0x003f).r(FUNC(linndrum_state::inport_r)); // /READ INPORT. + + // Writes to: 0x1f80 - 0x1fbf: /OUT STROBE EN. + // Outputs are selected by two 74LS138 (U20, U21). U20 is enabled when A3 is + // 0, U21 when it is 1. + map(0x1f80, 0x1f80).mirror(0x0030).w(FUNC(linndrum_state::display_w<DISPLAY_PATTERN>)); // U23 latch. + map(0x1f81, 0x1f81).mirror(0x0030).w(FUNC(linndrum_state::display_w<DISPLAY_STEP>)); // U22 latch. + map(0x1f82, 0x1f82).mirror(0x0030).w("latch_u18", FUNC(output_latch_device::write)); // LEDs. + map(0x1f83, 0x1f83).mirror(0x0030).w("latch_u17", FUNC(output_latch_device::write)); // LEDs. + map(0x1f84, 0x1f84).mirror(0x0030).w("latch_u16", FUNC(output_latch_device::write)); // LEDs & outputs. + map(0x1f85, 0x1f8f).mirror(0x0030).w(FUNC(linndrum_state::trigger_w)); // Data out. + + map(0x1fc0, 0x1fff).r(FUNC(linndrum_state::keyboard_r)); // /READ KEYBD. + map(0x2000, 0x3fff).ram().share(NVRAM_TAG); // 4 x HM6116LP4. U12-U9. +} + +void linndrum_state::io_map(address_map &map) +{ + map.global_mask(0xff); + + // The debouncing timer is started by the Z80's /IORQ signal itself. So + // there is no difference between read and write, and the address and data + // bits don't matter. + map(0x00, 0x00).mirror(0xff).r(FUNC(linndrum_state::start_debounce_r)).w(FUNC(linndrum_state::start_debounce_w)); +} + +void linndrum_state::machine_start() +{ + m_step_display.resolve(); + m_pattern_display.resolve(); + m_tape_sync_out.resolve(); + + save_item(NAME(m_debouncing)); + save_item(NAME(m_tempo_state)); + save_item(NAME(m_tape_sync_enabled)); + save_item(NAME(m_data_out_enabled)); +} + +void linndrum_state::machine_reset() +{ + update_tempo_timer(); +} + +void linndrum_state::linndrum(machine_config &config) +{ + // D0-D7 and A0-A11 are pulled low by RP10 and RP9. + Z80(config, m_maincpu, 4_MHz_XTAL / 2); // Divided by 74LS74, U8A. + m_maincpu->set_addrmap(AS_PROGRAM, &linndrum_state::memory_map); + m_maincpu->set_addrmap(AS_IO, &linndrum_state::io_map); + + NVRAM(config, NVRAM_TAG, nvram_device::DEFAULT_ALL_0); + + TIMER(config, m_tempo_timer).configure_generic( // 556, U30A. + FUNC(linndrum_state::tempo_timer_tick)); + TIMER(config, m_debounce_timer).configure_generic( // 556, U30B. + FUNC(linndrum_state::debounce_timer_elapsed)); + + // Latches connected to cathodes of LEDs (through resistors), so they are + // active-low. + + output_latch_device &u18(OUTPUT_LATCH(config, "latch_u18")); + u18.bit_handler<0>().set_output("led_1_8").invert(); + u18.bit_handler<1>().set_output("led_1_8_t").invert(); + u18.bit_handler<2>().set_output("led_1_16").invert(); + u18.bit_handler<3>().set_output("led_1_16_t").invert(); + u18.bit_handler<4>().set_output("led_1_32").invert(); + u18.bit_handler<5>().set_output("led_1_32_t").invert(); + u18.bit_handler<6>().set_output("led_hi").invert(); + u18.bit_handler<7>().set_output("led_play_stop").invert(); + + output_latch_device &u17(OUTPUT_LATCH(config, "latch_u17")); + u17.bit_handler<0>().set_output("led_a").invert(); + u17.bit_handler<1>().set_output("led_b").invert(); + u17.bit_handler<2>().set_output("led_c").invert(); + u17.bit_handler<3>().set_output("led_d").invert(); + u17.bit_handler<4>().set_output("led_e").invert(); + u17.bit_handler<5>().set_output("led_f").invert(); + u17.bit_handler<6>().set_output("led_load").invert(); + u17.bit_handler<7>().set_output("led_store").invert(); + + output_latch_device &u16(OUTPUT_LATCH(config, "latch_u16")); + u16.bit_handler<0>().set_output("led_percussion").invert(); + u16.bit_handler<1>().set_output("led_ext_sync").invert(); + u16.bit_handler<2>().set_output("led_pattern").invert(); + u16.bit_handler<2>().append_output("led_song"); // Inverted by U31A. + u16.bit_handler<3>().set(FUNC(linndrum_state::data_out_enable_w)); + u16.bit_handler<4>().set(FUNC(linndrum_state::trigger_beep_w)); + u16.bit_handler<5>().set(FUNC(linndrum_state::tape_sync_enable_w)); + // Output voltage divided by R24/R25 to 0V - 2.5V. + u16.bit_handler<6>().set_output("output_cassette"); + // Inverted by Q4/R22/R23. + u16.bit_handler<7>().set_output("output_trigger").invert(); +} + +DECLARE_INPUT_CHANGED_MEMBER(linndrum_state::tempo_pot_adjusted) +{ + update_tempo_timer(); +} + +// PORT_NAMEs are based on the annotations in the schematic. +INPUT_PORTS_START(linndrum) + PORT_START("keyboard_col_0") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("1") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("2") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("3") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("4") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("5") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("6") + + PORT_START("keyboard_col_1") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("7") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SONG/PAT.") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SONG#") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("END") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("DELETE") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("INSERT") + + PORT_START("keyboard_col_2") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("<-") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("->") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("ENTER") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("STORE") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("LOAD") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("EXT.SYNC") PORT_CODE(KEYCODE_S) + + PORT_START("keyboard_col_3") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("BPM/TRIG.") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SIDESTICK") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SNARE 1") PORT_CODE(KEYCODE_N) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SNARE 2") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SNARE 3") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("BASS 1") PORT_CODE(KEYCODE_B) + + PORT_START("keyboard_col_4") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("BASS 2") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("CRASH") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("PERC.") PORT_CODE(KEYCODE_P) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("CABASA1 / HIHAT1") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("CABASA2 / HIHAT2") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TAMP 1 / HIHAT O") + + PORT_START("keyboard_col_5") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TAMP 2 / HI TOM") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("HI CONGA / MID TOM") PORT_CODE(KEYCODE_T) + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("LO CONGA / LO TOM") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("COWBELL / RIDE 1") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("CLAPS / RIDE 2") PORT_CODE(KEYCODE_L) + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_UNUSED) + + // The play-stop button and footswitch input are both connected together + // in a way that if any of them is low, D6 of the keyboard port will read + // low. + PORT_START("play_stop") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("PLAY/STOP") PORT_CODE(KEYCODE_SPACE) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("PLAY/STOP FOOTSWITCH") + + // The trigger circuit will detect fast changes in the "trigger" inputs, and + // output a pulse. Slow changes won't trigger a pulse. So there is some + // flexibility in the types of input accepted. For now, trigger inputs + // are implemented as digital inputs (traditional trigger signals). + // TODO: Determine if active high or active low. + PORT_START("trigger_inputs") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("TRIGGER 5") + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("TRIGGER 4") + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("TRIGGER 3") + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("TRIGGER 2") + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("TRIGGER 1") + + PORT_START("pot_tempo") + PORT_ADJUSTER(50, "TEMPO") + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(linndrum_state::tempo_pot_adjusted), 0) +INPUT_PORTS_END + +ROM_START(linndrum) + ROM_REGION(0x2000, MAINCPU_TAG, 0) + ROM_LOAD("ld_2.1_a.u14", 0x000000, 0x001000, CRC(566d720e) SHA1(91b7a515e3d18a28b7f5428765ed79114a5a00fb)) + ROM_LOAD("ld_2.1_b.u13", 0x001000, 0x001000, CRC(9c9a5520) SHA1(6e4573051254051c75f9071fd8dfcd5a9184f9cc)) +ROM_END + +} // anonymous namespace + +SYST(1982, linndrum, 0, 0, linndrum, linndrum, linndrum_state, empty_init, "Linn Electronics", "LinnDrum", MACHINE_SUPPORTS_SAVE | MACHINE_NOT_WORKING | MACHINE_NO_SOUND) diff --git a/src/mame/mame.lst b/src/mame/mame.lst index f66b6545748..2157626eab7 100644 --- a/src/mame/mame.lst +++ b/src/mame/mame.lst @@ -25375,6 +25375,9 @@ free120 // (c) 1993 Liberty Electronics free200 // (c) 1983 Liberty Electronics free220 // (c) 1984 Liberty Electronics +@source:linn/linndrum.cpp +linndrum // Linn Electronics LinnDrum + @source:lsi/m3.cpp m3 // |