diff --git a/src/mame/layout/oberheim_xpander.lay b/src/mame/layout/oberheim_xpander.lay new file mode 100644 index 00000000000..e3cd836ec20 --- /dev/null +++ b/src/mame/layout/oberheim_xpander.lay @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mame/mame.lst b/src/mame/mame.lst index 71d5b50f36e..3d0a22a6b3c 100644 --- a/src/mame/mame.lst +++ b/src/mame/mame.lst @@ -35599,6 +35599,9 @@ obdmx @source:oberheim/ob8.cpp ob8 +@source:oberheim/xpander.cpp +xpander + @source:olivetti/m20.cpp m20 m40 diff --git a/src/mame/oberheim/xpander.cpp b/src/mame/oberheim/xpander.cpp new file mode 100644 index 00000000000..ec7a496784f --- /dev/null +++ b/src/mame/oberheim/xpander.cpp @@ -0,0 +1,1147 @@ +// license:BSD-3-Clause +// copyright-holders:m1macrophage + +/* +The Oberheim Xpander is a 6-voice digitally-controlled analog synthesizer, with +digital modulation sources (LFOs, EGs). Each voice can be configured +independently, which provides a multitimbrality of 6. The Xpander does not have +a keyboard. It is controlled by MIDI, and can accept CV/gate in. + +The user interface consists of multiple buttons, 6 rotary encoders, and 3 vacuum +fluorescent displays. There are individual outputs for each voice, along with +left, right and mono outs. Other than MIDI, inputs consist of 6 CV and 2 pedal +inputs, 6 gate and 1 trigger inputs (user-configurable as active high or active +low), and a control input ("chain advance", active low). + +The Xpander has two 6809-based computers. The main computer scans for button +presses, drives the VFDs, interprets the digital and analog inputs (MIDI, CVs, +triggers, etc.), and sends parameters to the voice computer. + +The voice board consists of 6 analog voices controlled by the voice computer. + +Each voice is built around a CEM3374 (dual oscillator) and a CEM3372 (VCF and +VCA). VCO2 can modulate either VCO1 or the VCF to produce FM effects. There is +also circuitry to generate pulse waves out of the saw ones, and to mix in noise. +The noise source is shared for all voices. The CEM3372 is combined with a 4051 +MUX and other support circuitry to implement 16 different filter modes. + +The voice computer generates 9 control voltages (CVs) for each voice: +- VCO pitch (1 and 2). +- VCO pulse-width (1 and 2). +- VCO volume (1 and 2). +- VCA amplitude. +- VCF cutoff frequency. +- VCF resonance. + +There are no analog LFOs or EGs. Those are implemented digitally, and their +effect is incorporated in the CV outputs. All 54 (6 x 9) CVs are generated using +a single 14-bit DAC, whose output is time-multiplexed to 54 Sample and Hold +(S&H) circuits. The CV generation circuit can operate in a "high resolution" +mode which surpases 14 bits (see voice_dac_enable_w()). This is used for pitch +CVs. + +The voice computer also selects VCO waveforms, switches between different VCF +modes, etc., by controlling digital switches and multiplexers. Finally, it also +controls the routing and amount of FM by configuring an MDAC (AD7523, one per +voice). + +PCBoards: +- Processor board: main computer. +- Pot board: buttons, rotary encoders, inputs, outputs. +- Display board: control of VFDS. +- Voice board: 6 analog voices, voice computer. +- Power supply. + +This driver is based on the Xpander's service manual and schematics, and is +intended as an educational tool. There is no attempt to emulate audio. +*/ + +#include "emu.h" +#include "bus/midi/midi.h" +#include "cpu/m6809/m6809.h" +#include "machine/6850acia.h" +#include "machine/adc0804.h" +#include "machine/clock.h" +#include "machine/nvram.h" +#include "machine/output_latch.h" +#include "machine/pit8253.h" +#include "machine/rescap.h" +#include "machine/timer.h" +#include "video/pwm.h" + +#include "oberheim_xpander.lh" + +#define LOG_CV_IN (1U << 1) +#define LOG_SWITCHES (1U << 2) +#define LOG_ENCODERS (1U << 3) +#define LOG_CPU_COMMS (1U << 4) +#define LOG_FIRQ_TIMER (1U << 5) +#define LOG_VOICE_TIMER (1U << 6) +#define LOG_DAC (1U << 7) +#define LOG_DAC_VERBOSE (1U << 8) +#define LOG_MEM_PROTECT (1U << 9) + +#define VERBOSE (LOG_GENERAL | LOG_ENCODERS | LOG_FIRQ_TIMER | LOG_DAC | LOG_MEM_PROTECT) +//#define LOG_OUTPUT_FUNC osd_printf_info + +#include "logmacro.h" + +namespace { + +constexpr const char MAINCPU_TAG[] = "main_68b09"; +constexpr const char VOICECPU_TAG[] = "voice_68b09"; +constexpr const char NVRAM_A_TAG[] = "nvram_a"; +constexpr const char NVRAM_B_TAG[] = "nvram_b"; +constexpr const char VOICERAM_TAG[] = "voiceram"; + +constexpr const int NUM_ENCODERS = 6; +constexpr const int NUM_ENCODER_POSITIONS = 30; + +class xpander_state : public driver_device +{ +public: + xpander_state(const machine_config &mconfig, device_type type, const char *tag) ATTR_COLD + : driver_device(mconfig, type, tag) + , m_maincpu(*this, MAINCPU_TAG) + , m_midiacia(*this, "midiacia") + , m_adc(*this, "adc") + , m_firq_timer(*this, "firq_timer") + , m_nvram_a_view(*this, "nvram_a_view") + , m_rom_0_view(*this, "rom_0_view") + , m_switch_io(*this, "switches_%d", 0U) + , m_memory_protect_io(*this, "memory_protect") + , m_gate_io(*this, "gate_inputs") + , m_cv_io(*this, "cv_in_%d", 1U) + , m_pedal_io(*this, "pedal_%d", 1U) + , m_vfd_devices(*this, "vfd_%d", 0U) + , m_vfd_outputs() + , m_cassmute(*this, "cassmute") + , m_voicecpu(*this, VOICECPU_TAG) + , m_voicepit(*this, "voice_pit_8253") + , m_voiceram(*this, VOICERAM_TAG) + , m_fm_mdac(*this, "voice_%d_fm_mdac", 1U) + , m_filter_mode(*this, "voice_%d_filter_mode", 1U) + , m_noise(*this, "voice_%d_noise", 1U) + , m_pan(*this, "voice_%d_pan", 1U) + , m_saw1(*this, "voice_%d_saw1", 1U) + , m_saw2(*this, "voice_%d_saw2", 1U) + , m_tri1(*this, "voice_%d_tri1", 1U) + , m_tri2(*this, "voice_%d_tri2", 1U) + , m_vcofm(*this, "voice_%d_vcofm", 1U) + , m_sync(*this, "voice_%d_sync", 1U) + , m_haltreq(4, false) + , m_encoder_dir(NUM_ENCODERS, false) + , m_encoder_changed(NUM_ENCODERS, false) + , m_vfd_anode_masks(3, 0) + , m_cv(NUM_VOICES, std::vector(NUM_CVS, -1)) + , m_fast(NUM_VOICES, std::vector(NUM_CVS - 1, false)) // `-1` because RES does not support fast updates. + { + for (int i = 0; i < m_vfd_devices.size(); ++i) + { + std::string format = "vfd_" + std::to_string(i + 1) + "_char_%d"; + m_vfd_outputs.push_back(output_finder<40>(*this, std::move(format), 1U)); + } + } + + void xpander(machine_config &config) ATTR_COLD; + + DECLARE_INPUT_CHANGED_MEMBER(encoder_moved); + DECLARE_INPUT_CHANGED_MEMBER(memory_protect_changed); + +protected: + void machine_start() override ATTR_COLD; + +private: + TIMER_DEVICE_CALLBACK_MEMBER(firq_timer_elapsed); + void firq_timer_preset_w(u8 data); + + u8 proc_datain_r(); + u8 gate_r(); + u8 switch_r(offs_t offset); + u8 encoder_dir_r(); + u8 encoder_sw_r(); + + u8 cv_in_r(); + u8 adc_r(offs_t offset); + void adc_w(offs_t offset, u8 data); + + void refresh_voicecpu_halt_line(); + + void cass_out_w(u8 data); + void haltset_w(u8 data); + void display_w(offs_t offset, u8 data); + void display_output_w(int display, offs_t offset, u32 data); + + u8 voice_datain_r(); + void voice_dataout_w(u8 data); + void voice_latch0_w(offs_t offset, u8 data); + void voice_latch1_w(offs_t offset, u8 data); + void voice_latch2_w(offs_t offset, u8 data); + + float get_dac_v() const; + void voice_update_cv(u8 voice, u8 cv_index, float cv, bool fast); + void voice_update_resonance_cv(u8 voice, float cv); + void voice_dac_enable_w(offs_t offset, u8 data); + void voice_dac_clear_w(u8 data); + void voice_dac_w(offs_t offset, u8 data); + + void voicepit_out0_changed(int state); + void voicepit_out2_changed(int state); + + void maincpu_map(address_map &map) ATTR_COLD; + void voicecpu_map(address_map &map) ATTR_COLD; + + static constexpr const u16 LOW7_MASK = 0x7f; + static constexpr const u16 HIGH7_MASK = LOW7_MASK << 7; + static constexpr const int NUM_VOICES = 6; + static constexpr const int NUM_CVS = 9; + static constexpr const int RES_CV_INDEX = 8; + static constexpr const char *CV_NAMES[NUM_CVS] = + { + "VCA", "PW1", "VOLA", "VCOF1", "VOLB", "VCFF", "PW2", "VCOF2", "RES" + }; + + // Main computer. + required_device m_maincpu; + required_device m_midiacia; + required_device m_adc; + required_device m_firq_timer; // 40103 presetable timer (U12). + memory_view m_nvram_a_view; + memory_view m_rom_0_view; + required_ioport_array<8> m_switch_io; + required_ioport m_memory_protect_io; + required_ioport m_gate_io; + required_ioport_array<6> m_cv_io; + required_ioport_array<2> m_pedal_io; + required_device_array m_vfd_devices; + std::vector> m_vfd_outputs; + output_finder<> m_cassmute; + + // Voice computer. + required_device m_voicecpu; + required_device m_voicepit; + required_shared_ptr m_voiceram; + output_finder m_fm_mdac; // Latch connected to AD7523/MP7523. + output_finder m_filter_mode; + output_finder m_noise; + output_finder m_pan; + output_finder m_saw1; + output_finder m_saw2; + output_finder m_tri1; + output_finder m_tri2; + output_finder m_vcofm; + output_finder m_sync; + + // Many bool variables represent signals in the schematic (e.g. HALTREQ). + // Some of those signals are active low in the schematic (e.g. HALTREQ*). + // But the variables here are always active-high (active == true). + + // Main computer state. + u8 m_firq_timer_preset = 0xff; // Preset for 40103 timer. Pulled high. + u8 m_selected_cv_in = 0x07; // MUX A-C inputs. Pulled high. + bool m_inhibit_cv_in = true; // MUX INHibit input. Pulled high. + std::vector m_haltreq; // Halt request to the voice board (HALTREQ). + std::vector m_encoder_dir; + std::vector m_encoder_changed; + std::vector m_vfd_anode_masks; + + // Voice computer state. + bool m_haltdis = 0; // Halt disable (HALTDS). + bool m_haltack = false; // Halt acknowledge (HALTAKN). + bool m_autodone = true; // Autotune done (AUTODNE). + u16 m_dac_data = 0; + float m_dac_fine_v = 0; + float m_dac_vref = 4.865F; // Sampled in C806 and buffered and scaled by U815. + bool m_allow_fast = false; + std::vector> m_cv; + std::vector> m_fast; +}; + +TIMER_DEVICE_CALLBACK_MEMBER(xpander_state::firq_timer_elapsed) +{ + // CPU clock divided by U10 (LS393, processor board). + static constexpr const XTAL TIMER_CLOCK = 16_MHz_XTAL / 64; // 250 KHz. + + // The FIRQ timer is a 40103 timer (U12, processor board). It is configured + // to reset to its preset value when the count reaches 0 (/TC connected to + // /PE). The FIRQ is triggered on the next clock cycle, once /TC goes high + // (/TC used as CLK on an 74LS74 that will drive /FIRQ low when clocked). + + if (param < 0) // param < 0 means the timer counted down to 0. + { + // The output is now low. It will go high on the next clock cycle, + // which will trigger a bunch of stuff (see 'else' below). The timer + // count needs to be set to the current value of `m_firq_timer_preset`, + // which could (in theory) change by next cycle. So pass the current + // value in `param`. The `-1` accounts for this single timer cycle. + m_firq_timer->adjust(1 * attotime::from_hz(TIMER_CLOCK), m_firq_timer_preset - 1); + } + else // Param >= 0 means counting restarted and output transitioned high. + { + // Using HOLD_LINE, because FIRQ will be cleared by circuitry that + // detects a FIRQ Acknowledge. That circuit consists of: + // U17 (74LS32), U21 (1/4 74LS04), U16 (74LS74). It clears the + // FIRQ line on the rising edge of Q when BS = 1, BA = 0 and A3 = 0. + m_maincpu->set_input_line(M6809_FIRQ_LINE, HOLD_LINE); + + // Schedule for the specified number of cycles. param = -1 ensure the + // logic in `if (param < 0)` above is activated when the timer elapses. + m_firq_timer->adjust(param * attotime::from_hz(TIMER_CLOCK), -1); + + // When FIRQ* activates, it issues a /CLR on 74LS259 (U12, display + // board). This also clears the grid mask as a side effect. See + // display_w() for more info. + for (int i = 0; i < m_vfd_devices.size(); ++i) + m_vfd_devices[i]->matrix(0, m_vfd_anode_masks[i]); + } +} + +void xpander_state::firq_timer_preset_w(u8 data) +{ + if (m_firq_timer_preset == data) + return; + m_firq_timer_preset = data; + LOGMASKED(LOG_FIRQ_TIMER, "FIRQ Timer Preset: %02x\n", data); +} + +u8 xpander_state::proc_datain_r() +{ + // LS367, U15 (progressor board) + // D0 - HALTAKN* + const u8 d0 = m_haltack ? 0 : 1; + // D1 - Memory write protect (low when protected) + const u8 d1 = BIT(m_memory_protect_io->read(), 0); + // D2 - Cassette DATA. + const u8 d2 = 1; // TODO: Implement. + // D3-D5 not connected. + return 0xf8 | (d2 << 2) | (d1 << 1) | (d0 << 0); +} + +u8 xpander_state::gate_r() +{ + // All component designations refer to the Pot board. + // D0-D5: GATE1-6. + // D6: CHAIN ADVANCE. + // D7: TRIGGER. + // All signals inverted by U22 (CA3081, D1-D7) and Q1 (NPN, D0). + // TRIGGER (D7) is inverted again by U2 (74LS02). Also connected to C17. + // Signals other than TRIGGER not connected to such a capacitor. + const u8 value = ~m_gate_io->read() ^ 0x80; + LOGMASKED(LOG_CV_IN, "Gate: %02x\n", value); + return value; +} + +u8 xpander_state::switch_r(offs_t offset) +{ + // A0-A2 used as ABC inputs of a 74LS42 (U16, pot board), which in turn + // controls which column of the switch matrix is active. + // Input D is connected to the SWITCH* signal, and outputs 8 and 9 (active + // when SWITCH* is high) are not connected. + const u8 column = offset & 0x07; + const u8 data = m_switch_io[column]->read(); + if (data != 0xff) + LOGMASKED(LOG_SWITCHES, "Pressed: %02x - %02x\n", column, data); + return data; +} + +static u8 byte_from_vector(const std::vector &v) +{ + assert(v.size() <= 8); + u8 data = 0; + for (int i = 0; i < v.size(); ++i) + if (v[i]) + data |= 1U << i; + return data; +} + +u8 xpander_state::encoder_dir_r() +{ + // The DIR* signal also resets encoder change detection flip-flops when + // active (low). + for (int i = 0; i < m_encoder_changed.size(); ++i) + m_encoder_changed[i] = false; + + const u8 data = byte_from_vector(m_encoder_dir); + LOGMASKED(LOG_ENCODERS, "Encoder dir_r: %02x\n", data); + return data; +} + +u8 xpander_state::encoder_sw_r() +{ + const u8 data = byte_from_vector(m_encoder_changed); + if (data != 0) + LOGMASKED(LOG_ENCODERS, "Encoder sw_r: %02x\n", data); + return data; +} + +u8 xpander_state::cv_in_r() +{ + assert(m_selected_cv_in >= 0 && m_selected_cv_in < 8); + if (m_inhibit_cv_in) + return 0; + + u8 cv = 0; + if (m_selected_cv_in < 6) + cv = m_cv_io[m_selected_cv_in]->read(); + else + cv = m_pedal_io[m_selected_cv_in - 6]->read(); + + LOGMASKED(LOG_CV_IN, "CV in: %02x - %02x\n", m_selected_cv_in, cv); + return cv; +} + +u8 xpander_state::adc_r(offs_t offset) +{ + // CV* signal mapped to: + + // a) U18 latch on pot board, controls U17 (4051) mux ( + // A0-A2 -> A-C, A3 -> INH). + if (!machine().side_effects_disabled()) + { + m_selected_cv_in = offset & 0x07; + m_inhibit_cv_in = offset & 0x08; + } + + // b) U20 on Pot Board (ADC0804). + const u8 data = m_adc->read(); + LOGMASKED(LOG_CV_IN, "ADC Read: %02x - %02x\n", offset, data); + return data; +} + +void xpander_state::adc_w(offs_t offset, u8 data) +{ + LOGMASKED(LOG_CV_IN, "ADC Write: %02x - %02x\n", offset, data); + // CV* signal mapped to: + // a) U18 latch on pot board, controls U17 (4051) mux (A0-A2 -> A-C, A3 -> INH). + m_selected_cv_in = offset & 0x07; + m_inhibit_cv_in = offset & 0x08; + // b) U20 on Pot Board (ADC0804). + m_adc->write(data); +} + +void xpander_state::refresh_voicecpu_halt_line() +{ + if (m_haltreq[0] && !m_haltdis) + { + m_voicecpu->set_input_line(INPUT_LINE_HALT, ASSERT_LINE); + m_haltack = true; + m_rom_0_view.select(1); + } + else + { + m_voicecpu->set_input_line(INPUT_LINE_HALT, CLEAR_LINE); + m_haltack = false; + m_rom_0_view.select(0); + } +} + +void xpander_state::cass_out_w(u8 data) +{ + // TODO: Implement. +} + +void xpander_state::haltset_w(u8 data) +{ + // Bits 0-3: HALTREQ* 0-3 + for (int i = 0; i < 4; ++i) + { + m_haltreq[i] = !BIT(data, i); // Active low. + if (i > 0 && m_haltreq[i]) + { + // The voice board is wired (with a rotary switch) to HALTREQ0*. + // HALTREQ(1-3)* are not used in the xpander. They a provision for + // for extending to more voice boards. For example, the Oberheim + // Matrix 12 uses two of those. + LOGMASKED(LOG_CPU_COMMS, "Unexpected HALTREQ %d asserted\n", i); + } + } + refresh_voicecpu_halt_line(); + + // Bit 4: RES* (voice cpu reset). + const bool reset_voice = !BIT(data, 4); // Active low. + const bool voice_resetting = (m_voicecpu->input_state(INPUT_LINE_RESET) == ASSERT_LINE); + if (reset_voice != voice_resetting) + { + m_voicecpu->set_input_line(INPUT_LINE_RESET, reset_voice ? ASSERT_LINE : CLEAR_LINE); + if (reset_voice) + LOGMASKED(LOG_CPU_COMMS, "Asserting voice CPU reset line\n"); + else + LOGMASKED(LOG_CPU_COMMS, "Clearing voice CPU reset line\n"); + } + + m_cassmute = !BIT(data, 5); // Bit 5: CASSMUTE*, active low. + // Bit 6 not connected. + cass_out_w(BIT(data, 7)); // Bit 7: CASS OUT. +} + +void xpander_state::display_w(offs_t offset, u8 data) +{ + // There are 3 vacuum fluorescent displays (VFDs). These are controlled in + // a similar way to multi-segment LED displays, and can be time-multiplexed + // in the same way, though they run at a higher voltage (55V in this case). + + // Each of the 3 VFDs has 40 16-segment characters. The segments in each + // display share a common anode, for a total of 3 x 16 = 48 anode signals. + // There is a gate signal for each of the 40 characters, and those are + // shared between the 3 displays, for a total of 40 gate signals. + + // All component designations refer to the display board. + + // U12 (74LS239) selects which anode signal latch will be enabled, based on + // A0-A2. There are 6 latches (74LS374), 2 per display. + const u8 display = (offset >> 1) & 0x03; + if (display < 3) // U12 outputs 6 and 7 are not connected. + { + if (offset & 0x01) // Modifying high-order byte. + m_vfd_anode_masks[display] = (u16(data) << 8) | (m_vfd_anode_masks[display] & 0x00ff); + else + m_vfd_anode_masks[display] = (m_vfd_anode_masks[display] & 0xff00) | data; + } + + // An 74LS42 (U9), combined with 5 x 4028 (U2, U6, U8, U14, U18) form a + // decoder that translates the latched A3-A8 to a single selected grid. + // However, decoding is only enabled when U12 output 5 is high. + const bool grid_enabled = (offset & 0x07) == 5; + u8 grid_offset = (offset >> 3) & 0x3f; + u64 grid_mask = 0; + if (grid_enabled && grid_offset < 40) // There are 40 grid signals. + grid_mask = u64(1) << grid_offset; + + // Refresh VFD devices with the latest state. + for (int i = 0; i < m_vfd_devices.size(); ++i) + m_vfd_devices[i]->matrix(grid_mask, m_vfd_anode_masks[i]); + + // Note that the output of U12 (anode select decoder), the grid mask, and + // the address driving it (A3-A8, U11, 74LS174) are latched until the next + // write, or until FIRQ is invoked. But there is no need to store state + // for these, the "matrix()" call above achieves the same, implicitly. +} + +void xpander_state::display_output_w(int display, offs_t offset, u32 data) +{ + // The FG405A2 is a non-standard 16-segment display. It includes a + // 14-segment character, a period, and a line under the character. + // Map the 16 segments of the FG405A2 to a `led14segsc`. The line under the + // character will be represented with the comma. + assert(display >= 0 && display < 3); + m_vfd_outputs[display][offset] = + bitswap<16>(data, 0, 1, 3, 6, 5, 4, 2, 7, 12, 11, 10, 13, 15, 14, 9, 8); +} + +void xpander_state::voice_dataout_w(u8 data) +{ + // D0 - HALTDS + const u8 d0 = BIT(data, 0); + if (m_haltdis != d0) + { + m_haltdis = d0; + LOGMASKED(LOG_CPU_COMMS, "Voice HALTDS: %d\n", m_haltdis); + refresh_voicecpu_halt_line(); + } + // D1 - AUTOST + m_voicepit->write_gate0(BIT(data, 1)); + // D2 - PW* + // TODO: pit gate2 is actually: PW* | OSC. For now, setting to PW*, though + // this is wrong. + m_voicepit->write_gate2(BIT(data, 2)); + // TODO: D3 - AUTO* + // D4-D5 - Not Connected. +} + +u8 xpander_state::voice_datain_r() +{ + // D0 - AUTODNE* + const u8 d0 = m_autodone ? 0 : 1; + // D1 - OSC + const u8 d1 = 1; // TODO: Implement. + // D2-D7 - Not connected (pulled up). + return 0xfc | (d1 << 1) | d0; +} + +void xpander_state::voice_latch0_w(offs_t offset, u8 data) // UX03, 74LS374. +{ + if (offset >= NUM_VOICES) + return; + m_fm_mdac[offset] = data; +} + +void xpander_state::voice_latch1_w(offs_t offset, u8 data) // UX02, 74HC174. +{ + if (offset >= NUM_VOICES) + return; + m_vcofm[offset] = BIT(data, 0); + m_saw2[offset] = BIT(data, 1); + m_tri1[offset] = BIT(data, 2); + m_sync[offset] = BIT(data, 3); + m_tri2[offset] = BIT(data, 4); + m_saw2[offset] = BIT(data, 5); +} + +void xpander_state::voice_latch2_w(offs_t offset, u8 data) // UX01, 74HC374. +{ + if (offset >= NUM_VOICES) + return; + m_filter_mode[offset] = data & 0x0f; + m_noise[offset] = BIT(data, 4); + m_pan[offset] = data >> 5; +} + +float xpander_state::get_dac_v() const // Returns output of U812. +{ + static constexpr const u16 MAX_DAC_DATA = (1U << 14) - 1; + return -m_dac_data * m_dac_vref / MAX_DAC_DATA; +} + +void xpander_state::voice_update_cv(u8 voice, u8 cv_index, float cv, bool fast) +{ + assert(voice < NUM_VOICES); + if (m_cv[voice][cv_index] == cv && m_fast[voice][cv_index] == fast) + return; + + m_cv[voice][cv_index] = cv; + m_fast[voice][cv_index] = fast; + LOGMASKED(LOG_DAC, "Voice %d - CV %s: %f, fast: %d\n", + voice, CV_NAMES[cv_index], cv, fast); +} + +void xpander_state::voice_update_resonance_cv(u8 voice, float cv) +{ + if (voice >= NUM_VOICES) + return; + if (m_cv[voice][RES_CV_INDEX] == cv) + return; + + m_cv[voice][RES_CV_INDEX] = cv; + LOGMASKED(LOG_DAC, "Voice %d - CV %s: %f\n", + voice, CV_NAMES[RES_CV_INDEX], cv); +} + +void xpander_state::voice_dac_enable_w(offs_t offset, u8 data) +{ + // All component designations refer to the voice board. + + // 4V reference used as a source for other reference voltages. + // Generated by D805, R856, R857, R854, U815. + static constexpr const float V_REF_U815 = 4; + static constexpr const float DEFAULT_UNSCALED_VREF = + V_REF_U815 * RES_VOLTAGE_DIVIDER(RES_K(18.2), RES_K(10)); + static constexpr const float VREF_SCALER = 1 + RES_K(2.43) / RES_K(1); // U815, R851, R852. + static constexpr const float DEFAULT_VREF = DEFAULT_UNSCALED_VREF * VREF_SCALER; + static constexpr const float CEM3374_NOMINAL_TEMPCO_V = 2.5; + + LOGMASKED(LOG_DAC_VERBOSE, "DAC: %04x: %02x\n", offset, data); + + // Updating the 7 LSBits for the DAC. Bit 0 is ignored. + m_dac_data = (m_dac_data & HIGH7_MASK) | (data >> 1); + + const bool is_hres = !BIT(offset, 8); + if (is_hres) + { + const u8 ref_mux_address = (offset >> 4) & 0x07; // A4-A6 (U805). + if (ref_mux_address >= 1 && ref_mux_address <= 6) + { + // Selects the voiceX temperature compensation voltage. + m_dac_vref = CEM3374_NOMINAL_TEMPCO_V * VREF_SCALER; + } + else if (ref_mux_address == 7) + { + m_dac_vref = DEFAULT_VREF; + } + // else: ref_mux_address = 0 disconnects the reference voltage. + // In that case, the last value sampled in C22 is used. So we keep + // the value of m_dac_vref the same. + } + else + { + // U805 disabled. But Y input of U014 (4053) enabled. + m_dac_vref = DEFAULT_VREF; + } + + if (BIT(offset, 7)) // FTSH (fine-tune sample & hold) active (active high). + { + // No S&H MUXes are selected, because input D on U803 is high. + // U814 is activated by FTSH, and samples the DAC voltage in C805/U815 ( + // m_dac_fine_v). This "fine" voltage will be added to the coarser + // voltage in a future DAC write. This mode is used for generating + // voltages at a resolution greater than 14 bits. + m_dac_fine_v = get_dac_v(); + return; + } + + // FTSH low enables one of the first 8 outputs of U803 (controlled + // by A5-A7). Each of those outputs enables a 4051 mux, whose + // address is controlled by A1-A3. + + const u8 selected_sh = (offset >> 4) & 0x07; // A4-A6. + if (selected_sh == 0) // U803 output 0 is not connected. + return; + + float dac_v = -RES_K(10) / RES_K(10) * get_dac_v(); + if (is_hres) // Turns on U814. + { + dac_v += -RES_K(10) / RES_K(30.1) * m_dac_fine_v + -RES_K(10) / RES_K(17.4) * m_dac_vref; + } + + const u8 sh_address = (offset >> 1) & 0x07; // A1-A3. + if (selected_sh == 7) + { + // Updates resonance (RES). In this case, the voice is selected by + // `sh_address` (U816). Note that RES is not affected by the FAST* + // signal. + voice_update_resonance_cv(sh_address, dac_v); + } + else + { + // Voice is selected by `selected_sh` (output of U803). Need a -1 + // because the first U803 output is not connected. The CV index for + // the given voice is selected by `sh_address`. + voice_update_cv(selected_sh - 1, sh_address, dac_v, m_allow_fast); + } +} + +void xpander_state::voice_dac_clear_w(u8 data) +{ + // (1) Clears latch U806. This results in: + // - No S&H mux selected (activates output 0 of U803, which is not + // connected to anything) + // - S&H address 0 selected, but this is a no-op since no S&H mux is + // selected (see above). + // - FTSH (active high) deactivated (set low). + // - HRES* (active low) activated (set low). + // - Mux U805 activated (by HRES* low), but address 0 selected. + // input 0 is not connected. + // - No active reference voltage to the DAC. Previously used reference + // sampled in C806. + // None of the above affect emulation, since they will be set to valid + // values on the next invocation to voice_dac_w(). + + // (2) Activates latch for the 7 MSBits of the 14-bit DAC. + // D0-D6 used for the 7 MSBits. + // D7, if set, will enable fast mode on the next invocation to + // voice_dac_w(). + m_dac_data = ((data & LOW7_MASK) << 7) | (m_dac_data & LOW7_MASK); + m_allow_fast = BIT(data, 7); + LOGMASKED(LOG_DAC_VERBOSE, "DAC clear %02x: %04x - %d\n", + data, m_dac_data, m_allow_fast); +} + +void xpander_state::voice_dac_w(offs_t offset, u8 data) +{ + if (BIT(offset, 0)) // VOICEN* (A0 high) + voice_dac_enable_w(offset, data); + else // CLEAR* (A0 low) + voice_dac_clear_w(data); +} + +void xpander_state::voicepit_out0_changed(int state) +{ + // OUT0 -> AUTODNE* -> 74LS04 (inverted) -> GATE1 + m_autodone = !state; + m_voicepit->write_gate1(state ? 0 : 1); + LOGMASKED(LOG_VOICE_TIMER, "PIT timer 0 out: %d\n", state); +} + +void xpander_state::voicepit_out2_changed(int state) +{ + if (state) // Interrupt triggered on positive transition of output. + { + // Using HOLD_LINE because there is circuitry that clears the IRQ* line + // when the CPU ACks the IRQ (U919 - 74LS74, U911 - 74LS08, looking + // at BS and BA*). + m_voicecpu->set_input_line(M6809_IRQ_LINE, HOLD_LINE); + } +} + +void xpander_state::maincpu_map(address_map &map) +{ + // Component designations refer to the Processor board, unless otherwise + // noted. The signal names below (e.g. DISP*, HALTSET*) reference those in + // the schematics. + map.unmap_value_high(); // Data bus pulled high by resistors in Pot board. + + // 1/2 74LS139 (U22, O0-O2) controls access to RAM and other ports. + // RAM write and select signals can only go active low when there is power + // (as determined by the PUP circuit). + // NVRAM_A can be write-protected in hardware by a memory-protect switch + // (SW6, active-closed, disables /WR signal). + map(0x000, 0x3fff).view(m_nvram_a_view); + m_nvram_a_view[0](0x000, 0x3fff).ram().share(NVRAM_A_TAG); + m_nvram_a_view[1](0x000, 0x3fff).readonly().share(NVRAM_A_TAG); + // NVRAM_B is not protected by SW6. + map(0x4000, 0x5fff).ram().share(NVRAM_B_TAG); // 1 x 6264 (U4). + + // The 74LS139 above (O3) along with an 74LS42 (U23) control access to all + // other ports / peripherals. + // Active when O3 is low and one of READ*, WRITE*, UART* is low. + map(0x6000, 0x61ff).mirror(0x0200).w(FUNC(xpander_state::display_w)); // DISP* and DISPLCLR* + map(0x6400, 0x6400).mirror(0x03ff).w(FUNC(xpander_state::firq_timer_preset_w)); // INTSET* + map(0x6800, 0x6800).mirror(0x03ff).w(FUNC(xpander_state::haltset_w)); // HALTSET* + // ACIA addressing: RS <- A0, CS1 <- A1, CS0 <- A2, /CS2 <- UART*. + map(0x6c06, 0x6c07).mirror(0x03f8).rw(m_midiacia, FUNC(acia6850_device::read), FUNC(acia6850_device::write)); // UART* + map(0x7000, 0x7000).mirror(0x03ff).r(FUNC(xpander_state::proc_datain_r)); // DATAIN* + map(0x7400, 0x7400).mirror(0x03ff).w("latch_u24_proc", FUNC(output_latch_device::write)); // LED2* + map(0x7800, 0x7800).mirror(0x03ff).unmaprw(); // Unused. U23 output 6 not connected. + + // Peripherals on Pot board mapped to 0x7c00-0x7fff (BEN* output of U23). + // Additional decoding for those is done by U15 on the Pot board (74LS42). + // A9 and A0-A5 are not used for decoding (mirror: 0x023f) + map(0x7c00, 0x7c00).mirror(0x023f).r(FUNC(xpander_state::encoder_dir_r)); // DIR* + map(0x7c40, 0x7c40).mirror(0x023f).r(FUNC(xpander_state::encoder_sw_r)); // SW* + map(0x7c80, 0x7c80).mirror(0x023f).w("latch_u14_pot", FUNC(output_latch_device::write)); // LED1* + map(0x7cc0, 0x7cc0).mirror(0x023f).r(FUNC(xpander_state::gate_r)); // GATE* + map(0x7d00, 0x7d00).mirror(0x023f).w("latch_u21_pot", FUNC(output_latch_device::write)); // PULL* + map(0x7d40, 0x7d47).mirror(0x0238).r(FUNC(xpander_state::switch_r)); // SWITCH* + map(0x7d80, 0x7d8f).mirror(0x0230).rw(FUNC(xpander_state::adc_r), FUNC(xpander_state::adc_w)); // CV* + map(0x7dc0, 0x7dc0).mirror(0x023f).unmaprw(); // Unused. U15 (Pot board) output 7 not connected. + + // ROM and VOICERAM decoding is done by 1/2 LS139 (U22), 2/4 LS04 (U21), + // 2/4 LS32 (U20). + // (0x8000, 0x9fff) conditionally accesses VOICERAM, when HALTAKN* active. + map(0x8000, 0x9fff).view(m_rom_0_view); + m_rom_0_view[0](0x8000, 0x9fff).rom().region(MAINCPU_TAG, 0x0000); // 1 x 2764 (U8). + m_rom_0_view[1](0x8000, 0x9fff).ram().share(VOICERAM_TAG); + map(0xa000, 0xffff).rom().region(MAINCPU_TAG, 0x2000); // 3 x 2764 (U7 - U5). +} + +void xpander_state::voicecpu_map(address_map &map) +{ + // All component designations refer to the Voice board. + // Signal names (e.g. LATCH0*) are from the schematic. + map.unmap_value_high(); // Data bus pulled high by resistors in Voice Board. + + // RAM's /CS connected to A15. + map(0x0000, 0x1fff).mirror(0x6000).ram().share(VOICERAM_TAG); // 1 x 6264 (U917). + + // ROM and port/peripheral decoding done by 1/2 LS139 (U915). + // Ports / peripherals enabled when U915 O0 is low AND one of + // READ*, WRITE*, TIMER* is low. Additional decoding done by 74LS42 (U918). + map(0x8000, 0x8007).mirror(0x03f8).w(FUNC(xpander_state::voice_latch0_w)); // LATCH0* + map(0x8400, 0x8407).mirror(0x03f8).w(FUNC(xpander_state::voice_latch1_w)); // LATCH1* + map(0x8800, 0x8807).mirror(0x03f8).w(FUNC(xpander_state::voice_latch2_w)); // LATCH2* + map(0x8c00, 0x8dff).mirror(0x0200).w(FUNC(xpander_state::voice_dac_w)); // SDAC (inverted by LS04). + map(0x9000, 0x9000).mirror(0x03ff).w(FUNC(xpander_state::voice_dataout_w)); // DATAOUT* + map(0x9400, 0x9400).mirror(0x03ff).r(FUNC(xpander_state::voice_datain_r)); // DATAIN* + map(0x9800, 0x9803).mirror(0x03fc).rw(m_voicepit, FUNC(pit8253_device::read), FUNC(pit8253_device::write)); // TIMER* + map(0x9c00, 0x9c00).mirror(0x03ff).unmaprw(); // Unused. U918 output 7 not connected. + + // ROMs only get enabled if BA* is high. I.e. disabled when main CPU has + // control of the voice cpu's bus. + map(0xa000, 0xffff).rom().region(VOICECPU_TAG, 0); // 3 x 6264 (U909, U912, U914) +} + +void xpander_state::machine_start() +{ + firq_timer_elapsed(*m_firq_timer, m_firq_timer_preset); // Reset the timer. + + m_cassmute.resolve(); + for (output_finder<40> &vfd_char_output : m_vfd_outputs) + vfd_char_output.resolve(); + + m_fm_mdac.resolve(); + m_filter_mode.resolve(); + m_noise.resolve(); + m_pan.resolve(); + m_saw1.resolve(); + m_saw2.resolve(); + m_tri1.resolve(); + m_tri2.resolve(); + m_vcofm.resolve(); + m_sync.resolve(); +} + +void xpander_state::xpander(machine_config &config) +{ + MC6809(config, m_maincpu, 16_MHz_XTAL / 2); + m_maincpu->set_addrmap(AS_PROGRAM, &xpander_state::maincpu_map); + + TIMER(config, m_firq_timer).configure_generic(FUNC(xpander_state::firq_timer_elapsed)); + + NVRAM(config, NVRAM_A_TAG, nvram_device::DEFAULT_ALL_0); + NVRAM(config, NVRAM_B_TAG, nvram_device::DEFAULT_ALL_0); + + ACIA6850(config, m_midiacia, 0); + m_midiacia->txd_handler().set("mdout", FUNC(midi_port_device::write_txd)); + m_midiacia->irq_handler().set_inputline(m_maincpu, M6809_IRQ_LINE); + + clock_device &acia_clock = CLOCK(config, "aciaclock", 16_MHz_XTAL / 32); // 500 KHz. + acia_clock.signal_handler().set(m_midiacia, FUNC(acia6850_device::write_txc)); + acia_clock.signal_handler().append(m_midiacia, FUNC(acia6850_device::write_rxc)); + + MIDI_PORT(config, "mdout", midiout_slot, "midiout"); + MIDI_PORT(config, "mdthru", midiout_slot, "midiout"); + midi_port_device &midi_in = MIDI_PORT(config, "mdin", midiin_slot, "midiin"); + midi_in.rxd_handler().set(m_midiacia, FUNC(acia6850_device::write_rxd)); + midi_in.rxd_handler().append("mdthru", FUNC(midi_port_device::write_txd)); + + ADC0804(config, m_adc, 16_MHz_XTAL / 32); // 500 KHz. + m_adc->vin_callback().set(FUNC(xpander_state::cv_in_r)); + + for (int i = 0; i < m_vfd_devices.size(); ++i) + { + PWM_DISPLAY(config, m_vfd_devices[i]).set_size(40, 16); // 40 x 16-segment display. + m_vfd_devices[i]->set_segmask(0xffffffffff, 0xffff); + m_vfd_devices[i]->output_digit().set([this, i](offs_t offset, u32 data) { display_output_w(i, offset, data); }); + } + + config.set_default_layout(layout_oberheim_xpander); + + MC6809(config, m_voicecpu, 16_MHz_XTAL / 2); + m_voicecpu->set_addrmap(AS_PROGRAM, &xpander_state::voicecpu_map); + + PIT8253(config, m_voicepit); + // TODO: Set clk<0>. Connected to "OSC". + // Clock inputs 1 and 2 connected to CPU's Q signal, which is 4 times slower + // than the XTAL input to the CPU. + m_voicepit->set_clk<1>(16_MHz_XTAL / 2 / 4); // 2 MHz. + m_voicepit->set_clk<2>(16_MHz_XTAL / 2 / 4); // 2 MHz. + m_voicepit->out_handler<0>().set(FUNC(xpander_state::voicepit_out0_changed)); + // Output 1 not connected. + m_voicepit->out_handler<2>().set(FUNC(xpander_state::voicepit_out2_changed)); + + // LED2*, U24 on Processor board. All outputs active low (connected to LED + // cathodes). + output_latch_device &u24(OUTPUT_LATCH(config, "latch_u24_proc")); + u24.bit_handler<0>().set_output("led_misc").invert(); + u24.bit_handler<1>().set_output("led_ramp_x").invert(); + u24.bit_handler<2>().set_output("led_lfo_x").invert(); + u24.bit_handler<3>().set_output("led_env_x").invert(); + u24.bit_handler<4>().set_output("led_vcf_vca").invert(); + u24.bit_handler<5>().set_output("led_vco1").invert(); // On Pot board. + u24.bit_handler<6>().set_output("led_vco2").invert(); // On Pot board. + u24.bit_handler<7>().set_output("led_fm_lag").invert(); // On Pot board. + + // LED1*, U14 on Pot board. All outputs active low (connected to LED + // cathodes). + output_latch_device &u14(OUTPUT_LATCH(config, "latch_u14_pot")); + u14.bit_handler<0>().set_output("led_x_sel").invert(); + u14.bit_handler<1>().set_output("led_mod_sorc").invert(); + u14.bit_handler<2>().set_output("led_mod").invert(); + u14.bit_handler<3>().set_output("led_on_off_a").invert(); + u14.bit_handler<4>().set_output("led_track").invert(); + u14.bit_handler<5>().set_output("led_page_2").invert(); + u14.bit_handler<6>().set_output("led_on_off_b").invert(); + u14.bit_handler<7>().set_output("led_value").invert(); + + // PULL*, U21 on Pot board. + // Configures the resting value for the gate inputs. Used to support both + // active low and active high inputs. + output_latch_device &u21(OUTPUT_LATCH(config, "latch_u21_pot")); + u21.bit_handler<0>().set_output("pull_1"); // Default for GATE 1 + u21.bit_handler<1>().set_output("pull_2"); // Default for GATE 2 + u21.bit_handler<2>().set_output("pull_3"); // Default for GATE 3 + u21.bit_handler<3>().set_output("pull_4"); // Default for GATE 4 + u21.bit_handler<4>().set_output("pull_5"); // Default for GATE 5 + u21.bit_handler<5>().set_output("pull_6"); // Default for GATE 6 + u21.bit_handler<6>().set_output("pull_7"); // Default for TRIGGER + // Bit 7 not connected. +} + +DECLARE_INPUT_CHANGED_MEMBER(xpander_state::encoder_moved) +{ + const int encoder = param; + if (oldval != newval) + m_encoder_changed[encoder] = true; + + static constexpr const int WRAP_BUFFER = 3; + const bool overflowed = newval <= WRAP_BUFFER && + oldval >= NUM_ENCODER_POSITIONS - WRAP_BUFFER; + const bool underflowed = newval >= NUM_ENCODER_POSITIONS - WRAP_BUFFER && + oldval <= WRAP_BUFFER; + m_encoder_dir[encoder] = ((newval > oldval) || overflowed) && !underflowed; + + LOGMASKED(LOG_ENCODERS, "Encoder %d changed from: %d to: %d (o: %d, u: %d), dir: %d\n", + encoder, oldval, newval, overflowed, underflowed, bool(m_encoder_dir[encoder])); +} + +DECLARE_INPUT_CHANGED_MEMBER(xpander_state::memory_protect_changed) +{ + const bool memory_protected = !BIT(m_memory_protect_io->read(), 0); + LOGMASKED(LOG_MEM_PROTECT, "NVRAM A protected: %d\n", memory_protected); + if (memory_protected) + m_nvram_a_view.select(1); + else + m_nvram_a_view.select(0); +} + +INPUT_PORTS_START(xpander) + PORT_START("switches_0") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("0") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("1") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("2") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("3") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("4") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("5") + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("6") + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("7") PORT_CODE(KEYCODE_1) + + PORT_START("switches_1") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("8") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("9") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("+") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("-") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("STORE") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("PAGE2") + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TUNE") PORT_CODE(KEYCODE_2) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("MASTER") + + PORT_START("switches_2") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("SINGLE") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("MULTI") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VOICE 1") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VOICE 2") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VOICE 3") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VOICE 4") PORT_CODE(KEYCODE_3) + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VOICE 5") + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VOICE 6") + + PORT_START("switches_3") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("P1 1") PORT_CODE(KEYCODE_4) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("P1 2") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("P1 3") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("P1 4") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("P1 5") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("P1 6") + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VCO1") + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VCO2") + + PORT_START("switches_4") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("P2 1") PORT_CODE(KEYCODE_5) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("P2 2") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("P2 3") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("P2 4") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("P2 5") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("P2 6") PORT_CODE(KEYCODE_6) + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("FM/LAG") + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TRACK X") + + PORT_START("switches_5") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("LEVER 1") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("LEVER 2") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("PEDAL 1") PORT_CODE(KEYCODE_7) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("PEDAL 2") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VIB") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("KEYBOARD") + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("LAG") + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VELOCITY") + + PORT_START("switches_6") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("RELEASE VELOCITY") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("PRESSURE") PORT_CODE(KEYCODE_8) + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("ENV") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("LFO") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TRACK") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("RAMP") + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_UNUSED) // No switch. Pulled up. + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_UNUSED) // No switch. Pulled up. + + PORT_START("switches_7") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VCF/VCA") PORT_CODE(KEYCODE_9) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("ENV X") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("LFO X") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("RAMP X") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("MISC.") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_UNUSED) // No switch. Pulled up. + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_UNUSED) // No switch. Pulled up. + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_UNUSED) // No switch. Pulled up. + + PORT_START("memory_protect") // SW6 on Processor board. + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("Memory Protect") PORT_TOGGLE PORT_CODE(KEYCODE_P) + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(xpander_state::memory_protect_changed), 0) + + // These inputs (except "chain advance") can be configured by the user as + // active low or active high. This configuration is manifested in the "PULL" + // signals (search for "latch_u21_pot"). Modeling as active low for now. + PORT_START("gate_inputs") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("GATE 1") + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("GATE 2") + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("GATE 3") + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("GATE 4") + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("GATE 5") + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("GATE 6") + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("CHAIN ADVANCE") // No corresponding 'PULL' signal. + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("TRIGGER") + + // Voltage inputs. + PORT_START("cv_in_1") + PORT_BIT(0xff, 0x00, IPT_DIAL) PORT_SENSITIVITY(30) + PORT_START("cv_in_2") + PORT_BIT(0xff, 0x00, IPT_DIAL) PORT_SENSITIVITY(30) + PORT_START("cv_in_3") + PORT_BIT(0xff, 0x00, IPT_DIAL) PORT_SENSITIVITY(30) + PORT_START("cv_in_4") + PORT_BIT(0xff, 0x00, IPT_DIAL) PORT_SENSITIVITY(30) + PORT_START("cv_in_5") + PORT_BIT(0xff, 0x00, IPT_DIAL) PORT_SENSITIVITY(30) + PORT_START("cv_in_6") + PORT_BIT(0xff, 0x00, IPT_DIAL) PORT_SENSITIVITY(30) + PORT_START("pedal_1") + PORT_BIT(0xff, 0x00, IPT_PEDAL1) PORT_SENSITIVITY(30) + PORT_START("pedal_2") + PORT_BIT(0xff, 0x00, IPT_PEDAL2) PORT_SENSITIVITY(30) + + // Rotary encoders. + PORT_START("encoder_0") + PORT_BIT(0x1f, 0x00, IPT_POSITIONAL) PORT_POSITIONS(NUM_ENCODER_POSITIONS) + PORT_WRAPS PORT_SENSITIVITY(20) PORT_KEYDELTA(1) + PORT_CODE_DEC(KEYCODE_Q) PORT_CODE_INC(KEYCODE_W) + PORT_FULL_TURN_COUNT(NUM_ENCODER_POSITIONS) + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(xpander_state::encoder_moved), 0) + PORT_START("encoder_1") + PORT_BIT(0x1f, 0x00, IPT_POSITIONAL) PORT_POSITIONS(NUM_ENCODER_POSITIONS) + PORT_WRAPS PORT_SENSITIVITY(20) PORT_KEYDELTA(1) + PORT_CODE_DEC(KEYCODE_E) PORT_CODE_INC(KEYCODE_R) + PORT_FULL_TURN_COUNT(NUM_ENCODER_POSITIONS) + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(xpander_state::encoder_moved), 1) + PORT_START("encoder_2") + PORT_BIT(0x1f, 0x00, IPT_POSITIONAL) PORT_POSITIONS(NUM_ENCODER_POSITIONS) + PORT_WRAPS PORT_SENSITIVITY(20) PORT_KEYDELTA(1) + PORT_CODE_DEC(KEYCODE_A) PORT_CODE_INC(KEYCODE_S) + PORT_FULL_TURN_COUNT(NUM_ENCODER_POSITIONS) + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(xpander_state::encoder_moved), 2) + PORT_START("encoder_3") + PORT_BIT(0x1f, 0x00, IPT_POSITIONAL) PORT_POSITIONS(NUM_ENCODER_POSITIONS) + PORT_WRAPS PORT_SENSITIVITY(20) PORT_KEYDELTA(1) + PORT_CODE_DEC(KEYCODE_D) PORT_CODE_INC(KEYCODE_F) + PORT_FULL_TURN_COUNT(NUM_ENCODER_POSITIONS) + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(xpander_state::encoder_moved), 3) + PORT_START("encoder_4") + PORT_BIT(0x1f, 0x00, IPT_POSITIONAL) PORT_POSITIONS(NUM_ENCODER_POSITIONS) + PORT_WRAPS PORT_SENSITIVITY(20) PORT_KEYDELTA(1) + PORT_CODE_DEC(KEYCODE_G) PORT_CODE_INC(KEYCODE_H) + PORT_FULL_TURN_COUNT(NUM_ENCODER_POSITIONS) + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(xpander_state::encoder_moved), 4) + PORT_START("encoder_5") + PORT_BIT(0x1f, 0x00, IPT_POSITIONAL) PORT_POSITIONS(NUM_ENCODER_POSITIONS) + PORT_WRAPS PORT_SENSITIVITY(20) PORT_KEYDELTA(1) + PORT_CODE_DEC(KEYCODE_J) PORT_CODE_INC(KEYCODE_K) + PORT_FULL_TURN_COUNT(NUM_ENCODER_POSITIONS) + PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(xpander_state::encoder_moved), 5) +INPUT_PORTS_END + +ROM_START(xpander) + ROM_REGION(0x8000, MAINCPU_TAG, 0) // 4 x 2764 8Kx8bit ROMs (U8 - U5, processor board). + ROM_DEFAULT_BIOS("1.2") + + ROM_SYSTEM_BIOS(0, "1.2", "Xpander Main Processor Revision 1.2") + ROMX_LOAD("exp1.2-0.u8", 0x000000, 0x002000, CRC(dc3801b1) SHA1(4064edc2fa0bea62684c28e8e004feb8229604fe), ROM_BIOS(0)) + ROMX_LOAD("exp1.2-1.u7", 0x002000, 0x002000, CRC(b57f8482) SHA1(233efc3da3a96777b10a4d6fe7e80864e9231f04), ROM_BIOS(0)) + ROMX_LOAD("exp1.2-2.u6", 0x004000, 0x002000, CRC(30f28710) SHA1(798c1b28fb59819baadbf0142ecd1348e759fa70), ROM_BIOS(0)) + ROMX_LOAD("exp1.2-3.u5", 0x006000, 0x002000, CRC(0bc8335e) SHA1(5897ad0cd66cf37b8e9b277654aab4eb83d1c8ab), ROM_BIOS(0)) + + ROM_SYSTEM_BIOS(1, "1.0", "Xpander Main Processor Revision 1.0") + ROMX_LOAD("exp1.0-0.u8", 0x000000, 0x002000, CRC(d93fb34c) SHA1(e470589562f6544507c276ccfc95632a6288eb18), ROM_BIOS(1)) + ROMX_LOAD("exp1.0-1.u7", 0x002000, 0x002000, CRC(80912a48) SHA1(064e9fff26a4ebf8902c3e085fa631bb5579a160), ROM_BIOS(1)) + ROMX_LOAD("exp1.0-2.u6", 0x004000, 0x002000, CRC(89e14d6e) SHA1(7aef67db9d78523777af6f1edf04fd6b0d11229b), ROM_BIOS(1)) + ROMX_LOAD("exp1.0-3.u5", 0x006000, 0x002000, CRC(21794b92) SHA1(e544375c3fc29931497cb6429db7a2812f77c553), ROM_BIOS(1)) + + ROM_REGION(0x6000, VOICECPU_TAG, 0) // 3 x 2764 8Kx8bit ROMs. Rev 1.4. + ROM_FILL(0x000000, 0x002000, 0xff) // U909. Spare 2764 ROM slot. Not populated. Data bus pulled high. + ROM_LOAD("ca1.4-0.u912", 0x002000, 0x002000, CRC(79f4490a) SHA1(5ab13ccc3b75df313a447520ca54a492102b7e3b)) + ROM_LOAD("ca1.4-1.u914", 0x004000, 0x002000, CRC(9b7a7914) SHA1(951bbc197a0140a93bb7621a7aedbe0f4037990c)) +ROM_END + +} // anonymous namespace + +// In production from 1984 to 1988. +SYST(1984, xpander, 0, 0, xpander, xpander, xpander_state, empty_init, "Oberheim", "Xpander", MACHINE_NOT_WORKING | MACHINE_NO_SOUND)