diff --git a/src/mame/mame.lst b/src/mame/mame.lst index 9ff06a824e42e..069e0aa50bafe 100644 --- a/src/mame/mame.lst +++ b/src/mame/mame.lst @@ -42390,6 +42390,9 @@ wizta @source:sequential/prophet600.cpp prpht600 +@source:sequential/sixtrak.cpp +sixtrak + @source:seta/albazc.cpp hanaroku diff --git a/src/mame/sequential/sixtrak.cpp b/src/mame/sequential/sixtrak.cpp new file mode 100644 index 0000000000000..7d2f1eccee5e1 --- /dev/null +++ b/src/mame/sequential/sixtrak.cpp @@ -0,0 +1,588 @@ +// license:BSD-3-Clause +// copyright-holders:m1macrophage + +/* +The Sequential Circuits Six-Trak (aka Model 610) is a 6-voice, MIDI-enabled, +digitally-controlled analog synthesizer. + +Each voice is built around a CEM3394, a single-oscillator "synth-on-a-chip". +Control voltages (CVs) for each CEM3394 are generated independently, for a +6-part multitimbrality. A single noise source can be mixed in every voice. +Modulation sources (e.g. envelope generators) for the VCA and VCF are computed +by the firmware and incorporated into the CVs. Because of this, the sample & +hold (S&H) circuits for the VCA amplitude and VCF frequency include an +additional RC circuit to smoothen the CV changes. + +The synth's computer is built around a Z80. It implements the user interface +(scans for key presses and controls LEDs), implements a sequencer, responds to +and controls MIDI, periodically refreshes CVs for each of the 6 voices, and +takes care of tuning. + +CVs are generated by a 12-bit DAC (0V - -4V). A MUX selects between the DAC +output and its inverted output (0V - 4V), resulting in a 13-bit resolution. +However, the full 13-bit resolution is only used for a few CVs. Most CVs just +use the 6 MSbits of the DAC. + +A set of MUXes route the generated CV to the appropriate voice and parameter. +Each of the 6 voice chips (CEM3394) has 8 CV-controlled parameters: +- VCO pitch. +- VCA amplitude. +- VCF resonance. +- VCF cutoff frequency. +- Noise mix. +- VCF cutoff frequency modulation, by the triangle output of the VCO. +- VCO pulse width. +- VCO shape. + +Known hardware revisions: +- Model 610 Rev A: serial numbers 1-900. +- Model 610 Rev B: serial numbers 901-?. + Changes to autotune circuit: disconnect U146-2 from whatever it was + connected to (not sure what), and connect it to U146-6. + Unsure if there are other changes. + +This driver is based on the service manual for the Six-Trak (newer than Rev A, +probably for Rev B), and is intended as an educational tool. +*/ + +#include "emu.h" +#include "bus/midi/midi.h" +#include "cpu/z80/z80.h" +#include "machine/6850acia.h" +#include "machine/7474.h" +#include "machine/clock.h" +#include "machine/nvram.h" +#include "machine/output_latch.h" +#include "machine/pit8253.h" +#include "machine/rescap.h" + +#define LOG_CV (1U << 1) +#define LOG_KEYS (1U << 2) +#define LOG_ADC_VALUE_KNOB (1U << 3) +#define LOG_ADC_PITCH_WHEEL (1U << 4) + +#define VERBOSE (LOG_GENERAL | LOG_CV) +//#define LOG_OUTPUT_FUNC osd_printf_info + +#include "logmacro.h" + +namespace { + +class sixtrak_state : public driver_device +{ +public: + sixtrak_state(const machine_config &mconfig, device_type type, const char *tag) ATTR_COLD; + + void sixtrak(machine_config &config) ATTR_COLD; + +protected: + void machine_start() override ATTR_COLD; + +private: + double get_dac_v(bool inverted) const; + double get_voltage_mux_out() const; + void update_cvs(); + + void dac_low_w(u8 data); + void dac_high_w(u8 data); + void voice_select_w(u8 data); + void param_select_w(u8 data); + void digit_w(u8 data); + u8 misc_r(); + u8 keys_r(); + + u64 iorq_wait_state(offs_t offset, u64 current_time); + void memory_map(address_map &map) ATTR_COLD; + void io_map(address_map &map) ATTR_COLD; + + required_device m_maincpu; + required_ioport_array<16> m_keys; + required_ioport m_footswitch; + required_ioport m_pitch_wheel; + required_ioport m_mod_wheel; + required_ioport m_track_vol_knob; + required_ioport m_speed_knob; + required_ioport m_value_knob; + required_ioport m_tune_knob; + output_finder<> m_left_digit; + output_finder<> m_right_digit; + + u8 m_key_row = 0; + u16 m_dac_value = 0; + u8 m_sh_voices = 0x3f; + u8 m_sh_param = 0; + u8 m_voltage_mux_input = 0; + std::array, 6> m_cvs; // 6 voices x 8 parameters. +}; + +sixtrak_state::sixtrak_state(const machine_config &mconfig, device_type type, const char *tag) + : driver_device(mconfig, type, tag) + , m_maincpu(*this, "maincpu") + , m_keys(*this, "key_row_%u", 0U) + , m_footswitch(*this, "footswitch") + , m_pitch_wheel(*this, "pitch_wheel") + , m_mod_wheel(*this, "mod_wheel") + , m_track_vol_knob(*this, "track_volume_knob") + , m_speed_knob(*this, "speed_knob") + , m_value_knob(*this, "value_knob") + , m_tune_knob(*this, "tune_knob") + , m_left_digit(*this, "digit_left") + , m_right_digit(*this, "digit_right") +{ + for (auto &voice_cvs : m_cvs) + std::fill(voice_cvs.begin(), voice_cvs.end(), 0.0); +} + +double sixtrak_state::get_dac_v(bool inverted) const +{ + // The 12-bit DAC (U110, 7541) and corresponding I2V converter (U156, LF411A) + // produce a voltage in the range 0V (all data bits 0) to -4V (all data + // bits 1). That voltage is inverted by U111A (5532) and surrounding + // resistors. + + // A MUX (U108, 4051) selects between the non-inverted and inverted voltages + // when updating CVs. This essentially adds 1 bit of precission to the + // 12-bit DAC, for the CVs that need it. But most CVs just use the 6 most + // significant bits of the DAC. The ADC comparator always compares against + // the inverted (0V - 4V) voltage. + + constexpr double DAC_MAX_VOLTAGE = -4.0; + constexpr double DAC_MAX_VALUE = double(make_bitmask(12)); + const double v = m_dac_value * DAC_MAX_VOLTAGE / DAC_MAX_VALUE; + return inverted ? -v : v; +} + +double sixtrak_state::get_voltage_mux_out() const +{ + // The pitch and mod wheel potentiometers (100K, linear) are attached to + // ground on one side and a shared 27K resistor (R1208) to 5V on the other. + // The pot wipers are attached to corresponding mux inputs. + constexpr double WHEEL_MAX_V = 5.0 * RES_VOLTAGE_DIVIDER(RES_K(27), RES_2_PARALLEL(RES_K(100), RES_K(100))); + + // All knobs are 10K linear potentiometers, and each one is attached to + // ground on one side and to a separate 2K resistor to 5V on the other. The + // pot wipers are attached to corresponding mux inputs. + constexpr double KNOB_MAX_V = 5.0 * RES_VOLTAGE_DIVIDER(RES_K(2), RES_K(10)); + + // The voltage MUX (U108, CD4051) routes potentiometer voltages to the ADC + // comparator and routes the appropriate DAC output to the CV S&H circuits. + // The MUX's output is always enabled. The firwmare controls whether the ADC + // or an S&H circuit is activated. + switch (m_voltage_mux_input) + { + case 0: return get_dac_v(false); + case 1: return get_dac_v(true); + case 2: return m_value_knob->read() * KNOB_MAX_V / 100.0; + case 3: return m_tune_knob->read() * KNOB_MAX_V / 100.0; + case 4: return m_mod_wheel->read() * WHEEL_MAX_V / 100.0; + case 5: return m_track_vol_knob->read() * KNOB_MAX_V / 100.0; + case 6: return m_pitch_wheel->read() * WHEEL_MAX_V / 100.0; + case 7: return m_speed_knob->read() * KNOB_MAX_V / 100.0; + } + + assert(false); // Execution should not reach here. + return 0; +} + +void sixtrak_state::update_cvs() +{ + if ((m_sh_voices & 0x3f) == 0x3f) + return; // Exit early if no voice S&Hs are activated. + + const double cv = get_voltage_mux_out(); + for (int voice = 0; voice < 6; ++voice) + { + if (!BIT(m_sh_voices, voice)) // Active low. + { + assert(m_sh_param < 8); + if (m_cvs[voice][m_sh_param] != cv) + { + m_cvs[voice][m_sh_param] = cv; + LOGMASKED(LOG_CV, "CV - voice: %u, param: %u, cv: %f - %03x - %x\n", + voice, m_sh_param, cv, m_dac_value, m_voltage_mux_input); + } + } + } +} + +void sixtrak_state::dac_low_w(u8 data) +{ + // D2-D7 -> Latch U105 -> low 6 bits of 12-bit DAC (U110, 7541). + m_dac_value = (m_dac_value & 0x0fc0) | ((data >> 2) & 0x3f); + update_cvs(); +} + +void sixtrak_state::dac_high_w(u8 data) +{ + // D0-D5 -> Latch U104 -> high 6 bits of 12-bit DAC (U110, 7541). + m_dac_value = (u16(data & 0x3f) << 6) | (m_dac_value & 0x003f); + update_cvs(); +} + +void sixtrak_state::voice_select_w(u8 data) +{ + // D0-D5 -> Latch U120 -> INH input of 6 x 4051. + // Selects which of the voice MUXes to activate. + m_sh_voices = data & 0x3f; + update_cvs(); +} + +void sixtrak_state::param_select_w(u8 data) +{ + // D0-D2 -> Latch U118 D0-D2 -> A, B, C inputs of all 6 S&H 4051. + // Selects which voice parameter to activate. + m_sh_param = data & 0x07; + + // D7-D5 -> Latch U118 D3-D5 -> A, B, C inputs of Voltage MUX 4051 (U110). + m_voltage_mux_input = bitswap<3>(data, 5, 6, 7); + + // D4-D7 -> Decoder U155 (CD4514) -> key row select. + m_key_row = (data >> 4) & 0x0f; + + update_cvs(); +} + +void sixtrak_state::digit_w(u8 data) +{ + static constexpr u8 PATTERNS_CD4511[0x10] = + { + 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7c, 0x07, 0x7f, 0x67, 0, 0, 0, 0, 0, 0 + }; + m_left_digit = PATTERNS_CD4511[BIT(data, 0, 4)]; + m_right_digit = PATTERNS_CD4511[BIT(data, 4, 4)]; +} + +u8 sixtrak_state::misc_r() +{ + // D0, D1 - autotune-related. + // TODO: emulate autotune. + const u8 d0 = 1; + const u8 d1 = 1; + + // D2: ADC comparator. + // If either of the MUX inputs B or C are high, they will activate the + // ADC comparator's (U109, LM311) output pullup resistor (R168) via diodes + // D128 and D129. + const bool comp_enabled = BIT(m_voltage_mux_input, 1, 2); + const u8 d2 = (comp_enabled && get_voltage_mux_out() >= get_dac_v(true)) ? 1 : 0; + if (comp_enabled) + { + if (m_voltage_mux_input == 2) + { + LOGMASKED(LOG_ADC_VALUE_KNOB, "ADC value - pot v: %f, dac v: %f, comp: %d\n", + get_voltage_mux_out(), get_dac_v(true), d2); + } + else if (m_voltage_mux_input == 6) + { + LOGMASKED(LOG_ADC_PITCH_WHEEL, "ADC pitch - input: %d, pot v: %f, dac v: %f, comp: %d\n", + m_pitch_wheel->read(), get_voltage_mux_out(), get_dac_v(true), d2); + } + } + + // D3: Control footswitch. + const u8 d3 = BIT(m_footswitch->read(), 0); + + return (d3 << 3) | (d2 << 2) | (d1 << 1) | d0; +} + +u8 sixtrak_state::keys_r() +{ + u8 data = m_keys[m_key_row]->read(); + if (m_key_row < 4) + { + // The first 4 rows of the key matrix do not have protection diodes for + // each key. If a key is pressed in the row being scanned, and one or + // more keys in the same column (within rows 0-3) are pressed, there will + // be a short between a high and low output of the CD4514 (assuming + // the schematic is not missing diodes on those outputs), and the press + // will likely not register. The first 4 rows are used for the synth's + // buttons. The piano roll keys have the typical diodes and don't have + // this issue. Furthermore, there are diodes protecting the rest of the + // rows from rows 0-3. + u8 inactive_row_presses = 0; + for (int row = 0; row < 4; ++row) + if (row != m_key_row) + inactive_row_presses |= m_keys[row]->read(); + if (data & inactive_row_presses) + LOGMASKED(LOG_KEYS, "Conflicting key presses: %02x %02x\n", data, inactive_row_presses); + data = data & ~inactive_row_presses; + } + if (data) + LOGMASKED(LOG_KEYS, "Key row: %d, value: %02x\n", m_key_row, data); + return data; +} + +u64 sixtrak_state::iorq_wait_state(offs_t offset, u64 current_time) +{ + // U123C and U123D (74LS02) will set /WAIT low when both /IORQ and the 2MHz + // clock are low. So the 2MHz clock needs to be high for an IORQ to proceed. + // The 2MHz clock is the CPU's clock (4MHz) divided by 2. + return current_time | 1; +} + +void sixtrak_state::memory_map(address_map &map) +{ + map(0x0000, 0x3fff).rom(); + map(0x4000, 0x47ff).ram().share("nvram1"); + map(0x4800, 0x4fff).ram().share("nvram2"); + map(0x5000, 0x57ff).mirror(0x0800).ram().share("nvram3"); + map(0x6000, 0x6001).mirror(0x1ffe).w("midiacia", FUNC(acia6850_device::write)); + map(0xe000, 0xe001).mirror(0x1ffe).r("midiacia", FUNC(acia6850_device::read)); +} + +void sixtrak_state::io_map(address_map &map) +{ + map.global_mask(0xff); + + map(0x00, 0x03).mirror(0xf4).rw("pit", FUNC(pit8253_device::read), FUNC(pit8253_device::write)); + + map(0x09, 0x09).mirror(0xf6).r(FUNC(sixtrak_state::misc_r)); + map(0x0a, 0x0a).mirror(0xf5).r(FUNC(sixtrak_state::keys_r)); + + map(0x08, 0x08).mirror(0xe0).w("latch_u149", FUNC(output_latch_device::write)); + map(0x09, 0x09).mirror(0xf0).w(FUNC(sixtrak_state::dac_low_w)); + map(0x0a, 0x0a).mirror(0xf0).w(FUNC(sixtrak_state::dac_high_w)); + map(0x0b, 0x0b).mirror(0xf0).w("latch_u102", FUNC(output_latch_device::write)); + map(0x0c, 0x0c).mirror(0xf0).w("latch_u101", FUNC(output_latch_device::write)); + map(0x0d, 0x0d).mirror(0xf0).w("latch_u148", FUNC(output_latch_device::write)); + map(0x0e, 0x0e).mirror(0xf0).w(FUNC(sixtrak_state::voice_select_w)); + map(0x0f, 0x0f).mirror(0xf0).w(FUNC(sixtrak_state::param_select_w)); + map(0x18, 0x18).mirror(0xe0).w(FUNC(sixtrak_state::digit_w)); +} + +void sixtrak_state::machine_start() +{ + save_item(NAME(m_key_row)); + save_item(NAME(m_dac_value)); + save_item(NAME(m_sh_voices)); + save_item(NAME(m_sh_param)); + save_item(NAME(m_voltage_mux_input)); + save_item(NAME(m_cvs)); + + m_left_digit.resolve(); + m_right_digit.resolve(); + + m_maincpu->space(AS_IO).install_readwrite_before_time( + 0x00, 0xff, ws_time_delegate(*this, FUNC(sixtrak_state::iorq_wait_state))); +} + +void sixtrak_state::sixtrak(machine_config &config) +{ + Z80(config, m_maincpu, 8_MHz_XTAL / 2); // U134 (74LS93), QA. + m_maincpu->set_addrmap(AS_PROGRAM, &sixtrak_state::memory_map); + m_maincpu->set_addrmap(AS_IO, &sixtrak_state::io_map); + + NVRAM(config, "nvram1", nvram_device::DEFAULT_ALL_0); // U119 (6116) + NVRAM(config, "nvram2", nvram_device::DEFAULT_ALL_0); // U117 (6116) + NVRAM(config, "nvram3", nvram_device::DEFAULT_ALL_0); // U112 (6116) + + auto &pit = PIT8253(config, "pit"); // U133 + pit.set_clk<2>(8_MHz_XTAL / 4); // U134 (74LS93), QB. + pit.out_handler<2>().set_inputline(m_maincpu, INPUT_LINE_IRQ0); + // TODO: also used for autotune. + + auto &aciaclock = CLOCK(config, "aciaclock", 8_MHz_XTAL / 16); // U134 (74LS93), QD. + aciaclock.signal_handler().set("midiacia", FUNC(acia6850_device::write_txc)); + aciaclock.signal_handler().append("midiacia", FUNC(acia6850_device::write_rxc)); + aciaclock.signal_handler().append("nmiff", FUNC(ttl7474_device::clock_w)); + + auto &acia = ACIA6850(config, "midiacia", 0); // U137 (or is it U157?) + acia.txd_handler().set("mdout", FUNC(midi_port_device::write_txd)); + acia.irq_handler().set("nmiff", FUNC(ttl7474_device::d_w)).invert(); + acia.write_dcd(0); + acia.write_cts(0); + + TTL7474(config, "nmiff", 0).output_cb().set_inputline(m_maincpu, INPUT_LINE_NMI).invert(); // U146B + + MIDI_PORT(config, "mdin", midiin_slot, "midiin").rxd_handler().set("midiacia", FUNC(acia6850_device::write_rxd)); + MIDI_PORT(config, "mdout", midiout_slot, "midiout"); + + auto &u148 = OUTPUT_LATCH(config, "latch_u148"); // 4174 (CD40174?) + // Bit 0: sound output on/off. + // Bit 1, 2, 4: autotune-related. + // Bit 3: Not connected. + u148.bit_handler<5>().set("nmiff", FUNC(ttl7474_device::preset_w)); + + auto &u102 = OUTPUT_LATCH(config, "latch_u102"); // 74LS174 + u102.bit_handler<0>().set_output("led_track_1").invert(); + u102.bit_handler<1>().set_output("led_track_2").invert(); + u102.bit_handler<2>().set_output("led_track_3").invert(); + u102.bit_handler<3>().set_output("led_track_4").invert(); + u102.bit_handler<4>().set_output("led_track_5").invert(); + u102.bit_handler<5>().set_output("led_track_6").invert(); + + auto &u101 = OUTPUT_LATCH(config, "latch_u101"); // 74LS174 + u101.bit_handler<0>().set_output("led_seq_a").invert(); + u101.bit_handler<1>().set_output("led_seq_b").invert(); + u101.bit_handler<2>().set_output("led_arp_up_down").invert(); + u101.bit_handler<3>().set_output("led_arp_assign").invert(); + u101.bit_handler<4>().set_output("led_stack_a").invert(); + u101.bit_handler<5>().set_output("led_stack_b").invert(); + + auto &u149 = OUTPUT_LATCH(config, "latch_u149"); // 74LS174 + u149.bit_handler<0>().set_output("led_program").invert(); + u149.bit_handler<1>().set_output("led_param").invert(); + u149.bit_handler<2>().set_output("led_value").invert(); + u149.bit_handler<3>().set_output("led_record").invert(); + u149.bit_handler<4>().set_output("led_record_track").invert(); + u149.bit_handler<5>().set_output("led_legato").invert(); +} + +INPUT_PORTS_START(sixtrak) + PORT_START("key_row_0") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("SEL 0") PORT_CODE(KEYCODE_0_PAD) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("SEL 1") PORT_CODE(KEYCODE_1_PAD) + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("SEL 2") PORT_CODE(KEYCODE_2_PAD) + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("SEL 3") PORT_CODE(KEYCODE_3_PAD) + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("SEL 4") PORT_CODE(KEYCODE_4_PAD) + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("SEL 5") PORT_CODE(KEYCODE_5_PAD) + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("SEL 6") PORT_CODE(KEYCODE_6_PAD) + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("SEL 7") PORT_CODE(KEYCODE_7_PAD) + + PORT_START("key_row_1") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("SEL 8") PORT_CODE(KEYCODE_8_PAD) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("SEL 9") PORT_CODE(KEYCODE_9_PAD) + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("PROGRAM") PORT_CODE(KEYCODE_G) + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("PARAM") PORT_CODE(KEYCODE_P) + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("VALUE") PORT_CODE(KEYCODE_V) + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("RECORD") + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("RECORD TRACK") PORT_CODE(KEYCODE_R) + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("LEGATO") + + PORT_START("key_row_2") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("TRACK 1") PORT_CODE(KEYCODE_1) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("TRACK 2") PORT_CODE(KEYCODE_2) + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("TRACK 3") PORT_CODE(KEYCODE_3) + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("TRACK 4") PORT_CODE(KEYCODE_4) + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("TRACK 5") PORT_CODE(KEYCODE_5) + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("TRACK 6") PORT_CODE(KEYCODE_6) + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_UNUSED) + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_UNUSED) + + PORT_START("key_row_3") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("SEQ A") + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("SEQ B") + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("UP/DOWN") + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("ARP ASSIGN") + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("STACK A") + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("STACK B") + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_UNUSED) + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_UNUSED) + + PORT_START("key_row_4") + PORT_BIT(0xff, IP_ACTIVE_HIGH, IPT_UNUSED) + + PORT_START("key_row_5") + PORT_BIT(0xff, IP_ACTIVE_HIGH, IPT_UNUSED) + + PORT_START("key_row_6") + PORT_BIT(0xff, IP_ACTIVE_HIGH, IPT_UNUSED) + + PORT_START("key_row_7") + PORT_BIT(0xff, IP_ACTIVE_HIGH, IPT_UNUSED) + + PORT_START("key_row_8") // C0 - G0 in schematic. + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_C2 + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_CS2 + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_D2 + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_DS2 + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_E2 + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_F2 + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_FS2 + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_G2 + + PORT_START("key_row_9") // G#0 - D#1 + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_GS2 + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_A2 + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_AS2 + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_B2 + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_C3 + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_CS3 + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_D3 + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_DS3 + + PORT_START("key_row_10") // E1 - B1 + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_E3 + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_F3 + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_FS3 + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_G3 + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_GS3 + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_A3 + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_AS3 + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_B3 + + PORT_START("key_row_11") // C2 - G2 + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_C4 + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_CS4 + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_D4 + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_DS4 + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_E4 + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_F4 + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_FS4 + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_G4 + + PORT_START("key_row_12") // G#2 - D#3 + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_GS4 + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_A4 + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_AS4 + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_B4 + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_C5 + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_CS5 + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_D5 + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_DS5 + + PORT_START("key_row_13") // E3 - B3 + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_E5 + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_F5 + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_FS5 + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_G5 + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_GS5 + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_A5 + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_AS5 + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_B5 + + PORT_START("key_row_14") // C4 + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_GM_C6 + PORT_BIT(0xfe, IP_ACTIVE_HIGH, IPT_UNUSED) + + PORT_START("key_row_15") + PORT_BIT(0xff, IP_ACTIVE_HIGH, IPT_UNUSED) + + PORT_START("footswitch") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("CONTROL FOOTSWITCH") PORT_CODE(KEYCODE_SPACE) + + PORT_START("pitch_wheel") // R1, 100K linear. + PORT_BIT(0x7f, 50, IPT_PADDLE) PORT_NAME("PITCH") PORT_MINMAX(0, 100) PORT_SENSITIVITY(30) PORT_KEYDELTA(5) PORT_CENTERDELTA(10) + + PORT_START("mod_wheel") // R2, 100K linear. + PORT_ADJUSTER(0, "MOD") + + PORT_START("track_volume_knob") // Knob, R119(?), 10K lineaar. + PORT_ADJUSTER(50, "TRACK VOL") + + PORT_START("speed_knob") // Knob, R115, 10K linear. + PORT_ADJUSTER(50, "SPEED") + + PORT_START("value_knob") // Knob, R163, 10K linear. + PORT_ADJUSTER(50, "VALUE") + + PORT_START("tune_knob") // Knob, R138(?), 10K linear. + PORT_ADJUSTER(50, "TUNE") +INPUT_PORTS_END + +// The firmware version can be displayed by pressing RECORD TRACK and SELECT 5. +ROM_START(sixtrak) + ROM_REGION(0x4000, "maincpu", 0) // U128 (27128) + ROM_DEFAULT_BIOS("v11") + + ROM_SYSTEM_BIOS(0, "v11", "Six-Trak V11 (1985)") // Last official release. + ROMX_LOAD("trak-11.bin", 0x000000, 0x004000, CRC(192e6a1c) SHA1(cc56e065f8974af73ae0a1c670f849c08212cb68), ROM_BIOS(0)) + ROM_IGNORE(1) // Binary is 0x4001 bytes. + + ROM_SYSTEM_BIOS(1, "v9", "Six-Trak V9") + ROMX_LOAD("trak-9.bin", 0x000000, 0x004000, CRC(d1e6261c) SHA1(bdad57290c24a9ce02c3a5161f8d12f8f96fc74a), ROM_BIOS(1)) +ROM_END + +} // anonymous namespace + +SYST(1984, sixtrak, 0, 0, sixtrak, sixtrak, sixtrak_state, empty_init, "Sequential Circuits", "Six-Trak (Model 610)", MACHINE_NOT_WORKING | MACHINE_NO_SOUND | MACHINE_SUPPORTS_SAVE)