Commit Graph

20 Commits

Author SHA1 Message Date
Mikkeli Matlock 37f34e1175 rename: Codename 206 -> Bari 206
Retires the working codename now rather than at 1.0, because the two identity
constants a DAW uses to recognise a plugin across sessions can only be changed
for free while nothing has saved a project with it. Verified: zero references
to the plugin across all 69 FL project backups, under any identity string.

Derived spellings:
    Cargo package     codename_206        -> bari_206
    bundle / DAW name Codename 206 |
                      206 prototype       -> Bari 206      (unified; drops "prototype")
    Rust types        Codename206(Params) -> Bari206(Params)
    CLAP_ID           com.mikkeli.codename-206
                                          -> org.novoyuuparosk.bari-206
    VST3_CLASS_ID     Codename206Maxi!    -> Bari206Multiband

CLAP_ID moves to reverse-DNS on a domain actually controlled, and stops
disagreeing with a VENDOR that was already Novoyuuparosk. VST3_CLASS_ID stays
exactly 16 bytes and drops "Maxi", which sat close to Image-Line's Maximus and
Maximizer in the same product category.

Author identity converges on Mikkeli Matlock <mikkeli@novoyuuparosk.org>;
Cargo authors had been carrying a personal gmail address while Plugin::EMAIL
already used the org one. VENDOR stays Novoyuuparosk, which is the org rather
than the person, and copyright stays with the person.

Deliberately unchanged: the repo, its remote, the local directory and the
homepage URL all keep "codename-206". Renaming those buys nothing and costs a
Gitea rename, a remote rewrite and a directory move, so the bare "codename-206"
is current branding rather than residue - and is the one spelling the STALE
list must not contain.

Driven by check-branding.sh rather than by grep: setting the canonical block
listed all 26 sites, including .github/workflows/ci.yaml, which no explicit
check covered. That gap is now closed by a positive check on the CI bundle
command, so a future rename fails in seconds instead of after a full compile.
STALE now carries both the retired and the new spellings, so the next rename
is self-checking too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LU1NRL7fAaZTtFMTTPn2b1
2026-08-31 22:55:41 +09:00
mikkeli 193cee0aef ci: gate pushes on a build, test and bundle
Migrated from musiikki/nih-plug-template, adapted for this repo: the
template gates VST3 behind an opt-in `vst3` feature and so builds twice,
while this plugin always ships VST3 (nih_plug is taken with its default
features), so one build covers both formats.

The branding check runs first because it needs no toolchain and finishes
in under a second, so a half-landed rename fails before a build is spent
on it. The bundle step is included because `cargo build` never exercises
the xtask bundler, and bundler.toml drifting from the crate name stays
invisible until you try to produce an artifact.

Performance is deliberately not gated. `Plugin::process()` cannot be
called from a test — `ProcessContext` requires a `&Transport`, whose
constructor and fields are `pub(crate)` to nih_plug — so any realtime
check would have to target the `src/dsp/` types directly, and a meaningful
one needs an audio deadline rather than a shared CI box.

Verified by running the exact step sequence as the gitea-runner user under
the service's environment: 28 branding checks, 20 unit tests, both bundles
produced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHt4DykxXaQnyaJfyS8KCn
2026-08-31 22:34:31 +09:00
mikkeli 466ab478c9 chore: relicense to GPL-3.0-or-later and credit the ISC upstream
Commit 057d630 switched this repo from GPL-3.0 to Apache-2.0, which the
build contradicts: src/lib.rs calls nih_export_vst3! unconditionally and
nih_plug is taken with default features, so every build links vst3-sys —
GPLv3-or-later by its own authors' choice. deploy.ps1 then installs that
binary. The declared licence and the artifact's actual terms disagreed,
and the original GPL-3.0 was correct.

Given the intent to distribute non-profit and open-source, GPL costs
nothing here, so this restores it rather than dropping VST3 to keep a
permissive licence.

Also adds the ISC notice owed to Robbert van der Helm: the plugin
scaffolding derives from the NIH-plug cookiecutter template and example
plugins, whose one condition is that the copyright and permission notice
travel with all copies. The README credited the template under Reference
Material, which is courtesy, not compliance. ISC is GPL-compatible, so the
notice is retained in NOTICE rather than superseded.

Corrects the README's claim that Steinberg's SDK licence must be accepted
before distributing. No SDK source is vendored or redistributed, so there
is nothing to accept at build time. The binding constraint is vst3-sys's
GPLv3; Steinberg's trademark in "VST" is a separate regime that no source
licence resolves.

