paia/fatman.cpp: Adding driver for FatMan. (#13324)

This commit is contained in:
m1macrophage 2025-02-03 00:12:22 -08:00 committed by GitHub
parent b6dca557c7
commit 2e003a2b32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 964 additions and 0 deletions

View File

@ -0,0 +1,386 @@
<?xml version="1.0"?>
<!--
license:CC0-1.0
copyright-holders:m1macrophage
-->
<mamelayout version="2">
<!-- Case -->
<element name="bezel">
<rect><color red="0.827" green="0.757" blue="0.627"/></rect>
</element>
<element name="case">
<rect><color red="0.078" green="0.094" blue="0.098"/></rect>
</element>
<element name="vent">
<rect><color red="0.027" green="0.024" blue="0.043"/></rect>
</element>
<element name="led" defstate="0">
<disk state="0"><color red="0.3" green="0.0" blue="0.0"/></disk>
<disk state="1"><color red="0.7" green="0.0" blue="0.0"/></disk>
</element>
<element name="led-power">
<disk><color red="0.7" green="0.0" blue="0.0"/></disk>
</element>
<!-- Silkscreen -->
<element name="rounded-rect-narrow">
<image><data><![CDATA[
<rect stroke="#d1dbd3" fill="none" stroke-width="4" width="260" height="539" rx="28" ry="38"/>
]]></data></image>
</element>
<element name="rounded-rect-wide">
<image><data><![CDATA[
<rect stroke="#d1dbd3" fill="none" stroke-width="4" width="441" height="539" rx="28" ry="38"/>
]]></data></image>
</element>
<element name="line">
<rect><color red="0.820" green="0.859" blue="0.827"/></rect>
</element>
<!-- Text -->
<element name="text-gate">
<text string="Gate" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-midi">
<text string="MIDI In" align="2"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-power">
<text string="POWER" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-vcos">
<text string="VCOs"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-vcf">
<text string="VCF"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-vca">
<text string="VCA"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-modulation">
<text string="Modulation"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-transient">
<text string="Transient"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-unison">
<text string="unison"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-both">
<text string="both"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<!-- Knob and switch text -->
<element name="text-pitch-caps">
<text string="PITCH" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-glide">
<text string="Glide" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-offset">
<text string="Offset" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-mix">
<text string="1-2 Mix" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-freq">
<text string="Freq." align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-res">
<text string="Res." align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-punch">
<text string="PUNCH" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-on">
<text string="on"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-off">
<text string="off"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-output">
<text string="Output" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-pitch">
<text string="Pitch" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-velocity">
<text string="Velocity" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-asr-paren">
<text string="A(S)R" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-asr">
<text string="ASR"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-ar">
<text string="AR"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-adsr">
<text string="ADSR" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-attack">
<text string="Attack" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-decay">
<text string="Decay" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-sustain">
<text string="Sustain" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-release">
<text string="Release" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-minus-half">
<text string="-1/2" align="2"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-plus-half">
<text string="+1/2" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-minus-one">
<text string="-1" align="2"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-plus-one">
<text string="+1" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-vco1">
<text string="VCO1" align="2"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-vco2">
<text string="VCO2" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-min">
<text string="Min" align="2"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-max">
<text string="Max" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-fast">
<text string="Fast" align="2"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<element name="text-slow">
<text string="Slow" align="1"><color red="0.820" green="0.859" blue="0.827"/></text>
</element>
<!-- Knobs -->
<element name="white-disk">
<disk><color red="0.820" green="0.859" blue="0.827"/></disk>
</element>
<element name="blue-disk">
<disk><color red="0.0" green="0.090" blue="0.322"/></disk>
</element>
<element name="gray-disk">
<disk><color red="0.239" green="0.231" blue="0.227"/></disk>
</element>
<element name="transparent-rect">
<rect><color red="0" green="0" blue="0" alpha="0"/></rect>
</element>
<element name="knob-value">
<simplecounter maxstate="100" digits="1">
<color red="0.820" green="0.859" blue="0.827"/>
</simplecounter>
</element>
<group name="knob">
<bounds width="178" height="109"/>
<element ref="gray-disk">
<bounds x="52" y="24" width="72" height="72"/>
</element>
<element ref="blue-disk">
<bounds x="61" y="33" width="54" height="54"/>
</element>
<element ref="white-disk">
<bounds x="45" y="93" width="8" height="8"/>
</element>
<element ref="white-disk">
<bounds x="29" y="54" width="9" height="9"/>
</element>
<element ref="white-disk">
<bounds x="45" y="16" width="10" height="10"/>
</element>
<element ref="white-disk">
<bounds x="82" y="0" width="11" height="11"/>
</element>
<element ref="white-disk">
<bounds x="120" y="15" width="12" height="12"/>
</element>
<element ref="white-disk">
<bounds x="135" y="53" width="13" height="13"/>
</element>
<element ref="white-disk">
<bounds x="119" y="90" width="14" height="14"/>
</element>
</group>
<group name="knob-no-top-circle">
<bounds width="178" height="109"/>
<element ref="gray-disk">
<bounds x="52" y="24" width="72" height="72"/>
</element>
<element ref="blue-disk">
<bounds x="61" y="33" width="54" height="54"/>
</element>
<element ref="white-disk">
<bounds x="45" y="93" width="8" height="8"/>
</element>
<element ref="white-disk">
<bounds x="29" y="54" width="9" height="9"/>
</element>
<element ref="white-disk">
<bounds x="45" y="16" width="10" height="10"/>
</element>
<element ref="white-disk">
<bounds x="120" y="15" width="12" height="12"/>
</element>
<element ref="white-disk">
<bounds x="135" y="53" width="13" height="13"/>
</element>
<element ref="white-disk">
<bounds x="119" y="90" width="14" height="14"/>
</element>
</group>
<group name="knob-min-max">
<bounds width="178" height="109"/>
<group ref="knob">
<bounds x="0" y="0" width="178" height="109"/>
</group>
<element ref="text-min">
<bounds x="0" y="94" width="40" height="15"/>
</element>
<element ref="text-max">
<bounds x="138" y="94" width="40" height="15"/>
</element>
</group>
<group name="knob-fast-slow">
<bounds width="178" height="109"/>
<group ref="knob">
<bounds x="0" y="0" width="178" height="109"/>
</group>
<element ref="text-fast">
<bounds x="0" y="94" width="40" height="15"/>
</element>
<element ref="text-slow">
<bounds x="138" y="94" width="40" height="15"/>
</element>
<element ref="transparent-rect" clickthrough="no">
<!-- Disable clicks on the `simplecounter` below, because they
change the value in an unintuitive way. -->
<bounds x="68" y="52" width="40" height="16"/>
</element>
<element ref="knob-value" inputraw="yes" inputtag="~knob_inputtag~" inputmask="0x7f">
<bounds x="68" y="52" width="40" height="16"/>
</element>
</group>
<!-- Switches -->
<element name="switch" defstate="0">
<rect>
<color red="0.2" green="0.2" blue="0.2"/>
<bounds x="0.0" y="0.0" width="1.0" height="1.0"/>
</rect>
<rect state="0">
<color red="0.7" green="0.7" blue="0.7"/>
<bounds x="0.0" y="0.7" width="1.0" height="0.3"/>
</rect>
<rect state="1">
<color red="0.7" green="0.7" blue="0.7"/>
<bounds x="0.0" y="0.0" width="1.0" height="0.3"/>
</rect>
</element>
<view name="fatman_desktop">
<bounds x="0" y="0" width="1470" height="780"/>
<!-- Chasis -->
<element ref="bezel"><bounds x="22" y="42" width="35" height="719"/></element>
<element ref="case"><bounds x="58" y="65" width="1365" height="668"/></element>
<element ref="vent"><bounds x="1268" y="255" width="19" height="125"/></element>
<element ref="vent"><bounds x="1300" y="255" width="19" height="125"/></element>
<element ref="vent"><bounds x="1333" y="255" width="19" height="125"/></element>
<element ref="bezel"><bounds x="1424" y="42" width="35" height="719"/></element>
<element ref="text-gate"><bounds x="679" y="82" width="54" height="17"/></element>
<element ref="led" name="gate_led"><bounds x="647" y="79" width="25" height="25"/></element>
<element ref="text-midi"><bounds x="1053" y="82" width="91" height="17"/></element>
<element ref="led" name="midi_led"><bounds x="1157" y="79" width="25" height="25"/></element>
<element ref="text-power"><bounds x="1310" y="82" width="90" height="17"/></element>
<element ref="led-power"><bounds x="1277" y="79" width="25" height="25"/></element>
<!-- VCO Section -->
<element ref="text-minus-half"><bounds x="89" y="268" width="40" height="15"/></element>
<element ref="text-minus-one"><bounds x="89" y="536" width="40" height="15"/></element>
<element ref="text-vco1"><bounds x="89" y="671" width="40" height="15"/></element>
<group ref="knob"><bounds x="89" y="174" width="178" height="109"/></group>
<group ref="knob-min-max"><bounds x="89" y="309" width="178" height="109"/></group>
<element ref="text-unison"><bounds x="89" y="441" width="178" height="14"/></element>
<group ref="knob-no-top-circle"><bounds x="89" y="442" width="178" height="109"/></group>
<element ref="text-both"><bounds x="89" y="573" width="178" height="14"/></element>
<group ref="knob-no-top-circle"><bounds x="89" y="577" width="178" height="109"/></group>
<element ref="text-plus-half"><bounds x="227" y="268" width="40" height="15"/></element>
<element ref="text-plus-one"><bounds x="227" y="536" width="40" height="15"/></element>
<element ref="text-vco2"><bounds x="227" y="671" width="40" height="15"/></element>
<element ref="text-pitch-caps"><bounds x="249" y="221" width="70" height="17"/></element>
<element ref="text-glide"><bounds x="249" y="354" width="70" height="17"/></element>
<element ref="text-offset"><bounds x="249" y="489" width="70" height="17"/></element>
<element ref="text-mix"><bounds x="249" y="622" width="70" height="17"/></element>
<element ref="text-vcos"><bounds x="236" y="162" width="76" height="23"/></element>
<element ref="rounded-rect-narrow"><bounds x="74" y="158" width="260" height="539"/></element>
<!-- VCF Section -->
<group ref="knob-min-max"><bounds x="359" y="174" width="178" height="109"/></group>
<group ref="knob-min-max"><bounds x="359" y="309" width="178" height="109"/></group>
<group ref="knob-min-max"><bounds x="359" y="442" width="178" height="109"/></group>
<param name="knob_inputtag" value="VCF_attack"/>
<group ref="knob-fast-slow"><bounds x="359" y="577" width="178" height="109"/></group>
<element ref="text-freq"><bounds x="519" y="221" width="70" height="17"/></element>
<element ref="text-pitch"><bounds x="519" y="354" width="70" height="17"/></element>
<element ref="text-asr-paren"><bounds x="519" y="489" width="70" height="17"/></element>
<element ref="text-attack"><bounds x="519" y="622" width="70" height="17"/></element>
<group ref="knob-min-max"><bounds x="560" y="174" width="178" height="109"/></group>
<group ref="knob-min-max"><bounds x="560" y="309" width="178" height="109"/></group>
<element ref="text-asr"><bounds x="635" y="457" width="28" height="15"/></element>
<element ref="switch" inputtag="VCF_sustain" inputmask="0x01">
<bounds x="635" y="477" width="28" height="40"/>
</element>
<element ref="text-ar"><bounds x="635" y="522" width="28" height="15"/></element>
<param name="knob_inputtag" value="VCF_release"/>
<group ref="knob-fast-slow"><bounds x="560" y="577" width="178" height="109"/></group>
<element ref="text-res"><bounds x="720" y="221" width="70" height="17"/></element>
<element ref="text-velocity"><bounds x="720" y="354" width="70" height="17"/></element>
<element ref="text-sustain"><bounds x="695" y="489" width="70" height="17"/></element>
<element ref="text-release"><bounds x="720" y="622" width="70" height="17"/></element>
<element ref="text-vcf"><bounds x="706" y="162" width="57" height="23"/></element>
<element ref="rounded-rect-wide"><bounds x="348" y="158" width="441" height="539"/></element>
<element ref="text-modulation"><bounds x="511" y="299" width="70" height="13"/></element>
<element ref="line"><bounds x="364" y="290" width="411" height="3"/></element>
<element ref="text-transient"><bounds x="511" y="568" width="70" height="13"/></element>
<element ref="line"><bounds x="364" y="560" width="411" height="3"/></element>
<!-- VCA Section -->
<element ref="text-on"><bounds x="886" y="189" width="28" height="15"/></element>
<element ref="switch"><bounds x="886" y="209" width="28" height="40"/></element>
<element ref="text-off"><bounds x="886" y="254" width="28" height="15"/></element>
<group ref="knob-min-max"><bounds x="811" y="309" width="178" height="109"/></group>
<param name="knob_inputtag" value="VCA_attack"/>
<group ref="knob-fast-slow"><bounds x="811" y="442" width="178" height="109"/></group>
<param name="knob_inputtag" value="VCA_sustain"/>
<group ref="knob-fast-slow"><bounds x="811" y="577" width="178" height="109"/></group>
<element ref="text-punch"><bounds x="925" y="221" width="70" height="17"/></element>
<element ref="text-adsr"><bounds x="971" y="354" width="70" height="17"/></element>
<element ref="text-attack"><bounds x="971" y="489" width="70" height="17"/></element>
<element ref="text-sustain"><bounds x="971" y="622" width="70" height="17"/></element>
<group ref="knob-min-max"><bounds x="1012" y="174" width="178" height="109"/></group>
<group ref="knob-min-max"><bounds x="1012" y="309" width="178" height="109"/></group>
<param name="knob_inputtag" value="VCA_decay"/>
<group ref="knob-fast-slow"><bounds x="1012" y="442" width="178" height="109"/></group>
<param name="knob_inputtag" value="VCA_release"/>
<group ref="knob-fast-slow"><bounds x="1012" y="577" width="178" height="109"/></group>
<element ref="text-output"><bounds x="1172" y="221" width="70" height="17"/></element>
<element ref="text-velocity"><bounds x="1172" y="354" width="70" height="17"/></element>
<element ref="text-decay"><bounds x="1172" y="489" width="70" height="17"/></element>
<element ref="text-release"><bounds x="1172" y="622" width="70" height="17"/></element>
<element ref="text-vca"><bounds x="1161" y="162" width="56" height="23"/></element>
<element ref="rounded-rect-wide"><bounds x="803" y="158" width="441" height="539"/></element>
<element ref="text-modulation"><bounds x="963" y="299" width="70" height="13"/></element>
<element ref="line"><bounds x="818" y="290" width="411" height="3"/></element>
<element ref="text-transient"><bounds x="963" y="430" width="70" height="13"/></element>
<element ref="line"><bounds x="818" y="425" width="411" height="3"/></element>
</view>
</mamelayout>

View File

@ -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

575
src/mame/paia/fatman.cpp Normal file
View File

@ -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<mcs51_cpu_device> m_maincpu;
required_device<fatman_rc_network_state> m_vca_adsr_state;
required_device<fatman_rc_network_state> m_vcf_ar_state;
required_device<pwm_display_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)