diff --git a/src/mame/layout/moog_source.lay b/src/mame/layout/moog_source.lay new file mode 100644 index 0000000000000..26a33ffbc9280 --- /dev/null +++ b/src/mame/layout/moog_source.lay @@ -0,0 +1,1178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mame/mame.lst b/src/mame/mame.lst index 4cc93d55fc4c1..bed66424af231 100644 --- a/src/mame/mame.lst +++ b/src/mame/mame.lst @@ -32671,6 +32671,9 @@ sshot // (c) 1979 Model Racing @source:modelracing/subhuntr.cpp subhuntr // 1979 Model Racing +@source:moog/source.cpp +moogsource // Moog Source + @source:morrow/microdec.cpp md2 // md3 // diff --git a/src/mame/moog/source.cpp b/src/mame/moog/source.cpp new file mode 100644 index 0000000000000..3af841304860a --- /dev/null +++ b/src/mame/moog/source.cpp @@ -0,0 +1,839 @@ +// license:BSD-3-Clause +// copyright-holders:m1macrophage + +/* +The Moog Source is a CPU-controlled analog monosynth. It lacks knobs and +sliders. Sound parameters are modified by pressing a button for a specific +parameter and using the encoder wheel to modify it. + +The architecture of this synthesizer is typical of digitally-controlled analog +synthesizers. The firmware is responsible for: +* Scanning and reacting to membrane button presses. This is a typical key + matrix setup (see buttons_latch_w(), buttons_a_r(), buttons_b_r()). +* Detecting which key is pressed on the keyboard (see get_keyboard_v()). +* Setting Control Voltages (aka CVs). Detailed info in cv_w(). +* Configuring audio and modulation routing through 4016 switches. See + output_latch_a_w(), output_latch_b_w(). +* Controlling the Loudness and Filter envelope generators (EGs). Starts the + Attack phase when a key is pressed, detects when an EG peaks and transitions + it to the Decay phase, and transitions EGs to the Release phase when a key is + released. +* Controlling the LED displays and cassette I/O. + +The 16 sound programs are stored in battery-backed NVRAM, and can also be stored +to (and loaded from) a cassette. + +This driver is based on the Source's schematics. Most of the circuitry +relevant to this driver is on Board 3 (digital board). Component designations in +comments refer to Board 3, unless otherwise noted. + +This driver attempts to accurately emulate the digital and digital-analog +interface of the synthesizer, including all analog behavior that is relevant to +the firmware. There are still some TODOs left to fully achieve this goal. There +is no attempt to emulate the analogue audio circuit. The driver includes an +interactive layout, and is intended as an educational tool. + +TODO: +- Emulation of MOD input to the CPU. +- Emulation of envelope generator timing. +- Cassette input/output. +*/ + +#include "emu.h" + +#include "cpu/z80/z80.h" +#include "machine/nvram.h" +#include "machine/rescap.h" + +#include "moog_source.lh" + +#define LOG_CV (1U << 1) +#define LOG_BUTTONS (1U << 2) +#define LOG_ENCODER (1U << 3) +#define LOG_KEYBOARD (1U << 4) +#define LOG_CV_KEYBOARD_APPROX (1U << 5) +#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 NVRAM_TAG[] = "nvram"; + +class source_state : public driver_device +{ +public: + source_state(const machine_config& mconfig, device_type type, + const char* tag) ATTR_COLD + : driver_device(mconfig, type, tag) + , m_maincpu(*this, MAINCPU_TAG) + , m_octave_io(*this, "octave_buttons") + , m_button_a_io(*this, "button_group_a_%d", 0U) + , m_button_b_io(*this, "button_group_b_%d", 0U) + , m_keyboard_io(*this, "keyboard_oct_%d", 1U) + , m_encoder(*this, "incremental_controller") + , m_trigger_io(*this, "trigger_in") + , m_contour_peaked_io(*this, "contour_peaked") + , m_octave_led(*this, "octave_led_%d") + , m_program_display(*this, "program_digit_%d") + , m_edit_display(*this, "edit_digit_%d") + , m_edit_led(*this, "edit_led") + , m_kb_track(*this, "kb_track") + , m_osc_waveform(*this, "osc_%d_waveform", 1U) + , m_sync(*this, "sync") + , m_lfo_to_filter(*this, "lfo_to_filter") + , m_lfo_to_osc(*this, "lfo_to_osc") + , m_lfo_shape(*this, "lfo_shape") + , m_trigger_out(*this, "trigger_out") + , m_cv(static_cast(CV::SIZE), -1) + {} + + void source(machine_config& config) ATTR_COLD; + + void machine_start() override ATTR_COLD; + void machine_reset() override ATTR_COLD; + + DECLARE_INPUT_CHANGED_MEMBER(octave_button_pressed); + DECLARE_INPUT_CHANGED_MEMBER(encoder_moved); + +private: + void update_octave_leds(); + + void edit_latch_w(u8 data); + void output_latch_a_w(u8 data); + void output_latch_b_w(u8 data); + void buttons_latch_w(u8 data); + void program_latch_w(u8 data); + void cassette_w(u8 data); + void cv_w(offs_t offset, u8 data); + + float get_keyboard_v() const; + u8 keyboard_r(); + u8 buttons_r(const required_ioport_array<6>& button_io, const char* name) const; + u8 buttons_a_r(); + u8 buttons_b_r(); + u8 encoder_r(); + + void memory_map(address_map& map) ATTR_COLD; + void io_map(address_map& map) ATTR_COLD; + + required_device m_maincpu; + required_ioport m_octave_io; + required_ioport_array<6> m_button_a_io; + required_ioport_array<6> m_button_b_io; + required_ioport_array<4> m_keyboard_io; + required_ioport m_encoder; + required_ioport m_trigger_io; + required_ioport m_contour_peaked_io; + + output_finder<2> m_octave_led; + output_finder<2> m_program_display; + output_finder<2> m_edit_display; + output_finder<> m_edit_led; + output_finder<> m_kb_track; + output_finder<2> m_osc_waveform; + output_finder<> m_sync; + output_finder<> m_lfo_to_filter; + output_finder<> m_lfo_to_osc; + output_finder<> m_lfo_shape; + output_finder<> m_trigger_out; + + bool m_octave_hi = true; // `true` due to internal pullups of 74LS367 and 7404. + u8 m_button_row_latch = 0xff; + bool m_encoder_incr = false; + + // All MUXes are CD4051B. + // Component designations refer to board 2 (synthesizer board). + // The enum names match the CV labels in the schematic, but some + // abbreviations are expanded. + enum class CV : int + { + // U2 + CUTOFF_COARSE = 0, + AUTO_TUNE_2, + INT_COARSE, + CUTOFF_FINE, + PW_1, + PW_2, + INT_FINE, + OCT_2, + + // U4 + FILTER_CONTOUR_LEVEL, + OCT_1, + GLIDE, + LOUDNESS_COUNTOUR_LEVEL, + OSC_2, + NOISE, + UNUSED, // Sampled in (C22, U10A), but not used. + OSC_1, + + // U5 + EMPHASIS, + NOT_CONNECTED, // U5, Y1 (pin 14) is not connected. + AMT, // Filter contout amount. + RATE, // Modulation (LFO) rate. + KEYBOARD_APPROX, + KEYBOARD_CV, + FILTER_CONTOUR_RATE, + LOUDNESS_CONTOUR_RATE, + + SIZE + }; + std::vector m_cv; + + static constexpr const float MAX_CV = 10; // In Volts. + static constexpr const u8 PATTERNS_7447[16] = + { + 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7c, 0x07, + 0x7f, 0x67, 0x58, 0x4c, 0x62, 0x69, 0x78, 0x00, + }; +}; + +void source_state::update_octave_leds() +{ + m_octave_led[0] = m_octave_hi ? 0 : 1; + m_octave_led[1] = m_octave_hi ? 1 : 0; +} + +void source_state::edit_latch_w(u8 data) +{ + // U3 (74LS378) 0-4 (D0-D4) -> U3 (7447, Board 4) A-D -> U4 (MAN 3610A). + // U3 4-5 not connected. + m_edit_display[0] = PATTERNS_7447[data & 0x0f]; + + // U4 (74LS378) 0-4 (D4-D7)-> U5 (7447, Board 4) A-D -> U6 (MAN 3610A). + m_edit_display[1] = PATTERNS_7447[(data >> 4) & 0x0f]; + + // U4 (74LS378) 5, 6 (D0, D7) -> J1-5 (cassette interface, "cassette out"). + // TODO: Add cassette support. +} + +void source_state::output_latch_a_w(u8 data) +{ + // Latch is U11, 74LS378 (Board 3). 6-bit latch, top 2 bits ignored. + // All component designations are for Board 2. + + // Keyboard tracking for filter, bits D0 and D1. + const u8 kb_track = data & 0x03; + if (kb_track == 1) + { + // 1/2 tracking. U36C on and U36B off. + // Keyboard CV mixed in via 342Kohm resistance (2 x 121K: R142, R143). + m_kb_track = 1; + } + else if (kb_track == 2 || kb_track == 3) + { + // Full tracking. + // Only U36B on (kb_track == 2), or both U36B and U36C on + // (kb_track == 3). + // In both cases, keyboard CV is mixed in via a 121KOhm resistor (R142). + m_kb_track = 2; + } + else + { + // Both U36B and U36B are off. Keyboard CV does not make it through. + m_kb_track = 0; + } + + // Osc 2 waveform, bits D2 and D3. + // 0 - Sawtooth (U32A closed, U32B open, U32C closed, U32D closed). + // 1 - Triangle (U32A closed, U32B closed, U32D open, U32C open). + // 2 - Square/pulse (U32A open, U32B closed, U32C open, U32D closed). + // 3 - Mix of Triange and Square/Pulse (probably unused) + // (U32A open, U32B closed, U32C open, U32D open). + m_osc_waveform[1] = (data >> 2) & 0x03; + + // Osc 1 waveform, bits D4 and D5. + // Same swithc configuration as above, but replace U32 with U23. + m_osc_waveform[0] = (data >> 4) & 0x03; +} + +void source_state::output_latch_b_w(u8 data) +{ + // Latch is U12, 74LS378 (Board 3). 6-bit latch, top 2 bits ignored. + + // D0 -> S21-16 -> Sync. Synchronizes osc 2 to osc 1. + // When sync is on, the pitch wheel is only routed to Osc 2. + // When 1, Q2 is "off", U18B and C are off, U18A on, pitch wheel routed + // to osc 2 only. + // When 0, Q2 is "on", U18B is on (routes pitch wheel to central pitch), + // U18C is "on", which turns off U18A (disables direct route to osc 2). + m_sync = BIT(data, 0); + + // D1 -> inverted by U2A (Board 3), and connected to cathode of led. + // The led is active low, but the inverter presents it as active high. + m_edit_led = BIT(data, 1); + + // D2 -> J2 S-TRIG OUT, inverted via Q1, R21, R20 (Board 3). + m_trigger_out = BIT(data, 2) ? 0 : 1; + + // Component designations below refer to Board 2. + + // D3 -> S22-4 -> U46A (inverted and level-shifted to -5-5V through 4007B + // and R213): mod to filter. + m_lfo_to_filter = BIT(data, 3) ? 0 : 1; + + // D4 -> S22-3 -> U18D (inverted and level-shifted to -5-5V through 4007B + // and R61): mod to osc. + m_lfo_to_osc = BIT(data, 4) ? 0 : 1; + + // D5 -> S22-2 (level shifted & inverted to -5-5V through 4007B and R214) + // 0 - Triange (U46C on, U46B on, turns off U46D). -1.5V - 1.5V. + // 1 - Square (U46C off, U46B off, U46D on/off controlled by square + // wave. Translates -14V - 14V wave to 0-5V. + m_lfo_shape = BIT(data, 5); +} + +void source_state::buttons_latch_w(u8 data) +{ + // U5, 74LS378. All output connected to diode cathodes. + // Connected to "Membrane switch interface", P1, "top left". + // (D0, D1, D2, D3, D4, D5) -> (P1-1, P1-5, P1-6, P1-2, P1-4, P1-3) + m_button_row_latch = data & 0x3f; // Only D0-D5 connected. +} + +void source_state::program_latch_w(u8 data) +{ + // U1, 74LS378 + // D0-D3 -> U1 (7447, Board 4) A-D -> right digit of MAN6630. + // D4 -> inverted (U2E, U2D, 7404) -> left 1/2 digit of MAN6630 + // (inputs a and b). + // D5 -> inverted (U2A, 7404) -> HOLD ->"plus" sign of MAN6630. + // Note that MAN6630 has 3 "digits". From right to left: + // - 7-segment digit. + // - 2-segment digit (can represent a "1"). + // - "+" sign. + // Here, we simulate this with two 7-segment digits. + + m_program_display[0] = PATTERNS_7447[data & 0x0f]; + + u8 digit1 = PATTERNS_7447[15]; // All segments off. + if (BIT(data, 4)) + { + digit1 |= PATTERNS_7447[1]; // Turn on segments for "1". + } + if (BIT(data, 5)) + { + // This enables two segments on the MAN6630 that display a "+" symbol. + // Since 7-segment displays don't support that, enable the segment for + // "-" instead. + digit1 |= 0x40; + } + m_program_display[1] = digit1; +} + +void source_state::cassette_w(u8 data) +{ + // Z80 D4 controlls a normally-open relay (K1) through U22A (74LS74). + // A low D4 powers the relay, which connects cassette jack J1-1 to J1-3. + // TODO: Add cassette support. +} + +void source_state::cv_w(offs_t offset, u8 data) +{ + // CVs are generated by writing to an AM6012 12-bit DAC, but only 8 bits are + // used: the 8 MSBs are connected to the data bus, and the 4 LSBs are + // grounded. The DAC is mapped to the Z80's port IO space. + // The DAC and support circuitry convert the 8 bit data (0-255) to a voltage + // (0-10V). That voltage is routed to the Sample & Hold circuit (a + // capacitor and a buffer) of a specific CV, controlled by A0-A4. + + // Interesting tidbit: In most designs, the DAC inputs are + // latched. In the Source, the DAC inputs are directly connected to the data + // bus. This means the DAC output voltage is constantly changing, in an + // attempt to track the data bus. + + if (!machine().side_effects_disabled()) + { + // U14, U15B, U16D,E and U17D generate WAIT states whenever there is + // an IO write. This lasts 32 cycles. + // For the first 8 cycles, no MUX is selected, to allow the DAC to + // settle. Then a specific channel in a specific MUX is enabled (based + // on the port address), and there's a WAIT for another 24 cycles, to + // allow the selected Sample & Hold capacitor to (dis)charge. + m_maincpu->adjust_icount(-(8 + 24)); + } + + // Z80 A0,A1,A2 connected to the A,B,C inputs (respectively) of all MUXes. + // Z80 A3,A4 select which MUX to enable via decoder 74LS155. + // The fourth output of the decoder is not connected. There are 3 muxes. + + if (offset >= static_cast(CV::SIZE)) + return; + + const float cv = MAX_CV * data / 255.0f; + if (cv == m_cv.at(offset)) + return; + m_cv.at(offset) = cv; + + if (offset == static_cast(CV::KEYBOARD_APPROX)) + LOGMASKED(LOG_CV_KEYBOARD_APPROX, + "CV %d: 0x%02x, %f\n", offset, data, cv); + else + LOGMASKED(LOG_CV, "CV %d: 0x%02x, %f\n", offset, data, cv); +} + +float source_state::get_keyboard_v() const +{ + // *** Detect which key is pressed. + + static constexpr const int OCTAVES = 4; + static constexpr const int KEYS_PER_OCTAVE = 12; + static constexpr const int KEYS = 3 * KEYS_PER_OCTAVE + 1; + static constexpr const int OCTAVE_KEYS[4] = + { + KEYS_PER_OCTAVE, KEYS_PER_OCTAVE, KEYS_PER_OCTAVE, 1 + }; + + // The circuit is structure such that the lowest note has priority. + // Scan from lowest, and exit the loop once a pressed key is found. + int pressed_key = -1; + for (int octave = 0; octave < OCTAVES; ++octave) + { + const u32 keys = m_keyboard_io[octave]->read(); + for (int key = 0; key < OCTAVE_KEYS[octave]; ++key) + { + if (BIT(keys, key)) + { + pressed_key = octave * KEYS_PER_OCTAVE + key; + break; + } + } + if (pressed_key >= 0) + break; + } + + // *** Convert pressed key to a voltage. + + static constexpr const float KEYBOARD_VREF = 8.24; // From schematic. + static constexpr const float RKEY = RES_R(100); + static constexpr const float R74 = RES_R(150); + static constexpr const float R76 = RES_K(220); + static constexpr const float R77 = RES_K(2.2); + + float kb_voltage = 0; + if (pressed_key >= 0) + { + // Pressing a key forms a voltage devider consisting of the lower and + // upper resistances as shown below. The resulting voltage is further + // reduced by another voltage divider (R77-R76) before being fed to + // comparator U31A. + const float lower_r = R74 + pressed_key * RKEY; + const float upper_r = (KEYS - pressed_key - 1) * RKEY; + const float v = KEYBOARD_VREF * RES_VOLTAGE_DIVIDER(upper_r, lower_r); + kb_voltage = v * RES_VOLTAGE_DIVIDER(R77, R76); + LOGMASKED(LOG_KEYBOARD, "Key %d - %f - %f\n", pressed_key, v, + kb_voltage); + } + return kb_voltage; +} + +u8 source_state::keyboard_r() +{ + // U32: 74LS367 + // U18: 74LS125 + + // D0 <- U32, KEYBD. + // Output of comparator U31A. Compares the "KYBD APPROX" CV with the voltage + // generated by the keyboad (see get_keyboard_r()). This bit is used by a + // successive approximation algorithm to detect the keyboard voltage. The + // firmware does a binary search by checking the result of the comparison + // and updating the "KYBD APPROX" CV accordingly. + // TODO: Compute keyboard voltage in an input callback. + static constexpr const int KB_APPROX_INDEX = + static_cast(CV::KEYBOARD_APPROX); + const u8 d0 = (get_keyboard_v() >= m_cv.at(KB_APPROX_INDEX)) ? 1 : 0; + + // D1, D2: Loudness and Filter contour peaks. + // D1 <- U32, FILT CNTR <- S22-11: 0 when envolope reaches almost 10V + // (with some hysteresis). + // D2 <- U32, LOUD CNTR <- S22-10: 0 when envelope reaches almost 10V + // (with some hysteresis). + // TODO: Treating as inputs for now, until contour timing is emulated. + const u8 contour_peaked = m_contour_peaked_io->read(); + const u8 d1 = BIT(contour_peaked, 0); + const u8 d2 = BIT(contour_peaked, 1); + + // D3: Octave. <- U32, OCT (P34-2 (octave 0 button) and P34-1 (octave +1 + // button) via U2B and U2C). + const u8 d3 = m_octave_hi ? 1 : 0; + + // D4 <- J1-4, CASSETTE IN (through "cassette return" circuit and U18D). + const u8 d4 = 1; // TODO: Implement. + + // D5 <- U32, MOD (->P34-5) 1 when s22-5 low, otherwise 0. + const u8 d5 = 0; // TODO: Implement. + + // D6 <- J2-5, S-TRIG IN, through U18C, pulled up by R23 and protected by + // R22. + const u8 d6 = BIT(m_trigger_io->read(), 0); + + // D7 <- U32, N.C. <- 1 (data bus is pulled high). + const u8 d7 = 1; + + return (d7 << 7) | (d6 << 6) | (d5 << 5) | (d4 << 4) | + (d3 << 3) | (d2 << 2) | (d1 << 1) | d0; +} + +u8 source_state::buttons_r( + const required_ioport_array<6>& button_io, const char* name) const +{ + // Button presses are active low, but the result is inverted by a CD4502. + // So they look active high to the firmware. + u8 pressed = 0x00; + for (int i = 0; i < 6; ++i) + { + if (!BIT(m_button_row_latch, i)) + pressed |= static_cast(~button_io[i]->read() & 0xff); + } + // Bits 6 and 7 are not connected to the button input and pulled high. + pressed |= 0xc0; + if (pressed & 0x3f) + { + LOGMASKED(LOG_BUTTONS, "Button read %s - %02X: %02X\n", + name, m_button_row_latch, pressed); + } + return pressed; +} + +u8 source_state::buttons_a_r() +{ + // U8, CD4502B (connceted to "Membrane switch interface", P2, "Bottom left") + // (D0, D1, D2, D3, D4, D5) <- (P2-1, P2-3, P2-2, P2-6, P2-5, P2-4) + return buttons_r(m_button_a_io, "A"); +} + +u8 source_state::buttons_b_r() +{ + // U9, CD4502B (connected to "Membrane switch interface", P3, "Bottom right") + // (D0, D1, D2, D3, D4, D5, D6, D7) <- (P3-2, P3-1, P3-3, P3-6, P3-5, P3-4) + return buttons_r(m_button_b_io, "B"); +} + +u8 source_state::encoder_r() +{ + // D0 contains whether the encoder was last incremented or decremented. + LOGMASKED(LOG_ENCODER, + "Encoder read: %d - %d\n", m_encoder->read(), m_encoder_incr); + // Reading the encoder's state also clears /INT (via U21B, U7A and U15A). + if (!machine().side_effects_disabled()) + m_maincpu->set_input_line(INPUT_LINE_IRQ0, CLEAR_LINE); + return m_encoder_incr ? 1 : 0; +} + +void source_state::memory_map(address_map& map) +{ + // Address decoding done through U26, 74LS138, E1=E2=0, E3=1, + // A0-A2 = Z80 A13-A15. + // Z80 A12 is not connected. + // The signal names below (e.g. "ROM /EN", "RAM /EN") match those in the + // schematics. + + // ROM /EN: 0x0000-0x1fff. + // 1 x 2532 (4K, 8bit) ROM, U23. + map(0x0000, 0x0fff).mirror(0x1000).rom(); + // 2 x 74LS378. Z80 and latch data lines are not connected in order. + map(0x0000, 0x0000).mirror(0x1fff).w(FUNC(source_state::edit_latch_w)); + + // RAM /EN: 0x2000-0x3fff. + // 2 x 6514 (1K, 4bit) NVRAMs. U27: D0-D3, U28: D4-D7. + // Z80 A0-A1 -> RAM A0-A1. Z80 A2-A8 -> RAM A3-A9. Z80 A9 -> RAM A2. + map(0x2000, 0x23ff).mirror(0x1c00).ram().share(NVRAM_TAG); + + // OUTPUT /EN: 0x4000-0x5fff. + // 2 output latches (74LS378, U11 and U12) enabled by 74LS155 (U13B), + // with Z80 A3-A4 as inputs to A0-A1. O2 and O3 are not connected, so + // A4=1 does not enable anything. + map(0x4000, 0x4000).mirror(0x1fe7).w(FUNC(source_state::output_latch_a_w)); + map(0x4008, 0x4008).mirror(0x1fe7).w(FUNC(source_state::output_latch_b_w)); + + // KYBD /EN: 0x6000-0x7fff. + // 74LS367, U32 + map(0x6000, 0x6000).mirror(0x1fff).r(FUNC(source_state::keyboard_r)); + // 74LS74, U22A. D <- Z80 D4. + map(0x6000, 0x6000).mirror(0x1fff).w(FUNC(source_state::cassette_w)); + + // FRONT PANEL /EN1: 0x8000-0x9fff. + // CD4502, U8. + map(0x8000, 0x8000).mirror(0x1fff).r(FUNC(source_state::buttons_a_r)); + // 74LS378, U5. + map(0x8000, 0x8000).mirror(0x1fff).w(FUNC(source_state::buttons_latch_w)); + + // FRONT PANEL /EN2: 0xa000-0xbfff. + // CD4502, U9 + map(0xa000, 0xa000).mirror(0x1fff).r(FUNC(source_state::buttons_b_r)); + + // DISPLAY /EN: 0xc000-0xdfff. + // 74LS378, U1. + map(0xc000, 0xc000).mirror(0x1fff).w(FUNC(source_state::program_latch_w)); + + // CNTRL /EN: 0xe000-0xffff. (typo in schematic: 0xefff-0xffff). + map(0xe000, 0xe000).mirror(0x1fff).r(FUNC(source_state::encoder_r)); +} + +void source_state::io_map(address_map& map) +{ + map.global_mask(0xff); + map(0x00, 0x1f).mirror(0xe0).w(FUNC(source_state::cv_w)); +} + +void source_state::machine_start() +{ + m_octave_led.resolve(); + m_program_display.resolve(); + m_edit_display.resolve(); + m_edit_led.resolve(); + m_kb_track.resolve(); + m_osc_waveform.resolve(); + m_sync.resolve(); + m_lfo_to_filter.resolve(); + m_lfo_to_osc.resolve(); + m_lfo_shape.resolve(); + m_trigger_out.resolve(); + + save_item(NAME(m_octave_hi)); + save_item(NAME(m_button_row_latch)); + save_item(NAME(m_encoder_incr)); + save_item(NAME(m_cv)); +} + +void source_state::machine_reset() +{ + update_octave_leds(); +} + +void source_state::source(machine_config& config) +{ + // /M1, /RFSH not Connected. + // /HALT, /NMI pulled up to 5V, with no other connection. + Z80(config, m_maincpu, 4_MHz_XTAL / 2); // Divided by 2 through U22B. + m_maincpu->set_addrmap(AS_PROGRAM, &source_state::memory_map); + m_maincpu->set_addrmap(AS_IO, &source_state::io_map); + + NVRAM(config, NVRAM_TAG, nvram_device::DEFAULT_ALL_0); // 2x6514: U27, U28. + + config.set_default_layout(layout_moog_source); +} + +DECLARE_INPUT_CHANGED_MEMBER(source_state::octave_button_pressed) +{ + // Inverters U2B and U2C (Board 3) are configured as an SR flip-flop, with + // SW1 and SW2 (Board 5) as Reset and Set respectively. + + // Inputs are active low. + const u8 input = m_octave_io->read(); + const bool octave_0 = (input & 0x01) == 0; // "0", SW1 (Board 5). + const bool octave_p1 = (input & 0x02) == 0; // "+1", SW2 (Board 5). + if (!octave_0 && octave_p1) + { + m_octave_hi = true; + } + else if (octave_0 && !octave_p1) + { + m_octave_hi = false; + } + else if (octave_0 && octave_p1) + { + // The selected octave is undefined in this case, so it is not updated. + // An octave will be selected when one of the two buttons is released. + } + else + { + // No buttons pressed. No change in selected octave. + } + update_octave_leds(); +} + +DECLARE_INPUT_CHANGED_MEMBER(source_state::encoder_moved) +{ + static constexpr const int WRAP_BUFFER = 10; + const bool overflowed = newval <= WRAP_BUFFER && + oldval >= 240 - WRAP_BUFFER; + const bool underflowed = newval >= 240 - WRAP_BUFFER && + oldval <= WRAP_BUFFER; + m_encoder_incr = ((newval > oldval) || overflowed) && !underflowed; + m_maincpu->set_input_line(INPUT_LINE_IRQ0, ASSERT_LINE); + LOGMASKED(LOG_ENCODER, "Encoder changed: %d %d\n", newval, m_encoder_incr); +} + +INPUT_PORTS_START(source) + PORT_START("button_group_a_0") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Memory STORE") PORT_CODE(KEYCODE_S) // r2p6 + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 13") // r2p4 + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 12") // r2p5 + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 16") // r2p1 + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 15") // r2p2 + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 14") // r2p3 + + PORT_START("button_group_b_0") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Level 2") // r1p5 + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Level 1") //r1p6 + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Filter Contour Decay") //r1p4 + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Filter Contour Amount") //r1p1 + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Filter Contour Release") //r1p2 + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Filter Contour Sustain") // r1p3 + + PORT_START("button_group_a_1") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) //NC + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 3") PORT_CODE(KEYCODE_3) + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 2") PORT_CODE(KEYCODE_2) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 6") PORT_CODE(KEYCODE_6) + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 5") PORT_CODE(KEYCODE_5) + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 4") PORT_CODE(KEYCODE_4) + + PORT_START("button_group_b_1") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Filter Attack") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Mixer: OSC 2") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC 2 Shape: Pulse") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Filter KB Track: OFF") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Filter Emphasis") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Filter Cutoff") + + PORT_START("button_group_a_2") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) // NC + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 10") PORT_CODE(KEYCODE_0) + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 11") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 7") PORT_CODE(KEYCODE_7) + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 8") PORT_CODE(KEYCODE_8) + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 9") PORT_CODE(KEYCODE_9) + + PORT_START("button_group_b_2") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Loudness Decay") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Loudness Attack") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("MOD Rate") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) // NC / PROGRAM 1 + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Loudness Release") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Loudness Sustain") + + PORT_START("button_group_a_3") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) // NC + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("MOD To Filter: OFF") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Program 1") PORT_CODE(KEYCODE_1) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC 2 Footage: 32'") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) // NC / PROGRAM 1 + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("MOD To Filter: ON") + + PORT_START("button_group_b_3") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Mixer: NOISE") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC 2 Interval") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC 2 Shape: Sawtooth") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC 1 Shape: Pulse") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Filter KB Track: 1/2") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Filter KB Track: FULL") + + PORT_START("button_group_a_4") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) // NC + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("MOD to Osc: OFF") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Memory HOLD") PORT_CODE(KEYCODE_H) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("KB Glide") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("MOD Shape: Triangle") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("MOD to Osc: ON") + + PORT_START("button_group_b_4") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC 2 Shape: Triange") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC 2 Footage: 16'") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SYNC: ON") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC 1 Footage: 32'") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC 1 Footage: 16'") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Mixer: OSC 1") + + PORT_START("button_group_a_5") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) // NC + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Trigger MULTI") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Trigger SINGLE") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) // NC / PROGRAM 1 + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("MOD Shape: Square") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) // NC / PROGRAM 1 + + PORT_START("button_group_b_5") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC 2 Footage: 8'") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) // NC / PROGRAM 1 + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SYNC: OFF") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC 1 Footage: 8'") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC 1 Shape: Triange") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("OSC 1 Shape: Sawtooth") + + PORT_START("octave_buttons") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Octave 0") // SW1 (Board 5). + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(source_state::octave_button_pressed), 0x01) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Octave +1") // SW2 (Board 5). + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(source_state::octave_button_pressed), 0x02) + + PORT_START("incremental_controller") + PORT_BIT(0xff, 0x00, IPT_POSITIONAL) PORT_POSITIONS(240) PORT_WRAPS + PORT_SENSITIVITY(25) PORT_KEYDELTA(3) + PORT_CODE_DEC(KEYCODE_LEFT) PORT_CODE_INC(KEYCODE_RIGHT) PORT_FULL_TURN_COUNT(240) + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(source_state::encoder_moved), 1) + + PORT_START("keyboard_oct_1") + PORT_BIT(0x001, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("C1") + PORT_BIT(0x002, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("C#1") + PORT_BIT(0x004, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("D1") + PORT_BIT(0x008, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("D#1") + PORT_BIT(0x010, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("E1") + PORT_BIT(0x020, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("F1") + PORT_BIT(0x040, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("F#1") + PORT_BIT(0x080, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("G1") + PORT_BIT(0x100, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("G#1") + PORT_BIT(0x200, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("A1") + PORT_BIT(0x400, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("A#1") + PORT_BIT(0x800, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("B1") + + PORT_START("keyboard_oct_2") + PORT_BIT(0x001, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("C2") + PORT_BIT(0x002, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("C#2") + PORT_BIT(0x004, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("D2") + PORT_BIT(0x008, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("D#2") + PORT_BIT(0x010, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("E2") + PORT_BIT(0x020, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("F2") + PORT_BIT(0x040, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("F#2") + PORT_BIT(0x080, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("G2") + PORT_BIT(0x100, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("G#2") + PORT_BIT(0x200, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("A2") + PORT_BIT(0x400, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("A#2") + PORT_BIT(0x800, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("B2") + + PORT_START("keyboard_oct_3") + PORT_BIT(0x001, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("C3") + PORT_BIT(0x002, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("C#3") + PORT_BIT(0x004, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("D3") + PORT_BIT(0x008, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("D#3") + PORT_BIT(0x010, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("E3") + PORT_BIT(0x020, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("F3") + PORT_BIT(0x040, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("F#3") + PORT_BIT(0x080, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("G3") + PORT_BIT(0x100, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("G#3") + PORT_BIT(0x200, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("A3") + PORT_BIT(0x400, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("A#3") + PORT_BIT(0x800, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("B3") + + PORT_START("keyboard_oct_4") + PORT_BIT(0x001, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("C4") + + PORT_START("trigger_in") // External trigger input (see keyboard_r()). + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("S TRIG IN") PORT_CODE(KEYCODE_T) + + // TODO: User can control when contours peak, until those are emulated. + PORT_START("contour_peaked") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Filter Contour Peaked") PORT_CODE(KEYCODE_Z) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Loudness Contour Peaked") PORT_CODE(KEYCODE_X) +INPUT_PORTS_END + +// It seems like the Source was launched with firmware Revision 2.2. +// There was also a Revision 3.2, and the last official firmware release was +// Revision 3.3. +ROM_START(moogsource) + ROM_REGION(0x1000, MAINCPU_TAG, 0) + ROM_DEFAULT_BIOS("r3.3") + + ROM_SYSTEM_BIOS(0, "r3.3", "Rev 3.3") + ROMX_LOAD("3p3.u23", 0x000000, 0x001000, CRC(4211331f) SHA1(8767ef6b1cbb032a89a78bdb77bb7dbc1c187974), ROM_BIOS(0)) +ROM_END + +} // Anonymous namespace. + +SYST(1981, moogsource, 0, 0, source, source, source_state, empty_init, "Moog Music", "Moog Source", MACHINE_SUPPORTS_SAVE | MACHINE_NO_SOUND); +