mirror of
https://github.com/holub/mame
synced 2025-07-06 10:29:38 +03:00
linn/linndrum.cpp: Added driver for LinnDrum. (#13330)
* linn/linndrum.cpp: Added driver for LinnDrum. * linndrum: Fixed typos.
This commit is contained in:
parent
59e516f8ed
commit
b29aa542f8
495
src/mame/linn/linndrum.cpp
Normal file
495
src/mame/linn/linndrum.cpp
Normal file
@ -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)
|
@ -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 //
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user