124template <
typename SampleType =
float>
177 void prepare(
double sample_rate,
double max_lookahead_ms = 10.0) {
178 sample_rate_ = sample_rate > 0.0 ? sample_rate : sample_rate_;
181 const auto capacity =
static_cast<std::size_t
>(
183 for (
auto& ring : lookahead_) ring.assign(capacity, SampleType{0});
185 update_coefficients();
193 for (
auto& ch : channels_) ch = ChannelState{};
194 for (
auto& ring : lookahead_) std::fill(ring.begin(), ring.end(), SampleType{0});
197 lookahead_valid_[0] = 0;
198 lookahead_valid_[1] = 0;
223 update_coefficients();
229 update_coefficients();
238 update_coefficients();
245 0.0, max_lookahead_ms_);
293 return channels_[
static_cast<std::size_t
>(channel & 1)].smoothed_reduction;
308 if (!std::isfinite(
static_cast<double>(input))) {
309 recover_audio_fault();
310 return SampleType{0};
312 const double level_db = detect_level_db(0,
static_cast<double>(input));
313 const double gain = channel_gain(0, level_db);
314 return static_cast<SampleType
>(delayed(0, input) * gain * makeup_linear_);
320 if (!std::isfinite(
static_cast<double>(
left)) ||
321 !std::isfinite(
static_cast<double>(
right))) {
322 recover_audio_fault();
323 left = SampleType{0};
324 right = SampleType{0};
327 const double left_db = detect_level_db(0,
static_cast<double>(
left));
328 const double right_db = detect_level_db(1,
static_cast<double>(
right));
332 const double shared_db = std::max(left_db, right_db);
333 const double left_used = left_db + stereo_link_ * (shared_db - left_db);
334 const double right_used = right_db + stereo_link_ * (shared_db - right_db);
336 const double left_gain = channel_gain(0, left_used);
337 const double right_gain = channel_gain(1, right_used);
339 const SampleType dry_left = delayed(0,
left);
340 const SampleType dry_right = delayed(1,
right);
341 left =
static_cast<SampleType
>(dry_left * left_gain * makeup_linear_);
342 right =
static_cast<SampleType
>(dry_right * right_gain * makeup_linear_);
354 void recover_audio_fault() noexcept {
355 for (
auto& ch : channels_) ch = ChannelState{};
356 write_index_[0] = write_index_[1] = 0;
357 lookahead_valid_[0] = lookahead_valid_[1] = 0;
360 struct ChannelState {
361 double mean_square = 0.0;
362 double release_stage = 0.0;
363 double smoothed_reduction = 0.0;
364 double sustain = 0.0;
368 double detect_level_db(
int channel,
double x) {
369 auto& ch = channels_[
static_cast<std::size_t
>(channel)];
374 ch.mean_square =
snap_to_zero(rms_coef_ * ch.mean_square + (1.0 - rms_coef_) * x * x);
379 double channel_gain(
int channel,
double level_db) {
380 auto& ch = channels_[
static_cast<std::size_t
>(channel)];
386 double release_coef = release_coef_;
387 if (program_dependent_) {
391 const double sustained =
393 ch.sustain = sustain_coef_ * ch.sustain + (1.0 - sustain_coef_) * sustained;
394 release_coef = release_coef_ + ch.sustain * (slow_release_coef_ - release_coef_);
397 ch.release_stage = std::max(
398 reduction, release_coef * ch.release_stage + (1.0 - release_coef) * reduction);
400 attack_coef_ * ch.smoothed_reduction + (1.0 - attack_coef_) * ch.release_stage);
402 return std::pow(10.0, -ch.smoothed_reduction / 20.0);
413 SampleType delayed(
int channel, SampleType input) {
414 if (lookahead_samples_ <= 0)
return input;
415 const auto index =
static_cast<std::size_t
>(channel);
417 if (
ring.empty())
return input;
419 const std::size_t
size =
ring.size();
421 const std::size_t
read =
422 (
write +
size -
static_cast<std::size_t
>(lookahead_samples_)) % size;
423 const SampleType out = lookahead_valid_[
index] >=
static_cast<std::size_t
>(lookahead_samples_)
428 if (lookahead_valid_[index] < size) ++lookahead_valid_[
index];
432 void update_coefficients() {
433 attack_coef_ = one_pole(attack_ms_);
434 release_coef_ = one_pole(release_ms_);
437 rms_coef_ = one_pole(rms_window_ms_);
442 double one_pole(
double tau_ms)
const {
446 void update_lookahead() {
447 lookahead_samples_ =
static_cast<int>(
449 const auto capacity = lookahead_[0].size();
450 if (capacity > 0 && lookahead_samples_ >=
static_cast<int>(capacity))
451 lookahead_samples_ =
static_cast<int>(capacity) - 1;
452 if (lookahead_samples_ < 0) lookahead_samples_ = 0;
455 void update_makeup() {
463 double sample_rate_ = 44100.0;
464 double max_lookahead_ms_ = 10.0;
466 double threshold_db_ = -18.0;
468 double knee_db_ = 6.0;
469 double attack_ms_ = 10.0;
470 double release_ms_ = 120.0;
471 double rms_window_ms_ = 10.0;
472 double lookahead_ms_ = 0.0;
473 double makeup_db_ = 0.0;
474 double stereo_link_ = 1.0;
475 Detector detector_ = Detector::peak;
476 bool program_dependent_ =
true;
477 bool auto_makeup_ =
true;
479 double attack_coef_ = 0.0;
480 double release_coef_ = 0.0;
481 double slow_release_coef_ = 0.0;
482 double sustain_coef_ = 0.0;
483 double rms_coef_ = 0.0;
484 double auto_makeup_db_ = 0.0;
485 double makeup_linear_ = 1.0;
486 int lookahead_samples_ = 0;
488 ChannelState channels_[2]{};
489 std::vector<SampleType> lookahead_[2]{};
490 std::size_t write_index_[2]{0, 0};
491 std::size_t lookahead_valid_[2]{0, 0};
Log-domain feedforward compressor with a soft-knee gain computer.
Definition feedforward_compressor.hpp:125
void set_detector(Detector d)
Definition feedforward_compressor.hpp:232
static constexpr double kRmsWindowMsMax
Definition feedforward_compressor.hpp:165
static constexpr double kRatioMin
Definition feedforward_compressor.hpp:156
static constexpr double kSustainTauMs
Definition feedforward_compressor.hpp:145
void set_makeup_gain_db(double db)
Definition feedforward_compressor.hpp:251
void set_lookahead_ms(double ms)
Lookahead in ms, clamped to the ceiling prepare() sized for — so this can never need an allocation.
Definition feedforward_compressor.hpp:243
void process_stereo(SampleType &left, SampleType &right)
One stereo pair, in place.
Definition feedforward_compressor.hpp:319
void set_auto_makeup(bool on)
Definition feedforward_compressor.hpp:257
static constexpr double kKneeDbMin
Definition feedforward_compressor.hpp:158
SampleType process(SampleType input)
One mono sample.
Definition feedforward_compressor.hpp:304
static constexpr double kAttackMsMin
Definition feedforward_compressor.hpp:160
static constexpr double kSustainThresholdDb
Definition feedforward_compressor.hpp:149
static constexpr double kRatioMax
Definition feedforward_compressor.hpp:157
static constexpr double kRmsWindowMsMin
Definition feedforward_compressor.hpp:164
void set_attack_ms(double ms)
Definition feedforward_compressor.hpp:220
static constexpr double kSlowReleaseRatio
Definition feedforward_compressor.hpp:141
void set_threshold_db(double db)
Definition feedforward_compressor.hpp:203
static constexpr double kAttackMsMax
Definition feedforward_compressor.hpp:161
void set_program_dependent_release(bool on)
Definition feedforward_compressor.hpp:249
double effective_makeup_db() const
Makeup gain currently in force, in dB — the auto value when auto-makeup is on, the manual value other...
Definition feedforward_compressor.hpp:287
void set_release_ms(double ms)
Definition feedforward_compressor.hpp:226
static constexpr double kMaxLookaheadMsCeiling
Definition feedforward_compressor.hpp:170
Detector detector() const
Definition feedforward_compressor.hpp:233
void set_ratio(double r)
Definition feedforward_compressor.hpp:209
static constexpr double kLevelEpsilon
Definition feedforward_compressor.hpp:134
void set_rms_window_ms(double ms)
Definition feedforward_compressor.hpp:235
CompressorDetector Detector
Definition feedforward_compressor.hpp:127
void reset()
Zeroes detector state and the lookahead buffer. Never allocates.
Definition feedforward_compressor.hpp:192
void prepare(double sample_rate, double max_lookahead_ms=10.0)
Sizes the lookahead ring buffer for the worst case.
Definition feedforward_compressor.hpp:177
static constexpr double kMakeupDbMax
Definition feedforward_compressor.hpp:166
GainReduction gain_reduction(int channel=0) const noexcept
Canonical non-negative gain-reduction magnitude for shared meters.
Definition feedforward_compressor.hpp:297
static constexpr double kKneeDbMax
Definition feedforward_compressor.hpp:159
static constexpr double kThresholdDbMin
Definition feedforward_compressor.hpp:154
void process_block_stereo(SampleType *left, SampleType *right, int n)
Definition feedforward_compressor.hpp:349
double gain_computer_db(double input_db) const
Gain reduction in dB (≤ 0) the static curve applies at input_db.
Definition feedforward_compressor.hpp:281
void set_stereo_link(double amount)
0 = fully independent per-channel detectors, 1 = one shared detector fed by the louder channel.
Definition feedforward_compressor.hpp:264
int latency_samples() const noexcept
The module's entire latency contribution: the lookahead delay, exactly.
Definition feedforward_compressor.hpp:271
static constexpr double kReleaseMsMin
Definition feedforward_compressor.hpp:162
void process_block(SampleType *io, int n)
Definition feedforward_compressor.hpp:345
double static_curve_db(double input_db) const
The static characteristic's output for an input level in dB — the gain computer alone,...
Definition feedforward_compressor.hpp:276
static constexpr double kReleaseMsMax
Definition feedforward_compressor.hpp:163
static constexpr double kThresholdDbMax
Definition feedforward_compressor.hpp:155
void set_knee_width_db(double w)
Definition feedforward_compressor.hpp:214
double gain_reduction_db(int channel=0) const
Smoothed gain reduction in dB (≥ 0, a magnitude) as of the last processed sample.
Definition feedforward_compressor.hpp:292
Canonical gain-reduction meter value.
Definition dynamics_contract.hpp:27
static GainReduction from_magnitude_db(double magnitude_db) noexcept
Adapt an attenuation magnitude, where reduction is positive dB.
Definition dynamics_contract.hpp:37
Shared envelope and gain-reduction vocabulary for dynamics processors.
The published compressor equations, once.
@ index
Definition gpu_pass.hpp:140
@ ring
Definition string.hpp:18
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 one_pole_retain(double tau_seconds, double sample_rate)
α = exp(−1/(τ·fs)) — the RETAIN coefficient (see the file doc block).
Definition dynamics_core.hpp:155
double power_db(double mean_square, double epsilon)
A mean square → dB, floored by an addend.
Definition dynamics_core.hpp:146
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
double amplitude_db(double amplitude, double epsilon)
An amplitude → dB, floored by an ADDEND rather than a max().
Definition dynamics_core.hpp:135
constexpr double kLevelEpsilon
Level-conversion floor, guarding log10(0).
Definition dynamics_core.hpp:125
@ size
Definition config.hpp:216
std::uint64_t read(std::span< const std::uint8_t > b, std::size_t p, unsigned n) noexcept
Definition spectral_frame_codec.hpp:111
void write(std::uint8_t *b, std::size_t p, std::uint64_t v, unsigned n) noexcept
Definition spectral_frame_codec.hpp:117
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
@ right
Definition multi_channel_meter.hpp:67
@ left
Definition multi_channel_meter.hpp:66
CompressorDetector
What the sidechain measures.
Definition feedforward_compressor.hpp:118
@ peak
Sample peak. Literal, fastest-responding.
Definition feedforward_compressor.hpp:119
@ rms
Running mean-square. Reads program loudness.
Definition feedforward_compressor.hpp:120
FeedforwardCompressorT< double > FeedforwardCompressor64
Definition feedforward_compressor.hpp:495
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
FeedforwardCompressorT< float > FeedforwardCompressor
Definition feedforward_compressor.hpp:494
@ i
Definition formant_filter_bank.hpp:20
The real-unit vocabulary shared by every DSP block in the catalog.