Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
bridged_t_resonator.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <algorithm>
6#include <cmath>
7
8namespace pulp::signal {
9
22 double c41 = 15e-9;
23 double c42 = 15e-9;
24 double r161 = 1.0e6;
25 double r165 = 47.0e3;
26 double r166 = 6.8e3;
27 double r167 = 1.0e6;
28 double r170 = 470.0e3;
29};
30
37struct Q43Leakage {
38 double alpha = 14.3150;
39 double v0 = -0.5560;
40 double m = 1.4765e-5;
41};
42
55inline double q43_collector_current(double vcomm, const Q43Leakage& leak) noexcept {
56 const double e = -leak.alpha * (vcomm - leak.v0);
57 const double scale = leak.m / leak.alpha;
58 if (e > 30.0) return -e * scale; // log1p(exp(e)) == e here
59 if (e < -30.0) return -std::exp(e) * scale; // log1p(x) == x here
60 return -std::log1p(std::exp(e)) * scale;
61}
62
88inline double q43_branch_resistance(double vcomm,
89 const BridgedTComponents& c,
90 const Q43Leakage& leak,
91 double guard_v = -0.01) noexcept {
92 const double series = c.r165 + c.r166;
93 if (vcomm > guard_v) return series;
94 const double ic = q43_collector_current(vcomm, leak);
95 const double den = vcomm + c.r165 * ic;
96 if (den == 0.0) return series;
97 return std::clamp(series * vcomm / den, c.r166, series);
98}
99
103inline double bridged_t_shunt_resistance(double r_e,
104 const BridgedTComponents& c) noexcept {
105 return 1.0 / (1.0 / c.r161 + 1.0 / c.r170 + 1.0 / r_e);
106}
107
109inline double bridged_t_center_frequency(double r_eff,
110 const BridgedTComponents& c) noexcept {
111 constexpr double two_pi = 6.28318530717958647692;
112 return 1.0 / (two_pi * std::sqrt(r_eff * c.r167 * c.c41 * c.c42));
113}
114
144 double target_hz) noexcept {
145 constexpr double kMinFarads = 1.0e-10;
146 constexpr double kMaxFarads = 1.0e-6;
147 if (!(target_hz > 0.0)) return c;
148 const double r_rest = c.r165 + c.r166;
149 const double current =
151 if (!(current > 0.0)) return c;
152
153 BridgedTComponents out = c;
154 const double scale = current / target_hz;
155 out.c41 = std::clamp(c.c41 * scale, kMinFarads, kMaxFarads);
156 out.c42 = std::clamp(c.c42 * scale, kMinFarads, kMaxFarads);
157 return out;
158}
159
160inline double bridged_t_q(double r_eff, const BridgedTComponents& c) noexcept {
161 const double alpha2 = r_eff * c.r167 * c.c41 * c.c42;
162 const double alpha1 = r_eff * (c.c41 + c.c42);
163 return std::sqrt(alpha2) / alpha1;
164}
165
198public:
199 struct Output {
200 double vbt = 0.0;
201 double vcomm = 0.0;
202 };
203
204 void set_components(const BridgedTComponents& c) noexcept { comps_ = c; }
205 const BridgedTComponents& components() const noexcept { return comps_; }
206
207 void set_leakage(const Q43Leakage& l) noexcept { leak_ = l; }
208 const Q43Leakage& leakage() const noexcept { return leak_; }
209
213 void set_sigh_enabled(bool enabled) noexcept { sigh_enabled_ = enabled; }
214 bool sigh_enabled() const noexcept { return sigh_enabled_; }
215
220 void set_rail_voltage(double v) noexcept { rail_v_ = v; }
221
222 void prepare(double sample_rate) noexcept {
223 h_ = 1.0 / sample_rate;
224 reset();
225 }
226
231 void reset() noexcept {
232 x1_ = 0.0;
233 x2_ = 0.0;
234 vcomm_delayed_ = 0.0;
235 r_e_ = comps_.r165 + comps_.r166;
236 }
237
242 void set_attack_shunt(bool active) noexcept { attack_shunt_ = active; }
243
245 Output process(double v_plus, double v_fb, double v_rp) noexcept {
246 // Shift logic reads a delayed Vcomm, which is what breaks the
247 // Vcomm -> iC -> R_eff -> Vcomm loop into something computable.
248 if (attack_shunt_) {
249 r_e_ = comps_.r166;
250 } else if (!sigh_enabled_) {
251 r_e_ = comps_.r165 + comps_.r166;
252 } else {
253 r_e_ = q43_branch_resistance(vcomm_delayed_, comps_, leak_);
254 }
255
256 const double g = 1.0 / bridged_t_shunt_resistance(r_e_, comps_);
257
258 // States are the capacitor voltages: x1 = v_C41 = V+ - Vcomm,
259 // x2 = v_C42 = Vbt - Vcomm. Only a21 moves with R_eff.
260 const double inv_r167_c41 = 1.0 / (comps_.r167 * comps_.c41);
261 const double a11 = -inv_r167_c41;
262 const double a12 = inv_r167_c41;
263 const double a21 = (1.0 / comps_.r167 - g) / comps_.c42;
264 const double a22 = -1.0 / (comps_.r167 * comps_.c42);
265 const double u2 = (g * v_plus - v_rp / comps_.r161 - v_fb / comps_.r170) / comps_.c42;
266
267 const double hh = 0.5 * h_;
268 const double m11 = 1.0 - hh * a11;
269 const double m12 = -hh * a12;
270 const double m21 = -hh * a21;
271 const double m22 = 1.0 - hh * a22;
272 const double r1 = (1.0 + hh * a11) * x1_ + hh * a12 * x2_;
273 const double r2 = hh * a21 * x1_ + (1.0 + hh * a22) * x2_ + h_ * u2;
274 const double det = m11 * m22 - m12 * m21;
275
276 x1_ = (m22 * r1 - m12 * r2) / det;
277 x2_ = (m11 * r2 - m21 * r1) / det;
278
279 Output out;
280 out.vcomm = v_plus - x1_;
281 out.vbt = std::clamp(out.vcomm + x2_, -rail_v_, rail_v_);
282 vcomm_delayed_ = out.vcomm;
283 return out;
284 }
285
290 void snap_denormals() noexcept {
291 x1_ = snap_to_zero(x1_);
292 x2_ = snap_to_zero(x2_);
293 vcomm_delayed_ = snap_to_zero(vcomm_delayed_);
294 }
295
297 double branch_resistance() const noexcept { return r_e_; }
298
301 double center_frequency_hz() const noexcept {
302 return bridged_t_center_frequency(bridged_t_shunt_resistance(r_e_, comps_), comps_);
303 }
304
305private:
306 BridgedTComponents comps_{};
307 Q43Leakage leak_{};
308 double h_ = 1.0 / 48000.0;
309 double rail_v_ = 15.0;
310 bool sigh_enabled_ = true;
311 bool attack_shunt_ = false;
312
313 double x1_ = 0.0;
314 double x2_ = 0.0;
315 double vcomm_delayed_ = 0.0;
316 double r_e_ = 47.0e3 + 6.8e3;
317};
318
319} // namespace pulp::signal
The TR-808 bass drum's bridged-T network: one second-order system with three inputs (pulse shaper,...
Definition bridged_t_resonator.hpp:197
const Q43Leakage & leakage() const noexcept
Definition bridged_t_resonator.hpp:208
void set_components(const BridgedTComponents &c) noexcept
Definition bridged_t_resonator.hpp:204
void set_sigh_enabled(bool enabled) noexcept
Cut the R161 leakage path, which is the pitch sigh's only mechanism.
Definition bridged_t_resonator.hpp:213
void prepare(double sample_rate) noexcept
Definition bridged_t_resonator.hpp:222
bool sigh_enabled() const noexcept
Definition bridged_t_resonator.hpp:214
double center_frequency_hz() const noexcept
Centre frequency the network is tuned to right now.
Definition bridged_t_resonator.hpp:301
Output process(double v_plus, double v_fb, double v_rp) noexcept
Advance one sample. Inputs are voltages at the three injection points.
Definition bridged_t_resonator.hpp:245
const BridgedTComponents & components() const noexcept
Definition bridged_t_resonator.hpp:205
void snap_denormals() noexcept
Snap a settled ring to exact zero so a silent voice cannot park denormals in the recursion.
Definition bridged_t_resonator.hpp:290
void set_attack_shunt(bool active) noexcept
Ground Q43's collector, shorting out R165.
Definition bridged_t_resonator.hpp:242
void set_rail_voltage(double v) noexcept
Supply rail, which the op-amp saturates against.
Definition bridged_t_resonator.hpp:220
double branch_resistance() const noexcept
Branch resistance in force on the most recent sample.
Definition bridged_t_resonator.hpp:297
void set_leakage(const Q43Leakage &l) noexcept
Definition bridged_t_resonator.hpp:207
void reset() noexcept
Clear the ring.
Definition bridged_t_resonator.hpp:231
Definition additive_bank.hpp:218
double bridged_t_q(double r_eff, const BridgedTComponents &c) noexcept
Definition bridged_t_resonator.hpp:160
@ c
Definition spectrum_trace.hpp:35
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
double bridged_t_center_frequency(double r_eff, const BridgedTComponents &c) noexcept
Centre frequency of the bridged-T for a given total shunt resistance.
Definition bridged_t_resonator.hpp:109
BridgedTComponents bridged_t_retuned_to(const BridgedTComponents &c, double target_hz) noexcept
Quality factor of the bridged-T network alone, with its three inputs grounded.
Definition bridged_t_resonator.hpp:143
@ e
Definition formant_filter_bank.hpp:20
double q43_branch_resistance(double vcomm, const BridgedTComponents &c, const Q43Leakage &leak, double guard_v=-0.01) noexcept
Resistance of the Vcomm-to-ground branch through R166, Q43's collector and R165 (DAFx-14 Eq.
Definition bridged_t_resonator.hpp:88
double q43_collector_current(double vcomm, const Q43Leakage &leak) noexcept
Q43's collector current for a given Vcomm (DAFx-14 Eq.
Definition bridged_t_resonator.hpp:55
double bridged_t_shunt_resistance(double r_e, const BridgedTComponents &c) noexcept
Total Vcomm-to-ground resistance seen by the network: R161 || R_e || R170.
Definition bridged_t_resonator.hpp:103
Component values of the TR-808 bass drum's bridged-T network, in SI units (ohms, farads).
Definition bridged_t_resonator.hpp:21
double r170
feedback-buffer injection
Definition bridged_t_resonator.hpp:28
double r165
shorted out while the attack envelope grounds Q43
Definition bridged_t_resonator.hpp:25
double c41
capacitive arm, op-amp inverting input side
Definition bridged_t_resonator.hpp:22
double r167
the resistive bridge
Definition bridged_t_resonator.hpp:27
double c42
capacitive arm, op-amp output side
Definition bridged_t_resonator.hpp:23
double r166
Vcomm-to-collector leg of the branch.
Definition bridged_t_resonator.hpp:26
double r161
retrigger-pulse injection; also Q43's leakage path
Definition bridged_t_resonator.hpp:24
Definition bridged_t_resonator.hpp:199
double vcomm
junction of R166, C41, C42 – drives Q43
Definition bridged_t_resonator.hpp:201
double vbt
op-amp output – the bass drum signal
Definition bridged_t_resonator.hpp:200
Softplus fit of Q43's collector current against Vcomm (DAFx-14 Eq.
Definition bridged_t_resonator.hpp:37
double v0
voltage offset of the knee
Definition bridged_t_resonator.hpp:39
double m
slope of the response below the knee
Definition bridged_t_resonator.hpp:40
double alpha
width of the transition region
Definition bridged_t_resonator.hpp:38