docs: update README for Stage 6 metering, pre-gain, and module split

Reflect actual code: per-channel pre-gain drive in signal flow + params,
makeup range -24..+24, lib.rs split into params/editor/meters, lock-free
atomic meters (not a mutex), and Stage 6 progress (|L|GR|R| meters, latching
ceiling lamp, rolling in/out/GR plot). Overview/Goals unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Mikkeli Matlock
2026-06-23 09:56:31 +09:00
parent 4c5165b0bc
commit 411b27fcf3
+33 -30
View File
@@ -34,12 +34,12 @@ Built with **Rust** + **NIH-plug** (VST3 + CLAP output) + **egui** for the UI.
```
Input
└─ Crossover filterbank (Linkwitz-Riley LR4 @ each crossover freq)
├─ Band 1 (low) → look-ahead delay → compressor VCA → gain stage ─┐ (bypassable)
├─ Band 2 (mid) → look-ahead delay → compressor VCA → gain stage ─┤ (bypassable)
└─ Band 3 (high) → look-ahead delay → compressor VCA → gain stage ─┤ (bypassable)
├─ Band 1 (low) → pre-gain → look-ahead delay → compressor VCA → makeup ─┐ (bypassable)
├─ Band 2 (mid) → pre-gain → look-ahead delay → compressor VCA → makeup ─┤ (bypassable)
└─ Band 3 (high) → pre-gain → look-ahead delay → compressor VCA → makeup ─┤ (bypassable)
Sum of bands ◄──────────────────────────────────────────────------┘
└─ 'All' channel → look-ahead delay → compressor VCA → gain stage
Sum of bands ◄─────────────────────────────────────────────────────------┘
└─ 'All' channel → pre-gain → look-ahead delay → compressor VCA → makeup
└─ output brickwall limiter (true-peak, 4x oversampled) → output
```
@@ -62,10 +62,11 @@ a first-class mode, not an afterthought.
- Bands sum phase-coherently to flat **magnitude** (the sum is an all-pass; lower bands get an all-pass at each later crossover to match phase — not a bit-exact time-domain null)
- Crossover frequencies are user-adjustable parameters
### Per-Band Compressor
- **Pre-gain (drive)**: scales the band *before* the detector, so it pushes harder into compression and feeds the sum/limiter hotter — a mild "compressed semi-distortion" without a dedicated saturator. Applied in the wiring (the compressor itself is untouched). Pairs with makeup for full input/output gain-staging
- Level detection: switchable peak / RMS (RMS window currently hardcoded small; can be exposed later)
- Gain computer: threshold, ratio, soft knee
- Attack / release envelopes (logarithmic ballistics)
- Makeup gain per band
- Makeup gain per band (24…+24 dB — attenuates as well as boosts)
- Look-ahead: circular delay buffer on the audio path; detector reads ahead
### 'All' Aggregate Channel
- Structurally **identical to a per-band compressor** — reuse the same comp/lim code/params, just fed the summed signal instead of a filtered band
@@ -85,20 +86,20 @@ a first-class mode, not an afterthought.
## Parameters
### Global
- `input_gain` — pre-gain before filterbank (dB)
- `output_ceiling` — brickwall ceiling (dBFS, default 0.0)
- `limiter_release_ms` — output limiter release time
- `look_ahead_ms` — look-ahead time (05 ms). Reported latency is **constant** (the max look-ahead); the knob only moves the detector tap within that fixed delay, so it is safe to adjust during playback (changing reported latency mid-stream crashes some hosts, FL included)
- `crossover_low_hz` — low/mid crossover frequency
- `crossover_high_hz` — mid/high crossover frequency
### Per-Channel Compressor (× 4: low, mid, high, **all** — one `#[nested]` params struct reused)
- `pre_gain_db` — drive into the compressor (24…+36 dB, smoothed)
- `detection` — peak / RMS level detection
- `threshold_db`
- `ratio` — 1.0 (off) to ∞ (limiting)
- `attack_ms`
- `release_ms`
- `knee_db` — soft knee width
- `makeup_gain_db`
- `makeup_db` — makeup gain (24…+24 dB)
- `bypass` — per-channel bypass (bypassing low+mid+high = simple full-band comp via the 'all' channel)
The 'all' channel uses the same struct so its UI and DSP are identical to a band; it just sits after the band sum.
@@ -110,24 +111,22 @@ Target layout (✅ = exists today; the rest is planned):
```
src/
lib.rs # ✅ Plugin trait + Params + egui editor (all inline for now)
params.rs # (planned) split Params out of lib.rs
lib.rs # ✅ Plugin trait + DSP wiring + process()
params.rs # ✅ Params structs, defaults, build_settings()
editor.rs # ✅ egui editor: meter panel + rolling plot (drawn via Painter) + slider columns
meters.rs # ✅ lock-free Meters (atomics): decayed bar values + raw plot feed
dsp/
mod.rs # ✅ module declarations
compressor.rs # ✅ full-band comp: peak/RMS detector, gain computer, ballistics, look-ahead delay
crossover.rs # ✅ LR4 3-band filterbank with all-pass phase compensation
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)
limiter.rs # ✅ look-ahead brickwall limiter (true-peak via oversampler)
oversampler.rs # ✅ 4x polyphase oversampler for true-peak detection (detection-only)
editor/
mod.rs # (planned) egui editor split out of lib.rs
widgets/
gain_curve.rs # (planned) custom egui Widget: gain curve display
band_meter.rs # (planned) per-band gain reduction meter
level_meter.rs# (planned) input/output level meter
```
The editor's meters and plot are drawn directly with egui's `Painter` in `editor.rs` rather than
as separate widget files; a `gain_curve` display and draggable crossover handles are still to come.
---
## Build Steps
@@ -176,12 +175,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 14 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`.
**Status (2026-06-23):** Stages 14 done — the full signal chain works: 3-band LR4 crossover →
per-band pre-gain + compressors (peak/RMS) → 'All' channel → **true-peak brickwall limiter** (4×
oversampled detection). `lib.rs` has been split into `params.rs`, `editor.rs`, and `meters.rs`.
Stage 6 metering is underway: per-channel **|L | GR | R| meters**, a **latching ceiling lamp**, and
a **rolling in/out/gain-reduction plot** (per-channel tabs + flow-speed selector). **Next: gain-curve
display and draggable crossover handles, then replace the placeholder slider UI.**
### Stage 1 — Skeleton plugin ✅
- [x] NIH-plug "passthrough" compiling and loading in DAW
@@ -211,13 +210,15 @@ Stage 6 visualisers (meters, gain curve).** DSP is in `src/dsp/` (`biquad.rs`, `
### Stage 5 — Basic egui UI *(basic version done early)*
- [x] Add `nih_plug_egui` editor
- [x] Sliders for all current parameters (`ParamSlider` grid)
- [ ] Per-band bypass toggles *(partial — single-band bypass present; per-band arrives with Stage 3)*
- [x] Per-band bypass toggles
- [x] Confirm UI controls update DSP in real time
### Stage 6 — Custom visualisations
- [ ] `level_meter.rs` — input/output RMS + peak meters
- [ ] `band_meter.rs` — per-band gain reduction meters (vertical bars)
- [ ] `gain_curve.rs` — static gain curve display per band (threshold/ratio/knee)
- [x] Per-channel level meters (output level, `|L | GR | R|` cluster)
- [x] Per-channel gain-reduction meters (vertical bars) + latching ceiling lamp
- [x] Rolling in/out/gain-reduction plot (per-channel tabs, flow-speed selector)
- [ ] Static gain-curve display per band (threshold/ratio/knee)
- [ ] Draggable crossover handles on a frequency display
- [ ] Replace the placeholder slider columns with the real UI
---
## Key Implementation Notes
@@ -240,8 +241,10 @@ far below audibility. We therefore do **not** set the register ourselves or flus
NIH-plug provides `Smoother` — use it for all gain/threshold params to avoid zipper noise.
### Thread safety
Params are atomics. The editor and audio thread communicate only through params and
`Arc<Mutex<...>>` meter data. Never pass DSP state to the UI directly.
Params are atomics. The editor and audio thread communicate only through params and a shared
`Arc<Meters>` of **lock-free atomics** (`meters.rs`) — never a mutex on the audio path. `process()`
publishes one value per meter per block (gated on the editor being open); the editor reads them
each frame. Never pass DSP state to the UI directly.
### VST3 licensing
You must accept Steinberg's VST3 SDK licence before distributing VST3 binaries.