Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::signal::DiodeBridgeCompressorT< SampleType > Class Template Reference

Two transformer brackets around one diode bridge, with a feedback sidechain. More...

#include <diode_bridge_compressor.hpp>

Public Member Functions

void prepare (double sample_rate)
 Never allocates — every member is fixed-size POD.
void reset ()
void set_threshold_db (double db)
void set_ratio (double r)
void set_knee_db (double w)
void set_attack_ms (double ms)
void set_release_ms (double ms)
void set_makeup_db (double db)
void set_character (double character01)
 0..1 colour depth — the one macro.
void set_mix_percent (double percent)
 Parallel dry/wet as a percentage, matching the catalog table's units.
void set_sc_hpf_hz (double hz)
void set_auto_release (bool on)
 Program-dependent dual-slope release.
void set_feedback (bool on)
 On is the authentic topology: the detector senses the output.
void set_adaa (bool on)
int latency_samples () const noexcept
 Zero.
double gain_reduction_db () const
 Smoothed gain reduction in dB (≤ 0) as of the last processed sample.
GainReduction gain_reduction () const noexcept
double control_drive () const
 The control drive x currently applied to the bridge.
double static_curve_db (double input_db) const
 The static characteristic's gain reduction (≤ 0) for an input level in dB, ignoring the loop and the detector.
double static_curve_feedback_db (double input_db) const
 The gain reduction the FEEDBACK loop settles at for a given input level — the fixed point of GR = static_curve(L_in + GR), solved by iteration because the soft knee makes the closed form piecewise.
SampleType process (SampleType input)
void process_block (SampleType *io, int n)

Static Public Member Functions

static double worst_case_gain ()
 The bound the Forge registry cites (series law 8): makeup ceiling times both brackets' peak gain.

Static Public Attributes

static constexpr double kThresholdDbMin = -40.0
static constexpr double kThresholdDbMax = 6.0
static constexpr double kRatioMin = 1.5
static constexpr double kRatioMax = 20.0
static constexpr double kAttackMsMin = 0.5
static constexpr double kAttackMsMax = 100.0
static constexpr double kReleaseMsMin = 50.0
static constexpr double kReleaseMsMax = 2000.0
static constexpr double kKneeDbMin = 0.0
static constexpr double kKneeDbMax = 18.0
static constexpr double kMakeupDbMax = 24.0
static constexpr double kScHpfHzMin = 20.0
static constexpr double kScHpfHzMax = 400.0
static constexpr double kLimitRatio = 20.0
 Ratio at or above which the limit (infinite-ratio) region engages.
static constexpr double kCtrlSmoothMs = 1.0
 Control-signal smoothing.
static constexpr double kAutoSlowFactor = 6.0
 Slow/fast release ratio of the auto (program-dependent) mode.
static constexpr double kSustainTauMs = 200.0
 Time constant of the "has compression been sustained" detector that blends the two auto-release slopes.
static constexpr double kSustainThresholdDb = 3.0
 How much gain reduction counts as sustained, for that blend.
static constexpr double kEnvelopeEpsilon = 1e-9
 Envelope-to-dB log guard.

Detailed Description

template<typename SampleType = float>
class pulp::signal::DiodeBridgeCompressorT< SampleType >

Two transformer brackets around one diode bridge, with a feedback sidechain.

Feedback is the default, and it changes what the ratio knob means

With feedback on, the detector senses the OUTPUT, so the gain computer's argument already contains the reduction it is about to apply. In the hard region that is a fixed point:

GR = (L_in + GR − thr)·(1/ρ − 1) ⇒ GR = (L_in − thr)·(1/ρ − 1)/(2 − 1/ρ)

At L_in − thr = 6 dB and ρ = 4 the feed-forward answer is −4.5 dB and the feedback answer is −2.57 dB: less reduction, and an EFFECTIVE ratio of 1.75:1 rather than 4:1. That program-dependent softening is the documented signature of this lineage, not a calibration error — the manual ratio control reads "aggressive" only relative to a far gentler realised curve. static_curve_feedback_db() states the closed form so a caller can see the curve it is actually getting.

The loop closes through a ONE-SAMPLE delay in the control path only. The audio path is strictly feed-forward — gain multiply, two memoryless shapers, one-pole filters — so latency_samples() is 0 and the impulse response starts at n = 0.

The worst-case gain bound (series law 8)

The bridge can only attenuate (g ∈ (0, 1]), each bracket has peak gain exactly 1, and the only element that can boost is the makeup control. So

