summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author m1macrophage <168948267+m1macrophage@users.noreply.github.com>2025-01-02 01:16:09 -0800
committer GitHub <noreply@github.com>2025-01-02 10:16:09 +0100
commit6a403fc29993062aa451f5557cc88abc2fe7b2e7 (patch)
treeaf6362792c9cdaa5b467e6f4c6a5e28c7f07a770
parent91710ed6690e05b324b6f09c055f9dd106b9728d (diff)
Advanced skeleton driver for the Memorymoog. (#13090)
* Advanced skeleton driver for Memorymoog. * Replace MACHINE_IS_SKELETON with MACHINE_NOT_WORKING | MACHINE_NOT_SOUND. * Removed an unecessary semicolon, converted an 'if' to an 'else if', improved comments. * Style fixes matching those in commit 9e463d2 for moog/source.cpp.
-rw-r--r--src/mame/mame.lst3
-rw-r--r--src/mame/moog/memorymoog.cpp1137
2 files changed, 1140 insertions, 0 deletions
diff --git a/src/mame/mame.lst b/src/mame/mame.lst
index 4f2ba735862..990f5ef398b 100644
--- a/src/mame/mame.lst
+++ b/src/mame/mame.lst
@@ -32729,6 +32729,9 @@ sshot // (c) 1979 Model Racing
@source:modelracing/subhuntr.cpp
subhuntr // 1979 Model Racing
+@source:moog/memorymoog.cpp
+memorymoog // Moog Memorymoog
+
@source:moog/source.cpp
moogsource // Moog Source
diff --git a/src/mame/moog/memorymoog.cpp b/src/mame/moog/memorymoog.cpp
new file mode 100644
index 00000000000..2d1f4160538
--- /dev/null
+++ b/src/mame/moog/memorymoog.cpp
@@ -0,0 +1,1137 @@
+// license:BSD-3-Clause
+// copyright-holders:m1macrophage
+
+/*
+The memorymoog is a CPU-controlled, 6-voice, polyphonic analog synthesizer.
+
+The architecture is typical of polyphonic analog synthesizers of the time. The
+firmware:
+* Scans the keyboard and buttons.
+* Scans the value of potentiometers. This synthesizer lacks an ADC, so the
+ conversion is done by outputing a voltage to the DAC, and comparing that
+ to the voltage generated by the pot, using successive approximation (binary
+ search).
+* Sets control voltages.
+* Routes audio and modulation by controlling analog switches.
+* Controls LEDs, displays and cassette I/O.
+
+Each voice receives its own pitch and triggers, but all other parameters are
+shared across voices. The modulation sources (envelope generators, LFOs) are
+analog.
+
+This driver is based on the memorymoog service manual and schematics. It is
+intended as an educational tool. Even though it is marked as a skeleton,
+emulation of the digital and digital-analog interface is pretty far along.
+TODOs throughout the code call out the missing pieces. There is no attempt to
+emulate the analog audio circuits.
+
+There is no layout yet. You can run mame with `-output console`, in order to
+observe internal stage changes.
+
+The strings used in output and port names, enums, read and write handlers, etc.,
+reflect those in the schematics.
+
+PCBoards:
+1 - 6 voice boards (1A-1F)
+2 - Common Analog
+3 - Contour & Glide
+4 - Digital
+5 - DMUX
+6 - Right-side control (RSC)
+7 - Left-side control (LSC)
+8 - Display
+9 - Jack interface
+10 - Lehd-hand control (LHC)
+11 - AUX1
+12 - AUX2
+13 - Power Supply
+
+The boards most pertinent to this driver are 4 and 5 (digital logic and
+digital-to-analog conversion) and 6, 7, 8 and 10 (user interface). But other
+boards are ocassionally referenced as well.
+
+TODO:
+- Interactive layout.
+- Cassette input/output.
+- Enough analog emulation for autotune to work.
+*/
+
+#include "emu.h"
+
+#include "cpu/z80/z80.h"
+#include "machine/nvram.h"
+#include "machine/output_latch.h"
+#include "machine/z80ctc.h"
+#include "machine/z80daisy.h"
+#include "video/pwm.h"
+
+#define LOG_KEYPRESS (1U << 1)
+#define LOG_CV (1U << 2)
+#define LOG_ADC (1U << 3)
+
+#define VERBOSE (LOG_GENERAL|LOG_CV)
+//#define LOG_OUTPUT_FUNC osd_printf_info
+
+#include "logmacro.h"
+
+namespace {
+
+constexpr const char MAINCPU_TAG[] = "z80";
+constexpr const char CTC_TAG[] = "ctc";
+constexpr const char NVRAM_TAG[] = "nvram";
+
+class memorymoog_state : public driver_device
+{
+public:
+ memorymoog_state(const machine_config& mconfig, device_type type, const char* tag) ATTR_COLD
+ : driver_device(mconfig, type, tag)
+ , m_maincpu(*this, MAINCPU_TAG)
+ , m_ctc(*this, CTC_TAG)
+ , m_keyboard_io(*this, "keyboard_column_%d", 0U)
+ , m_switch_a_io(*this, "switch_a_row_%d", 0U)
+ , m_switch_b_io(*this, "switch_b_row_%d", 0U)
+ , m_pot_io(*this, "pot_%d", 0U)
+ , m_octave_io(*this, "octave_buttons")
+ , m_octave_minus_1_led(*this, "oct_m1")
+ , m_octave_0_led(*this, "oct_0")
+ , m_digit_device(*this, "pwm_digit_device")
+ , m_digits(*this, "digit_%d", 1U)
+ , m_char_device(*this, "pwm_char_device")
+ , m_chars(*this, "char_%d", 1U)
+ , m_led_matrix_device(*this, "pwm_led_matrix_device")
+ , m_leds(8)
+ , m_cv(NUM_CVS, -1)
+ {
+ for (int i = 0; i < 8; ++i)
+ {
+ for (int j = 0; j < 4; ++j)
+ {
+ m_leds[i].push_back(
+ output_finder<>(
+ *this, std::string("led_") + BOARD_6_LED_NAMES[i][j]));
+ }
+ for (int j = 0; j < 4; ++j)
+ {
+ m_leds[i].push_back(
+ output_finder<>(
+ *this, std::string("led_") + BOARD_7_LED_NAMES[i][j]));
+ }
+ }
+ }
+
+ void memorymoog(machine_config& config) ATTR_COLD;
+
+ DECLARE_INPUT_CHANGED_MEMBER(octave_button_pressed);
+
+protected:
+ void machine_start() override ATTR_COLD;
+
+private:
+ float get_cv() const;
+ bool adc_comparator_on() const;
+
+ u8 u26_low4_r();
+
+ template<int N> u8 key_matrix_r(
+ const required_ioport_array<N>& input, u8 input_mask, u8 selection,
+ const char* name);
+ u8 keyboard_r();
+ u8 switches_a_r();
+ u8 switches_b_r();
+
+ void update_sh();
+ void keyboard_w(u8 data);
+ void switches_w(u8 data);
+ void tape_relay_w(u8 data);
+ void dac_low_w(u8 data);
+ void cv_mux_control_w(offs_t offset, u8 data);
+ void led_drive_w(u8 data);
+ void led_latch_w(u8 data);
+ void led_update_w(offs_t offset, u8 data);
+ void digit_latch_w(u8 data);
+ void digit_update_w(offs_t offset, u8 data);
+ void char_latch_a_w(u8 data);
+ void char_latch_b_w(u8 data);
+ void char_update_w(offs_t offset, u8 data);
+
+ void memory_map(address_map& map) ATTR_COLD;
+ void io_map(address_map& map) ATTR_COLD;
+
+ required_device<z80_device> m_maincpu;
+ required_device<z80ctc_device> m_ctc;
+ required_ioport_array<8> m_keyboard_io;
+ required_ioport_array<8> m_switch_a_io;
+ required_ioport_array<6> m_switch_b_io;
+ required_ioport_array<31> m_pot_io;
+
+ u8 m_keyboard_columns = 0xff; // U28 (board 4).
+ u8 m_switch_rows = 0xff; // U6 (board 7).
+ u16 m_dac_latch = 0; // 12-bit DAC. U2: 8 MSbits, U1: 4LSbits (board 5).
+ s16 m_selected_sh = -1; // S&H DMUX. 0-63, -1 for "none selected".
+ s16 m_selected_pot = 0; // Potentiomenter MUX. 0-30.
+ bool m_positive_hysteresis = true;
+ bool m_negative_hysteresis = false;
+
+ required_ioport m_octave_io;
+ output_finder<> m_octave_minus_1_led; // LED 2, board 10.
+ output_finder<> m_octave_0_led; // LED 1, board 10.
+ bool m_octave_low = false;
+
+ // MAN6610. U3, board 7.
+ required_device<pwm_display_device> m_digit_device;
+ output_finder<2> m_digits;
+
+ // LT-1604. U4, board 7.
+ required_device<pwm_display_device> m_char_device;
+ output_finder<8> m_chars;
+ u16 m_char_led_source = 0x3fff;
+
+ required_device<pwm_display_device> m_led_matrix_device;
+ u8 m_led_sink = 0; // Also used as the sink for the LT-1604.
+ u8 m_led_source = 0;
+
+ std::vector<std::vector<output_finder<>>> m_leds;
+ std::vector<float> m_cv; // Control voltages. See CV_NAMES below.
+
+ // When these strings get converted to output names, they will include
+ // the "led_" prefix.
+ static constexpr const char* BOARD_6_LED_NAMES[8][4] =
+ {
+ {"osc1_2'", "osc1_4'", "osc1_8'", "osc1_16'"},
+ {"osc2_2'", "osc2_4'", "osc2_8'", "osc2_16'"},
+ {"osc3_2'", "osc3_4'", "osc3_8'", "osc3_16'"},
+ {"kybd_control", "sync_2to1", "NOT_CONNECTED_1", "low"},
+ {"osc3_ramp", "osc3_pulse", "osc3_tri", "release"},
+ {"osc2_ramp", "osc2_pulse", "osc2_tri", "uncond_cont"},
+ {"osc1_ramp", "osc1_pulse", "osc1_tri", "return_to_zero"},
+ {"2/3_kybd_trk", "NOT_CONNECTED_2", "1/3_kybd_trk", "kybd_follow"},
+ };
+ static constexpr const char* BOARD_7_LED_NAMES[8][4] =
+ {
+ {"saw_lfo", "osc2_freq_lfo", "hold", "kybd_mode"},
+ {"tri_lfo", "osc1_freq_lfo", "kybd_out", "mono"},
+ {"ramp_lfo", "osc3_freq_lfo", "arpeggiator", "glide"},
+ {"square_lfo", "pw1_lfo", "NOT_CONNECTED_3", "mult_trig"},
+ {"filter_lfo", "pw3_lfo", "fp2_osc2", "vm_freq1"},
+ {"sh_lfo", "pw2_lfo", "fp1_filter", "fp2_mod"},
+ {"osc3_amt", "fp1_pitch", "vm_pw2", "vm_filter"},
+ {"invert", "fp1_volume", "vm_pw1", "vm_freq2"},
+ };
+
+ static constexpr const int NUM_CVS = 64;
+ static constexpr const char* CV_NAMES[NUM_CVS] =
+ {
+ // U10
+ "GLIDE",
+ "GLIDE_MONO",
+ "PITCH_BEND_AMT",
+ "MOD_AMT",
+ "FOOT_PEDAL_1_AMT",
+ "FOOT_PEDAL_2_AMT",
+ "MOD_RATE",
+ "MOD_RATE_SEQ",
+
+ // U11
+ "VOICE_MOD_OSC3_AMT",
+ "VOICE_MOD_FILT_ENV",
+ "OSC_1_PW",
+ "OSC_2_FREQ",
+ "OSC_2_PW",
+ "OSC_3_FREQ",
+ "OSC_3_PW",
+ "OSC_1_AMT",
+
+ // U12
+ "OSC_2_AMT",
+ "OSC_3_AMT",
+ "NOISE_AMT",
+ "VCF_FREQ",
+ "EMPHASIS",
+ "VCF_CONTOUR_AMT",
+ "VCF_ATTACK",
+ "VCF_DECAY",
+
+ // U13
+ "VCF_SUSTAIN",
+ "VCF_RELEASE",
+ "LOUD_ATTACK",
+ "LOUD_DECAY",
+ "LOUD_SUSTAIN",
+ "LOUD_RELEASE",
+ "PROGRAMMABLE_VOL",
+ "NOT_CONNECTED_A",
+
+ // U14
+ "OSC_1_OCT",
+ "OSC_2_OCT",
+ "OSC_3_OCT",
+ "NOT_CONNECTED_B",
+ "NOT_CONNECTED_C",
+ "NOT_CONNECTED_D",
+ "TRANSPOSE_SCALE",
+ "MONO_KYBD_CV",
+
+ // U15
+ "RAW_KYBD_CV_A",
+ "RAW_KYBD_CV_B",
+ "RAW_KYBD_CV_C",
+ "RAW_KYBD_CV_D",
+ "RAW_KYBD_CV_E",
+ "RAW_KYBD_CV_F",
+ "AUTOTUNE_A1",
+ "AUTOTUNE_A2",
+
+ // U16
+ "AUTOTUNE_A3",
+ "AUTOTUNE_B1",
+ "AUTOTUNE_B2",
+ "AUTOTUNE_B3",
+ "AUTOTUNE_C1",
+ "AUTOTUNE_C2",
+ "AUTOTUNE_C3",
+ "AUTOTUNE_D1",
+
+ // U17
+ "AUTOTUNE_D2",
+ "AUTOTUNE_D3",
+ "AUTOTUNE_E1",
+ "AUTOTUNE_E2",
+ "AUTOTUNE_E3",
+ "AUTOTUNE_F1",
+ "AUTOTUNE_F2",
+ "AUTOTUNE_F3",
+ };
+
+ static constexpr const float V_REF = 10;
+};
+
+float memorymoog_state::get_cv() const
+{
+ // According to the Technical Service Info manual, the DAC is calibrated to
+ // output 10V (V_REF) when the upper 8 bits are all on and the lower 4 are
+ // all off.
+ return V_REF * m_dac_latch / 0xff0;
+}
+
+bool memorymoog_state::adc_comparator_on() const
+{
+ if (m_selected_pot == 31)
+ {
+ LOG("Firmware error: addressed unconnected mux input\n");
+ return true;
+ }
+
+ const float pot_v = m_pot_io[m_selected_pot]->read() * V_REF / 100;
+
+ // For details on how hysteresis is applied, see Technical Service Info on
+ // A/D circuitry.
+ // If both the positive and negative hysteresis circuits are enabled, the
+ // total will work out to ~0.
+ float hysteresis = 0;
+ if (m_positive_hysteresis)
+ hysteresis += 0.05;
+ if (m_negative_hysteresis)
+ hysteresis -= 0.05;
+
+ const float v = pot_v + hysteresis;
+ const float dac_v = get_cv();
+ const bool comp_on = v > dac_v;
+ if (m_selected_pot == 0)
+ {
+ LOGMASKED(LOG_ADC, "Comparator: %f %f %d %04x\n", v, dac_v, comp_on,
+ m_dac_latch);
+ }
+ return comp_on;
+}
+
+u8 memorymoog_state::u26_low4_r()
+{
+ // Component designations refer to Board 4 (digital), unless otherwise
+ // noted.
+
+ // The 2 MSbits of U26 are mapped to a different address. See keyboard_r().
+
+ // D0 <- Comparator (LM393, U23B) <- tape in (labelled "TO TAPE", J16.
+ // Maybe mixed up with "FROM TAPE"?). When not receiving tape input,
+ // the comparator will be comparing 2.5V against 2.5V, with some hysteresis.
+ // So it will settle to 0 or 1, arbitrarily, depending on the biasing
+ // resistor (R41-R44) tolerances.
+ // TODO: Emulate cassette.
+ const u8 d0 = 1;
+
+ // D1 <- Inverted by Q2 <- clock In (J11). J11 is is normalled to connection
+ // "DMUX P517-4" <- Common Analog board S21-3 <- Square LFO.
+ // D1 will either track the square LFO, or an external clock (if connected).
+ // TODO: Emulate LFO.
+ const u8 d1 = 1;
+
+ // D2 <- Approximation Comparator. Will be 1 when the scanned pot voltage is
+ // greater than the DAC output voltage.
+ const u8 d2 = adc_comparator_on() ? 1 : 0;
+
+ // D3 <- A connector labelled "N/C" (P47-2). Pulled high.
+ const u8 d3 = 1;
+
+ return 0xf0 | (d3 << 3) | (d2 << 2) | (d1 << 1) | d0;
+}
+
+template<int N> u8 memorymoog_state::key_matrix_r(
+ const required_ioport_array<N>& input, u8 input_mask, u8 selection,
+ const char* name)
+{
+ static_assert(N > 0 && N <= 8);
+
+ u8 pressed = 0xff;
+ for (int i = 0; i < N; ++i)
+ if (!BIT(selection, i)) // `selection` is active-low.
+ pressed &= input[i]->read();
+ pressed |= ~input_mask;
+
+ if (pressed != 0xff)
+ LOGMASKED(LOG_KEYPRESS, "Pressed %s %02X: %02X\n", name, selection,
+ pressed);
+
+ return pressed; // Returned value is active-low.
+}
+
+u8 memorymoog_state::keyboard_r()
+{
+ return key_matrix_r<8>(m_keyboard_io, 0xff, m_keyboard_columns, "Keyboard");
+}
+
+u8 memorymoog_state::switches_a_r()
+{
+ return key_matrix_r<8>(m_switch_a_io, 0x3f, m_switch_rows, "Switches A");
+}
+
+u8 memorymoog_state::switches_b_r()
+{
+ return key_matrix_r<6>(m_switch_b_io, 0x3f, m_switch_rows, "Switches B");
+}
+
+void memorymoog_state::update_sh()
+{
+ if (m_selected_sh < 0)
+ return;
+
+ const float cv = get_cv();
+ if (m_cv[m_selected_sh] == cv)
+ return;
+
+ m_cv[m_selected_sh] = cv;
+ // TODO: all autotune CVs are divided by a 115K-10K divider.
+ LOGMASKED(LOG_CV, "CV: %02d %-20s %04X - %f\n", m_selected_sh,
+ CV_NAMES[m_selected_sh], m_dac_latch, cv);
+}
+
+void memorymoog_state::keyboard_w(u8 data)
+{
+ m_keyboard_columns = data;
+ // TODO: D0 also connected to tape out J14 (labeled "FROM TAPE". Should it
+ // be "TO TAPE"?). See u26_low4_r().
+}
+
+void memorymoog_state::switches_w(u8 data)
+{
+ m_switch_rows = data;
+}
+
+void memorymoog_state::tape_relay_w(u8 data)
+{
+ // U33, D0.
+ // TODO: Implement.
+}
+
+void memorymoog_state::dac_low_w(u8 data)
+{
+ // Updates the 4 least significant bits of the 12-bit DAC.
+ m_dac_latch = (m_dac_latch & 0x0ff0) | (data & 0x0f);
+ m_positive_hysteresis = !BIT(data, 4); // Active low.
+ m_negative_hysteresis = BIT(data, 5); // Active high.
+ update_sh();
+}
+
+void memorymoog_state::cv_mux_control_w(offs_t offset, u8 data)
+{
+ // Writing to this port does multiple things:
+ // - Latches MSB (8 bits) for the 12-bit DAC.
+ // - Selects the CV to be written.
+ // - Generates wait states.
+ // - Selects which potentiometer's voltage is being compared to the DAC.
+
+ // Generate wait states.
+ if (!machine().side_effects_disabled())
+ {
+ // U31 (74LS393) and U12 (74LS02) create a 16-cycle wait state.
+ // U31, U33 (74LS74), U21 and U14 (74LS04) create a 4-cycle delay to
+ // allow the DAC to settle. The S&H MUX is then enabled, and there's
+ // another 12 cycles of waiting for the S&H capacitor to (dis)charge.
+ m_maincpu->adjust_icount(-(4 + 12));
+ }
+
+ // D0-D7 are latched onto the 8 most significant bits of the 12-bit DAC.
+ m_dac_latch = (u16(data) << 4) | (m_dac_latch & 0x000f);
+
+ // An S&H mux is selected and a CV updated only if A6 = 0.
+ if (!BIT(offset, 6))
+ {
+ // A3-A5 control which of the 8 DMUXes is enabled.
+ // A0-A2 control which of the 8 paths within each DMUX is enabled.
+ m_selected_sh = offset & 0x3f;
+ update_sh();
+ }
+ else
+ {
+ m_selected_sh = -1; // No DMUX selected.
+ }
+
+ // The potentiometer whose voltage is being measured will always be
+ // selected, regardless of the value of A6. For that purpose:
+ // - A5 is ignored.
+ // - A3-A4 control which of the 4 MUXes is enabled.
+ // - A0-A2 control which of the 8 paths within each MUX is enabled.
+ m_selected_pot = offset & 0x1f;
+}
+
+void memorymoog_state::led_drive_w(u8 data)
+{
+ // Latched by 74LS273 (U1, board 7), and buffered and inverted by
+ // ULN2074 (U2, U3, board 7). Buffer output is connected to LED cathodes,
+ // and therefore is active low.
+ m_led_sink = ~data;
+ // Inverting because pwm_display_device expects inputs as active-high.
+ m_led_matrix_device->matrix(~m_led_sink, ~m_led_source);
+ m_char_device->matrix(~m_led_sink, ~m_char_led_source);
+}
+
+void memorymoog_state::led_latch_w(u8 data)
+{
+ // Latched by 2x74LS378. D0-D3 by U2, board 6. D4-D7 by U8, board 7.
+ // Active low. When 0, a PNP transistor switches 6V to the LED anodes.
+ m_led_source = data;
+ // Inverting because pwm_display_device expects inputs as active-high.
+ m_led_matrix_device->matrix(~m_led_sink, ~m_led_source);
+}
+
+void memorymoog_state::led_update_w(offs_t offset, u8 data)
+{
+ // This is a callback from the pwm_display_device for LEDs.
+ // The offset should be decoded as: x = offset >> 6, y = offset & 0x3f.
+ m_leds[offset & 0x3f][offset >> 6] = data;
+}
+
+void memorymoog_state::digit_latch_w(u8 data)
+{
+ // TODO: Verify polarity.
+ static constexpr const u8 PATTERNS[16] = // 7447 (U2, board 7).
+ {
+ 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7c, 0x07,
+ 0x7f, 0x67, 0x58, 0x4c, 0x62, 0x69, 0x78, 0x00,
+ };
+
+ // Inverting `data` because digit selection is active low.
+ const u8 selection_mask = (~data >> 4) & 0x03;
+ m_digit_device->matrix(selection_mask, PATTERNS[data & 0x0f]);
+}
+
+void memorymoog_state::digit_update_w(offs_t offset, u8 data)
+{
+ // Digits are ordered from left to right. So offset 0 (corresponding to
+ // digit_1) is the most significant digit.
+ m_digits[offset] = data;
+}
+
+void memorymoog_state::char_latch_a_w(u8 data)
+{
+ // 74LS377, U4, board 7. Q7 not connected.
+ // D0-D6 -> A, B, C, D, E, F, G1 on LT-1604.
+ // These are the low order 7 bits of LT-1604.
+ // Bits are active low. When low, they connect corresponding LT-1604 inputs
+ // to 6V.
+ m_char_led_source = (m_char_led_source & 0x3f80) | (data & 0x7f);
+ // Inverting because pwm_display_device expects inputs as active-high.
+ m_char_device->matrix(~m_led_sink, ~m_char_led_source);
+}
+
+void memorymoog_state::char_latch_b_w(u8 data)
+{
+ // 74LS377, U5, board 7. Q7 not connected.
+ // D0-D6 -> G2, H, J, K, L, M, N on LT-1604.
+ // These are the high order 7 bits of LT-1604.
+ // Bits are active low. When low, they connect corresponding LT-1604 inputs
+ // to 6V.
+ m_char_led_source = (u16(data & 0x7f) << 7) | (m_char_led_source & 0x7f);
+ // Inverting because pwm_display_device expects inputs as active-high.
+ m_char_device->matrix(~m_led_sink, ~m_char_led_source);
+}
+
+void memorymoog_state::char_update_w(offs_t offset, u8 data)
+{
+ // Characters are ordered from left to right.
+ m_chars[offset] = data;
+}
+
+void memorymoog_state::memory_map(address_map& map)
+{
+ // Memory decoding done by 3 x 74LS138 (U9, U11, U17).
+ // "S4X" below refers to the expansion connector. This was unused in the
+ // initial revisions of the memorymoog, and AFAIK was only used for the
+ // memorymoog Plus upgrade (not yet emulated).
+ // U9 - Active when 0 = A14 = A15 = /MREQ = /RD (leverages U12 NOR)
+ // A0 <- A12, A1 <- A13, A2 <- 0.
+ // Outputs: 0 -> ROM U2, 1 -> ROM U3, 2 -> ROM U4, 3 -> S4X:/ROM.
+ // 4-7 -> N.C.
+ // U11 - Active when /MREQ = 0, A14 = 1, A15 = 0.
+ // A0 <- A11, A1 <- A12, A2 <- A13.
+ // Outputs: 0 -> RAM U5, 1 -> RAM U6, 2 -> RAM U7, 3 -> RAM U8
+ // 4-7 -> /RAM 1-4 on S4X connector.
+ // U8 RAM was not populated in initial production models.
+ // RAM /CS decoding also includes 74LS04 and 72LS26 to ensure /CS
+ // is only active when there is no SHUTDOWN signal detected.
+ // U17 - Active when A14 = 1, A15 = 1, /MREQ = 0.
+ // A0 <- A11, A1 <- A12, A2 <- A13.
+ // Outputs: 0 -> U18 /EN2, 1 -> U19 /EN2, 2 -> U20 /EN2.
+ // 3-6 -> N.C., 7 -> S4X: SEQ.PORT + TRANCEIVER BUS U29.
+ // U18 - Active on /MREQ=0 A_high = 0xC0-0xC7.
+ // U19 - Active on /MREQ=0 A_high = 0xC8-0xCF.
+ // U20 - Active on /MREQ=0 A_high = 0xD0-0x7F
+ // 0xd800-0xf7ff -> N.C.
+ // 0xf800-0xffff -> S4X SEQ PORT (also disables traceiver U29).
+
+ map(0x0000, 0x2fff).rom(); // U2-U4, 2532.
+ // map(0x3000, 0x3fff) // S4X: ROM
+ map(0x4000, 0x57ff).ram().share(NVRAM_TAG); // U5-U7 6116
+ // map(0x5800, 0x5fff) // U8 6116, but slot was not populated until
+ // the Memorymoog Plus upgrade, which is not yet emulated.
+ // map(0x6000, 0x7fff) // S4X: RAM1-4
+
+ map(0xc000, 0xc000).mirror(0x00ff).r(FUNC(memorymoog_state::u26_low4_r));
+ map(0xc100, 0xc100).mirror(0x00ff).r(FUNC(memorymoog_state::keyboard_r));
+ map(0xc100, 0xc100).mirror(0x00ff).w(FUNC(memorymoog_state::keyboard_w));
+ map(0xc200, 0xc200).mirror(0x00ff).portr("rear_panel_inputs");
+ map(0xc300, 0xc300).mirror(0x00ff).w(FUNC(memorymoog_state::tape_relay_w));
+ // Unused: map(0xc400, 0xc4ff)
+ map(0xc500, 0xc500).mirror(0x00ff).w(FUNC(memorymoog_state::dac_low_w));
+ map(0xc600, 0xc600).mirror(0x00ff).w("latch_u54", FUNC(output_latch_device::write));
+ map(0xc700, 0xc700).mirror(0x00ff).w("latch_u55", FUNC(output_latch_device::write));
+
+ map(0xc800, 0xc800).mirror(0x00ff).w("latch_u56", FUNC(output_latch_device::write));
+ map(0xc900, 0xc900).mirror(0x00ff).w("latch_u57", FUNC(output_latch_device::write));
+ map(0xca00, 0xca00).mirror(0x00ff).w("latch_u58", FUNC(output_latch_device::write));
+ map(0xcb00, 0xcb00).mirror(0x00ff).w("latch_u59", FUNC(output_latch_device::write));
+ map(0xcc00, 0xcc00).mirror(0x00ff).w("latch_u60", FUNC(output_latch_device::write));
+ map(0xcd00, 0xcd00).mirror(0x00ff).w("latch_u61", FUNC(output_latch_device::write));
+ map(0xce00, 0xce00).mirror(0x00ff).w("latch_u51", FUNC(output_latch_device::write));
+ map(0xcf00, 0xcf00).mirror(0x00ff).w("trigger_latch_u52", FUNC(output_latch_device::write));
+
+ map(0xd000, 0xd000).mirror(0x00ff).w(FUNC(memorymoog_state::led_drive_w));
+ map(0xd100, 0xd100).mirror(0x00ff).w(FUNC(memorymoog_state::led_latch_w));
+ map(0xd200, 0xd200).mirror(0x00ff).w(FUNC(memorymoog_state::char_latch_a_w));
+ map(0xd300, 0xd300).mirror(0x00ff).w(FUNC(memorymoog_state::char_latch_b_w));
+ map(0xd400, 0xd400).mirror(0x00ff).w(FUNC(memorymoog_state::digit_latch_w));
+ // TODO: map(0xd500, 0xd5ff) not labelled. Connected to pin 3 of connector
+ // S44 to board 7.
+ map(0xd600, 0xd600).mirror(0x00ff).r(FUNC(memorymoog_state::switches_b_r));
+ map(0xd700, 0xd700).mirror(0x00ff).r(FUNC(memorymoog_state::switches_a_r));
+ map(0xd700, 0xd700).mirror(0x00ff).w(FUNC(memorymoog_state::switches_w));
+}
+
+void memorymoog_state::io_map(address_map& map)
+{
+ map.global_mask(0xff);
+ map(0x00, 0x7f).w(FUNC(memorymoog_state::cv_mux_control_w));
+ map(0x80, 0x83).mirror(0x7c).rw(m_ctc, FUNC(z80ctc_device::read), FUNC(z80ctc_device::write));
+}
+
+void memorymoog_state::machine_start()
+{
+ m_digits.resolve();
+ m_chars.resolve();
+ m_octave_minus_1_led.resolve();
+ m_octave_0_led.resolve();
+ for (std::vector<output_finder<>>& led_row : m_leds)
+ for (output_finder<>& led_output : led_row)
+ led_output.resolve();
+
+ save_item(NAME(m_keyboard_columns));
+ save_item(NAME(m_switch_rows));
+ save_item(NAME(m_dac_latch));
+ save_item(NAME(m_selected_sh));
+ save_item(NAME(m_selected_pot));
+ save_item(NAME(m_positive_hysteresis));
+ save_item(NAME(m_negative_hysteresis));
+ save_item(NAME(m_octave_low));
+ save_item(NAME(m_char_led_source));
+ save_item(NAME(m_led_sink));
+ save_item(NAME(m_led_source));
+ save_item(NAME(m_cv));
+}
+
+const z80_daisy_config memorymoog_daisy_chain[] =
+{
+ { CTC_TAG },
+ { nullptr }
+};
+
+void memorymoog_state::memorymoog(machine_config& config)
+{
+ Z80(config, m_maincpu, 4_MHz_XTAL / 2); // Division done by U16.
+ m_maincpu->set_addrmap(AS_PROGRAM, &memorymoog_state::memory_map);
+ m_maincpu->set_addrmap(AS_IO, &memorymoog_state::io_map);
+ m_maincpu->set_daisy_config(memorymoog_daisy_chain);
+ // /NMI pulled high.
+
+ Z80CTC(config, m_ctc, 4_MHz_XTAL / 2); // Same clock line as CPU.
+ m_ctc->intr_callback().set_inputline(m_maincpu, INPUT_LINE_IRQ0);
+ // TODO: Implement the rest of the connections:
+ // CLK/TRG 0,1 and ZC/TO 0,1 connected to S4X.
+ // CLK/TRG 2, 3 used for autotune.
+ // ZC/TO 2 N.C.
+ // IEI pulled up.
+ // IEO N.C.
+
+ NVRAM(config, NVRAM_TAG, nvram_device::DEFAULT_ALL_0); // 3x6116LP: U5,6,7.
+
+ PWM_DISPLAY(config, m_digit_device).set_size(2, 7);
+ m_digit_device->set_segmask(0x03, 0x7f);
+ m_digit_device->output_digit().set(FUNC(memorymoog_state::digit_update_w));
+
+ PWM_DISPLAY(config, m_char_device).set_size(8, 14);
+ m_char_device->set_segmask(0xff, 0x3fff);
+ m_char_device->output_digit().set(FUNC(memorymoog_state::char_update_w));
+
+ PWM_DISPLAY(config, m_led_matrix_device).set_size(8, 8);
+ m_led_matrix_device->output_x().set(FUNC(memorymoog_state::led_update_w));
+
+ // All latches below are on board 5 (DMUX board).
+
+ // U54-U58 are connected on the +-7.5V inverted data bus.
+
+ // Bits 0, 1 and 3 are not connected.
+ output_latch_device& u54(OUTPUT_LATCH(config, "latch_u54"));
+ u54.bit_handler<2>().set_output("CONTOUR_KBYD_TRK").invert();
+ u54.bit_handler<4>().set_output("VOICE_MOD_FILT").invert();
+ u54.bit_handler<5>().set_output("VOICE_MOD_INVERT_ENABLE").invert();
+
+ // Bit 0 translated from -+7.5V to ~ 0.7/15V via Zener CR4 (IN5237A,
+ // Vz ~= 8.2V) and R164 (10K, connected to +15V).
+ output_latch_device& u55(OUTPUT_LATCH(config, "latch_u55"));
+ u55.bit_handler<0>().set_output("CONTOURED_OSC_3_AMT").invert();
+ u55.bit_handler<1>().set_output("VOICE_MOD_PW_1").invert();
+ u55.bit_handler<2>().set_output("VOIDE_MOD_PW_2").invert();
+ u55.bit_handler<3>().set_output("VOICE_MOD_FREQ_2").invert();
+ u55.bit_handler<4>().set_output("VOICE_MOD_FREQ_1").invert();
+ u55.bit_handler<5>().set_output("TRANSPOSE_ENABLE").invert();
+
+ output_latch_device& u56(OUTPUT_LATCH(config, "latch_u56"));
+ u56.bit_handler<0>().set_output("SAW_SW_ENABLE").invert();
+ u56.bit_handler<1>().set_output("RAMP_SW_ENABLE").invert();
+ u56.bit_handler<2>().set_output("SQUARE_SW_ENABLE").invert();
+ u56.bit_handler<3>().set_output("TRIANGLE_SW_ENABLE").invert();
+ u56.bit_handler<4>().set_output("SH_SW_ENABLE").invert();
+ u56.bit_handler<5>().set_output("MOD_VCF").invert();
+
+ output_latch_device& u57(OUTPUT_LATCH(config, "latch_u57"));
+ u57.bit_handler<0>().set_output("MOD_PW_2").invert();
+ u57.bit_handler<1>().set_output("MOD_PW_3").invert();
+ u57.bit_handler<2>().set_output("MOD_PW_1").invert();
+ u57.bit_handler<3>().set_output("MOD_FREQ_3").invert();
+ u57.bit_handler<4>().set_output("MOD_FREQ_1").invert();
+ u57.bit_handler<5>().set_output("MOD_FREQ_2").invert();
+
+ output_latch_device& u58(OUTPUT_LATCH(config, "latch_u58"));
+ u58.bit_handler<0>().set_output("TUNE").invert();
+ u58.bit_handler<1>().set_output("FP_1_VOL").invert();
+ u58.bit_handler<2>().set_output("FP_2_OSC_2").invert();
+ u58.bit_handler<3>().set_output("FP_1_FILT").invert();
+ u58.bit_handler<4>().set_output("FP_2_MOD").invert();
+ u58.bit_handler<5>().set_output("FP_1_PITCH").invert();
+
+ // U59-U61 are on the 0/15V data bus.
+
+ output_latch_device& u59(OUTPUT_LATCH(config, "latch_u59"));
+ u59.bit_handler<0>().set_output("OSC_1_TRI");
+ u59.bit_handler<1>().set_output("OSC_1_SAW");
+ u59.bit_handler<2>().set_output("OSC_1_PULSE");
+ u59.bit_handler<3>().set_output("OSC_2_TRI");
+ u59.bit_handler<4>().set_output("OSC_2_SAW");
+ u59.bit_handler<5>().set_output("OSC_2_PULSE");
+
+ output_latch_device& u60(OUTPUT_LATCH(config, "latch_u60"));
+ u60.bit_handler<0>().set_output("OSC_3_TRI");
+ u60.bit_handler<1>().set_output("OSC_3_SAW");
+ u60.bit_handler<2>().set_output("OSC_3_PULSE");
+ u60.bit_handler<3>().set_output("OSC_3_KYBD_TRK");
+ u60.bit_handler<4>().set_output("1/3-FILT");
+ u60.bit_handler<5>().set_output("2/3-FILT");
+
+ // Bits 2-5 not connected.
+ output_latch_device& u61(OUTPUT_LATCH(config, "latch_u61"));
+ u61.bit_handler<0>().set_output("SYNC_ENABLE");
+
+ // U51-U52 are on the 0/5V data bus.
+
+ output_latch_device& u51(OUTPUT_LATCH(config, "latch_u51"));
+ u51.bit_handler<0>().set_output("MOD_OSC_RESET");
+ u51.bit_handler<1>().set_output("UNCOND_ATTACK");
+ u51.bit_handler<2>().set_output("RETURN_TO_ZERO");
+ u51.bit_handler<3>().set_output("OSC_3_LOW_FREQ");
+ // Bit 4 not connected.
+ u51.bit_handler<5>().set_output("S-TRIG"); // Converted to 0-15V.
+ u51.bit_handler<5>().append_output("Y-TRIG").invert(); // 0-15V, inverted.
+
+ output_latch_device& u52(OUTPUT_LATCH(config, "trigger_latch_u52"));
+ u52.bit_handler<0>().set_output("TRIG_A");
+ u52.bit_handler<1>().set_output("TRIG_B");
+ u52.bit_handler<2>().set_output("TRIG_C");
+ u52.bit_handler<3>().set_output("TRIG_D");
+ u52.bit_handler<4>().set_output("TRIG_E");
+ u52.bit_handler<5>().set_output("TRIG_F");
+}
+
+DECLARE_INPUT_CHANGED_MEMBER(memorymoog_state::octave_button_pressed)
+{
+ // All components are on Board 10.
+ // Comparators U1A and U1B along with surrounding components form an
+ // SR flip-flop, triggered by SW1 and SW2.
+
+ // Octave buttons will affect pich directly, without the firmware knowing
+ // about it (they are not connected to the CPU).
+
+ // Inputs are active low.
+ const u8 input = m_octave_io->read();
+ const bool oct_minus_1 = (input & 0x01) == 0; // SW1
+ const bool oct_0 = (input & 0x02) == 0; // SW2
+
+ if (oct_minus_1 && !oct_0)
+ {
+ m_octave_low = true;
+ }
+ else if (!oct_minus_1 && oct_0)
+ {
+ m_octave_low = false;
+ }
+ else if (oct_minus_1 && oct_0)
+ {
+ // The selected octave is undefined in this case, so it isn't updated.
+ // An octave will be selected once one of the two buttons is released.
+ }
+ else
+ {
+ // No buttons pressed. No change in selected octave.
+ }
+
+ m_octave_minus_1_led = m_octave_low ? 1 : 0;
+ m_octave_0_led = m_octave_low ? 0 : 1;
+}
+
+// All strings in PORT_NAME(...) match those in the schematic.
+INPUT_PORTS_START(memorymoog)
+ PORT_START("keyboard_column_0")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("C0") PORT_CODE(KEYCODE_Z)
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("C#0")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("D0")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("D#0")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("E0")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("F0")
+ PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("F#0")
+ PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("G0")
+
+ PORT_START("keyboard_column_1")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("G#0")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("A0")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("A#0")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("B0") PORT_CODE(KEYCODE_X)
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("C1")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("C#1")
+ PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("D1")
+ PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("D#1")
+
+ PORT_START("keyboard_column_2")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("E1")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("F1")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("F#1")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("G1")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("G#1")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("A1")
+ PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("A#1")
+ PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("B1")
+
+ PORT_START("keyboard_column_3")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("C2")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("C#2")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("D2")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("D#2")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("E2")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("F2")
+ PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("F#2")
+ PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("G2")
+
+ PORT_START("keyboard_column_4")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("G#2")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("A2")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("A#2")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("B2")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("C3")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("C#3")
+ PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("D3")
+ PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("D#3")
+
+ PORT_START("keyboard_column_5")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("E3")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("F3")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("F#3")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("G3")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("G#3")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("A3")
+ PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("A#3")
+ PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("B3")
+
+ PORT_START("keyboard_column_6")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("C4")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("C#4")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("D4")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("D#4")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("E4")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("F4")
+ PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("F#4")
+ PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("G4") PORT_CODE(KEYCODE_C)
+
+ PORT_START("keyboard_column_7")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("G#4")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("A4")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("A#4")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("B4")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("C5") PORT_CODE(KEYCODE_V)
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_UNUSED) // NC
+ PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_UNUSED) // NC
+ PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_UNUSED) // NC
+
+ PORT_START("switch_a_row_0")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("A") PORT_CODE(KEYCODE_A)
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("B")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("C")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("D")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC2_FREQ_LFO")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SAW_LFO")
+
+ PORT_START("switch_a_row_1")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("3")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("6")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("9") PORT_CODE(KEYCODE_9)
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("ENTER")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC1_FREQ_LFO")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TRI_LFO")
+
+ PORT_START("switch_a_row_2")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("2")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("5")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("8")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("0")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC3_FREQ_LFO")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SAW_LFO")
+
+ PORT_START("switch_a_row_3")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("1")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("4")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("7")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("RECORD_INTERLOCK")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("PW1_LFO")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("PULSE_LFO")
+
+ PORT_START("switch_a_row_4")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("MONO")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("HOLD")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("GLIDE")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("FP2_OSC2")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("PW3_LFO")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("FILTER_LFO")
+
+ PORT_START("switch_a_row_5")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("MULT_TRIG")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("KYBD_MODE")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VM_PW1")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("FP2_MOD")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("PW2_LFO")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SH_LFO")
+
+ PORT_START("switch_a_row_6")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("FP1_FILTER")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("ARPEGIATOR")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VM_FILTER")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VM_OSC1_FREQ")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("FP1_VOLUME")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("CONTR_OSC3_AMT")
+
+ PORT_START("switch_a_row_7")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("KB_OUT")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("AUTO_TUNE")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VM_PW2")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VM_OSC2_FREQ")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("FP1_PITCH")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("INVERT")
+
+ PORT_START("switch_b_row_0")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC1-2'") PORT_CODE(KEYCODE_O)
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC1-4'")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC1-8'")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC1-16'")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_UNUSED) // NC
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_UNUSED) // NC
+
+ PORT_START("switch_b_row_1")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC2-2'")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC2-4'")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC2-8'")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC2-16'")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_UNUSED) // NC
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_UNUSED) // NC
+
+ PORT_START("switch_b_row_2")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC3-2'")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC3-4'")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC3-8'")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC3-16'")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("LOW")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("KYBD_CONTROL")
+
+ PORT_START("switch_b_row_3")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC1_PULSE")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC1_RAMP")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC1_TRI") PORT_CODE(KEYCODE_T)
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SYNC_2_TO_1")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("1/3_KYBD_TRACK")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("2/3_KYBD_TRACK")
+
+ PORT_START("switch_b_row_4")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC3_PULSE")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC3_RAMP")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC3_TRI")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_UNUSED) // NC
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("RELEASE")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("KYBD_FOLLOW")
+
+ PORT_START("switch_b_row_5")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC2_PULSE")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC2_RAMP") PORT_CODE(KEYCODE_R)
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC2_TRI")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_UNUSED) // NC
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("RETURN_TO_ZERO")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("UNCOND_CONT")
+
+ PORT_START("octave_buttons")
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Octave -1") PORT_CODE(KEYCODE_1)
+ PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(memorymoog_state::octave_button_pressed), 0x01) // SW1 (Board 10).
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Octave 0") PORT_CODE(KEYCODE_0)
+ PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(memorymoog_state::octave_button_pressed), 0x02) // SW2 (Board 10).
+
+ // 4051 MUX U9 (board 7)
+ PORT_START("pot_0")
+ PORT_ADJUSTER(50, "GLIDE")
+ PORT_START("pot_1")
+ PORT_ADJUSTER(50, "BEND AMT")
+ PORT_START("pot_2")
+ PORT_ADJUSTER(50, "MOD AMT")
+ PORT_START("pot_3")
+ PORT_ADJUSTER(50, "F.P. 1 AMT")
+ PORT_START("pot_4")
+ PORT_ADJUSTER(50, "F.P. 2 AMT")
+ PORT_START("pot_5")
+ PORT_ADJUSTER(50, "MOD RATE")
+ PORT_START("pot_6")
+ PORT_ADJUSTER(50, "V.M. OSC 3")
+ PORT_START("pot_7")
+ PORT_ADJUSTER(50, "V.M. FILTER CONTR.")
+
+ // 4051 MUX U3 (board 6)
+ PORT_START("pot_8")
+ PORT_ADJUSTER(50, "POLY MOD OSC 3") // TODO: maybe not a pot?
+ PORT_START("pot_9")
+ PORT_ADJUSTER(50, "POLY MOD FLT ENV.") // TODO: maybe not a pot?
+ PORT_START("pot_10")
+ PORT_ADJUSTER(50, "PW1")
+ PORT_START("pot_11")
+ PORT_ADJUSTER(50, "FREQ 2")
+ PORT_START("pot_12")
+ PORT_ADJUSTER(50, "PW2")
+ PORT_START("pot_13")
+ PORT_ADJUSTER(50, "FREQ 3")
+ PORT_START("pot_14")
+ PORT_ADJUSTER(50, "PW3")
+ PORT_START("pot_15")
+ PORT_ADJUSTER(50, "MIX OSC LEVEL 1")
+
+ // 4051 MUX U4 (board 6)
+ PORT_START("pot_16")
+ PORT_ADJUSTER(50, "MIX OSC 2 LEVEL")
+ PORT_START("pot_17")
+ PORT_ADJUSTER(50, "MIX OSC 3 LEVEL")
+ PORT_START("pot_18")
+ PORT_ADJUSTER(50, "NOISE LEVEL")
+ PORT_START("pot_19")
+ PORT_ADJUSTER(50, "CUTOFF")
+ PORT_START("pot_20")
+ PORT_ADJUSTER(50, "EMPH")
+ PORT_START("pot_21")
+ PORT_ADJUSTER(50, "CNTR AMT")
+ PORT_START("pot_22")
+ PORT_ADJUSTER(50, "FLT ATTACK")
+ PORT_START("pot_23")
+ PORT_ADJUSTER(50, "FLT DECAY")
+
+ // 4051 MUX U4 (board 6)
+ PORT_START("pot_24")
+ PORT_ADJUSTER(50, "FLT SUSTAIN")
+ PORT_START("pot_25")
+ PORT_ADJUSTER(50, "FLT RELEASE")
+ PORT_START("pot_26")
+ PORT_ADJUSTER(50, "LOUD ATTACK")
+ PORT_START("pot_27")
+ PORT_ADJUSTER(50, "LOUD DECAY")
+ PORT_START("pot_28")
+ PORT_ADJUSTER(50, "LOUD SUSTAIN")
+ PORT_START("pot_29")
+ PORT_ADJUSTER(50, "LOUD RELEASE")
+ PORT_START("pot_30")
+ PORT_ADJUSTER(50, "PRO VOLUME")
+ // I/O 7 not connected.
+
+ PORT_START("rear_panel_inputs") // U30 6-bit buffer.
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Release")
+ PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Hold")
+ PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program Advance")
+ PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program Backstep")
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Glide")
+ PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Foot Pedal In (1 or 2)")
+ PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_UNUSED)
+ PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_UNUSED)
+INPUT_PORTS_END
+
+// (Firmware version history below is based on
+// https://forum.moogmusic.com/viewtopic.php?t=27119, and on Service Bulletins).
+// The memorymoog was released with firwmare revision 1.4. Revisions 1.5 and
+// 1.6 followed shortly after. Rev 1.6 is, apparently, the most common firmware
+// for the non autotune-updated (see below) memorymoog.
+// Rev 2.2 was short-lived, and quickly replaced with Rev 2.4, along with
+// the "autotune update": a hardware update for more reliable autotuning (
+// service bulletin 840A, date 10/6/83, for serial numbers below 2723).
+// Version 2.5 was released shortly after, and is, apparently, the most common
+// firmware for non-"plus" memory moogs with the autotune update.
+// The "plus" hardware upgrade (not yet emulated) added MIDI and sequencer
+// support, and was accompanied by firmware Rev 4.0. This was followed by
+// Rev 4.1, which was the last official firmware update.
+ROM_START(memorymoog)
+ ROM_REGION(0x3000, MAINCPU_TAG, 0)
+ ROM_DEFAULT_BIOS("r2.4")
+
+ ROM_SYSTEM_BIOS(0, "r2.4", "Rev 2.4, October 1983")
+ ROMX_LOAD("v2p4.u2", 0x000000, 0x001000, CRC(bb70cc39) SHA1(c0073d430ea4d3dd823c0678f482a6b3c49d926c), ROM_BIOS(0))
+ ROMX_LOAD("v2p4.u3", 0x001000, 0x001000, CRC(088e1a3b) SHA1(4b9f568279fc7bf1a11ec2fb8f744dc261b0fcc4), ROM_BIOS(0))
+ ROMX_LOAD("v2p4.u4", 0x002000, 0x001000, CRC(580db768) SHA1(98cd285342758abf7736002e0fbd30f4f7ecb96d), ROM_BIOS(0))
+ROM_END
+
+} // anonymous namespace
+
+// In production from 1982 to 1985.
+SYST(1982, memorymoog, 0, 0, memorymoog, memorymoog, memorymoog_state, empty_init, "Moog Music", "Memorymoog", MACHINE_NOT_WORKING | MACHINE_NO_SOUND | MACHINE_SUPPORTS_SAVE)
+