Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
vca_compressor.hpp
Go to the documentation of this file.
1#pragma once
2
129
133#include <pulp/signal/units.hpp>
134
135#include <algorithm>
136#include <cmath>
137#include <cstddef>
138#include <vector>
139
140namespace pulp::signal {
141
143template <typename SampleType = float>
145public:
146 // ── Design parameters (the complete roster) ───────────────────────────
147
156 static constexpr double kFloorLinear = 1e-12;
157
165 static constexpr double kRatioKDefault = 4.0;
166 static constexpr double kRatioKMin = 2.0;
167 static constexpr double kRatioKMax = 8.0;
168
169 // Control ranges. These mirror the catalog node's parameter table; the
170 // table is the canonical declaration and these are the same numbers at the
171 // call site, not a second independent one.
172 static constexpr double kThresholdDbMin = -60.0;
173 static constexpr double kThresholdDbMax = 0.0;
174 static constexpr double kRatioMin = 1.0;
175 static constexpr double kRatioMax = 20.0;
176 static constexpr double kNegRatioMin = -20.0;
177 static constexpr double kNegRatioMax = -1.0;
178 static constexpr double kKneeDbMin = 0.0;
179 static constexpr double kKneeDbMax = 24.0;
180 static constexpr double kTimeMsMin = 1.0;
181 static constexpr double kTimeMsMax = 500.0;
182 static constexpr double kMakeupDbMax = 24.0;
183 static constexpr double kLookaheadMsMax = 10.0;
184
191 static constexpr double kCeilingDbDefault = 96.0;
192 static constexpr double kCeilingDbMin = 60.0;
193 static constexpr double kCeilingDbMax = 144.0;
194
195 // ── Lifecycle ─────────────────────────────────────────────────────────
196
200 void prepare(double sample_rate) {
201 sample_rate_ = sample_rate > 0.0 ? sample_rate : sample_rate_;
202
203 const auto capacity = static_cast<std::size_t>(std::llround(
204 units::ms_to_samples(kLookaheadMsMax, sample_rate_))) +
205 1;
206 lookahead_.assign(capacity, SampleType{0});
207
208 update_coefficients();
209 update_lookahead();
210 reset();
211 }
212
214 void reset() {
215 mean_square_ = 0.0;
216 level_db_ = 10.0 * std::log10(kFloorLinear);
217 gain_reduction_db_ = 0.0;
218 gain_linear_ = units::db_to_linear(makeup_db_);
219 std::fill(lookahead_.begin(), lookahead_.end(), SampleType{0});
220 write_index_ = 0;
221 lookahead_valid_ = 0;
222 }
223
224 // ── Controls (real units throughout) ──────────────────────────────────
225
226 void set_threshold_db(double db) {
227 threshold_db_ = std::clamp(dynamics::retain_finite(db, threshold_db_),
229 }
230
232 void set_ratio(double r) {
233 ratio_ = std::clamp(dynamics::retain_finite(r, ratio_), kRatioMin, kRatioMax);
234 }
235
241 void set_negative_ratio_mode(bool enabled) { negative_ratio_ = enabled; }
242
246 void set_neg_ratio_amount(double r) {
247 neg_ratio_ = std::clamp(dynamics::retain_finite(r, neg_ratio_),
249 }
250
253 void set_knee_db(double db) {
254 knee_db_ = std::clamp(dynamics::retain_finite(db, knee_db_),
256 }
257
260 void set_time_ms(double ms) {
261 time_ms_ = std::clamp(dynamics::retain_finite(ms, time_ms_),
263 update_coefficients();
264 }
265
269 ratio_k_ = std::clamp(dynamics::retain_finite(k, ratio_k_),
271 update_coefficients();
272 }
273
274 void set_makeup_db(double db) {
275 makeup_db_ = std::clamp(dynamics::retain_finite(db, makeup_db_),
277 }
278
281 void set_lookahead_ms(double ms) {
282 lookahead_ms_ = std::clamp(dynamics::retain_finite(ms, lookahead_ms_),
283 0.0, kLookaheadMsMax);
284 update_lookahead();
285 }
286
289 void set_mix(double mix01) {
290 mix_ = std::clamp(dynamics::retain_finite(mix01, mix_), 0.0, 1.0);
291 }
292
293 void set_ceiling_db(double db) {
294 ceiling_db_ = std::clamp(dynamics::retain_finite(db, ceiling_db_),
296 }
297
298 // ── Read-only taps ────────────────────────────────────────────────────
299
304 int latency_samples() const noexcept { return lookahead_samples_; }
305
313 double static_curve_db(double input_db) const {
314 return dynamics::soft_knee_output_db(input_db, threshold_db_, knee_db_, active_ratio());
315 }
316
319 double gain_computer_db(double input_db) const {
320 return std::max(static_curve_db(input_db) - input_db, -ceiling_db_);
321 }
322
325 double gain_computer_unclamped_db(double input_db) const {
326 return static_curve_db(input_db) - input_db;
327 }
328
331 double active_ratio() const noexcept { return negative_ratio_ ? neg_ratio_ : ratio_; }
332
334 double gain_reduction_db() const noexcept { return gain_reduction_db_; }
335 GainReduction gain_reduction() const noexcept {
336 return GainReduction::from_signed_db(gain_reduction_db_);
337 }
338 double level_db() const noexcept { return level_db_; }
339 double mean_square() const noexcept { return mean_square_; }
340 double current_gain_linear() const noexcept { return gain_linear_; }
341
347 double attack_coef() const noexcept { return attack_coef_; }
348 double release_coef() const noexcept { return release_coef_; }
349
350 // ── Processing ────────────────────────────────────────────────────────
351
352 SampleType process(SampleType input) {
353 // Do not admit a non-finite sample into either the direction-switched
354 // detector or the lookahead ring: both are recursive state and would
355 // otherwise remain poisoned after the bad sample has passed.
356 if (!std::isfinite(static_cast<double>(input))) {
357 recover_audio_fault();
358 return SampleType{0};
359 }
360 level_db_ = detect_level_db(static_cast<double>(input));
361 gain_reduction_db_ = gain_computer_db(level_db_);
362 gain_linear_ = units::db_to_linear(gain_reduction_db_ + makeup_db_);
363
364 // The detector read `x[n]` un-delayed above; the AUDIO is what the
365 // lookahead delays, which is the whole point — it gives the detector a
366 // head start on the sample it is about to gate.
367 const double dry = static_cast<double>(delayed(input));
368 return static_cast<SampleType>(dry * (mix_ * gain_linear_ + (1.0 - mix_)));
369 }
370
371 void process_block(SampleType* io, int n) {
372 for (int i = 0; i < n; ++i) io[i] = process(io[i]);
373 }
374
375private:
376 void recover_audio_fault() noexcept {
377 mean_square_ = 0.0;
378 level_db_ = 10.0 * std::log10(kFloorLinear);
379 gain_reduction_db_ = 0.0;
380 gain_linear_ = units::db_to_linear(makeup_db_);
381 write_index_ = 0;
382 lookahead_valid_ = 0;
383 }
384
388 double detect_level_db(double x) {
389 const double p = x * x;
390 const double a = p > mean_square_ ? attack_coef_ : release_coef_;
391 mean_square_ = snap_to_zero(mean_square_ + a * (p - mean_square_));
392 return 10.0 * std::log10(std::max(mean_square_, kFloorLinear));
393 }
394
397 SampleType delayed(SampleType input) {
398 if (lookahead_samples_ <= 0 || lookahead_.empty()) return input;
399 const std::size_t size = lookahead_.size();
400 const std::size_t read =
401 (write_index_ + size - static_cast<std::size_t>(lookahead_samples_)) % size;
402 const SampleType out = lookahead_valid_ >= static_cast<std::size_t>(lookahead_samples_)
403 ? lookahead_[read]
404 : SampleType{0};
405 lookahead_[write_index_] = input;
406 write_index_ = (write_index_ + 1) % size;
407 if (lookahead_valid_ < size) ++lookahead_valid_;
408 return out;
409 }
410
416 void update_coefficients() {
417 release_coef_ = units::ms_to_onepole_coef(time_ms_, sample_rate_);
418 attack_coef_ = units::ms_to_onepole_coef(time_ms_ / ratio_k_, sample_rate_);
419 }
420
421 void update_lookahead() {
422 lookahead_samples_ =
423 static_cast<int>(std::llround(units::ms_to_samples(lookahead_ms_, sample_rate_)));
424 const auto capacity = lookahead_.size();
425 if (capacity > 0 && lookahead_samples_ >= static_cast<int>(capacity))
426 lookahead_samples_ = static_cast<int>(capacity) - 1;
427 if (lookahead_samples_ < 0) lookahead_samples_ = 0;
428 }
429
430 double sample_rate_ = 44100.0;
431
432 double threshold_db_ = -20.0;
433 double ratio_ = 4.0;
434 double neg_ratio_ = -4.0;
435 double knee_db_ = 10.0;
436 double time_ms_ = 30.0;
437 double ratio_k_ = kRatioKDefault;
438 double makeup_db_ = 0.0;
439 double lookahead_ms_ = 0.0;
440 double mix_ = 1.0;
441 double ceiling_db_ = kCeilingDbDefault;
442 bool negative_ratio_ = false;
443
444 double attack_coef_ = 0.0;
445 double release_coef_ = 0.0;
446 int lookahead_samples_ = 0;
447
448 double mean_square_ = 0.0;
449 double level_db_ = 10.0 * -12.0; // 10·log10(kFloorLinear), as a constant expression.
450 double gain_reduction_db_ = 0.0;
451 double gain_linear_ = 1.0;
452
453 std::vector<SampleType> lookahead_{};
454 std::size_t write_index_ = 0;
455 std::size_t lookahead_valid_ = 0;
456};
457
460
461} // namespace pulp::signal
Canonical gain-reduction meter value.
Definition dynamics_contract.hpp:27
static GainReduction from_signed_db(double signed_db) noexcept
Adapt a signed gain value, where attenuation is negative dB.
Definition dynamics_contract.hpp:32
True-RMS, log-domain, single-time-constant feedforward compressor.
Definition vca_compressor.hpp:144
static constexpr double kTimeMsMax
Definition vca_compressor.hpp:181
SampleType process(SampleType input)
Definition vca_compressor.hpp:352
static constexpr double kLookaheadMsMax
Definition vca_compressor.hpp:183
static constexpr double kTimeMsMin
Definition vca_compressor.hpp:180
double gain_computer_db(double input_db) const
Gain reduction in dB (SIGNED, ≤ 0 — see the file doc block) the static curve commands at input_db,...
Definition vca_compressor.hpp:319
void reset()
Zeroes the detector and the delay line. Never allocates.
Definition vca_compressor.hpp:214
void set_lookahead_ms(double ms)
Clamped to the ceiling prepare() sized for, so this can never need an allocation.
Definition vca_compressor.hpp:281
static constexpr double kCeilingDbMax
Definition vca_compressor.hpp:193
static constexpr double kThresholdDbMin
Definition vca_compressor.hpp:172
void set_ceiling_db(double db)
Definition vca_compressor.hpp:293
static constexpr double kMakeupDbMax
Definition vca_compressor.hpp:182
double attack_coef() const noexcept
The detector's two one-pole coefficients, in the a of y += a·(x − y) convention of units::ms_to_onepo...
Definition vca_compressor.hpp:347
double level_db() const noexcept
Definition vca_compressor.hpp:338
double mean_square() const noexcept
Definition vca_compressor.hpp:339
void set_time_ms(double ms)
THE time control — one knob for both directions.
Definition vca_compressor.hpp:260
double current_gain_linear() const noexcept
Definition vca_compressor.hpp:340
static constexpr double kKneeDbMin
Definition vca_compressor.hpp:178
static constexpr double kThresholdDbMax
Definition vca_compressor.hpp:173
static constexpr double kRatioKMax
Definition vca_compressor.hpp:167
double gain_reduction_db() const noexcept
Metering taps, as of the last processed sample.
Definition vca_compressor.hpp:334
void set_mix(double mix01)
Dry/wet.
Definition vca_compressor.hpp:289
static constexpr double kRatioMin
Definition vca_compressor.hpp:174
void set_knee_db(double db)
0 = hard knee; > 0 = OverEasy-style soft knee, total width in dB (±width/2 around threshold).
Definition vca_compressor.hpp:253
static constexpr double kCeilingDbMin
Definition vca_compressor.hpp:192
static constexpr double kRatioMax
Definition vca_compressor.hpp:175
static constexpr double kCeilingDbDefault
Definition vca_compressor.hpp:191
static constexpr double kNegRatioMin
Definition vca_compressor.hpp:176
void set_threshold_db(double db)
Definition vca_compressor.hpp:226
void prepare(double sample_rate)
Sizes the lookahead ring for the worst case in the declared range.
Definition vca_compressor.hpp:200
double active_ratio() const noexcept
The ratio currently in force: the negative amount in "infinity+" mode, the positive ratio otherwise.
Definition vca_compressor.hpp:331
void set_ratio(double r)
Positive compression ratio, used when negative-ratio mode is off.
Definition vca_compressor.hpp:232
static constexpr double kKneeDbMax
Definition vca_compressor.hpp:179
void set_attack_release_ratio_k(double k)
The fixed attack/release lock.
Definition vca_compressor.hpp:268
int latency_samples() const noexcept
The module's entire latency contribution: the lookahead delay, exactly.
Definition vca_compressor.hpp:304
static constexpr double kNegRatioMax
Definition vca_compressor.hpp:177
GainReduction gain_reduction() const noexcept
Definition vca_compressor.hpp:335
static constexpr double kFloorLinear
Definition vca_compressor.hpp:156
static constexpr double kRatioKMin
Definition vca_compressor.hpp:166
static constexpr double kRatioKDefault
Definition vca_compressor.hpp:165
void set_makeup_db(double db)
Definition vca_compressor.hpp:274
void set_negative_ratio_mode(bool enabled)
"Infinity+": lets the static curve run with a NEGATIVE ratio, so output keeps falling as input rises ...
Definition vca_compressor.hpp:241
void set_neg_ratio_amount(double r)
The ratio used when negative-ratio mode is on.
Definition vca_compressor.hpp:246
double gain_computer_unclamped_db(double input_db) const
The same value UNCLAMPED, so a test can show where the floor engages and where it does not.
Definition vca_compressor.hpp:325
void process_block(SampleType *io, int n)
Definition vca_compressor.hpp:371
double release_coef() const noexcept
Definition vca_compressor.hpp:348
double static_curve_db(double input_db) const
The static characteristic's output level for an input level, in dB — the gain computer alone,...
Definition vca_compressor.hpp:313
Shared envelope and gain-reduction vocabulary for dynamics processors.
The published compressor equations, once.
double 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.
Definition dynamics_core.hpp:193
double retain_finite(double candidate, double current) noexcept
Control setters retain their last valid value when automation supplies a non-finite sample.
Definition dynamics_core.hpp:117
T ms_to_onepole_coef(T ms, T sample_rate)
A time constant in ms → the a in the one-pole smoother y += a·(x − y), i.e.
Definition units.hpp:202
T ms_to_samples(T ms, T sample_rate)
Milliseconds → samples at sample_rate Hz.
Definition units.hpp:147
T db_to_linear(T db)
Amplitude dB → linear gain. 0 dB → 1, −6.0206 dB → 0.5.
Definition units.hpp:71
Definition additive_bank.hpp:218
VcaCompressorT< float > VcaCompressor
Definition vca_compressor.hpp:458
VcaCompressorT< double > VcaCompressor64
Definition vca_compressor.hpp:459
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
@ a
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
@ e
Definition formant_filter_bank.hpp:20
The real-unit vocabulary shared by every DSP block in the catalog.