linn/linndrum.cpp: Modeled click filter, improved hihat decay emulation, and did cleanups. (#13610)

* linn/linndrum.cpp: Modeled click filter, improved hihat decay emulation.

Primary changes:
* Hihat decay knob will alway have an effect, not just at trigger time.
* Modeling the "click" filter.
* sound/flt_biquad.cpp: Fixed LOWPASS1P and HIGHPASS1P modes for flt_biquad.

Other changes:
* Renamed input for hihat decay pot.
* Updated "mux drums" section comments.
* Updated snare_w comments.
* Other minor comment changes.

* flt_biquad: Added HIGHPASS1P1Z implementation and used in the linndrum.
Reverted HIGHPASS1P changes.
This commit is contained in:
m1macrophage 2025-04-22 05:53:19 -07:00 committed by GitHub
parent d75e59c5a4
commit 4b2084a6c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 172 additions and 49 deletions

View File

@ -527,6 +527,7 @@ void filter_biquad_device::recalc()
break;
// For highpass and friends, block the entire signal.
case biquad_type::HIGHPASS1P:
case biquad_type::HIGHPASS1P1Z:
case biquad_type::HIGHPASS:
case biquad_type::BANDPASS:
case biquad_type::PEAK:
@ -563,6 +564,13 @@ void filter_biquad_device::recalc()
m_a1 = -m_a1;
m_b1 = m_b2 = m_a2 = 0.0;
break;
case biquad_type::HIGHPASS1P1Z:
normal = 1.0 / (K + 1.0);
m_b0 = normal;
m_b1 = -normal;
m_a1 = (K - 1.0) * normal;
m_b2 = m_a2 = 0.0;
break;
case biquad_type::LOWPASS:
m_b0 = Ksquared * normal;
m_b1 = 2.0 * m_b0;

View File

@ -25,7 +25,8 @@ public:
PEAK,
LOWSHELF,
HIGHSHELF,
RAWPARAMS
RAWPARAMS,
HIGHPASS1P1Z
};
struct biquad_params

View File

@ -67,7 +67,6 @@ copyright-holders:m1macrophage
<element name="txt_tune_4"><text string="LO"><color red="0.82" green="0.43" blue="0.31"/></text></element>
<element name="txt_tune_5"><text string="HI"><color red="0.82" green="0.43" blue="0.31"/></text></element>
<element name="txt_tune_6"><text string="LO"><color red="0.82" green="0.43" blue="0.31"/></text></element>
<element name="txt_tune_7"><text string="HIHAT"><color red="0.82" green="0.43" blue="0.31"/></text></element>
<element name="txt_mixer"><text string="MIXER"><color red="0.82" green="0.43" blue="0.31"/></text></element>
<element name="txt_left_1"><text string="LEFT" align="1"><color red="0.82" green="0.43" blue="0.31"/></text></element>
@ -534,13 +533,16 @@ copyright-holders:m1macrophage
<element ref="txt_tuning"><bounds x="1473" y="435" width="118" height="24"/></element>
<element ref="line"><bounds x="1183" y="445" width="295" height="2"/></element>
<element ref="line"><bounds x="1590" y="445" width="295" height="2"/></element>
<repeat count="7">
<repeat count="6">
<param name="i" start="1" increment="1"/>
<param name="x" start="1183" increment="106"/>
<param name="knob_input" value="pot_tuning_~i~"/>
<group ref="knob"><bounds x="~x~" y="495" width="54" height="54"/></group>
<element ref="txt_tune_~i~"><bounds x="~x~" y="560" width="54" height="17"/></element>
</repeat>
<param name="knob_input" value="pot_hihat_decay"/>
<group ref="knob"><bounds x="1819" y="495" width="54" height="54"/></group>
<element ref="txt_hihat"><bounds x="1819" y="560" width="54" height="17"/></element>
<element ref="txt_snare"><bounds x="1183" y="582" width="54" height="17"/></element>
<element ref="txt_toms"><bounds x="1395" y="582" width="54" height="17"/></element>
<element ref="line"><bounds x="1286" y="590" width="108" height="2"/></element>
@ -608,10 +610,11 @@ copyright-holders:m1macrophage
add_simplecounter_knob(view, "knob_pot_tempo", "pot_tempo", 1.5)
add_simplecounter_knob(view, "knob_pot_volume", "pot_volume", 1.5)
for i = 1, 7 do
for i = 1, 6 do
local knob_input = "pot_tuning_" .. i
add_simplecounter_knob(view, "knob_" .. knob_input, knob_input, 3.5)
end
add_simplecounter_knob(view, "knob_pot_hihat_decay", "pot_hihat_decay", 3.5)
for i = 1, 16 do
local pan_input = "pot_pan_" .. i