worst_case_gain = 10^(24/20) · 1.0 · 1.0 = 15.849
static double worst_case_gain()
The bound the Forge registry cites (series law 8): makeup ceiling times both brackets' peak gain.
Definition diode_bridge_compressor.hpp:839

bounds the ratio of output peak to input peak at every setting and every input. The suite asserts it directly; it is not an estimate.

Patch notes

  • Stereo-bus glue — ratio 1.5–2, threshold for 2–4 dB of reduction, attack 10–30 ms, auto release, character 0.3. Slow feedback ballistics plus a touch of third-harmonic bridge colour thickens a mix without pumping.
  • Drum-bus attitude — ratio 4, attack 3 ms, release 400 ms, character 0.6. The fast attack grabs transients and β rising with x puts the smack in as the reduction deepens.
  • Vocal levelling — ratio 3, knee 9 dB, sidechain high-pass 120 Hz, feedback on. The soft feedback knee flatters sustained sources.
  • Parallel colour — mix 30–50 %, ratio 10, character 1. Heavy squash and full colour blended under the dry signal: transformer weight as a tone rather than as levelling.
  • Bass control — the sidechain high-pass IS the "does it duck to the kick?" knob. 30–50 Hz to make it respond to low end, 150 Hz to let the low end through while riding the mids.
  • Limit mode — ratio at or above kLimitRatio engages the brickwall region; pair with a fast attack for a ceiling that still carries the bridge and transformer signature.

Out of scope, and why

A true per-sample diode ODE solve (the distortion.hpp treatment) is overkill for a bridge whose operating region is near-linear by design; the closed-form divider plus a mild ADAA'd cubic captures the audible behaviour at a fraction of the cost. Transformer hysteresis (Jiles-Atherton) belongs to the tape tier — a line-level bracket runs far below a tape head's flux. Vari-mu (Fairchild 670 lineage) is a different gain element entirely, grid bias rather than diode current, and a separate catalog entry. Mid/side and true stereo linking are graph concerns; this class is mono, and a linked pair sums detectors at the graph layer.

Member Function Documentation

◆ control_drive()

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::control_drive ( ) const
inline

The control drive x currently applied to the bridge.

◆ gain_reduction()

template<typename SampleType = float>
GainReduction pulp::signal::DiodeBridgeCompressorT< SampleType >::gain_reduction ( ) const
inlinenoexcept

◆ gain_reduction_db()

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::gain_reduction_db ( ) const
inline

Smoothed gain reduction in dB (≤ 0) as of the last processed sample.

◆ latency_samples()

template<typename SampleType = float>
int pulp::signal::DiodeBridgeCompressorT< SampleType >::latency_samples ( ) const
inlinenoexcept

Zero.

The audio path is a gain multiply, two memoryless ADAA shapers and one-pole filters; ADAA's half-sample group delay is sub-sample colouration and the one-poles add phase, not delay (series law 5).

◆ prepare()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::prepare ( double sample_rate)
inline

Never allocates — every member is fixed-size POD.

Kept as prepare() rather than folded into the setters because the filters and followers need the sample rate before any of them is meaningful.

◆ process()

template<typename SampleType = float>
SampleType pulp::signal::DiodeBridgeCompressorT< SampleType >::process ( SampleType input)
inline

◆ process_block()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::process_block ( SampleType * io,
int n )
inline

◆ reset()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::reset ( )
inline

◆ set_adaa()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::set_adaa ( bool on)
inline

◆ set_attack_ms()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::set_attack_ms ( double ms)
inline

◆ set_auto_release()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::set_auto_release ( bool on)
inline

Program-dependent dual-slope release.

◆ set_character()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::set_character ( double character01)
inline

0..1 colour depth — the one macro.

Drives the bridge's internal operating amplitude and both brackets' saturation depth together, which is what makes it one knob rather than three.

◆ set_feedback()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::set_feedback ( bool on)
inline

On is the authentic topology: the detector senses the output.

◆ set_knee_db()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::set_knee_db ( double w)
inline

◆ set_makeup_db()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::set_makeup_db ( double db)
inline

◆ set_mix_percent()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::set_mix_percent ( double percent)
inline

Parallel dry/wet as a percentage, matching the catalog table's units.

◆ set_ratio()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::set_ratio ( double r)
inline

◆ set_release_ms()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::set_release_ms ( double ms)
inline

◆ set_sc_hpf_hz()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::set_sc_hpf_hz ( double hz)
inline

◆ set_threshold_db()

