diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/devices/sound/flt_biquad.cpp | 8 | ||||
-rw-r--r-- | src/devices/sound/flt_biquad.h | 3 | ||||
-rw-r--r-- | src/mame/layout/linn_linndrum.lay | 9 | ||||
-rw-r--r-- | src/mame/linn/linndrum.cpp | 201 |
4 files changed, 172 insertions, 49 deletions
diff --git a/src/devices/sound/flt_biquad.cpp b/src/devices/sound/flt_biquad.cpp index de6bfe63462..e56c355ac95 100644 --- a/src/devices/sound/flt_biquad.cpp +++ b/src/devices/sound/flt_biquad.cpp @@ -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; diff --git a/src/devices/sound/flt_biquad.h b/src/devices/sound/flt_biquad.h index ad58a91de79..702e42f9dac 100644 --- a/src/devices/sound/flt_biquad.h +++ b/src/devices/sound/flt_biquad.h @@ -25,7 +25,8 @@ public: PEAK, LOWSHELF, HIGHSHELF, - RAWPARAMS + RAWPARAMS, + HIGHPASS1P1Z }; struct biquad_params diff --git a/src/mame/layout/linn_linndrum.lay b/src/mame/layout/linn_linndrum.lay index ede9314f83e..b60391d9ff4 100644 --- a/src/mame/layout/linn_linndrum.lay +++ b/src/mame/layout/linn_linndrum.lay @@ -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 diff --git a/src/mame/linn/linndrum.cpp b/src/mame/linn/linndrum.cpp index 1bfd8566775..edfb52c15e2 100644 --- a/src/mame/linn/linndrum.cpp +++ b/src/mame/linn/linndrum.cpp @@ -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) |