The published compressor equations, once. More...
#include <algorithm>#include <cmath>Go to the source code of this file.
Namespaces | |
| namespace | pulp |
| namespace | pulp::signal |
| namespace | pulp::signal::dynamics |
Functions | |
| double | pulp::signal::dynamics::retain_finite (double candidate, double current) noexcept |
| Control setters retain their last valid value when automation supplies a non-finite sample. | |
| double | pulp::signal::dynamics::amplitude_db (double amplitude, double epsilon) |
| An amplitude → dB, floored by an ADDEND rather than a max(). | |
| double | pulp::signal::dynamics::power_db (double mean_square, double epsilon) |
| A mean square → dB, floored by an addend. | |
| double | pulp::signal::dynamics::one_pole_retain (double tau_seconds, double sample_rate) |
| α = exp(−1/(τ·fs)) — the RETAIN coefficient (see the file doc block). | |
| double | pulp::signal::dynamics::knee_term_db (double over_db, double knee_db, double slope) |
| The knee branch's quadratic term: the dB the characteristic bends away from unity inside the knee, (slope − 1)·(over + W/2)² / (2W). | |
| double | pulp::signal::dynamics::soft_knee_output_db (double input_db, double threshold_db, double knee_db, double ratio) |
| The characteristic in the OUTPUT-LEVEL domain: input dB → output dB. | |
| double | pulp::signal::dynamics::soft_knee_reduction_db (double over_db, double knee_db, double slope) |
| The same characteristic in the GAIN-REDUCTION domain: dB over threshold → reduction in dB, SIGNED and ≤ 0 for a compressing slope. | |
Variables | |
| constexpr double | pulp::signal::dynamics::kLevelEpsilon = 1e-12 |
| Level-conversion floor, guarding log10(0). | |
The published compressor equations, once.
Four lineages in this catalog — the transparent feedforward design, the Blackmer/VCA one, the 1176-style FET one and the diode-bridge one — differ in topology, in gain element and in where their ballistics live. What they do NOT differ in is the static characteristic they all compute, which is the soft-knee gain computer of
Giannoulis, D., Massberg, M. & Reiss, J.D., "Digital Dynamic Range Compressor Design — A Tutorial and Analysis", Journal of the Audio Engineering Society 60(6), pp. 399–408, 2012.
Written per module it became four transcriptions of one published equation, each with its own branch conditions and its own idea of which domain the answer comes back in. This header is the one transcription.
What it owns: the knee's quadratic term, the two domain forms of the characteristic that wrap it, the add-then-log level conversions, and the one-pole RETAIN coefficient. All free functions over double, all pure.
What it does NOT own — and this is the more important half:
This is the trap in the whole subsystem, so it is written down here rather than in one module's doc block where the other three cannot see it. Gain reduction is expressed three ways across the catalog, all correct in their own module and none interchangeable:
| Module | gain_reduction_db() | Why |
|---|---|---|
| feedforward | POSITIVE magnitude | its decoupled detector runs a max(), which is only correct on positives |
| VCA | SIGNED, ≤ 0 | it is added to makeup before one dB→linear conversion, matching the paper's equations |
| FET | POSITIVE magnitude | same max()-free branching detector, but the divider is calibrated in positive dB of attenuation |
| diode-bridge | SIGNED, ≤ 0 | its static curve returns reduction directly |
The specific hazard, carried here from the feedforward module's doc block because it applies to any future member of this family: running a max() on the NEGATIVE gain-computer output silently swaps attack and release. The detector snaps toward LESS reduction and eases into more. That version still compresses, still sounds like a working compressor, and fails a step-response test immediately — which is why every module in this family has one.
soft_knee_reduction_db returns the SIGNED form (≤ 0 for a compressing ratio). A caller wanting the positive magnitude negates once, at its own boundary, and then stays in positives.
soft_knee_output_db returns an output LEVEL; soft_knee_reduction_db returns a gain REDUCTION. They are the same equation and algebraically output = input + reduction — but they are NOT equal in floating point on the linear branch, where one computes T + over/R and the other computes (1/R − 1)·over. Deriving either from the other would perturb three modules' results in the last bits, which is a behaviour change made to save a function. So both ship, adjacent, sharing the knee term — which IS bit-exact between them — and a module picks the domain its own public contract is stated in.
The two also differ in which branch they take exactly ON the knee's lower edge (2·over == −W): the level form enters the quadratic branch, where the term evaluates to zero, and the reduction form returns zero directly. Same value, different path — preserved as-is so each module's arithmetic is untouched.
one_pole_retain returns α = exp(−1/(τ·fs)), the α in y = α·y + (1−α)·x. That is the COMPLEMENT of units::ms_to_onepole_coef, which returns the a in y += a·(x − y). Both conventions are in use in this family and they are one subtraction apart, so the name says which one this is. τ is the 63.2 % time constant in SECONDS; the 10–90 % rise is τ·ln 9 and the 60 dB decay is τ·ln 1000, per units.hpp.
Note also that BallisticsFilterT's coefficient is built on an exp(−2.2/·) 10–90 % convention. Composing it where a τ is meant redefines the user's time by a factor of ln 9.
RT contract: everything here is pure, stateless scalar arithmetic over POD. Nothing allocates, locks, or performs I/O, so all of it is safe per sample on the audio thread. amplitude_db/power_db cost one log10, one_pole_retain one exp, and the characteristic none — it is arithmetic and branches only.