perf: remove per-sample waste from the compressor/limiter hot path
Found by reading the audio-thread code, not by profiling - no profiler is set up in this project yet. All four are algorithmic or arithmetic wins, none change the DSP result; the existing tests still pass. - oversampler: max_true_peak recomputed its circular-history tap index (including a % TAPS_PER_PHASE) inside the phase x channel loop, though the index depends only on pos/k - 8x redundant. Hoisted to a per-sample precomputed index array. This runs unconditionally every sample (it feeds the limiter's true-peak detector, not gated by metering), so it was the highest-value fix. - limiter: the sliding-window max rescanned the entire look-ahead ring (~73 samples at 48kHz/1.5ms) every sample - O(window) per sample. Replaced with an O(1)-amortized monotonic-deque sliding max, backed by preallocated ring arrays and RT-safe. Covered by a new test against a naive brute-force reference. Also precomputed the constant true-peak margin gain in prepare() instead of a powf every sample. - compressor: replaced per-sample log10/powf(10, x/20) with the equivalent ln/exp forms, mirroring nih-plug's own gain_to_db_fast / db_to_gain_fast. Called 4x/sample (3 bands + 'All'). - both: replaced % capacity ring-index math (a real integer division, since capacity is not a power of two) with a conditional-subtract wrap_add helper. Left the oversampler's % TAPS_PER_PHASE alone - that divisor is a compile-time constant LLVM already turns into a multiply. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LU1NRL7fAaZTtFMTTPn2b1
This commit is contained in:
+10
-4
@@ -97,15 +97,21 @@ impl Oversampler {
|
||||
self.history[ch][slot] = input[ch];
|
||||
}
|
||||
|
||||
// The tap index only depends on `pos` and `k`, not on `phase` or `ch` — hoist it out of
|
||||
// those loops instead of recomputing (and re-dividing) it PHASES*MAX_CHANNELS times over.
|
||||
let mut idx = [0usize; TAPS_PER_PHASE];
|
||||
for (k, slot) in idx.iter_mut().enumerate() {
|
||||
// k = 0 is the newest sample, increasing k goes back in time.
|
||||
*slot = (self.pos + TAPS_PER_PHASE - k) % TAPS_PER_PHASE;
|
||||
}
|
||||
|
||||
let mut peak = 0.0f32;
|
||||
for ch in 0..n {
|
||||
let hist = &self.history[ch];
|
||||
for phase in &self.coeffs {
|
||||
let mut acc = 0.0f32;
|
||||
for (k, &c) in phase.iter().enumerate() {
|
||||
// k = 0 is the newest sample, increasing k goes back in time.
|
||||
let idx = (self.pos + TAPS_PER_PHASE - k) % TAPS_PER_PHASE;
|
||||
acc += c * hist[idx];
|
||||
for (&c, &i) in phase.iter().zip(idx.iter()) {
|
||||
acc += c * hist[i];
|
||||
}
|
||||
peak = peak.max(acc.abs());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user