Move parameter structs, defaults, and build_settings into src/params.rs;
move the egui editor into src/editor.rs. lib.rs now holds only the plugin
shell, DSP wiring, and process(). No behavior change (16/16 tests pass).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
The global controls were one non-wrapping horizontal row that pushed Ceiling off
the right edge, and the window was fixed-size, so some controls (Ceiling, the full
'All' column) couldn't be reached. Wrapped the editor in a ResizableWindow, moved
the global controls into a vertical label|slider grid, and put everything in a
vertical ScrollArea. Placeholder layout still (Stage 6 replaces it), just usable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the output limiter stage after the 'All' channel. Guarantees the output
never exceeds the ceiling: fixed 1.5 ms look-ahead, stereo-linked sliding-max
peak detection over the look-ahead window -> gain = ceiling/window_max, decoupled
smoothing (fast attack / user release), and a final clamp as the hard guarantee.
- src/dsp/limiter.rs: Limiter (sample-peak; true-peak via oversampling is 4b)
- src/lib.rs: wired as final stage; new globals output_ceiling_db (-24..0) and
limiter_release_ms; latency now the constant three-stage total (bands+All+limiter);
two UI sliders added to the global row
- 14 unit tests (4 new: ceiling guarantee on spikes, loud-sine limiting,
transparency below ceiling, latency)
- README/docs updated (Stage 4 split into 4a done / 4b oversampling)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CLAP shows name/vendor/type correctly in FL; VST3 still shows stale/missing
metadata (suspected FL caching by the unchanged VST3_CLASS_ID). Deferred — use
CLAP meanwhile; likely fix is regenerating the class ID and/or clearing FL's DB.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Investigated the planned global FTZ for the IIR filters and found NIH-plug already
handles it: process_wrapper wraps process()/reset() in a ScopedFtz guard that
enables CPU Flush-To-Zero (x86 MXCSR bit 15 / AArch64 FPCR bit 24, via inline asm,
restored on drop) on the vst3, clap, and standalone paths. SSE is baseline on
x86_64 so FTZ is always active for our build.
So adding our own guard would just duplicate the framework. Instead, removed the
now-redundant flush_denormal() from compressor.rs (the biquads and envelope/RMS
tails already relied on this FTZ) for a single consistent story, and rewrote the
README 'Denormal flushing' note to document that the framework handles it (FTZ,
not DAZ — sufficient for our feed-forward IIR).
No functional change; 10 unit tests still pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Splits the input into low/mid/high with a Linkwitz-Riley 24 dB/oct crossover,
compresses each band, sums them, then runs the sum through a fourth 'All'
compressor. Bypassing the three bands collapses the plugin to a simple full-band
comp driven by 'All' (the crossover sums flat in magnitude).
- src/dsp/biquad.rs: generic RBJ biquad (Transposed Direct Form II), LP/HP/AP
- src/dsp/crossover.rs: 3-band LR4 filterbank; lower band all-pass-compensated at
the higher crossover so the bands sum to flat magnitude (an all-pass, not a
bit-exact null — that only holds for linear-phase FIR). Mirrors nih-plug's
crossover plugin design.
- src/lib.rs: 4 Compressor instances (low/mid/high/all) + Crossover; params
restructured to 4 nested CompressorParams (id_prefix low/mid/high/all) plus
global crossover_low_hz/crossover_high_hz/look_ahead_ms; 4-column lo|mid|hi|all
egui UI; latency = two series stages (bands + all), constant, reported once
- 10 unit tests (adds biquad LP/AP magnitude, crossover flat-magnitude
reconstruction, band-split sanity)
- README: Stage 3 marked done; corrected the 'sum flat' expectation to flat
magnitude (IIR LR sums to an all-pass, not a time-domain null)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Compressor: switchable peak / RMS detection (EnumParam<DetectionMode> in lib.rs
-> use_rms bool in CompressorSettings; DSP stays framework-agnostic). RMS is a
one-pole running mean of the linked squared level with a hardcoded 5 ms window,
updated whenever active so peak<->RMS switching is seamless. New unit test
(RMS compresses a sine less than peak); 6 tests total.
- README: reconciled Implementation Order checklists with actual progress
(Stages 1-2 done; look-ahead/latency + basic UI pulled forward), annotated the
project structure (implemented vs planned), and corrected the Latency and
Denormal-flushing notes to match the code (set_latency_samples once / constant
latency; in-code denormal flush). Overview and goals left unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements the single full-band feed-forward compressor (the engine that will be
reused per band + for the 'All' channel). Design follows Giannoulis et al. 2012:
log-domain gain computer with a quadratic soft knee feeding a smooth decoupled
peak detector for attack/release ballistics. Stereo-linked peak detection.
Look-ahead uses a fixed audio delay with a constant reported latency (set once in
initialize); the knob only moves the detector tap within that delay. This avoids
renegotiating latency from process(), which crashed FL Studio when the look-ahead
was adjusted during playback.
- src/dsp/compressor.rs: Compressor + CompressorSettings, RT-safe (no alloc in
process; buffers sized in prepare; envelope denormals flushed in-code)
- src/lib.rs: nested CompressorParams (threshold/ratio/knee/attack/release/makeup/
bypass) + global look-ahead; egui ParamSlider grid; latency reported once
- 5 unit tests (static curve, knee continuity, steady-state convergence, constant
latency); Cargo.toml lib crate-type added so tests link
- README: 'All' channel architecture already documented; look-ahead spec updated
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sets up the Rust/NIH-plug build (pinned to nih-plug rev f36931f) producing
VST3 and CLAP bundles via 'cargo xtask bundle'. Implements the first landmark:
a full-band gain plugin with a single egui ParamSlider.
- Cargo workspace + xtask bundler, .cargo alias, bundler.toml
- src/lib.rs: Codename206 plugin (gain param, egui editor)
- deploy.ps1/.bat: build + install to system VST3/CLAP folders (real copy,
not a junction, for FL Studio); -User flag for a no-admin dev loop
- LICENSE: GPL-3.0 (required by NIH-plug's VST3 bindings)
- README: corrected architecture for the 'All' aggregate channel (4th comp/lim
stack on the summed bands; all-bands-bypassed = simple full-band comp)
- .gitignore excludes /target and the local nih-plug + _template reference clones
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>