From 3299e78baec5dd80b5fa708a8d57c8290b71b0e1 Mon Sep 17 00:00:00 2001 From: m1macrophage <168948267+m1macrophage@users.noreply.github.com> Date: Wed, 2 Apr 2025 01:31:23 -0700 Subject: [PATCH] Implemented VA EG (Envelope Generator) and VCA (Voltage Controlled Amplifier) sound devices. (#13545) * sound/va_eg.cpp, sound/va_vca.cpp: Envelope generator and voltage-controlled amplifier. Implemented RC-based envelope generator and voltage-controlled amplifier devices. Replaced custom implementations in paia/fatman, linn/linndrum and oberheim/dmx. * Fixing comments. * More comment fixes. * Adding comments based on feedback. * Fixed typo. --- scripts/src/sound.lua | 23 +++ src/devices/sound/va_eg.cpp | 129 ++++++++++++++ src/devices/sound/va_eg.h | 57 ++++++ src/devices/sound/va_vca.cpp | 76 ++++++++ src/devices/sound/va_vca.h | 58 ++++++ src/mame/linn/linndrum.cpp | 198 +++++---------------- src/mame/oberheim/dmx.cpp | 332 +++++++++++------------------------ src/mame/paia/fatman.cpp | 103 +++-------- 8 files changed, 510 insertions(+), 466 deletions(-) create mode 100644 src/devices/sound/va_eg.cpp create mode 100644 src/devices/sound/va_eg.h create mode 100644 src/devices/sound/va_vca.cpp create mode 100644 src/devices/sound/va_vca.h diff --git a/scripts/src/sound.lua b/scripts/src/sound.lua index 22996ed36b0..53d5d6b7925 100644 --- a/scripts/src/sound.lua +++ b/scripts/src/sound.lua @@ -1166,6 +1166,29 @@ if (SOUNDS["UPD7752"]~=null) then } end +-------------------------------------------------- +-- Virtual analog envelope generators (EGs) +--@src/devices/sound/va_eg.h,SOUNDS["VA_EG"] = true +-------------------------------------------------- + +if (SOUNDS["VA_EG"]~=null) then + files { + MAME_DIR .. "src/devices/sound/va_eg.cpp", + MAME_DIR .. "src/devices/sound/va_eg.h", + } +end + +-------------------------------------------------- +-- Virtual analog voltage-controlled amplifiers (VCAs) +--@src/devices/sound/va_vca.h,SOUNDS["VA_VCA"] = true +-------------------------------------------------- + +if (SOUNDS["VA_VCA"]~=null) then + files { + MAME_DIR .. "src/devices/sound/va_vca.cpp", + MAME_DIR .. "src/devices/sound/va_vca.h", + } +end --------------------------------------------------- -- VLM5030 speech synthesizer diff --git a/src/devices/sound/va_eg.cpp b/src/devices/sound/va_eg.cpp new file mode 100644 index 00000000000..e2674c0e699 --- /dev/null +++ b/src/devices/sound/va_eg.cpp @@ -0,0 +1,129 @@ +// license:BSD-3-Clause +// copyright-holders:m1macrophage + +#include "emu.h" +#include "va_eg.h" +#include "machine/rescap.h" + +DEFINE_DEVICE_TYPE(VA_RC_EG, va_rc_eg_device, "va_rc_eg", "RC-based Envelope Generator") + +va_rc_eg_device::va_rc_eg_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, VA_RC_EG, tag, owner, clock) + , device_sound_interface(mconfig, *this) + , m_stream(nullptr) + // Initialize to a valid state. + , m_r(RES_M(1)) + , m_c(CAP_U(1)) + , m_rc_inv(1.0F / (m_r * m_c)) + , m_v_start(0) + , m_v_end(0) +{ +} + +va_rc_eg_device &va_rc_eg_device::set_r(float r) +{ + assert(r > 0); + if (r == m_r) + return *this; + if (m_stream != nullptr) + m_stream->update(); + + set_target_v(m_v_end); // Snapshots voltage, using the old `r` value. + m_r = r; + m_rc_inv = 1.0F / (m_r * m_c); + return *this; +} + +va_rc_eg_device &va_rc_eg_device::set_c(float c) +{ + assert(c > 0); + if (c == m_c) + return *this; + if (m_stream != nullptr) + m_stream->update(); + + set_target_v(m_v_end); // Snapshots voltage, using the old `c` value. + m_c = c; + m_rc_inv = 1.0F / (m_r * m_c); + return *this; +} + +va_rc_eg_device &va_rc_eg_device::set_target_v(float v) +{ + if (v == m_v_end) + return *this; + if (m_stream != nullptr) + m_stream->update(); + + if (has_running_machine()) + { + const attotime now = machine().time(); + m_v_start = get_v(now); + m_v_end = v; + m_t_start = now; + } + else + { + m_v_start = 0; + m_v_end = v; + m_t_start = attotime::zero; + } + return *this; +} + +va_rc_eg_device &va_rc_eg_device::set_instant_v(float v) +{ + if (m_stream != nullptr) + m_stream->update(); + + m_v_start = v; + m_v_end = v; + m_t_start = has_running_machine() ? machine().time() : attotime::zero; + return *this; +} + +float va_rc_eg_device::get_v(const attotime &t) const +{ + assert(t >= m_t_start); + const float delta_t = float((t - m_t_start).as_double()); + return m_v_start + (m_v_end - m_v_start) * (1.0F - expf(-delta_t * m_rc_inv)); +} + +float va_rc_eg_device::get_v() const +{ + return get_v(machine().time()); +} + +void va_rc_eg_device::device_start() +{ + m_stream = stream_alloc(0, 1, SAMPLE_RATE_OUTPUT_ADAPTIVE); + save_item(NAME(m_r)); + save_item(NAME(m_c)); + save_item(NAME(m_rc_inv)); + save_item(NAME(m_v_start)); + save_item(NAME(m_v_end)); + save_item(NAME(m_t_start)); +} + +void va_rc_eg_device::sound_stream_update(sound_stream &stream, const std::vector &inputs, std::vector &outputs) +{ + // The envelope stage will be considered done once the voltage reaches + // within MIN_DELTA of the target. + static constexpr const float MIN_DELTA = 0.0001F; + + assert(inputs.size() == 0 && outputs.size() == 1); + write_stream_view &out = outputs[0]; + attotime t = out.start_time(); + + if (fabsf(get_v(t) - m_v_end) < MIN_DELTA) + { + // Avoid expensive get_v() calls if the envelope stage has completed. + out.fill(m_v_end); + return; + } + + const int n = out.samples(); + const attotime dt = out.sample_period(); + for (int i = 0; i < n; ++i, t += dt) + out.put(i, get_v(t)); +} diff --git a/src/devices/sound/va_eg.h b/src/devices/sound/va_eg.h new file mode 100644 index 00000000000..520487100b0 --- /dev/null +++ b/src/devices/sound/va_eg.h @@ -0,0 +1,57 @@ +// license:BSD-3-Clause +// copyright-holders:m1macrophage + +#ifndef MAME_SOUND_VA_EG_H +#define MAME_SOUND_VA_EG_H + +#pragma once + +// Building block for emulating envelope generators (EGs) based on a single RC +// circuit. The controlling source sets a target voltage and the device +// interpolates up or down to it through a RC circuit. The voltage is published +// as a sound stream and also available from `get_v()`. +// +// For example, emulating a CPU-controlled ADSR EG will look something like: +// - Machine configuration: rc_eg.set_c(C); +// - Start attack: rc_eg.set_r(attack_r).set_target_v(max_v); +// - Start decay: rc_eg.set_r(decay_r).set_target_v(sustain_v); +// - Start release: rc_eg.set_r(release_r).set_target_v(0); +// +class va_rc_eg_device : public device_t, public device_sound_interface +{ +public: + va_rc_eg_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0) ATTR_COLD; + + // These setters can be used during both: configuration and normal operation. + // `r` and `c` must be > 0. To instantly set the capacitor's voltage to + // a specific value, use set_instant_v(). + va_rc_eg_device &set_r(float r); + va_rc_eg_device &set_c(float v); + + // Sets target voltage to (dis)charge towards. + va_rc_eg_device &set_target_v(float v); + + // Sets the voltage to the given value, instantly. + va_rc_eg_device &set_instant_v(float v); + + float get_v(const attotime &t) const; + float get_v() const; // Get voltage at the machine's current time. + +protected: + void device_start() override ATTR_COLD; + void sound_stream_update(sound_stream &stream, const std::vector &inputs, std::vector &outputs) override; + +private: + sound_stream *m_stream; + + float m_r; + float m_c; + float m_rc_inv; + float m_v_start; + float m_v_end; + attotime m_t_start; +}; + +DECLARE_DEVICE_TYPE(VA_RC_EG, va_rc_eg_device) + +#endif // MAME_SOUND_VA_EG_H diff --git a/src/devices/sound/va_vca.cpp b/src/devices/sound/va_vca.cpp new file mode 100644 index 00000000000..d918dd25fba --- /dev/null +++ b/src/devices/sound/va_vca.cpp @@ -0,0 +1,76 @@ +// license:BSD-3-Clause +// copyright-holders:m1macrophage + +#include "emu.h" +#include "va_vca.h" + +#include + +// Default max peak-to-peak voltage for the CV input. +static constexpr const float DEFAULT_MAX_VPP = 100; + +DEFINE_DEVICE_TYPE(VA_VCA, va_vca_device, "va_vca", "Voltage Controlled Amplifier") + +va_vca_device::va_vca_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, VA_VCA, tag, owner, clock) + , device_sound_interface(mconfig, *this) + , m_stream(nullptr) + , m_has_cv_stream(false) + , m_min_cv(-DEFAULT_MAX_VPP) + , m_max_cv(DEFAULT_MAX_VPP) + , m_cv_scale(1.0F) + , m_fixed_gain(1.0F) +{ +} + +va_vca_device &va_vca_device::configure_streaming_cv(bool use_streaming_cv) +{ + m_has_cv_stream = use_streaming_cv; + return *this; +} + +va_vca_device &va_vca_device::configure_cem3360_linear_cv() +{ + // TODO: For now, the CEM3360 is treated as a linear device. But since it + // is OTA-based, it likely has a tanh response. This requires more research. + + // Typical linear CV for max gain, as reported on the CEM3360 datasheet. + static constexpr const float CEM3360_MAX_GAIN_CV = 1.93F; + m_min_cv = 0; + m_max_cv = CEM3360_MAX_GAIN_CV; + m_cv_scale = 1.0F / CEM3360_MAX_GAIN_CV; + return *this; +} + +void va_vca_device::set_fixed_cv(float cv) +{ + m_stream->update(); + m_fixed_gain = cv_to_gain(cv); +} + +void va_vca_device::device_start() +{ + const int input_count = m_has_cv_stream ? 2 : 1; + m_stream = stream_alloc(input_count, 1, SAMPLE_RATE_OUTPUT_ADAPTIVE); + save_item(NAME(m_fixed_gain)); +} + +void va_vca_device::sound_stream_update(sound_stream &stream, const std::vector &inputs, std::vector &outputs) +{ + if (m_has_cv_stream) + { + for (int i = 0; i < outputs[0].samples(); i++) + outputs[0].put(i, inputs[0].get(i) * cv_to_gain(inputs[1].get(i))); + } + else + { + for (int i = 0; i < outputs[0].samples(); i++) + outputs[0].put(i, inputs[0].get(i) * m_fixed_gain); + } +} + +float va_vca_device::cv_to_gain(float cv) const +{ + return std::clamp(cv, m_min_cv, m_max_cv) * m_cv_scale; +} + diff --git a/src/devices/sound/va_vca.h b/src/devices/sound/va_vca.h new file mode 100644 index 00000000000..ee155426019 --- /dev/null +++ b/src/devices/sound/va_vca.h @@ -0,0 +1,58 @@ +// license:BSD-3-Clause +// copyright-holders:m1macrophage + +#ifndef MAME_SOUND_VA_VCA_H +#define MAME_SOUND_VA_VCA_H + +#pragma once + +// Emulates a voltage-controled amplifier (VCA). The control value (CV) can +// be set directly (set_fixed_cv()), or it can be provided in a sound stream +// (input 1), by a device in va_eg.h, for example. When the cv is provided via a +// stream, this is also a ring modulator. +// +// The behavior of specific VCAs can be emulated by using the respective +// configure_* functions. +// Note that "CV" ("control value") could either refer to a control voltage, or +// a control current, depending on the device. +class va_vca_device : public device_t, public device_sound_interface +{ +public: + va_vca_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0) ATTR_COLD; + + // When streaming is enabled, the CV will be obtained from input 1 of the + // sound stream. Otherwise, the value set with `set_fixed_cv` will be used. + va_vca_device &configure_streaming_cv(bool use_streaming_cv); + + // By default, the CV will be treated as a typical gain (output = cv * input) + // The configure_*() functions below might change this. + + // CEM3360 (or AS3360) VCA in linear CV configuration: CV connected to pin + // Vc, and pins Vo and Ve connected to each other. The CV input (fixed or + // streaming) should be the voltage at the Vc pin. + va_vca_device &configure_cem3360_linear_cv(); + + // Ignored when streaming CVs are enabled. + void set_fixed_cv(float cv); + +protected: + void device_start() override ATTR_COLD; + void sound_stream_update(sound_stream &stream, const std::vector &inputs, std::vector &outputs) override; + +private: + float cv_to_gain(float cv) const; + + // Configuration. No need to include in save state. + sound_stream *m_stream; + bool m_has_cv_stream; + float m_min_cv; + float m_max_cv; + float m_cv_scale; + + // State. + float m_fixed_gain; +}; + +DECLARE_DEVICE_TYPE(VA_VCA, va_vca_device) + +#endif // MAME_SOUND_VA_VCA_H diff --git a/src/mame/linn/linndrum.cpp b/src/mame/linn/linndrum.cpp index 1a3fc345a62..a3d848db99f 100644 --- a/src/mame/linn/linndrum.cpp +++ b/src/mame/linn/linndrum.cpp @@ -101,6 +101,8 @@ Example: #include "sound/flt_vol.h" #include "sound/mixer.h" #include "sound/spkrdev.h" +#include "sound/va_eg.h" +#include "sound/va_vca.h" #include "speaker.h" #include "linn_linndrum.lh" @@ -113,7 +115,7 @@ Example: #define LOG_TAPE_SYNC_ENABLE (1U << 6) #define LOG_MIX (1U << 7) #define LOG_PITCH (1U << 8) -#define LOG_HAT_VCA (1U << 9) +#define LOG_HAT_EG (1U << 9) #define VERBOSE (LOG_GENERAL) //#define LOG_OUTPUT_FUNC osd_printf_info @@ -182,156 +184,6 @@ enum mixer_channels } // anonymous namespace -// This device combines the CEM3360 and its envelope generator (EG) that process -// the hi-hat voice. -// TODO: Look into implementing the CEM3360 and a generic EG as devices under -// src/devices/sound. -class linndrum_hat_vca_device : public device_t, public device_sound_interface -{ -public: - linndrum_hat_vca_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0) ATTR_COLD; - - void trigger(); - void set_open(bool open_hat); - -protected: - void device_add_mconfig(machine_config &config) override ATTR_COLD; - void device_start() override ATTR_COLD; - void device_reset() override ATTR_COLD; - void sound_stream_update(sound_stream &stream, std::vector const &inputs, std::vector &outputs) override; - -private: - static float get_cem3360_gain(float cv); - - TIMER_DEVICE_CALLBACK_MEMBER(trigger_timer_tick); - - static constexpr const float C22 = CAP_U(1); - static constexpr const float R33 = RES_M(1); - static constexpr const float R34 = RES_K(10); - static constexpr const float DECAY_POT_R_MAX = RES_K(100); - - sound_stream *m_stream = nullptr; - - required_ioport m_decay_pot; - required_device m_trigger_timer; // U37B (LM556). - - float m_rc_inv = 1.0F / (R33 * C22); - bool m_decaying = true; - bool m_decay_done = true; - attotime m_decay_start_time; -}; - -DEFINE_DEVICE_TYPE(LINNDRUM_HAT_VCA, linndrum_hat_vca_device, "linndrum_hat_vca", "LinnDrum CEM3360 VCA and EG"); - -linndrum_hat_vca_device::linndrum_hat_vca_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : device_t(mconfig, LINNDRUM_HAT_VCA, tag, owner, clock) - , device_sound_interface(mconfig, *this) - , m_decay_pot(*this, ":pot_tuning_7") - , m_trigger_timer(*this, "hat_trigger_timer") -{ -} - -void linndrum_hat_vca_device::trigger() -{ - m_stream->update(); - m_decaying = false; - m_decay_done = false; - m_trigger_timer->adjust(PERIOD_OF_555_MONOSTABLE(RES_K(510), CAP_U(0.01))); // R8, C4. - LOGMASKED(LOG_HAT_VCA, "Hat VCA trigerred.\n"); -} - -void linndrum_hat_vca_device::set_open(bool open_hat) -{ - // The envelope generator can run in two different modes. - // - Open hat: the capacitor is discharged through a 1M resistor. - // - Closed hat: U90 (CD4053 MUX) adds a parallel discharge path through the - // "hihat decay" knob. - m_stream->update(); - float r = R33; - if (!open_hat) - { - const float r_decay = DECAY_POT_R_MAX * m_decay_pot->read() / 100.0F; - r = RES_2_PARALLEL(R33, R34 + r_decay); - } - m_rc_inv = 1.0F / (r * C22); - LOGMASKED(LOG_HAT_VCA, "Hat decay. Open: %d, r: %g\n", open_hat, r); -} - -void linndrum_hat_vca_device::device_add_mconfig(machine_config &config) -{ - TIMER(config, m_trigger_timer).configure_generic(FUNC(linndrum_hat_vca_device::trigger_timer_tick)); -} - -void linndrum_hat_vca_device::device_start() -{ - m_stream = stream_alloc(1, 1, machine().sample_rate()); - save_item(NAME(m_rc_inv)); - save_item(NAME(m_decaying)); - save_item(NAME(m_decay_done)); - save_item(NAME(m_decay_start_time)); -} - -void linndrum_hat_vca_device::device_reset() -{ - set_open(false); -} - -void linndrum_hat_vca_device::sound_stream_update(sound_stream &stream, std::vector const &inputs, std::vector &outputs) -{ - static constexpr const float MIN_GAIN = 0.0001F; // A gain lower than this will be treated as 0. - static constexpr const float MAX_EG_CV = 5; - static constexpr const float CV_SCALE = RES_VOLTAGE_DIVIDER(RES_K(8.2), RES_K(10)); // R67, R66. - - assert(inputs.size() == 1 && outputs.size() == 1); - const read_stream_view &in = inputs[0]; - write_stream_view &out = outputs[0]; - - if (m_decay_done) - { - out.fill(0); - return; - } - - const int n = in.samples(); - if (!m_decaying) - { - const float gain = get_cem3360_gain(MAX_EG_CV * CV_SCALE); - for (int i = 0; i < n; ++i) - out.put(i, gain * in.get(i)); - return; - } - - attotime t = in.start_time() - m_decay_start_time; - assert(t >= attotime::from_double(0)); - float gain = 0; - for (int i = 0; i < n; ++i, t += in.sample_period()) - { - // TODO: The CEM3360 is based on an OTA, which means it likely has a - // tanh, rather than a linear response. But this needs more research. - const float decay = expf(-t.as_double() * m_rc_inv); - gain = get_cem3360_gain(decay * MAX_EG_CV * CV_SCALE); - out.put(i, gain * in.get(i)); - } - - if (gain < MIN_GAIN) - m_decay_done = true; -} - -float linndrum_hat_vca_device::get_cem3360_gain(float cv) -{ - // Typical linear CV for max gain, as reported on the CEM3360 datasheet. - static constexpr const float MAX_GAIN_CV = 1.93F; - return std::clamp(cv / MAX_GAIN_CV, 0, 1); -} - -TIMER_DEVICE_CALLBACK_MEMBER(linndrum_hat_vca_device::trigger_timer_tick) -{ - m_stream->update(); - m_decaying = true; - m_decay_done = false; - m_decay_start_time = machine().time(); - LOGMASKED(LOG_HAT_VCA, "Hat VCA started decay.\n"); -} class linndrum_audio_device : public device_t { @@ -361,6 +213,7 @@ private: static s32 get_ls267_freq(const std::array& freq_range_hz, float cv); static float get_snare_tom_pitch_cv(float v); + TIMER_DEVICE_CALLBACK_MEMBER(hat_trigger_timer_tick); TIMER_DEVICE_CALLBACK_MEMBER(mux_timer_tick); TIMER_DEVICE_CALLBACK_MEMBER(snare_timer_tick); TIMER_DEVICE_CALLBACK_MEMBER(click_timer_tick); @@ -374,11 +227,14 @@ private: // Mux drums. required_ioport m_mux_tuning_trimmer; + required_ioport m_hat_decay_pot; required_memory_region_array m_mux_samples; required_device m_mux_timer; // 74LS627 (U77A). required_device_array m_mux_dac; // AM6070 (U88). required_device_array m_mux_volume; // CD4053 (U90), R60, R62. - required_device m_hat_vca; + required_device m_hat_trigger_timer; // U37B (LM556). + required_device m_hat_eg; + required_device m_hat_vca; // CEM3360 (U91B). std::array m_mux_counting = { false, false, false, false, false, false, false, false }; std::array m_mux_counters = { 0, 0, 0, 0, 0, 0, 0, 0 }; @@ -450,6 +306,13 @@ private: static constexpr const float MUX_DAC_IREF = VPLUS / (RES_K(15) + RES_K(15)); // R55 + R57. static constexpr const float TOM_DAC_IREF = MUX_DAC_IREF; // Configured in the same way. + // Constants for hi hat envelope generator circuit. + static constexpr const float HAT_C22 = CAP_U(1); + static constexpr const float HAT_R33 = RES_M(1); + static constexpr const float HAT_R34 = RES_K(10); + static constexpr const float HAT_DECAY_POT_R_MAX = RES_K(100); + static constexpr const float HAT_MAX_CV = VCC * RES_VOLTAGE_DIVIDER(RES_K(8.2), RES_K(10)); // R67, R66. + // The audio pipeline operates on voltage magnitudes. This scaler normalizes // the final output's range to approximately: -1 - 1. static constexpr const float VOLTAGE_TO_SOUND_SCALER = 0.2F; @@ -486,10 +349,13 @@ DEFINE_DEVICE_TYPE(LINNDRUM_AUDIO, linndrum_audio_device, "linndrum_audio_device linndrum_audio_device::linndrum_audio_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : device_t(mconfig, LINNDRUM_AUDIO, tag, owner, clock) , m_mux_tuning_trimmer(*this, ":pot_mux_tuning") + , m_hat_decay_pot(*this, ":pot_tuning_7") , m_mux_samples(*this, ":sample_mux_drum_%u", 0) , m_mux_timer(*this, "mux_drum_timer") , m_mux_dac(*this, "mux_drums_virtual_dac_%u", 1) , m_mux_volume(*this, "mux_drums_volume_control_%u", 1) + , m_hat_trigger_timer(*this, "hat_trigger_timer") + , m_hat_eg(*this, "hat_eg") , m_hat_vca(*this, "hat_vca") , m_snare_samples(*this, ":sample_snare") , m_sidestick_samples(*this, ":sample_sidestick") @@ -538,9 +404,22 @@ void linndrum_audio_device::mux_drum_w(int voice, u8 data, bool is_strobe) if (voice == MV_HAT) { - m_hat_vca->set_open(BIT(data, 2)); + float r = HAT_R33; + const bool is_open_hat = BIT(data, 2); + if (!is_open_hat) + { + const float r_decay = HAT_DECAY_POT_R_MAX * m_hat_decay_pot->read() / 100.0F; + r = RES_2_PARALLEL(HAT_R33, HAT_R34 + r_decay); + } + m_hat_eg->set_r(r); + LOGMASKED(LOG_HAT_EG, "Hat decay. Open: %d, r: %g\n", is_open_hat, r); + if (is_strobe) - m_hat_vca->trigger(); + { + m_hat_eg->set_instant_v(HAT_MAX_CV); + m_hat_trigger_timer->adjust(PERIOD_OF_555_MONOSTABLE(RES_K(510), CAP_U(0.01))); // R8, C4. + LOGMASKED(LOG_HAT_EG, "Hat EG triggered.\n"); + } } LOGMASKED(LOG_STROBES, "Strobed mux drum %s: %02x (gain: %f)\n", @@ -697,8 +576,11 @@ void linndrum_audio_device::device_add_mconfig(machine_config &config) m_mux_dac[voice]->add_route(0, m_mux_volume[voice], get_dac_scaler(MUX_DAC_IREF)); } - LINNDRUM_HAT_VCA(config, m_hat_vca); + TIMER(config, m_hat_trigger_timer).configure_generic(FUNC(linndrum_audio_device::hat_trigger_timer_tick)); // LM556 (U37B). + VA_RC_EG(config, m_hat_eg).set_c(HAT_C22); + VA_VCA(config, m_hat_vca).configure_streaming_cv(true).configure_cem3360_linear_cv(); m_mux_volume[MV_HAT]->add_route(0, m_hat_vca, 1.0); + m_hat_eg->add_route(0, m_hat_vca, 1.0); // *** Snare / sidestick section. @@ -879,6 +761,12 @@ float linndrum_audio_device::get_snare_tom_pitch_cv(float v_tune) return std::clamp(cv, 0, VCC); } +TIMER_DEVICE_CALLBACK_MEMBER(linndrum_audio_device::hat_trigger_timer_tick) +{ + m_hat_eg->set_target_v(0); + LOGMASKED(LOG_HAT_EG, "Hat EG started decay.\n"); +} + TIMER_DEVICE_CALLBACK_MEMBER(linndrum_audio_device::mux_timer_tick) { // The timer on the actual hardware ticks 4 times per voice. A combination diff --git a/src/mame/oberheim/dmx.cpp b/src/mame/oberheim/dmx.cpp index ddf08e68060..bf9e1896818 100644 --- a/src/mame/oberheim/dmx.cpp +++ b/src/mame/oberheim/dmx.cpp @@ -82,6 +82,8 @@ Usage notes: #include "sound/flt_rc.h" #include "sound/mixer.h" #include "sound/spkrdev.h" +#include "sound/va_eg.h" +#include "sound/va_vca.h" #include "video/dl1416.h" #include "speaker.h" @@ -93,9 +95,7 @@ Usage notes: #define LOG_SOUND (1U << 4) #define LOG_PITCH (1U << 5) #define LOG_VOLUME (1U << 6) -#define LOG_SAMPLES (1U << 7) -#define LOG_SAMPLES_DECAY (1U << 8) -#define LOG_METRONOME (1U << 9) +#define LOG_METRONOME (1U << 7) #define VERBOSE (LOG_GENERAL) //#define LOG_OUTPUT_FUNC osd_printf_info @@ -170,216 +170,6 @@ struct dmx_voice_card_config const filter_components filter; }; -// The combination of the gain control circuit (which includes decay for some -// voices), and the multiplying DAC form a VCA. The gain control circuit sets -// the reference current into the DAC, and the DAC multiplies that with the -// digital value to produce and output current. -class dmx_voice_card_vca_device : public device_t, public device_sound_interface -{ -public: - dmx_voice_card_vca_device(const machine_config &mconfig, const char *tag, device_t *owner, const dmx_voice_card_config &config) ATTR_COLD; - dmx_voice_card_vca_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0) ATTR_COLD; - - void start(int trigger_mode); - void decay(); - - bool in_decay() const { return m_decaying; } - -protected: - void device_start() override ATTR_COLD; - void device_reset() override ATTR_COLD; - void sound_stream_update(sound_stream &stream, const std::vector &inputs, std::vector &outputs) override; - -private: - void init_gain_and_decay_variations(const dmx_voice_card_config &config) ATTR_COLD; - - bool has_decay() const { return !m_decay_rc_inv.empty(); } - bool has_decay_variations() const { return m_decay_rc_inv.size() > 1; } - - // Configuration. Do not include in save state. - sound_stream *m_stream = nullptr; - const bool m_gain_control; // Is VCA configured for gain variations? - std::vector m_gain; // Gain variations. - std::vector m_decay_rc_inv; // Decay 1/RC variations. - - // Device state. - float m_selected_gain = 1; - bool m_decaying = false; - bool m_decay_done = false; - float m_selected_rc_inv = 1; - attotime m_decay_start_time; -}; - -DEFINE_DEVICE_TYPE(DMX_VOICE_CARD_VCA, dmx_voice_card_vca_device, "dmx_voice_card_vca", "DMX Voice Card VCA"); - -dmx_voice_card_vca_device::dmx_voice_card_vca_device(const machine_config &mconfig, const char *tag, device_t *owner, const dmx_voice_card_config &config) - : device_t(mconfig, DMX_VOICE_CARD_VCA, tag, owner, 0) - , device_sound_interface(mconfig, *this) - , m_gain_control(!config.pitch_control) -{ - init_gain_and_decay_variations(config); -} - -dmx_voice_card_vca_device::dmx_voice_card_vca_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : device_t(mconfig, DMX_VOICE_CARD_VCA, tag, owner, clock) - , device_sound_interface(mconfig, *this) - , m_gain_control(false) -{ -} - -void dmx_voice_card_vca_device::start(int trigger_mode) -{ - assert(trigger_mode >= 1 && trigger_mode <= 3); - - m_stream->update(); - m_decaying = false; - m_decay_done = false; - - if (m_gain_control) - m_selected_gain = m_gain[trigger_mode]; - else - m_selected_gain = m_gain[0]; - - if (has_decay_variations()) - m_selected_rc_inv = m_decay_rc_inv[trigger_mode]; - else if (has_decay()) - m_selected_rc_inv = m_decay_rc_inv[0]; - else - m_selected_rc_inv = 1; - - LOGMASKED(LOG_VOLUME, "Selected gain: %f, 1/RC: %f\n", - m_selected_gain, m_selected_rc_inv); -} - -void dmx_voice_card_vca_device::decay() -{ - assert(has_decay()); - if (!has_decay()) - return; - - m_stream->update(); - m_decaying = true; - m_decay_start_time = machine().time(); -} - -void dmx_voice_card_vca_device::device_start() -{ - m_stream = stream_alloc(1, 1, machine().sample_rate()); - - save_item(NAME(m_selected_gain)); - save_item(NAME(m_decaying)); - save_item(NAME(m_decay_done)); - save_item(NAME(m_selected_rc_inv)); - save_item(NAME(m_decay_start_time)); -} - -void dmx_voice_card_vca_device::device_reset() -{ - m_selected_gain = 1; - m_decaying = false; - m_decay_done = false; - m_selected_rc_inv = 1; -} - -void dmx_voice_card_vca_device::sound_stream_update(sound_stream &stream, const std::vector &inputs, std::vector &outputs) -{ - // Gain lower than MIN_GAIN will be treated as 0. - static constexpr const float MIN_GAIN = 0.0001F; - - const read_stream_view &in = inputs[0]; - write_stream_view &out = outputs[0]; - const int n = in.samples(); - - if (!m_decaying) // Just gain variation without decay. - { - for (int i = 0; i < n; ++i) - out.put(i, m_selected_gain * in.get(i)); - - LOGMASKED(LOG_SAMPLES, "%s VCA - just gain: %f. Samples: %f, %f.\n", - tag(), m_selected_gain, in.get(0), in.get(n - 1)); - return; - } - - if (m_decay_done) // Avoid expensive expf() call if volume has decayed. - { - out.fill(0); - LOGMASKED(LOG_SAMPLES, "%s VCA - decay done.\n", tag()); - return; - } - - attotime t = in.start_time() - m_decay_start_time; - assert(!m_decaying || t >= attotime::from_double(0)); - - float gain = 1; - for (int i = 0; i < n; ++i, t += in.sample_period()) - { - const float decay = expf(-t.as_double() * m_selected_rc_inv); - gain = decay * m_selected_gain; - out.put(i, gain * in.get(i)); - } - - if (gain < MIN_GAIN) - m_decay_done = true; - - LOGMASKED(LOG_SAMPLES_DECAY, "%s VCA - in decay: %f. Samples: %f, %f.\n", - tag(), gain, in.get(0), in.get(n - 1)); -} - -void dmx_voice_card_vca_device::init_gain_and_decay_variations(const dmx_voice_card_config &config) -{ - static constexpr const float VD = 0.6; // Diode drop. - static constexpr const float R8 = RES_K(2.7); - static constexpr const float R9 = RES_K(5.6); - static constexpr const float MAX_IREF = VCC / (R8 + R9); - - const float r12 = config.r12; - const float r17 = config.r17; - const float c3 = config.c3; - - // Precompute gain variations. - m_gain.clear(); - m_gain.push_back(MAX_IREF); - if (m_gain_control) // Configured for gain variations. - { - // The equations below were derived from Kirchhoff analysis and verified - // with simulations: https://tinyurl.com/22wxwh8h - - // For trigger mode 1. - m_gain.push_back((r12 * r17 * VCC + R8 * r12 * VD + R8 * r17 * VD) / - ((R8 * r12 * r17) + (r12 * r17 * R9) + (R8 * r17 * R9) + (R8 * r12 * R9))); - // For trigger mode 2. - m_gain.push_back((r12 * VCC + R8 * VD) / (r12 * R8 + R8 * R9 + r12 * R9)); - // For trigger mode 3. - m_gain.push_back(m_gain[0]); - } - for (int i = 0; i < m_gain.size(); ++i) - { - LOGMASKED(LOG_VOLUME, "%s: Gain variation %d: %f uA, %f\n", - tag(), i, m_gain[i] * 1e6F, m_gain[i] / MAX_IREF); - m_gain[i] /= MAX_IREF; // Normalize. - } - - // Precompute decay variations. - m_decay_rc_inv.clear(); - if (config.decay != dmx_voice_card_config::decay_mode::DISABLED && c3 > 0) - { - std::vector r_lower; - r_lower.push_back(R9); // For when there are no variations. - if (m_gain_control) - { - r_lower.push_back(RES_3_PARALLEL(R9, r12, r17)); // For trigger mode 1. - r_lower.push_back(RES_2_PARALLEL(R9, r12)); // For trigger mode 2. - r_lower.push_back(R9); // For trigger mode 3. - } - for (float r : r_lower) - { - m_decay_rc_inv.push_back(1.0F / ((R8 + r) * c3)); - LOGMASKED(LOG_VOLUME, "%s: Decay 1/RC variation %d: %f\n", - tag(), m_decay_rc_inv.size() - 1, m_decay_rc_inv.back()); - } - } -} - // Emulates the original DMX voice cards, including the cymbal card. Later // DMX models shipped with the "Mark II" voice cards for the Tom voices. // The Mark II cards are not yet emulated. @@ -407,6 +197,11 @@ private: void compute_pitch_variations(); void select_pitch(); + void init_gain_and_decay_variations() ATTR_COLD; + bool has_decay() const { return !m_decay_r.empty(); } + bool has_decay_variations() const { return m_decay_r.size() > 1; } + bool has_gain_variations() const { return !m_config.pitch_control; } + bool is_decay_enabled() const; bool is_early_decay_enabled() const; TIMER_DEVICE_CALLBACK_MEMBER(clock_callback); @@ -415,7 +210,8 @@ private: required_device m_timer; // 555, U5. required_device m_dac; // AM6070, U8. Compatible with DAC76. - required_device m_vca; + required_device m_dac_mult; // AM6070 is a multiplying DAC. + optional_device m_eg; // Volume envelope generator. Input to U8 Iref. required_device_array m_filters; // Configuration. Do not include in save state. @@ -424,11 +220,14 @@ private: std::vector m_cv; // 555 CV (pin 5) voltage variations. std::vector m_sample_t; // Sample period variations. s32 m_t1_percent = T1_DEFAULT_PERCENT; + std::vector m_gain; // Gain variations. + std::vector m_decay_r; // Decay resistance variations. // Device state. bool m_counting = false; u16 m_counter = 0; // 4040 counter. u8 m_trigger_mode = 0; // Valid modes: 1-3. 0 OK after reset. + bool m_decaying = false; }; DEFINE_DEVICE_TYPE(DMX_VOICE_CARD, dmx_voice_card_device, "dmx_voice_card", "DMX Voice Card"); @@ -438,12 +237,14 @@ dmx_voice_card_device::dmx_voice_card_device(const machine_config &mconfig, cons , device_sound_interface(mconfig, *this) , m_timer(*this, "555_u5") , m_dac(*this, "dac_u8") - , m_vca(*this, "dmx_vca") + , m_dac_mult(*this, "dac_mult_u8") + , m_eg(*this, "envelope_generator") , m_filters(*this, "aa_sk_filter_%d", 0) , m_config(config) , m_sample_rom(sample_rom) { init_pitch(); + init_gain_and_decay_variations(); } dmx_voice_card_device::dmx_voice_card_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) @@ -451,7 +252,8 @@ dmx_voice_card_device::dmx_voice_card_device(const machine_config &mconfig, cons , device_sound_interface(mconfig, *this) , m_timer(*this, "555_u5") , m_dac(*this, "dac_u8") - , m_vca(*this, "dmx_vca") + , m_dac_mult(*this, "dac_mult_u8") + , m_eg(*this, "envelope_generator") , m_filters(*this, "aa_sk_filter_%d", 0) // Need non-zero entries for the filter for validation to pass. , m_config(dmx_voice_card_config{.filter={1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}) @@ -473,11 +275,18 @@ void dmx_voice_card_device::trigger(bool tr0, bool tr1) m_stream->update(); m_counter = 0; m_counting = true; + m_decaying = false; + if (m_config.pitch_control) select_pitch(); - m_vca->start(m_trigger_mode); - LOGMASKED(LOG_SOUND, "Trigger: (%d, %d) %d %f\n", tr0, tr1, m_trigger_mode); + const float gain = has_gain_variations() ? m_gain[m_trigger_mode] : m_gain[0]; + if (has_decay()) // Gain is controled by an envelope generator. + m_eg->set_instant_v(gain); + else // Constant gain. + m_dac_mult->set_fixed_cv(gain); + + LOGMASKED(LOG_SOUND, "Trigger: (%d, %d) %d, %f\n", tr0, tr1, m_trigger_mode, gain); } void dmx_voice_card_device::set_pitch_adj(s32 t1_percent) @@ -494,7 +303,15 @@ void dmx_voice_card_device::device_add_mconfig(machine_config &config) TIMER(config, m_timer).configure_generic(FUNC(dmx_voice_card_device::clock_callback)); DAC76(config, m_dac, 0U); - DMX_VOICE_CARD_VCA(config, m_vca, m_config); + VA_VCA(config, m_dac_mult); + m_dac->add_route(0, m_dac_mult, 1.0); + + if (has_decay()) + { + VA_RC_EG(config, m_eg).set_c(m_config.c3); + m_dac_mult->configure_streaming_cv(true); + m_eg->add_route(0, m_dac_mult, 1.0); + } FILTER_BIQUAD(config, m_filters[0]).opamp_sk_lowpass_setup( m_config.filter.r15, m_config.filter.r14, SK_R3, SK_R4, @@ -506,12 +323,10 @@ void dmx_voice_card_device::device_add_mconfig(machine_config &config) m_config.filter.r19, m_config.filter.r20, SK_R3, SK_R4, m_config.filter.c8, m_config.filter.c9); - m_dac->add_route(ALL_OUTPUTS, m_vca, 1.0); - m_vca->add_route(ALL_OUTPUTS, m_filters[0], 1.0); - - m_filters[0]->add_route(ALL_OUTPUTS, m_filters[1], 1.0); - m_filters[1]->add_route(ALL_OUTPUTS, m_filters[2], 1.0); - m_filters[2]->add_route(ALL_OUTPUTS, *this, 1.0); + m_dac_mult->add_route(0, m_filters[0], 1.0); + m_filters[0]->add_route(0, m_filters[1], 1.0); + m_filters[1]->add_route(0, m_filters[2], 1.0); + m_filters[2]->add_route(0, *this, 1.0); } void dmx_voice_card_device::device_start() @@ -521,18 +336,22 @@ void dmx_voice_card_device::device_start() save_item(NAME(m_counting)); save_item(NAME(m_counter)); save_item(NAME(m_trigger_mode)); + save_item(NAME(m_decaying)); } void dmx_voice_card_device::device_reset() { m_trigger_mode = 0; + m_decaying = false; reset_counter(); compute_pitch_variations(); + if (m_eg) + m_eg->set_instant_v(0); } void dmx_voice_card_device::sound_stream_update(sound_stream &stream, const std::vector &inputs, std::vector &outputs) { - outputs[0] = inputs[0]; + outputs[0].copy(inputs[0]); } void dmx_voice_card_device::reset_counter() @@ -662,6 +481,56 @@ void dmx_voice_card_device::select_pitch() 1.0 / sampling_t.as_double()); } +void dmx_voice_card_device::init_gain_and_decay_variations() +{ + static constexpr const float VD = 0.6; // Diode drop. + static constexpr const float R8 = RES_K(2.7); + static constexpr const float R9 = RES_K(5.6); + static constexpr const float MAX_IREF = VCC / (R8 + R9); + + const float r12 = m_config.r12; + const float r17 = m_config.r17; + const float c3 = m_config.c3; + + // Precompute gain variations. + m_gain.clear(); + m_gain.push_back(MAX_IREF); + if (has_gain_variations()) // Configured gain variations. + { + // The equations below were derived from Kirchhoff analysis and verified + // with simulations: https://tinyurl.com/22wxwh8h + + // For trigger mode 1. + m_gain.push_back((r12 * r17 * VCC + R8 * r12 * VD + R8 * r17 * VD) / + ((R8 * r12 * r17) + (r12 * r17 * R9) + (R8 * r17 * R9) + (R8 * r12 * R9))); + // For trigger mode 2. + m_gain.push_back((r12 * VCC + R8 * VD) / (r12 * R8 + R8 * R9 + r12 * R9)); + // For trigger mode 3. + m_gain.push_back(m_gain[0]); + } + for (int i = 0; i < m_gain.size(); ++i) + { + LOGMASKED(LOG_VOLUME, "%s: Gain variation %d: %f uA, %f\n", + tag(), i, m_gain[i] * 1e6F, m_gain[i] / MAX_IREF); + m_gain[i] /= MAX_IREF; // Normalize. + } + + // Precompute decay resistance variations. + m_decay_r.clear(); + if (m_config.decay != dmx_voice_card_config::decay_mode::DISABLED && c3 > 0) + { + m_decay_r.push_back(R8 + R9); // For when there are no variations. + if (has_gain_variations()) // Gain variations imply decay variations. + { + m_decay_r.push_back(R8 + RES_3_PARALLEL(R9, r12, r17)); // For trigger mode 1. + m_decay_r.push_back(R8 + RES_2_PARALLEL(R9, r12)); // For trigger mode 2. + m_decay_r.push_back(R8 + R9); // For trigger mode 3. + } + for (int i = 0; i < m_decay_r.size(); ++i) + LOGMASKED(LOG_VOLUME, "%s: Decay R variation %d: %f\n", tag(), i, m_decay_r[i]); + } +} + bool dmx_voice_card_device::is_decay_enabled() const { switch (m_config.decay) @@ -721,12 +590,19 @@ TIMER_DEVICE_CALLBACK_MEMBER(dmx_voice_card_device::clock_callback) // the counter's bit 10 transitions to 1. static constexpr const u16 LATE_DECAY_START = 1 << 10; - if (!m_vca->in_decay() && is_decay_enabled()) + if (!m_decaying && is_decay_enabled()) { if ((is_early_decay_enabled() && m_counter >= EARLY_DECAY_START) || m_counter >= LATE_DECAY_START) { - m_vca->decay(); + assert(has_decay()); + m_decaying = true; + if (has_decay_variations()) + m_eg->set_r(m_decay_r[m_trigger_mode]); + else + m_eg->set_r(m_decay_r[0]); + m_eg->set_target_v(0); + LOGMASKED(LOG_SOUND, "%s: Start decay\n", tag()); } } } diff --git a/src/mame/paia/fatman.cpp b/src/mame/paia/fatman.cpp index 91adaa1ef78..0b60930772c 100644 --- a/src/mame/paia/fatman.cpp +++ b/src/mame/paia/fatman.cpp @@ -56,6 +56,7 @@ until a restart or reset (F3). #include "bus/midi/midiinport.h" #include "bus/midi/midioutport.h" #include "machine/rescap.h" +#include "sound/va_eg.h" #include "video/pwm.h" #include "attotime.h" @@ -71,67 +72,6 @@ until a restart or reset (F3). #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_device : public device_t -{ -public: - fatman_rc_network_state_device(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_device, "fatman_rc_network_state", "Fatman EG RC network"); - -fatman_rc_network_state_device::fatman_rc_network_state_device(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::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_device::set_c(float c) -{ - m_c = c; -} - -void fatman_rc_network_state_device::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_device::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"; @@ -142,8 +82,8 @@ 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_vca_adsr(*this, "vca_adsr_eg") + , m_vcf_ar(*this, "vcf_ar_eg") , m_midi_led_pwm(*this, "midi_led_pwm_device") , m_gate_led(*this, "gate_led") , m_dsw_io(*this, "dsw") @@ -170,7 +110,7 @@ private: 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(); + void update_vca_adsr(); int vcf_ar_attack_r() const; void vcf_ar_release_w(int state); @@ -189,8 +129,8 @@ private: 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_vca_adsr; + required_device m_vcf_ar; required_device m_midi_led_pwm; // D2 output_finder<> m_gate_led; // D13 required_ioport m_dsw_io; @@ -265,8 +205,7 @@ void fatman_state::midi_rxd_w(int state) 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; + return (m_vca_adsr->get_v() >= V_EG_COMP) ? 1 : 0; } void fatman_state::vca_adsr_decay_w(int state) @@ -277,7 +216,7 @@ void fatman_state::vca_adsr_decay_w(int state) m_vca_adsr_decay = decay; LOGMASKED(LOG_EG, "vca decay: %d\n", m_vca_adsr_decay); - update_vca_adsr_state(); + update_vca_adsr(); } void fatman_state::vca_adsr_release_w(int state) @@ -292,10 +231,10 @@ void fatman_state::vca_adsr_release_w(int state) // 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(); + update_vca_adsr(); } -void fatman_state::update_vca_adsr_state() +void fatman_state::update_vca_adsr() { // 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. @@ -303,15 +242,14 @@ void fatman_state::update_vca_adsr_state() 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()); + m_vca_adsr->set_r(r96 + R95).set_target_v(V_GND); } 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 current_v = m_vca_adsr->get_v(); 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); + m_vca_adsr->set_r(r92 + R91).set_target_v(std::min(sustain_v, current_v)); } else if (m_vca_adsr_release) // "Invalid" state. { @@ -324,12 +262,12 @@ void fatman_state::update_vca_adsr_state() 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()); + m_vca_adsr->set_r(effective_r).set_target_v(target_v); } 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()); + m_vca_adsr->set_r(POT_R90 + R93 + r94).set_target_v(V_PLUS); } } @@ -343,8 +281,7 @@ int fatman_state::vcf_ar_attack_r() const // 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; + return (m_vcf_ar->get_v() >= V_EG_COMP) ? 1 : 0; } void fatman_state::vcf_ar_release_w(int state) @@ -359,12 +296,12 @@ void fatman_state::vcf_ar_release_w(int state) 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()); + m_vcf_ar->set_r(r82 + R81).set_target_v(V_GND); } 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()); + m_vcf_ar->set_r(R80 + R83 + r84).set_target_v(V_PLUS); } } @@ -505,8 +442,8 @@ void fatman_state::fatman(machine_config &config) 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); + VA_RC_EG(config, m_vca_adsr).set_c(C19); + VA_RC_EG(config, m_vcf_ar).set_c(C22); midi_port_device &midi_in(MIDI_PORT(config, "mdin", midiin_slot, "midiin")); MIDI_PORT(config, "mdthru", midiout_slot, "midiout");