Copyright in the Irene <irene@novoyuuparosk.org> commits is held by the
repository owner (an agent identity), so no third-party agreement was
needed for this relicense.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHt4DykxXaQnyaJfyS8KCn
2026-08-31 22:32:05 +09:00
Mikkeli Matlock a4c542b2d9 feat: serial low-level shaper (Low Slope + Low Curve) before the compressor
Add a per-channel below-threshold shaper composed in series ahead of the comp:
gain = low_shape(level) + comp(level + low_shape(level)). The compressor's
threshold now sees the shaped level, so a Low Slope boost lifts quiet material
up into compression (and a cut pulls it out). Anchored at the -60 dB silence
floor. Low Curve bends the shaper toward a bounded saturation so the serial
composition doesn't blow up (0 = straight line).

gain_computer split into comp_gain_db + low_gain_db and composed; shared with
the editor gain-curve display. Slider order rearranged to read in signal order
(pre-gain -> low shaper -> compressor -> output). Defaults (slope 1, curve 0)
reproduce the plain compressor; 17 tests pass.

Known: the bipolar behaviour isn't final yet (milestone commit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 00:53:40 +09:00
Mikkeli Matlock b1477f7ec6 feat: static gain-curve display for the selected channel
Add editor/gain_curve.rs: a square panel beside the scrolling plot showing the
channel transfer (out vs in, -60..0 dB) for whichever channel the plot tab
selects. Plots the full wet path — out = (in + pre_gain) + gain_reduction + makeup
— using the shared Compressor::gain_computer (now pub) so it matches the DSP and
the GR meter. Unity-reference diagonal + threshold marker; mix not folded in.

Update README structure/status to reflect the editor/ widget module and the
completed Stage 6 visualisers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 15:24:08 +09:00
Mikkeli Matlock 42c5f7dcd2 feat: per-channel dry/wet mix (parallel compression) replacing bypass
Replace the per-channel bypass toggle with a smoothed dry/wet `mix` (0..100%,
default 100%). The blend is applied at the compressor output:
  out = delayed_input * ((1 - mix) + mix * wet_gain)
Dry and wet share the same delayed input, so it's phase-aligned (parallel
compression, no comb filtering). mix=0 is bit-identical to the old bypass.

The detector now runs even at mix 0, so the GR meter shows the wet gain
reduction regardless of mix, while the level/plot out trace reads the mixed
output. "Bands at 0% = simple full-band comp via All" still holds.

Update README + parameter docs (bypass -> mix).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 19:25:38 +09:00
Mikkeli Matlock eee94379bc docs: note the crossover automation caveat (editor-only lo<=hi limit)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 16:57:20 +09:00
Mikkeli Matlock 514fd964f6 fix: freeze the scope when the transport is stopped
FL keeps calling process() with silence while stopped/paused, so the scope
scrolled silence instead of holding. Gate the plot's bucket advance on
context.transport().playing, so it freezes on stop/pause and resumes on play.
Bar meters still fall to silence as before. Update README thread-safety notes
for the ScopeRing feed and transport gating.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 16:25:08 +09:00
Mikkeli Matlock 3ae860188e docs: restore planned editor/widgets layout as a future UI-redesign target
Keep the widget-module breakdown (meter/plot/gain_curve/crossover) documented
as the intended split for when editor.rs is redesigned, clearly marked as
deferred rather than current.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 23:17:51 +09:00
Mikkeli Matlock 411b27fcf3 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>
2026-06-23 09:56:31 +09:00
Mikkeli Matlock 20c5a17a61 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>
2026-06-19 15:50:06 +09:00
Mikkeli Matlock f6c45123fa Stage 4a: base-rate look-ahead brickwall limiter
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>
2026-06-19 14:45:26 +09:00
Mikkeli Matlock ada0b00313 docs: note VST3 metadata display issue in FL as a known/deferred issue
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>
2026-06-19 12:42:22 +09:00
Mikkeli Matlock d776e564fd Denormals: rely on NIH-plug's FTZ, drop redundant in-code flush
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>
2026-06-18 01:34:56 +09:00
Mikkeli Matlock 1d939535be Stage 3: 3-band LR4 crossover + per-band compressors into the 'All' channel
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>
2026-06-17 20:04:24 +09:00
Mikkeli Matlock 0f66e9638c Add peak/RMS detection switch; sweep docs to match code
- 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>
2026-06-15 20:02:23 +09:00
Mikkeli Matlock 8d9eaeaffc Stage 2: full-band compressor with constant-latency look-ahead
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>
2026-06-15 19:33:31 +09:00
Mikkeli Matlock e9337473b9 minor desc change 2026-06-14 21:24:43 +09:00
Mikkeli Matlock d0734ff803 Scaffold NIH-plug project: full-band gain VST3/CLAP with one-slider egui GUI
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>
2026-06-14 21:22:34 +09:00
Mikkeli Matlock c398b86d2b initial commit 2026-06-14 20:31:27 +09:00