From 24677b9d23efa57af5ec00baae880d1e035a8d11 Mon Sep 17 00:00:00 2001
From: m1macrophage <168948267+m1macrophage@users.noreply.github.com>
Date: Sun, 2 Feb 2025 19:39:10 -0800
Subject: [PATCH] paia/fatman.cpp: Adding driver for FatMan.
---
src/mame/layout/paia_fatman.lay | 386 +++++++++++++++++++++
src/mame/mame.lst | 3 +
src/mame/paia/fatman.cpp | 575 ++++++++++++++++++++++++++++++++
3 files changed, 964 insertions(+)
create mode 100644 src/mame/layout/paia_fatman.lay
create mode 100644 src/mame/paia/fatman.cpp
diff --git a/src/mame/layout/paia_fatman.lay b/src/mame/layout/paia_fatman.lay
new file mode 100644
index 0000000000000..3feb50aa302e7
--- /dev/null
+++ b/src/mame/layout/paia_fatman.lay
@@ -0,0 +1,386 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ]]>
+
+
+
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/mame/mame.lst b/src/mame/mame.lst
index f06a901c7dce5..c644dd58203df 100644
--- a/src/mame/mame.lst
+++ b/src/mame/mame.lst
@@ -35957,6 +35957,9 @@ penta // bootleg
@source:pacman/schick.cpp
schick // Microhard
+@source:paia/fatman.cpp
+fatman // PAiA FatMan
+
@source:paia/midi2cv8.cpp
midi2cv8 // PAiA midi2cv8 (V/Oct)
midi2cv8_vhz // PAiA midi2cv8 with V/Hz daughterboard
diff --git a/src/mame/paia/fatman.cpp b/src/mame/paia/fatman.cpp
new file mode 100644
index 0000000000000..4bafd1cb1c4a6
--- /dev/null
+++ b/src/mame/paia/fatman.cpp
@@ -0,0 +1,575 @@
+// license:BSD-3-Clause
+// copyright-holders:m1macrophage
+
+/*
+The PAiA FatMan is a monophonic analog synthesizer, sold as a kit. It consists
+of 2 sawtooth (ramp) oscillators, a resonant 2-pole lowpass VCF (State Variable
+topology), and a VCA.
+
+The VCF's cutoff frequency is controlled by a knob, an A(S)R EG, pitch, and
+velocity. The VCA is controlled by an ADSR EG and velocity. The depth of these
+modulation sources is set using knobs.
+
+The FatMan is controlled by MIDI. It listens to note on and off messages on a
+channel specified by dipswitches. It converts those to pitch and velocity
+control voltages, and a gate signal. Those are also exposed externally by
+corresponding outputs on the unit.
+
+The two control voltages are exponential ("V/Hz"). The 8-bit DAC is configured
+to generate voltages in the range 3-6 V, which the firmware uses for the top
+octave of pitches. That voltage can be divided by 2, 4 or 8, using resistor
+dividers and a MUX, to generate the voltages needed for lower octaves. There is
+only a single DAC, and the voltages for pitch and velocity are sampled from it
+by two Sample & Hold circuits controlled by the firmware.
+
+This driver is based on the schematics, user manual and documentation provided
+by the manufacturer on their website. This driver cannot replace the real
+device. Specifically, there is no attempt to emulate audio. This is just an
+educational tool.
+
+The driver emulates the digital functionality of FatMan, the analog outputs (CV
+and gate), and most of the analog functionality that has an effect on the
+firmware (the two envelope generators with all their controls, and the "sustain"
+switch. The "punch" switch is not yet emulated).
+
+Usage:
+
+This driver requires supplying a MIDI input to MAME.
+Example:
+./mame -listmidi # List MIDI devices, physical or virtual (e.g. DAWs).
+./mame -window fatman -midiin "{midi device}"
+
+The layout will display some LEDs, and the value of the functional knob. But
+to actually see control voltage and EG control signals, you need to look at
+the error logs (run mame with `-log` and look at error.log).
+
+The knobs for the two EGs can be controlled by the Slider Control menu. Other
+knobs are not emulated.
+
+Keep in mind that changing the MIDI channel via dipswitches won't take effect
+until a restart or reset (F3).
+*/
+
+#include "emu.h"
+#include "attotime.h"
+#include "cpu/mcs51/mcs51.h"
+#include "bus/midi/midiinport.h"
+#include "bus/midi/midioutport.h"
+#include "machine/rescap.h"
+#include "video/pwm.h"
+
+#include "paia_fatman.lh"
+
+#define LOG_EG (1U << 1)
+#define LOG_CV (1U << 2)
+#define LOG_DSW (1U << 3)
+
+#define VERBOSE (LOG_GENERAL | LOG_EG | LOG_CV)
+//#define LOG_OUTPUT_FUNC osd_printf_info
+
+#include "logmacro.h"
+
+// Emulates a (DC) RC circuit with a variable resistance. Useful for emulating
+// envelope generators. This is not a simulation, just uses the standard RC
+// equations to return voltage at a specific time.
+class fatman_rc_network_state : public device_t
+{
+public:
+ fatman_rc_network_state(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0) ATTR_COLD;
+
+ void set_c(float c);
+ void reset(float v_target, float r, const attotime &t);
+ float get_v(const attotime &t) const;
+
+protected:
+ void device_start() override ATTR_COLD;
+
+private:
+ // Initialize to a slow (large RC) but valid charging state.
+ float m_c = CAP_U(1000);
+ float m_r = RES_M(100);
+ float m_v_start = 0;
+ float m_v_end = 1;
+ attotime m_start_t;
+};
+
+DEFINE_DEVICE_TYPE(FATMAN_RC_NETWORK_STATE, fatman_rc_network_state, "fatman_rc_network_state", "Fatman EG RC network");
+
+fatman_rc_network_state::fatman_rc_network_state(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, FATMAN_RC_NETWORK_STATE, tag, owner, clock)
+{
+}
+
+void fatman_rc_network_state::device_start()
+{
+ save_item(NAME(m_c));
+ save_item(NAME(m_r));
+ save_item(NAME(m_v_start));
+ save_item(NAME(m_v_end));
+ save_item(NAME(m_start_t));
+}
+
+void fatman_rc_network_state::set_c(float c)
+{
+ m_c = c;
+}
+
+void fatman_rc_network_state::reset(float v_target, float r, const attotime &t)
+{
+ m_v_start = get_v(t);
+ m_v_end = v_target;
+ m_r = r;
+ m_start_t = t;
+}
+
+float fatman_rc_network_state::get_v(const attotime &t) const
+{
+ assert(t >= m_start_t);
+ const attotime delta_t = t - m_start_t;
+ return m_v_start + (m_v_end - m_v_start) * (1.0F - expf(-delta_t.as_double() / (m_r * m_c)));
+}
+
+
+namespace {
+
+constexpr const char MAINCPU_TAG[] = "8031";
+
+class fatman_state : public driver_device
+{
+public:
+ fatman_state(const machine_config &mconfig, device_type type, const char *tag) ATTR_COLD
+ : driver_device(mconfig, type, tag)
+ , m_maincpu(*this, MAINCPU_TAG)
+ , m_vca_adsr_state(*this, "vca_dsr_eg")
+ , m_vcf_ar_state(*this, "vcf_ar_eg")
+ , m_midi_led_pwm(*this, "midi_led_pwm_device")
+ , m_gate_led(*this, "gate_led")
+ , m_dsw_io(*this, "dsw")
+ , m_vca_attack_pot(*this, "VCA_attack")
+ , m_vca_decay_pot(*this, "VCA_decay")
+ , m_vca_sustain_pot(*this, "VCA_sustain")
+ , m_vca_release_pot(*this, "VCA_release")
+ , m_vcf_attack_pot(*this, "VCF_attack")
+ , m_vcf_sustain_switch(*this, "VCF_sustain")
+ , m_vcf_release_pot(*this, "VCF_release")
+ {
+ }
+
+ void fatman(machine_config &config) ATTR_COLD;
+
+protected:
+ void machine_start() override ATTR_COLD;
+
+private:
+ u8 dsw_midi_channel_r() const;
+ int midi_rxd_r() const;
+ void midi_rxd_w(int state);
+
+ int vca_adsr_attack_r() const;
+ void vca_adsr_decay_w(int state);
+ void vca_adsr_release_w(int state);
+ void update_vca_adsr_state();
+
+ int vcf_ar_attack_r() const;
+ void vcf_ar_release_w(int state);
+
+ void cv_dac_w(offs_t offset, u8 data);
+ void cv_mux_w(u8 data);
+ void pitch_cv_sample_w(int state);
+ void velocity_cv_sample_w(int state);
+
+ float compute_cv() const;
+ void update_pitch_cv();
+ void update_velocity_cv();
+ void update_cvs();
+
+ void program_map(address_map &map) ATTR_COLD;
+ void external_memory_map(address_map &map) ATTR_COLD;
+
+ required_device m_maincpu;
+ required_device m_vca_adsr_state;
+ required_device m_vcf_ar_state;
+ required_device m_midi_led_pwm; // D2
+ output_finder<> m_gate_led; // D13
+ required_ioport m_dsw_io;
+ required_ioport m_vca_attack_pot;
+ required_ioport m_vca_decay_pot;
+ required_ioport m_vca_sustain_pot;
+ required_ioport m_vca_release_pot;
+ required_ioport m_vcf_attack_pot;
+ required_ioport m_vcf_sustain_switch;
+ required_ioport m_vcf_release_pot;
+
+ u8 m_midi_rxd_bit = 1; // Initial value is 1, for serial "idle".
+ bool m_vca_adsr_decay = false;
+ bool m_vca_adsr_release = false;
+ bool m_vcf_ar_release = false;
+ u8 m_cv_dac = 0;
+ u8 m_cv_mux = 0;
+ bool m_sampling_pitch = false;
+ bool m_sampling_velocity = false;
+ float m_pitch_cv = 0;
+ float m_velocity_cv = 0;
+
+ static constexpr const float V_PLUS = 9;
+ static constexpr const float V_GND = 0;
+ static constexpr const float V_MINUS = -12;
+
+ // Voltage reference for both EG comparators.
+ static constexpr const float R85 = RES_R(2200);
+ static constexpr const float R86 = RES_K(10);
+ static constexpr const float V_EG_COMP = V_PLUS * RES_VOLTAGE_DIVIDER(R85, R86);
+
+ // VCA ADSR components.
+ static constexpr const float POT_R90 = RES_K(1); // Sustain potentiometer.
+ static constexpr const float R91 = RES_R(100);
+ static constexpr const float POT_R92 = RES_M(1); // Decay rheostat.
+ static constexpr const float R93 = RES_R(10);
+ static constexpr const float POT_R94 = RES_M(1); // Attack rheostat.
+ static constexpr const float R95 = RES_R(100);
+ static constexpr const float POT_R96 = RES_M(1); // Release rheostat.
+ static constexpr const float C19 = CAP_U(2.2);
+
+ // VCF A(S)R components.
+ static constexpr const float R80 = RES_K(1);
+ static constexpr const float R81 = RES_R(100);
+ static constexpr const float POT_R82 = RES_M(1); // Release rheostat.
+ static constexpr const float R83 = RES_R(100);
+ static constexpr const float POT_R84 = RES_M(1); // Attack rheostat.
+ static constexpr const float C22 = CAP_U(2.2);
+};
+
+u8 fatman_state::dsw_midi_channel_r() const
+{
+ // Lower 4 bits in reverse order.
+ const u8 channel = bitswap<4>(m_dsw_io->read(), 0, 1, 2, 3);
+ LOGMASKED(LOG_DSW, "DSW MIDI channel: %d\n", channel + 1);
+ return channel;
+}
+
+int fatman_state::midi_rxd_r() const
+{
+ return m_midi_rxd_bit;
+}
+
+void fatman_state::midi_rxd_w(int state)
+{
+ m_midi_rxd_bit = state ? 1 : 0;
+
+ // MIDI IN state is inverted twice (IC7:A and IC7:C) and connected to the
+ // cathode of LED D2. So the LED will be on when MIDI IN is low.
+ m_midi_led_pwm->write_element(0, 0, state ? 0 : 1);
+}
+
+int fatman_state::vca_adsr_attack_r() const
+{
+ const float v = m_vca_adsr_state->get_v(machine().time());
+ return (v >= V_EG_COMP) ? 1 : 0;
+}
+
+void fatman_state::vca_adsr_decay_w(int state)
+{
+ const bool decay = state > 0;
+ if (m_vca_adsr_decay == decay)
+ return;
+
+ m_vca_adsr_decay = decay;
+ LOGMASKED(LOG_EG, "vca decay: %d\n", m_vca_adsr_decay);
+ update_vca_adsr_state();
+}
+
+void fatman_state::vca_adsr_release_w(int state)
+{
+ const bool release = state > 0;
+ if (m_vca_adsr_release == release)
+ return;
+
+ m_vca_adsr_release = release;
+ LOGMASKED(LOG_EG, "vca release: %d\n", m_vca_adsr_release);
+
+ // LED cathode connected to ground, anode connected to IC7:F inverter,
+ // which inverts the 'release' signal.
+ m_gate_led = m_vca_adsr_release ? 0 : 1;
+ update_vca_adsr_state();
+}
+
+void fatman_state::update_vca_adsr_state()
+{
+ // This function only needs to be called on a state change. For instance, if
+ // either m_vca_adsr_decay or m_vca_adsr_release has changed.
+
+ if (m_vca_adsr_decay && m_vca_adsr_release) // Release.
+ {
+ const float r96 = m_vca_release_pot->read() * POT_R96 / 100.0F;
+ m_vca_adsr_state->reset(V_GND, r96 + R95, machine().time());
+ }
+ else if (m_vca_adsr_decay) // Decay.
+ {
+ const attotime now = machine().time();
+ const float sustain_v = V_PLUS * m_vca_sustain_pot->read() / 100.0F;
+ const float current_v = m_vca_adsr_state->get_v(now);
+ const float r92 = m_vca_decay_pot->read() * POT_R92 / 100.0F;
+ m_vca_adsr_state->reset(std::min(sustain_v, current_v), r92 + R91, now);
+ }
+ else if (m_vca_adsr_release) // "Invalid" state.
+ {
+ // release == 1 and decay == 0 is not a useful EG state. But it is
+ // emulated for completeness. The firmware enters it transiently when
+ // switching from Attack to Release mode.
+ const float r94 = m_vca_attack_pot->read() * POT_R94 / 100.0F;
+ const float r_attack = POT_R90 + R93 + r94;
+ const float r96 = m_vca_release_pot->read() * POT_R96 / 100.0F;
+ const float r_release = r96 + R95;
+ const float target_v = V_PLUS * RES_VOLTAGE_DIVIDER(r_attack, r_release);
+ const float effective_r = RES_2_PARALLEL(r_attack, r_release);
+ m_vca_adsr_state->reset(target_v, effective_r, machine().time());
+ }
+ else // Attack.
+ {
+ const float r94 = m_vca_attack_pot->read() * POT_R94 / 100.0F;
+ m_vca_adsr_state->reset(V_PLUS, POT_R90 + R93 + r94, machine().time());
+ }
+}
+
+int fatman_state::vcf_ar_attack_r() const
+{
+ const bool sustain_enabled = !(m_vcf_sustain_switch->read() & 0x01);
+ if (sustain_enabled)
+ {
+ // When the sustain switch (S3) is "on" (i.e. grounded), the MCU never
+ // receives a 1, so the firmware doesn't know that the EG attack
+ // completed, and it doesn't start the EG release.
+ return 0;
+ }
+ const float v = m_vcf_ar_state->get_v(machine().time());
+ return (v >= V_EG_COMP) ? 1 : 0;
+}
+
+void fatman_state::vcf_ar_release_w(int state)
+{
+ const bool release = state > 0;
+ if (m_vcf_ar_release == release)
+ return;
+
+ m_vcf_ar_release = release;
+ LOGMASKED(LOG_EG, "vcf release: %d\n", m_vcf_ar_release);
+
+ if (m_vcf_ar_release) // Start release.
+ {
+ const float r82 = m_vcf_release_pot->read() * POT_R82 / 100.0;
+ m_vcf_ar_state->reset(V_GND, r82 + R81, machine().time());
+ }
+ else // Start attack.
+ {
+ const float r84 = m_vcf_attack_pot->read() * POT_R84 / 100.0;
+ m_vcf_ar_state->reset(V_PLUS, R80 + R83 + r84, machine().time());
+ }
+}
+
+void fatman_state::cv_dac_w(offs_t offset, u8 data)
+{
+ if (m_cv_dac == data)
+ return;
+ m_cv_dac = data;
+ update_cvs();
+}
+
+void fatman_state::cv_mux_w(u8 data)
+{
+ // P3.4 and P3.5 are level-shifted and inverted by Q1 and Q2, and used
+ // as the control inputs (high and low order bits, respectively) of the
+ // 4052 MUX (IC9).
+ const u8 mux = bitswap<2>(~data, 4, 5);
+ if (m_cv_mux == mux)
+ return;
+ m_cv_mux = mux;
+ update_cvs();
+}
+
+void fatman_state::pitch_cv_sample_w(int state)
+{
+ const bool old_state = m_sampling_pitch;
+ // P1.0 is level-shifted with a comparator (without inverting), and
+ // connected to 4016 'control A' (active high).
+ m_sampling_pitch = state > 0;
+ if (!old_state && state)
+ update_pitch_cv();
+}
+
+void fatman_state::velocity_cv_sample_w(int state)
+{
+ const bool old_state = m_sampling_velocity;
+ // Same as pitch_cv_sample_w above, but for P1.1 and 4016 'control B'
+ m_sampling_velocity = state > 0;
+ if (!old_state && state)
+ update_velocity_cv();
+}
+
+float fatman_state::compute_cv() const
+{
+ static constexpr float MAX_CV = 6;
+ static constexpr float HALF_CV = MAX_CV / 2;
+ static constexpr float MUX_DIVISOR[4] = {1, 2, 4, 8};
+
+ assert(m_cv_mux >= 0 && m_cv_mux < 4);
+ return (HALF_CV + m_cv_dac * HALF_CV / 255) / MUX_DIVISOR[m_cv_mux];
+}
+
+void fatman_state::update_pitch_cv()
+{
+ if (!m_sampling_pitch)
+ return;
+
+ const float new_cv = compute_cv();
+ if (new_cv == m_pitch_cv)
+ return;
+
+ m_pitch_cv = new_cv;
+ LOGMASKED(LOG_CV, "Pitch CV %f\n", m_pitch_cv);
+}
+
+void fatman_state::update_velocity_cv()
+{
+ if (!m_sampling_velocity)
+ return;
+
+ const float new_cv = compute_cv();
+ if (new_cv == m_velocity_cv)
+ return;
+
+ m_velocity_cv = new_cv;
+ LOGMASKED(LOG_CV, "Velocity CV %f\n", m_velocity_cv);
+}
+
+void fatman_state::update_cvs()
+{
+ update_pitch_cv();
+ update_velocity_cv();
+}
+
+void fatman_state::program_map(address_map &map)
+{
+ // A13-A15 are not connected.
+ map(0x0000, 0x1fff).mirror(0xe000).rom().region(MAINCPU_TAG, 0);
+}
+
+void fatman_state::external_memory_map(address_map &map)
+{
+ // Address lines ignored on external memory writes.
+ map(0x0000, 0x0000).mirror(0xffff).w(FUNC(fatman_state::cv_dac_w));
+}
+
+void fatman_state::machine_start()
+{
+ m_gate_led.resolve();
+ save_item(NAME(m_midi_rxd_bit));
+ save_item(NAME(m_vca_adsr_decay));
+ save_item(NAME(m_vca_adsr_release));
+ save_item(NAME(m_vcf_ar_release));
+ save_item(NAME(m_cv_dac));
+ save_item(NAME(m_cv_mux));
+ save_item(NAME(m_sampling_pitch));
+ save_item(NAME(m_sampling_velocity));
+ save_item(NAME(m_pitch_cv));
+ save_item(NAME(m_velocity_cv));
+}
+
+void fatman_state::fatman(machine_config &config)
+{
+ I8031(config, m_maincpu, 12_MHz_XTAL);
+ m_maincpu->set_addrmap(AS_PROGRAM, &fatman_state::program_map);
+ m_maincpu->set_addrmap(AS_IO, &fatman_state::external_memory_map);
+
+ // The `set_constant()`s on inputs 1 and 3 ensure that those inputs, when
+ // read, will return whatever was written to them (the input is
+ // essentially ANDed to the value that was written to the port. See mcs51
+ // implementation). The firmware depends on that functionality for at least
+ // some of these inputs.
+
+ m_maincpu->port_in_cb<1>().set_constant(0x0f).mask(0x0f);
+ m_maincpu->port_in_cb<1>().append(FUNC(fatman_state::dsw_midi_channel_r)).lshift(4).mask(0xf0);
+
+ m_maincpu->port_out_cb<1>().set(FUNC(fatman_state::pitch_cv_sample_w)).bit(0);
+ m_maincpu->port_out_cb<1>().append(FUNC(fatman_state::velocity_cv_sample_w)).bit(1);
+ m_maincpu->port_out_cb<1>().append(FUNC(fatman_state::vca_adsr_decay_w)).bit(2);
+ m_maincpu->port_out_cb<1>().append(FUNC(fatman_state::vca_adsr_release_w)).bit(3);
+
+ m_maincpu->port_in_cb<3>().set_constant(0x72).mask(0x72); // Bits 1, 4, 5, 6.
+ m_maincpu->port_in_cb<3>().append(FUNC(fatman_state::midi_rxd_r)).mask(0x01);
+ m_maincpu->port_in_cb<3>().append(FUNC(fatman_state::vca_adsr_attack_r)).lshift(2).mask(0x04);
+ m_maincpu->port_in_cb<3>().append(FUNC(fatman_state::vcf_ar_attack_r)).lshift(3).mask(0x08);
+ m_maincpu->port_in_cb<3>().append_ioport("dsw").mask(0x80);
+
+ m_maincpu->port_out_cb<3>().set(FUNC(fatman_state::vcf_ar_release_w)).bit(1);
+ m_maincpu->port_out_cb<3>().append(FUNC(fatman_state::cv_mux_w)).mask(0x30); // Bits 4, 5.
+
+ FATMAN_RC_NETWORK_STATE(config, m_vca_adsr_state).set_c(C19);
+ FATMAN_RC_NETWORK_STATE(config, m_vcf_ar_state).set_c(C22);
+
+ midi_port_device &midi_in(MIDI_PORT(config, "mdin", midiin_slot, "midiin"));
+ MIDI_PORT(config, "mdthru", midiout_slot, "midiout");
+ midi_in.rxd_handler().set(FUNC(fatman_state::midi_rxd_w));
+ midi_in.rxd_handler().append("mdthru", FUNC(midi_port_device::write_txd));
+
+ PWM_DISPLAY(config, m_midi_led_pwm).set_size(1, 1);
+ m_midi_led_pwm->output_x().set_output("midi_led");
+ m_midi_led_pwm->set_interpolation(0.2);
+ m_midi_led_pwm->set_bri_levels(0.0001);
+ m_midi_led_pwm->set_refresh(attotime::from_hz(30));
+
+ config.set_default_layout(layout_paia_fatman);
+}
+
+INPUT_PORTS_START(fatman)
+ PORT_START("dsw")
+ PORT_DIPNAME(0x0f, 0x00, "MIDI Channel") PORT_DIPLOCATION("SW1:1,2,3,4")
+ PORT_DIPSETTING( 0x00, "1")
+ PORT_DIPSETTING( 0x08, "2")
+ PORT_DIPSETTING( 0x04, "3")
+ PORT_DIPSETTING( 0x0c, "4")
+ PORT_DIPSETTING( 0x02, "5")
+ PORT_DIPSETTING( 0x0a, "6")
+ PORT_DIPSETTING( 0x06, "7")
+ PORT_DIPSETTING( 0x0e, "8")
+ PORT_DIPSETTING( 0x01, "9")
+ PORT_DIPSETTING( 0x09, "10")
+ PORT_DIPSETTING( 0x05, "11")
+ PORT_DIPSETTING( 0x0d, "12")
+ PORT_DIPSETTING( 0x03, "13")
+ PORT_DIPSETTING( 0x0b, "14")
+ PORT_DIPSETTING( 0x07, "15")
+ PORT_DIPSETTING( 0x0f, "16")
+ PORT_DIPNAME(0x70, 0x00, "Not Connected") PORT_DIPLOCATION("SW1:5,6,7")
+ PORT_DIPNAME(0x80, 0x00, DEF_STR(Unused)) PORT_DIPLOCATION("SW1:8")
+ PORT_DIPSETTING( 0x00, DEF_STR(On))
+ PORT_DIPSETTING( 0x80, DEF_STR(Off))
+
+ // VCA EG Controls.
+ PORT_START("VCA_attack") // POT_R94
+ PORT_ADJUSTER(2, "VCA EG Attack")
+ PORT_START("VCA_decay") // POT_R92
+ PORT_ADJUSTER(1, "VCA EG Decay")
+ PORT_START("VCA_sustain") // POT_R90
+ PORT_ADJUSTER(50, "VCA EG Sustain")
+ PORT_START("VCA_release") // POT_R96
+ PORT_ADJUSTER(3, "VCA EG Release")
+
+ // VCF EG Controls.
+ PORT_START("VCF_attack") // POT_R82
+ PORT_ADJUSTER(1, "VCF EG Attack")
+ PORT_START("VCF_release") // POT_R84
+ PORT_ADJUSTER(30, "VCF EG Release")
+ PORT_START("VCF_sustain") // S3
+ PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_OTHER) PORT_NAME("VCF EG Sustain") PORT_CODE(KEYCODE_S) PORT_TOGGLE
+INPUT_PORTS_END
+
+ROM_START(fatman)
+ ROM_REGION(0x2000, MAINCPU_TAG, 0)
+ ROM_DEFAULT_BIOS("fatman9")
+ ROM_SYSTEM_BIOS(0, "fatman9", "FatMan Operating System 1.9 (C) 1995")
+ ROMX_LOAD("fatmopv1.9.ic3", 0x000000, 0x002000, CRC(e2a4b9a9) SHA1(3738f0c9b3b158884fddb103b19314899d7e60c0), ROM_BIOS(0))
+ROM_END
+
+} // anonymous namespace
+
+SYST(1992, fatman, 0, 0, fatman, fatman, fatman_state, empty_init, "PAiA Electronics", "FatMan", MACHINE_SUPPORTS_SAVE | MACHINE_NOT_WORKING | MACHINE_NO_SOUND)