template<typename SampleType = float>
void pulp::signal::DiodeBridgeCompressorT< SampleType >::set_threshold_db ( double db)
inline

◆ static_curve_db()

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::static_curve_db ( double input_db) const
inline

The static characteristic's gain reduction (≤ 0) for an input level in dB, ignoring the loop and the detector.

Exposed so a caller or a test can plot the curve without running audio through it. This lineage's ratio control maps its top of range onto a true limiter, so the resolved slope — not the ratio — is what goes to the shared equation: 1/ρ normally, exactly 0 at and above kLimitRatio.

◆ static_curve_feedback_db()

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::static_curve_feedback_db ( double input_db) const
inline

The gain reduction the FEEDBACK loop settles at for a given input level — the fixed point of GR = static_curve(L_in + GR), solved by iteration because the soft knee makes the closed form piecewise.

The iteration is AVERAGED (gr ← ½(gr + f(gr))), and that is not a refinement — the plain iteration does not converge at the top of the ratio control. f's slope is 1/ρ − 1, which is −0.95 at ρ = 20 and exactly −1 in the limit region, so plain iteration either crawls or oscillates forever between two values without ever narrowing. Averaging maps that slope to (1/ρ)/2 ∈ [0, ½], so the worst case is now the GENTLEST ratio rather than the harshest, and the limit region converges in a single step. Not an RT path — this is a curve query.

◆ worst_case_gain()

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::worst_case_gain ( )
inlinestatic

The bound the Forge registry cites (series law 8): makeup ceiling times both brackets' peak gain.

The bridge contributes at most 1.

Member Data Documentation

◆ kAttackMsMax

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kAttackMsMax = 100.0
staticconstexpr

◆ kAttackMsMin

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kAttackMsMin = 0.5
staticconstexpr

◆ kAutoSlowFactor

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kAutoSlowFactor = 6.0
staticconstexpr

Slow/fast release ratio of the auto (program-dependent) mode.

[design parameter] default 6.0, range 3.0 .. 10.0.

◆ kCtrlSmoothMs

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kCtrlSmoothMs = 1.0
staticconstexpr

Control-signal smoothing.

Short: its job is to keep β(x) continuous per sample so the memoryless-ADAA assumption holds, not to shape the ballistics — those are the detector's. [design parameter] default 1.0 ms, range 0.2 .. 5.0 ms.

◆ kEnvelopeEpsilon

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kEnvelopeEpsilon = 1e-9
staticconstexpr

Envelope-to-dB log guard.

Far below the quietest envelope anyone measures (−180 dB) and far above float epsilon. [design parameter] default 1e-9, range 1e-12 .. 1e-6.

◆ kKneeDbMax

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kKneeDbMax = 18.0
staticconstexpr

◆ kKneeDbMin

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kKneeDbMin = 0.0
staticconstexpr

◆ kLimitRatio

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kLimitRatio = 20.0
staticconstexpr

Ratio at or above which the limit (infinite-ratio) region engages.

Must be at or below kRatioMax for the control's top position to reach it. [design parameter] default 20, range 15 .. 25.

◆ kMakeupDbMax

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kMakeupDbMax = 24.0
staticconstexpr

◆ kRatioMax

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kRatioMax = 20.0
staticconstexpr

◆ kRatioMin

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kRatioMin = 1.5
staticconstexpr

◆ kReleaseMsMax

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kReleaseMsMax = 2000.0
staticconstexpr

◆ kReleaseMsMin

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kReleaseMsMin = 50.0
staticconstexpr

◆ kScHpfHzMax

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kScHpfHzMax = 400.0
staticconstexpr

◆ kScHpfHzMin

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kScHpfHzMin = 20.0
staticconstexpr

◆ kSustainTauMs

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kSustainTauMs = 200.0
staticconstexpr

Time constant of the "has compression been sustained" detector that blends the two auto-release slopes.

[design parameter] default 200 ms, range 50 .. 1000 ms.

◆ kSustainThresholdDb

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kSustainThresholdDb = 3.0
staticconstexpr

How much gain reduction counts as sustained, for that blend.

[design parameter] default 3 dB, range 1 .. 12 dB.

◆ kThresholdDbMax

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kThresholdDbMax = 6.0
staticconstexpr

◆ kThresholdDbMin

template<typename SampleType = float>
double pulp::signal::DiodeBridgeCompressorT< SampleType >::kThresholdDbMin = -40.0
staticconstexpr

The documentation for this class was generated from the following file: