perf: lift idle per-sample smoothers out of the process loop
Every sample ran 20 Smoother::next() calls and 4 db_to_gain_fast() - one set per channel for pre-gain, makeup, mix, low slope and low curve - whether or not anything was actually moving. Measured standalone at ~17 ns/sample, so roughly 8% of the chain, paid permanently for the rare case where a knob is in motion. Params here only change on block boundaries (we do not split blocks on events), so a smoother idle at the top of a block stays idle for that block. When every smoother feeding a channel is idle - i.e. nobody is touching anything, which is the overwhelming common case - its per-sample values are constants and lift out of the loop. The moving case keeps the original per-sample path unchanged. Unlike the true-peak early-out this is an always-on saving, not a signal-dependent one. Note this path has no automated coverage: process() needs a host to drive it, so the DSP tests do not reach it. The logic is small and the moving case is byte-for-byte the old code, but automation smoothing is worth an ear in FL before trusting it - move a pre-gain or mix knob and listen for zipper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LU1NRL7fAaZTtFMTTPn2b1
This commit is contained in:
+55
-10
@@ -216,6 +216,43 @@ impl Plugin for Codename206 {
|
||||
let mut gr = [0.0f32; meters::NUM_CHANNELS];
|
||||
let mut lim_gr = 0.0f32;
|
||||
|
||||
// Params only change on block boundaries here (we do not split blocks on events), so a
|
||||
// smoother that is idle now stays idle for this whole block. When every smoother feeding
|
||||
// a channel is idle - the overwhelming common case, i.e. nobody is touching a knob -
|
||||
// its per-sample values are constants, so lift them out of the sample loop entirely.
|
||||
// That removes 5 Smoother::next() calls and a db_to_gain per channel per sample.
|
||||
let chan_static = |p: ¶ms::CompressorParams| {
|
||||
!p.pre_gain_db.smoothed.is_smoothing()
|
||||
&& !p.makeup_db.smoothed.is_smoothing()
|
||||
&& !p.mix.smoothed.is_smoothing()
|
||||
&& !p.low_slope.smoothed.is_smoothing()
|
||||
&& !p.low_curve.smoothed.is_smoothing()
|
||||
};
|
||||
let band_static = [
|
||||
chan_static(&self.params.low),
|
||||
chan_static(&self.params.mid),
|
||||
chan_static(&self.params.high),
|
||||
];
|
||||
let all_static = chan_static(&self.params.all);
|
||||
let mut band_pre = [1.0f32; 3];
|
||||
for b in 0..3 {
|
||||
if band_static[b] {
|
||||
band_pre[b] = util::db_to_gain_fast(band_params[b].pre_gain_db.smoothed.next());
|
||||
band_set[b].makeup_db = band_params[b].makeup_db.smoothed.next();
|
||||
band_set[b].mix = band_params[b].mix.smoothed.next();
|
||||
band_set[b].low_slope = band_params[b].low_slope.smoothed.next();
|
||||
band_set[b].low_curve = band_params[b].low_curve.smoothed.next();
|
||||
}
|
||||
}
|
||||
let mut all_pre_static = 1.0f32;
|
||||
if all_static {
|
||||
all_pre_static = util::db_to_gain_fast(self.params.all.pre_gain_db.smoothed.next());
|
||||
all_set.makeup_db = self.params.all.makeup_db.smoothed.next();
|
||||
all_set.mix = self.params.all.mix.smoothed.next();
|
||||
all_set.low_slope = self.params.all.low_slope.smoothed.next();
|
||||
all_set.low_curve = self.params.all.low_curve.smoothed.next();
|
||||
}
|
||||
|
||||
let mut in_frame = [0.0f32; 2];
|
||||
let mut band_in = [[0.0f32; 2]; 3];
|
||||
let mut band_out = [[0.0f32; 2]; 3];
|
||||
@@ -243,14 +280,18 @@ impl Plugin for Codename206 {
|
||||
for b in 0..3 {
|
||||
// `_fast` skips the below-(-100dB) branch, which pre_gain_db's -24..+36 range
|
||||
// never reaches, in exchange for a cheaper exp() instead of a general powf().
|
||||
let pre = util::db_to_gain_fast(band_params[b].pre_gain_db.smoothed.next());
|
||||
let pre = if band_static[b] {
|
||||
band_pre[b]
|
||||
} else {
|
||||
band_set[b].makeup_db = band_params[b].makeup_db.smoothed.next();
|
||||
band_set[b].mix = band_params[b].mix.smoothed.next();
|
||||
band_set[b].low_slope = band_params[b].low_slope.smoothed.next();
|
||||
band_set[b].low_curve = band_params[b].low_curve.smoothed.next();
|
||||
util::db_to_gain_fast(band_params[b].pre_gain_db.smoothed.next())
|
||||
};
|
||||
for ch in 0..n {
|
||||
band_in[b][ch] *= pre;
|
||||
}
|
||||
band_set[b].makeup_db = band_params[b].makeup_db.smoothed.next();
|
||||
band_set[b].mix = band_params[b].mix.smoothed.next();
|
||||
band_set[b].low_slope = band_params[b].low_slope.smoothed.next();
|
||||
band_set[b].low_curve = band_params[b].low_curve.smoothed.next();
|
||||
self.comps[b].process(&band_in[b][..n], &mut band_out[b][..n], &band_set[b]);
|
||||
for ch in 0..n {
|
||||
summed[ch] += band_out[b][ch];
|
||||
@@ -274,14 +315,18 @@ impl Plugin for Codename206 {
|
||||
}
|
||||
|
||||
// 'All' aggregate channel over the summed bands (driven before its compressor).
|
||||
let all_pre = util::db_to_gain_fast(self.params.all.pre_gain_db.smoothed.next());
|
||||
let all_pre = if all_static {
|
||||
all_pre_static
|
||||
} else {
|
||||
all_set.makeup_db = self.params.all.makeup_db.smoothed.next();
|
||||
all_set.mix = self.params.all.mix.smoothed.next();
|
||||
all_set.low_slope = self.params.all.low_slope.smoothed.next();
|
||||
all_set.low_curve = self.params.all.low_curve.smoothed.next();
|
||||
util::db_to_gain_fast(self.params.all.pre_gain_db.smoothed.next())
|
||||
};
|
||||
for ch in 0..n {
|
||||
summed[ch] *= all_pre;
|
||||
}
|
||||
all_set.makeup_db = self.params.all.makeup_db.smoothed.next();
|
||||
all_set.mix = self.params.all.mix.smoothed.next();
|
||||
all_set.low_slope = self.params.all.low_slope.smoothed.next();
|
||||
all_set.low_curve = self.params.all.low_curve.smoothed.next();
|
||||
self.comps[ALL].process(&summed[..n], &mut out_frame[..n], &all_set);
|
||||
|
||||
// Output brickwall limiter.
|
||||
|
||||
Reference in New Issue
Block a user