View File

@ -28,11 +28,12 @@ There are multiple voice architectures:
There is a single, time-multiplexed DAC used for all voices, and each voice
has its own output. All 8 voices can be played simultaneously, but only a
single loudness variation of each. The bass drum is post-processed by a
CEM3320 VCF with a hardcoded envelope, but all other voices lack a
reconstruction filter. The hat is post-processed by a CEM3360 VCA, which can
operate in 2 variations: slow decay (open hat) and faster, user-controlled
decay (closed hat). There's a single clock for all voices, which is tuned by
a trimmer on the circuit board.
CEM3320 VCF with a hardcoded envelope, the output of which is routed to the
mixer and an individual output jack. The rest of the voices are sent to the
mixer unfiltered, but their individual outputs have a ~15.9KHz RC LPF. The hat
is post-processed by a CEM3360 VCA, which can operate in 2 variations: slow
decay (open hat) and faster, user-controlled decay (closed hat). There's a
single clock for all voices, which is tuned by a trimmer on the circuit board.
* The "snare / sidestick" section consists of a single voice core (and single
DAC) which can either play the sidestick, or the snare voice (4K samples
@ -57,14 +58,10 @@ There are multiple voice architectures:
The driver is based on the LinnDrum's service manual and schematics, and is
intended as an educational tool.
Most of the digital functionality is emulated. Audio is partially emulated.
Reasons for MACHINE_IMPERFECT_SOUND:
* Missing a few sample checksums.
* Missing bass drum LPF and filter envelope.
* Missing snare / sidestick volume envelope.
* Missing tom / conga LPF and filter envelope.
* Inaccurate filter for "click".
* Linear, instead of tanh response for hi-hat VCA.
PCBoards:
@ -96,6 +93,7 @@ Example:
#include "machine/rescap.h"
#include "machine/timer.h"
#include "sound/dac76.h"
#include "sound/flt_biquad.h"
#include "sound/flt_rc.h"
#include "sound/flt_vol.h"
#include "sound/mixer.h"
@ -200,6 +198,7 @@ public:
DECLARE_INPUT_CHANGED_MEMBER(mux_drum_tuning_changed);
DECLARE_INPUT_CHANGED_MEMBER(snare_tuning_changed);
DECLARE_INPUT_CHANGED_MEMBER(tom_tuning_changed);
DECLARE_INPUT_CHANGED_MEMBER(hat_decay_changed);
protected:
void device_add_mconfig(machine_config &config) override ATTR_COLD;
@ -222,6 +221,7 @@ private:
void update_mux_drum_pitch();
void update_snare_pitch();
void update_tom_pitch();
void update_hat_decay();
// Mux drums.
required_ioport m_mux_tuning_trimmer;
@ -233,6 +233,8 @@ private:
required_device<timer_device> m_hat_trigger_timer; // U37B (LM556).
required_device<va_rc_eg_device> m_hat_eg;
required_device<va_vca_device> m_hat_vca; // CEM3360 (U91B).
bool m_hat_open = false;
bool m_hat_triggered = false;
std::array<bool, NUM_MUX_VOICES> m_mux_counting = { false, false, false, false, false, false, false, false };
std::array<u16, NUM_MUX_VOICES> m_mux_counters = { 0, 0, 0, 0, 0, 0, 0, 0 };
@ -266,7 +268,8 @@ private:
required_ioport_array<NUM_MIXER_CHANNELS> m_volume;
required_ioport_array<NUM_MIXER_CHANNELS> m_pan;
required_ioport m_master_volume;
required_device_array<filter_rc_device, NUM_MIXER_CHANNELS> m_voice_hpf;
required_device_array<filter_rc_device, NUM_MIXER_CHANNELS - 1> m_voice_hpf;
required_device<filter_biquad_device> m_click_bpf;
required_device<mixer_device> m_left_mixer; // 4558 op-amp (U1A).
required_device<mixer_device> m_right_mixer; // 4558 op-amp (U1B).
required_device<speaker_device> m_left_out; // 4558 op-amp (U2A).
@ -289,14 +292,15 @@ private:
RES_R(0), // low conga
RES_K(5.1), // cowbell
RES_K(2.4), // clap
RES_K(0), // click
RES_R(0), // click
};
static constexpr const float MIXER_R_FEEDBACK = RES_K(33);
static constexpr const float MIXER_R_BEEP = RES_K(510);
static constexpr const float MIXER_R_BEEP = RES_K(510); // Same value for left and right.
static constexpr const float OUTPUT_R_INPUT = RES_K(10); // Input resistor of output stage opamp.
static constexpr const float OUTPUT_R_FEEDBACK = RES_K(10);
static constexpr const float OUTPUT_C_FEEDBACK = CAP_P(1000);
static constexpr const float R_ON_CD4053 = RES_R(125); // Typical Ron resistance for 15V supply (-7.5V / 7.5V).
static constexpr const float VPLUS = 15; // Volts.
static constexpr const float VCC = 5; // Volts.
@ -311,7 +315,7 @@ private:
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.
static constexpr const float HAT_EG2CV_SCALER = 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.
@ -349,7 +353,7 @@ 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_hat_decay_pot(*this, ":pot_hihat_decay")
, 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)
@ -376,6 +380,7 @@ linndrum_audio_device::linndrum_audio_device(const machine_config &mconfig, cons
, m_pan(*this, ":pot_pan_%u", 1)
, m_master_volume(*this, ":pot_volume")
, m_voice_hpf(*this, "voice_hpf_%u", 1)
, m_click_bpf(*this, "click_bpf")
, m_left_mixer(*this, "lmixer")
, m_right_mixer(*this, "rmixer")
, m_left_out(*this, "lspeaker")
@ -397,28 +402,18 @@ void linndrum_audio_device::mux_drum_w(int voice, u8 data, bool is_strobe)
// remain unchanged, or it will get attenuated by a voltage divider.
// The MUX is always configured in the "no attenuation" mode for the clap
// and cowbell voices.
static constexpr const float ATTENUATION = RES_VOLTAGE_DIVIDER(RES_K(10), RES_K(3.3)); // R60, R62.
static constexpr const float ATTENUATION = RES_VOLTAGE_DIVIDER(RES_K(10), RES_K(3.3) + R_ON_CD4053); // R60, R62.
const bool attenuate = !BIT(data, 1) && voice != MV_CLAP && voice != MV_COWBELL;
m_mux_volume[voice]->set_gain(attenuate ? ATTENUATION : 1);
if (voice == MV_HAT)
{
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);
m_hat_open = BIT(data, 2);
m_hat_triggered = is_strobe;
update_hat_decay();
if (is_strobe)
{
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_HAT_EG, "Hat EG write: %x, %d.\n", data, is_strobe);
}
LOGMASKED(LOG_STROBES, "Strobed mux drum %s: %02x (gain: %f)\n",
@ -450,6 +445,10 @@ void linndrum_audio_device::snare_w(u8 data)
// the DAC. Bits D2 and D3 from the voice data bus (latched by U42, 74LS74)
// control the voltage at the end of R72 and R71.
// While there is a capacitor (C29, 0.01 UF) attached to the DAC's Iref
// input, it is too small to have a musical effect. Changes in current will
// stablizie within 0.1 ms, and such changes only happen at voice trigger.
static constexpr const float R0 = RES_K(3.3); // R70.
static constexpr const float R1 = RES_K(380); // R69.
static constexpr const float R2 = RES_K(22); // R72.
@ -514,9 +513,7 @@ void linndrum_audio_device::tom_w(u8 data)
void linndrum_audio_device::strobe_click_w(u8 /*data*/)
{
static constexpr const float R10 = RES_K(100);
static constexpr const float C12 = CAP_U(0.01);
m_click_timer->adjust(PERIOD_OF_555_MONOSTABLE(R10, C12));
m_click_timer->adjust(PERIOD_OF_555_MONOSTABLE(RES_K(100), CAP_U(0.01))); // R10, C12.
m_click->level_w(1);
LOGMASKED(LOG_STROBES, "Strobed click.\n");
}
@ -553,6 +550,11 @@ DECLARE_INPUT_CHANGED_MEMBER(linndrum_audio_device::tom_tuning_changed)
update_tom_pitch();
}
DECLARE_INPUT_CHANGED_MEMBER(linndrum_audio_device::hat_decay_changed)
{
update_hat_decay();
}
void linndrum_audio_device::device_add_mconfig(machine_config &config)
{
// *** Mux drums section.
@ -578,7 +580,7 @@ void linndrum_audio_device::device_add_mconfig(machine_config &config)
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);
m_hat_eg->add_route(0, m_hat_vca, HAT_EG2CV_SCALER);
// *** Snare / sidestick section.
@ -619,7 +621,6 @@ void linndrum_audio_device::device_add_mconfig(machine_config &config)
}
// *** Mixer.
const std::array<device_sound_interface *, NUM_MIXER_CHANNELS> voice_outputs =
{
m_mux_volume[MV_BASS],
@ -642,7 +643,9 @@ void linndrum_audio_device::device_add_mconfig(machine_config &config)
MIXER(config, m_left_mixer); // U1A
MIXER(config, m_right_mixer); // U1B
for (int i = 0; i < voice_outputs.size(); ++i)
assert(voice_outputs.size() - 1 == MIX_CLICK);
for (int i = 0; i < voice_outputs.size() - 1; ++i) // Skip "click".
{
// The filter and gain will be configured in update_volume_and_pan().
FILTER_RC(config, m_voice_hpf[i]);
@ -651,6 +654,11 @@ void linndrum_audio_device::device_add_mconfig(machine_config &config)
m_voice_hpf[i]->add_route(0, m_right_mixer, 1.0);
}
FILTER_BIQUAD(config, m_click_bpf); // Configured in update_volume_and_pan().
voice_outputs[MIX_CLICK]->add_route(0, m_click_bpf, 1.0);
m_click_bpf->add_route(0, m_left_mixer, 1.0);
m_click_bpf->add_route(0, m_right_mixer, 1.0);
auto &beep_hpf = FILTER_RC(config, "beep_hpf");
const float rc_r = RES_2_PARALLEL(MIXER_R_BEEP, MIXER_R_BEEP);
beep_hpf.set_rc(filter_rc_device::HIGHPASS, rc_r, 0, 0, CAP_U(0.1)); // C17. Cutoff: ~6.24 Hz.
@ -677,6 +685,8 @@ void linndrum_audio_device::device_add_mconfig(machine_config &config)
void linndrum_audio_device::device_start()
{
save_item(NAME(m_hat_open));
save_item(NAME(m_hat_triggered));
save_item(NAME(m_mux_counting));
save_item(NAME(m_mux_counters));
save_item(NAME(m_snare_counting));
@ -696,6 +706,7 @@ void linndrum_audio_device::device_reset()
update_mux_drum_pitch();
update_snare_pitch();
update_tom_pitch();
update_hat_decay();
}
void linndrum_audio_device::write_dac(dac76_device &dac, u8 sample)
@ -749,7 +760,8 @@ float linndrum_audio_device::get_snare_tom_pitch_cv(float v_tune)
TIMER_DEVICE_CALLBACK_MEMBER(linndrum_audio_device::hat_trigger_timer_tick)
{
m_hat_eg->set_target_v(0);
m_hat_triggered = false;
update_hat_decay();
LOGMASKED(LOG_HAT_EG, "Hat EG started decay.\n");
}
@ -851,7 +863,8 @@ void linndrum_audio_device::update_volume_and_pan(int channel)
// Since we are interested in voltage gain, rather than actual voltage,
// use 1V as the voice's output.
static constexpr const float V_VOICE = 1;
// DC-blocking capacitor. Same value for all voice outputs.
// DC-blocking capacitor. Same value for all voice outputs except for the
// "click" and "beep" sounds.
static constexpr const float C_VOICE = CAP_U(10);
const s32 volume = m_volume[channel]->read();
@ -864,8 +877,9 @@ void linndrum_audio_device::update_volume_and_pan(int channel)
const float r_right_gnd = R1 + RES_2_PARALLEL(r_pan_right, R3);
const float r_left_gnd = R2 + RES_2_PARALLEL(r_pan_left, R4);
// Resistance to ground as seen from the voice's output.
const float r_voice_gnd = r0 + RES_3_PARALLEL(r_vol_bottom, r_left_gnd, r_right_gnd);
// Resistance to ground as seen from the volume pot's wiper and the voice's output.
const float r_wiper_gnd = RES_3_PARALLEL(r_vol_bottom, r_left_gnd, r_right_gnd);
const float r_voice_gnd = r0 + r_wiper_gnd;
float gain_left = 0;
float gain_right = 0;
@ -891,11 +905,83 @@ void linndrum_audio_device::update_volume_and_pan(int channel)
// Using -gain_*, because the summing op-amps are inverting.
m_left_mixer->set_input_gain(channel, -gain_left);
m_right_mixer->set_input_gain(channel, -gain_right);
m_voice_hpf[channel]->filter_rc_set_RC(filter_rc_device::HIGHPASS, r_voice_gnd, 0, 0, C_VOICE);
LOGMASKED(LOG_MIX, "Gain update for %s - left: %f, right: %f, HPF cutoff: %.2f Hz\n",
MIXER_CHANNEL_NAMES[channel], gain_left, gain_right,
1.0F / (2 * float(M_PI) * r_voice_gnd * C_VOICE));
if (channel == MIX_CLICK)
{
// Compared to the other voices, the click uses a different value for
// the DC blocking capacitor. Furthermore, there is a capacitor to ground
// at the volume pot wiper. The resulting filter acts as an HPF (cutoff:
// ~1.15 KHz) when the click volume is at max, and becomes a BPF whose
// characteristics change as the volume decreases.
// Capacitor at the output of the 556 timer.
static constexpr const float C_CLICK_DCBLOCK = CAP_U(0.01); // C37
// Capacitor to ground, at the wiper of the "click" volume fader.
static constexpr const float C_CLICK_WIPER = CAP_U(0.047); // No designation.
// All "click" filter configurations result in singificant attenutation
// (< 0.2x), which makes the click very quiet. The filter characteristics
// (gain and Fc) have been verified with simulations. So it is possible
// there is an error in the schematic. Different capacitor values, or a
// supply of 15V (instead of the stated 5V) for the 556 timer generating
// the click could explain this.
// For now, scale by some number to match the volume of other voices.
static constexpr const float CLICK_GAIN_CORRECTION = 5;
float fc = 0;
float q = 1;
float gain = 1;
if (volume >= 100)
{
// HPF transfer function: H(s) = (g * s) / (s + w) where
// g = C1 / (C1 + C2)
// w = 1 / (R * (C1 + C2))
fc = 1.0F / (2 * float(M_PI) * r_voice_gnd * (C_CLICK_DCBLOCK + C_CLICK_WIPER));
gain = C_CLICK_DCBLOCK / (C_CLICK_DCBLOCK + C_CLICK_WIPER);
m_click_bpf->modify(filter_biquad_device::biquad_type::HIGHPASS1P1Z, fc, 1, gain * CLICK_GAIN_CORRECTION);
}
else if (volume > 0)
{
// BPF transfer function: H(s) = (A * s) / (s ^ 2 + B * s + C)
// Where:
// A = 1 / (R1 * C2)
// B = (C1 * R1 + C1 * R2 + C2 * R2) / (R1 * R2 * C1 * C2)
// C = 1 / (R1 * R2 * C1 * C2).
// From the standard transfer function for BPFs, we have:
// A = gain * (w / Q)
// B = w / Q
// C = w ^ 2
// The calculations of Fc, Q and gain below are derived from the
// equations above, with some algebra.
const float x = sqrtf(r0 * r_wiper_gnd * C_CLICK_DCBLOCK * C_CLICK_WIPER);
const float y = C_CLICK_DCBLOCK * r0 + C_CLICK_DCBLOCK * r_wiper_gnd + C_CLICK_WIPER * r_wiper_gnd;
fc = 1.0F / (2 * float(M_PI) * x);
q = x / y;
gain = r_wiper_gnd * C_CLICK_DCBLOCK / y;
// The transfer function above includes the effect of the volume
// potentiometer. But `gain_left` and `gain_right` already incorporate
// that effect. So it needs to be undone from the filter's gain.
gain *= 1.0F / RES_VOLTAGE_DIVIDER(r0, r_wiper_gnd);
m_click_bpf->modify(filter_biquad_device::biquad_type::BANDPASS, fc, q, gain * CLICK_GAIN_CORRECTION);
}
// Else, if the volume is 0, don't change the BPF's configuration to avoid divisions by 0.
LOGMASKED(LOG_MIX, "Gain update for %s - left: %f, right: %f, %s cutoff: %.2f Hz, Q: %.3f, Gain: %.3f\n",
MIXER_CHANNEL_NAMES[MIX_CLICK], gain_left, gain_right,
(volume >= 100) ? "HPF" : "BPF", fc, q, gain);
}
else
{
// The rest of the voices just have a DC-blocking filter. Its exact cutoff
// will depend on the volume and pan settings, but it won't be audible.
m_voice_hpf[channel]->filter_rc_set_RC(filter_rc_device::HIGHPASS, r_voice_gnd, 0, 0, C_VOICE);
LOGMASKED(LOG_MIX, "Gain update for %s - left: %f, right: %f, HPF cutoff: %.2f Hz\n",
MIXER_CHANNEL_NAMES[channel], gain_left, gain_right,
1.0F / (2 * float(M_PI) * r_voice_gnd * C_VOICE));
}
}
void linndrum_audio_device::update_master_volume()
@ -996,6 +1082,31 @@ void linndrum_audio_device::update_tom_pitch()
knob_index, knob_value, cv, freq);
}
void linndrum_audio_device::update_hat_decay()
{
// When the hat is configed as "open", the EG will discharge through a 1M
// resistor. When the hat is "closed", a CD4053 will add a parallel
// discharge path through the "hihat decay" pot.
float eg_r = HAT_R33;
if (!m_hat_open)
{
const float r_decay_pot = HAT_DECAY_POT_R_MAX * m_hat_decay_pot->read() / 100.0F;
eg_r = RES_2_PARALLEL(HAT_R33, HAT_R34 + R_ON_CD4053 + r_decay_pot);
}
m_hat_eg->set_r(eg_r);
// When the hat is strobed, it will trigger a 556 (U37A) timer in monostable
// mode (see mux_drum_w()). The timer's output pin is connected to the EG
// capacitor via a diode, and will maintain that capacitor at VCC until the
// timer expires. At that point, the EG capacitor will discharge through eg_r.
if (m_hat_triggered)
m_hat_eg->set_instant_v(VCC);
else
m_hat_eg->set_target_v(0);
LOGMASKED(LOG_HAT_EG, "Hat decay. Open: %d, EG R: %f\n", m_hat_open, eg_r);
}
namespace {
constexpr const char MAINCPU_TAG[] = "z80";
@ -1443,8 +1554,8 @@ INPUT_PORTS_START(linndrum)
PORT_ADJUSTER(52, "HI CONGAS TUNING") PORT_CHANGED_MEMBER(AUDIO_TAG, FUNC(linndrum_audio_device::tom_tuning_changed), 0)
PORT_START("pot_tuning_6")
PORT_ADJUSTER(40, "LO CONGAS TUNING") PORT_CHANGED_MEMBER(AUDIO_TAG, FUNC(linndrum_audio_device::tom_tuning_changed), 0)
PORT_START("pot_tuning_7")
PORT_ADJUSTER(50, "HIHAT DECAY")
PORT_START("pot_hihat_decay")
PORT_ADJUSTER(50, "HIHAT DECAY") PORT_CHANGED_MEMBER(AUDIO_TAG, FUNC(linndrum_audio_device::hat_decay_changed), 0)
PORT_START("pot_pan_1")
PORT_ADJUSTER(50, "BASS PAN") PORT_CHANGED_MEMBER(AUDIO_TAG, FUNC(linndrum_audio_device::mix_changed), MIX_BASS)