Stage 4b: true-peak limiting via 4x polyphase oversampling
Upgrades the brickwall limiter from sample-peak to true-peak (inter-sample). - src/dsp/oversampler.rs: 4x polyphase windowed-sinc (4 phases x 12 taps, Blackman, each phase normalized to unity DC). Detection-only: max_true_peak() returns the inter-sample max magnitude and discards the upsampled samples; the audio path is untouched. Built in prepare(), no realtime allocation. Cost ~ one base-rate FIR per channel; its small group delay is absorbed by the limiter look-ahead, so no added reported latency. - src/dsp/limiter.rs: detector peak = max(sample_peak, oversampler.max_true_peak()); targets a 0.3 dB margin under the ceiling to cover the 4x detection residual. - 16 unit tests (2 new: detects ~3 dB fs/4 inter-sample overshoot; preserves DC amplitude). README/docs updated: Stage 4 complete. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -75,7 +75,7 @@ a first-class mode, not an afterthought.
|
||||
### Output Limiter
|
||||
- Brickwall, ceiling = 0 dBFS or user-defined (`output_ceiling`). Look-ahead + sliding-max peak detection + a ceiling clamp guarantee the output never exceeds the ceiling
|
||||
- Short attack (≤ 0.1 ms), auto-release (release time user-set)
|
||||
- **Sample-peak today**; 4x-oversampled true-peak (inter-sample) detection is the remaining Stage-4 work
|
||||
- **True-peak**: 4× polyphase oversampling estimates the inter-sample peak (detection only — the upsampled signal is discarded); the limiter targets a 0.3 dB margin under the ceiling to cover the 4× residual
|
||||
### Latency
|
||||
- Reported via `context.set_latency_samples()` in `initialize()` — **never** from `process()`; renegotiating latency mid-stream crashes some hosts (FL included)
|
||||
- Reported latency is a **constant** (the max look-ahead); the look-ahead control only moves the detector tap within that fixed delay
|
||||
@@ -119,7 +119,7 @@ src/
|
||||
biquad.rs # ✅ generic biquad (Transposed Direct Form II)
|
||||
limiter.rs # ✅ look-ahead brickwall limiter (sample-peak; true-peak pending)
|
||||
delay.rs # (planned) look-ahead delay (currently inside compressor.rs / limiter.rs)
|
||||
oversampler.rs # (planned) 4x oversampler for true-peak detection
|
||||
oversampler.rs # ✅ 4x polyphase oversampler for true-peak detection (detection-only)
|
||||
editor/
|
||||
mod.rs # (planned) egui editor split out of lib.rs
|
||||
widgets/
|
||||
@@ -176,11 +176,12 @@ is essential — without it FL silently skips a plugin it has seen before.)
|
||||
|
||||
Work through these stages in order — each stage produces a loadable, audible plugin.
|
||||
|
||||
**Status (2026-06-19):** Stages 1–3 plus the **base-rate brickwall limiter** (Stage 4a) are done:
|
||||
3-band LR4 crossover → per-band compressors (peak/RMS) → 'All' channel → look-ahead brickwall
|
||||
limiter, with a basic 4-column UI. **Next: Stage 4b — 4× oversampling for true-peak (inter-sample)
|
||||
limiting** (deferred as the CPU-heavy part). DSP is in `src/dsp/` (`biquad.rs`, `crossover.rs`,
|
||||
`compressor.rs`, `limiter.rs`); params and the egui editor are still inline in `src/lib.rs`.
|
||||
**Status (2026-06-19):** Stages 1–4 done — the full signal chain works: 3-band LR4 crossover →
|
||||
per-band compressors (peak/RMS) → 'All' channel → **true-peak brickwall limiter** (4× oversampled
|
||||
detection), with a basic 4-column UI. **Next: split `params.rs`/`editor/` out of `lib.rs`, then
|
||||
Stage 6 visualisers (meters, gain curve).** DSP is in `src/dsp/` (`biquad.rs`, `crossover.rs`,
|
||||
`compressor.rs`, `limiter.rs`, `oversampler.rs`); params and the egui editor are still inline in
|
||||
`src/lib.rs`.
|
||||
|
||||
### Stage 1 — Skeleton plugin ✅
|
||||
- [x] NIH-plug "passthrough" compiling and loading in DAW
|
||||
@@ -200,13 +201,13 @@ limiting** (deferred as the CPU-heavy part). DSP is in `src/dsp/` (`biquad.rs`,
|
||||
- [x] Apply per-band compressor to each band
|
||||
- [x] Sum bands back together
|
||||
- [x] Run the summed signal through the 'All' channel compressor before output
|
||||
### Stage 4 — Output brickwall limiter + oversampler *(4a done; 4b = oversampling)*
|
||||
### Stage 4 — Output brickwall limiter + oversampler ✅
|
||||
- [x] Look-ahead delay (circular buffer) — inside `compressor.rs` and `limiter.rs`, no separate `delay.rs`
|
||||
- [x] Wire look-ahead: detector reads N samples ahead of the VCA
|
||||
- [x] Report latency — `context.set_latency_samples()` once; now the constant three-stage total (bands + 'All' + limiter)
|
||||
- [x] Brickwall output limiter (`limiter.rs`): look-ahead + sliding-max peak detect + ceiling clamp guarantee; limits **sample** peaks
|
||||
- [ ] `oversampler.rs` (4x, polyphase FIR / windowed sinc) ⬅ next
|
||||
- [ ] True-peak (inter-sample) limiting on top of the brickwall, via the oversampler
|
||||
- [x] Report latency — `context.set_latency_samples()` once; constant three-stage total (bands + 'All' + limiter)
|
||||
- [x] Brickwall output limiter (`limiter.rs`): look-ahead + sliding-max + ceiling clamp guarantee
|
||||
- [x] `oversampler.rs` — 4× polyphase windowed-sinc, detection-only (returns the inter-sample max)
|
||||
- [x] True-peak limiting: limiter peak = max(sample, inter-sample); targets a 0.3 dB margin under the ceiling for the 4× residual
|
||||
### Stage 5 — Basic egui UI *(basic version done early)*
|
||||
- [x] Add `nih_plug_egui` editor
|
||||
- [x] Sliders for all current parameters (`ParamSlider` grid)
|
||||
|
||||
Reference in New Issue
Block a user