The diode-bridge compressor lineage (Neve 2254 / 33609): gain reduction by pushing control current through a bridge of four diodes, bracketed by two coloured transformers. More...
#include <pulp/signal/ballistics_filter.hpp>#include <pulp/signal/dc_blocker.hpp>#include <pulp/signal/denormal.hpp>#include <pulp/signal/dynamics_core.hpp>#include <pulp/signal/dynamics_contract.hpp>#include <pulp/signal/junction.hpp>#include <pulp/signal/tpt_filter.hpp>#include <pulp/signal/units.hpp>#include <algorithm>#include <cmath>#include <cstdint>Go to the source code of this file.
Classes | |
| class | pulp::signal::DiodeBridgeGainT< SampleType > |
| The diode bridge as a gain element: an exact 1/(1 + x) divider plus the audio-path curvature that divider's diodes actually impose. More... | |
| class | pulp::signal::TransformerBracketT< SampleType > |
| One input or output transformer: band-limit, mild saturation, DC-blocked. More... | |
| class | pulp::signal::DiodeBridgeCompressorT< SampleType > |
| Two transformer brackets around one diode bridge, with a feedback sidechain. More... | |
Namespaces | |
| namespace | pulp |
| namespace | pulp::signal |
Typedefs | |
| using | pulp::signal::DiodeBridgeGain = DiodeBridgeGainT<float> |
| using | pulp::signal::DiodeBridgeGain64 = DiodeBridgeGainT<double> |
| using | pulp::signal::TransformerBracket = TransformerBracketT<float> |
| using | pulp::signal::TransformerBracket64 = TransformerBracketT<double> |
| using | pulp::signal::DiodeBridgeCompressor = DiodeBridgeCompressorT<float> |
| using | pulp::signal::DiodeBridgeCompressor64 = DiodeBridgeCompressorT<double> |
The diode-bridge compressor lineage (Neve 2254 / 33609): gain reduction by pushing control current through a bridge of four diodes, bracketed by two coloured transformers.
The thing that separates this lineage from the VCA and FET ones is WHERE THE COLOUR COMES FROM. In a VCA or FET design the gain element is asked to be clean and the character is argued about in the ballistics; here the audio physically passes THROUGH a nonlinear variable resistor, so the gain element itself generates harmonics, and it generates MORE of them the harder it is compressing. Everything in this header exists to make that mechanism measurable rather than merely described.
DiodeBridgeGainT is the variable attenuator, TransformerBracketT is one input/output transformer, and DiodeBridgeCompressorT is the complete device: two brackets around one bridge, with a feedback sidechain composed from BallisticsFilterT. They ship together because the compressor is the only sensible consumer of the other two and all three share one calibration story — splitting them would scatter one set of decisions across three files, exactly as distortion.hpp argues for its clipper family.
A diode's small-signal dynamic resistance is r_d = n·V_T/(I + Is) — the reciprocal of the slope of Shockley's law, so resistance falls as bias current rises. Put that variable resistance in a divider against a fixed series resistance Rs and the audio gain is
x is the whole control law, and it is DIMENSIONLESS (series law 7): Rs, n and V_T fold into it and never appear separately, which is why the worked table in the spec is independent of all three. x ≥ 0 always, so g ∈ (0, 1] — the gain element can never boost, and that is the structural fact the whole worst-case-gain bound rests on.
The exponential is not written here. junction.hpp owns the thermal voltage, the overflow-safe exponent, and the closed-form conductance; this header calls JunctionPair::conductance(knee_voltage(I)) to get 1/r_d and therefore x. There is exactly one junction implementation in this library and this module is a consumer of it, not a second author of it.
One diode would rectify the control current straight into the audio path as a thump. Four in a bridge, two legs of two, driven differentially, put the control current at the audio tap as a balanced COMMON-MODE term that cancels to first order, while the audio — injected differentially — sees a matched pair of variable resistances. Matching sets the residual control feedthrough; this model assumes ideal matching, so feedthrough is zero and the only surviving artefact is the intended audio-path curvature. The audio therefore sees an ANTIPARALLEL MATCHED PAIR, which is why the curvature below is odd-symmetric (third harmonic) rather than rectifying.
Neve kept the signal tiny across the bridge — attenuate going in, recover with a large makeup afterwards — so the diodes sit in their near-linear region and the gain law stays accurate. The price is noise: that makeup stage amplifies the bridge's own thermal noise along with its colour. Here the trade is explicit and tunable instead of fixed: character sets how hard the bridge is driven internally, DECOUPLED from the level path, so colour and loudness are independent controls (series law 3 — the macro is the surface, not an internal level).
First-order antiderivative antialiasing (ADAA), not oversampling, on both memoryless shapers. The curvature is mild by construction (the operating amplitude is small by design), and ADAA reports zero integer latency, which is what lets latency_samples() be 0. First-order ADAA carries a half-sample group delay; that is sub-sample colouration, not integer latency, and is not reported. set_adaa(false) exists so the suite can measure the alias reduction against the naive evaluation, not as a shipped mode. References: Parker, Zavalishin & Le Bivic, DAFx-16; Bilbao, Esqueda, Parker & Välimäki, IEEE Signal Processing Letters 24(7):1049–1053, 2017.
No randomness anywhere in this file — no dither, no analog drift — so renders are bit-identical per (params, input) by construction. Stated rather than assumed, because it is easy to suppose a "vintage" model needs drift. If a later revision wants it, it must use seeded Xorshift32/OuWalkT from rng.hpp, seeded at reset(), never macro-exposed.
The model implements the publicly documented topology and behaviour: four-diode bridge, common-mode cancellation of the control current, large input attenuation ahead of the bridge with makeup after, feedback detection, transformer brackets, slow-attack low-frequency de-sensitisation, and a smooth-at-rest to overdriven-when-driven slope. Every time constant, curvature, and saturation coefficient below is a [design parameter] with a default and range chosen for that behavior.
RT contract: prepare() sets sample rate and coefficients; nothing in this header allocates, at any point, including prepare() — all state is POD and fixed-size, and zero-init is a valid fresh instance. set_*, process() and reset() never allocate, never lock, never perform I/O, and are safe per sample on the audio thread. process() costs a bounded handful of multiplies plus, on a control change, at most one exp/pow; there is no iteration and no data-dependent work.
References: Shockley, "The Theory of p-n Junctions in Semiconductors and p-n Junction Transistors", Bell System Technical Journal 28(3):435–489, 1949 — the exponential law and the dynamic resistance r_d = n·V_T/I. Giannoulis, Massberg & Reiss, "Digital Dynamic Range Compressor Design — A Tutorial and Analysis", JAES 60(6):399–408, 2012 — the soft-knee gain computer, used as the standard formulation it is. Parker/Zavalishin/Le Bivic and Bilbao/Esqueda/Parker/Välimäki as above for ADAA.