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:
@@ -34,12 +34,12 @@ Built with **Rust** + **NIH-plug** (VST3 + CLAP output) + **egui** for the UI.
|
|||||||
```
|
```
|
||||||
Input
|
Input
|
||||||
└─ Crossover filterbank (Linkwitz-Riley LR4 @ each crossover freq)
|
└─ Crossover filterbank (Linkwitz-Riley LR4 @ each crossover freq)
|
||||||
├─ Band 1 (low) → look-ahead delay → compressor VCA → gain stage ─┐ (bypassable)
|
├─ Band 1 (low) → pre-gain → look-ahead delay → compressor VCA → makeup ─┐ (bypassable)
|
||||||
├─ Band 2 (mid) → look-ahead delay → compressor VCA → gain stage ─┤ (bypassable)
|
├─ Band 2 (mid) → pre-gain → look-ahead delay → compressor VCA → makeup ─┤ (bypassable)
|
||||||
└─ Band 3 (high) → look-ahead delay → compressor VCA → gain stage ─┤ (bypassable)
|
└─ Band 3 (high) → pre-gain → look-ahead delay → compressor VCA → makeup ─┤ (bypassable)
|
||||||
│
|
│
|
||||||
Sum of bands ◄──────────────────────────────────────────────------┘
|
Sum of bands ◄─────────────────────────────────────────────────────------┘
|
||||||
└─ 'All' channel → look-ahead delay → compressor VCA → gain stage
|
└─ 'All' channel → pre-gain → look-ahead delay → compressor VCA → makeup
|
||||||
└─ output brickwall limiter (true-peak, 4x oversampled) → output
|
└─ 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)
|
- 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
|
- Crossover frequencies are user-adjustable parameters
|
||||||
### Per-Band Compressor
|
### 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)
|
- Level detection: switchable peak / RMS (RMS window currently hardcoded small; can be exposed later)
|
||||||
- Gain computer: threshold, ratio, soft knee
|
- Gain computer: threshold, ratio, soft knee
|
||||||
- Attack / release envelopes (logarithmic ballistics)
|
- 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
|
- Look-ahead: circular delay buffer on the audio path; detector reads ahead
|
||||||
### 'All' Aggregate Channel
|
### '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
|
- 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
|
## Parameters
|
||||||
|
|
||||||
### Global
|
### Global
|
||||||
- `input_gain` — pre-gain before filterbank (dB)
|
|
||||||
- `output_ceiling` — brickwall ceiling (dBFS, default 0.0)
|
- `output_ceiling` — brickwall ceiling (dBFS, default 0.0)
|
||||||
- `limiter_release_ms` — output limiter release time
|
- `limiter_release_ms` — output limiter release time
|
||||||
- `look_ahead_ms` — look-ahead time (0–5 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)
|
- `look_ahead_ms` — look-ahead time (0–5 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_low_hz` — low/mid crossover frequency
|
||||||
- `crossover_high_hz` — mid/high crossover frequency
|
- `crossover_high_hz` — mid/high crossover frequency
|
||||||
### Per-Channel Compressor (× 4: low, mid, high, **all** — one `#[nested]` params struct reused)
|
### 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
|
- `detection` — peak / RMS level detection
|
||||||
- `threshold_db`
|
- `threshold_db`
|
||||||
- `ratio` — 1.0 (off) to ∞ (limiting)
|
- `ratio` — 1.0 (off) to ∞ (limiting)
|
||||||
- `attack_ms`
|
- `attack_ms`
|
||||||
- `release_ms`
|
- `release_ms`
|
||||||
- `knee_db` — soft knee width
|
- `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)
|
- `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.
|
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,23 +111,21 @@ Target layout (✅ = exists today; the rest is planned):
|
|||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
lib.rs # ✅ Plugin trait + Params + egui editor (all inline for now)
|
lib.rs # ✅ Plugin trait + DSP wiring + process()
|
||||||
params.rs # (planned) split Params out of lib.rs
|
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/
|
dsp/
|
||||||
mod.rs # ✅ module declarations
|
mod.rs # ✅ module declarations
|
||||||
compressor.rs # ✅ full-band comp: peak/RMS detector, gain computer, ballistics, look-ahead delay
|
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
|
crossover.rs # ✅ LR4 3-band filterbank with all-pass phase compensation
|
||||||
biquad.rs # ✅ generic biquad (Transposed Direct Form II)
|
biquad.rs # ✅ generic biquad (Transposed Direct Form II)
|
||||||
limiter.rs # ✅ look-ahead brickwall limiter (sample-peak; true-peak pending)
|
limiter.rs # ✅ look-ahead brickwall limiter (true-peak via oversampler)
|
||||||
delay.rs # (planned) look-ahead delay (currently inside compressor.rs / limiter.rs)
|
|
||||||
oversampler.rs # ✅ 4x polyphase oversampler for true-peak detection (detection-only)
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -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.
|
Work through these stages in order — each stage produces a loadable, audible plugin.
|
||||||
|
|
||||||
**Status (2026-06-19):** Stages 1–4 done — the full signal chain works: 3-band LR4 crossover →
|
**Status (2026-06-23):** 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
|
per-band pre-gain + compressors (peak/RMS) → 'All' channel → **true-peak brickwall limiter** (4×
|
||||||
detection), with a basic 4-column UI. **Next: split `params.rs`/`editor/` out of `lib.rs`, then
|
oversampled detection). `lib.rs` has been split into `params.rs`, `editor.rs`, and `meters.rs`.
|
||||||
Stage 6 visualisers (meters, gain curve).** DSP is in `src/dsp/` (`biquad.rs`, `crossover.rs`,
|
Stage 6 metering is underway: per-channel **|L | GR | R| meters**, a **latching ceiling lamp**, and
|
||||||
`compressor.rs`, `limiter.rs`, `oversampler.rs`); params and the egui editor are still inline in
|
a **rolling in/out/gain-reduction plot** (per-channel tabs + flow-speed selector). **Next: gain-curve
|
||||||
`src/lib.rs`.
|
display and draggable crossover handles, then replace the placeholder slider UI.**
|
||||||
|
|
||||||
### Stage 1 — Skeleton plugin ✅
|
### Stage 1 — Skeleton plugin ✅
|
||||||
- [x] NIH-plug "passthrough" compiling and loading in DAW
|
- [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)*
|
### Stage 5 — Basic egui UI *(basic version done early)*
|
||||||
- [x] Add `nih_plug_egui` editor
|
- [x] Add `nih_plug_egui` editor
|
||||||
- [x] Sliders for all current parameters (`ParamSlider` grid)
|
- [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
|
- [x] Confirm UI controls update DSP in real time
|
||||||
### Stage 6 — Custom visualisations
|
### Stage 6 — Custom visualisations
|
||||||
- [ ] `level_meter.rs` — input/output RMS + peak meters
|
- [x] Per-channel level meters (output level, `|L | GR | R|` cluster)
|
||||||
- [ ] `band_meter.rs` — per-band gain reduction meters (vertical bars)
|
- [x] Per-channel gain-reduction meters (vertical bars) + latching ceiling lamp
|
||||||
- [ ] `gain_curve.rs` — static gain curve display per band (threshold/ratio/knee)
|
- [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
|
- [ ] Draggable crossover handles on a frequency display
|
||||||
|
- [ ] Replace the placeholder slider columns with the real UI
|
||||||
---
|
---
|
||||||
|
|
||||||
## Key Implementation Notes
|
## 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.
|
NIH-plug provides `Smoother` — use it for all gain/threshold params to avoid zipper noise.
|
||||||
|
|
||||||
### Thread safety
|
### Thread safety
|
||||||
Params are atomics. The editor and audio thread communicate only through params and
|
Params are atomics. The editor and audio thread communicate only through params and a shared
|
||||||
`Arc<Mutex<...>>` meter data. Never pass DSP state to the UI directly.
|
`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
|
### VST3 licensing
|
||||||
You must accept Steinberg's VST3 SDK licence before distributing VST3 binaries.
|
You must accept Steinberg's VST3 SDK licence before distributing VST3 binaries.
|
||||||
|
|||||||
Reference in New Issue
Block a user