From 6a403fc29993062aa451f5557cc88abc2fe7b2e7 Mon Sep 17 00:00:00 2001 From: m1macrophage <168948267+m1macrophage@users.noreply.github.com> Date: Thu, 2 Jan 2025 01:16:09 -0800 Subject: 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. --- src/mame/mame.lst | 3 + src/mame/moog/memorymoog.cpp | 1137 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1140 insertions(+) create mode 100644 src/mame/moog/memorymoog.cpp 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 u8 key_matrix_r( + const required_ioport_array& 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 m_maincpu; + required_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 m_digit_device; + output_finder<2> m_digits; + + // LT-1604. U4, board 7. + required_device m_char_device; + output_finder<8> m_chars; + u16 m_char_led_source = 0x3fff; + + required_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>> m_leds; + std::vector 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 u8 memorymoog_state::key_matrix_r( + const required_ioport_array& 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>& 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) + -- cgit v1.2.3