All notable changes to Niner (formerly Slammer) are documented here.
- Save/delete confirmation no longer overlaps the TEST button. The
transient status flash (previously rendered in 6pt mono next to the DEL
button, where it landed on top of TEST) now takes over the LED display
itself for a moment —
SAVED/DELETEDin the same red 7-seg font as the preset name, then reverts. Errors are shortened to fit the display.
- Factory preset bank refreshed and bundled as JSON. Each factory
preset is now an
assets/factory_presets/*.jsonfile (same on-disk schema as user presets) loaded viainclude_str!. Replaces the ~635-lineParamSnapshotliteral block. The 13-preset bank has been re-tuned in production (Clean Sub, Punchy Techno, 909-ish, 808, 909, hh, Init, overdose, psy, sd1, sd2, tight, clap), and a newninerpreset is included. The deprecated909oldfactory has been removed.
- OUTPUT display redesigned. Lit area now ends at a controlled boundary inside the master row instead of stretching across the full panel. DECAY/DRIFT/VOL and AMT/RCT/DRV sit on the chassis surface, not on the display. Display width is also resizable per-instance via the layout editor's corner-drag handle.
- GR meter is now in-display, in red. "GR" label aligned with the BPM
text on the lit area; tick marks at every 3 dB (0/3/6/9/12/15) instead
of just 0/6/12. Numbers in
RED_LEDto match the LED look. - BPM text is independently movable from the OUTPUT display. Standalone
no longer falsely reports
HOSTmode — fixed a transport-detection edge case where one PipeWire/JACK buffer withplaying=falseat startup would lock the standalone into DAW-sync mode. - Header chrome on one line. "UI 1× · KICK SYNTHESIZER · v0.7.1" all share the header centre line and pack tightly behind the TEST button. Each text element is independently draggable in the layout editor.
- SAT-section selectors widened from 44 → 68 px so labels like "dIOdE" and "tAnH" fit with breathing room. LCD lit area uses tighter insets so the dark background height matches the adjacent arrow buttons. L/R arrow glyphs swapped to the slimmer ◂ ▸ variants matching the preset bar, sized at 12 pt.
- Status chips unified. COMP, LIM, PRECISE, CLAP, POST all render at
the same 8 pt monospace, all draggable in the layout editor, all use
the same
LABEL ●ordering (label first, LED right). - Compact-knob layout for AMT/RCT/DRV, ATK/REL/KNE, LVL/FREQ/TAIL — matches the SAT-row knob density (DRV/MIX/CDRV/NDEC/ACC).
- Corner-drag resize for any selected instrumented element. Drag the
bright-green handle at the bottom-right corner;
size_scaleclamped to 0.2..=5.0.OverrideEntry::size_scalealready existed in the schema — this release wires it intoinstrument()and the corner overlay. - All header text elements now
instrument_text'd (KICK SYNTHESIZER, v0.7.1, UI 1×). - Independent move for the GR section, BPM, COMP/LIM/PRECISE/CLAP/POST chips, and master.output_display.
- Hex-socket Allen bolts replace the small round screws. Each corner has a distinct fixed rotation angle so no two screws look perfectly aligned. Drawn procedurally over the chassis bake (matte cap, recessed hex socket with depth shading, light specular). Each screw is movable and resizable in the layout editor.
- Chassis re-baked without screws so the procedural hex bolts are the
only screw rendering — no phantom circles peeking out from under the
caps. Old
screws.pngoverlay path retained but unused at default.
This release also carries the deep refactor work from late April:
- Layout editor gated behind
layout_editorCargo feature (default off) — productionlibniner.sois ~280 KB smaller.assets/baked_layout.jsoninclude_bytes!shim ships the locked layout in default builds.
- Test gap closure: 149 → 179 tests (+30, +20%). New coverage for sequencer, oscillator (Goertzel-based fundamental measurement), filter shelves + notch, noise spectrum.
- RT performance:
KickParamscached once per buffer (was 4×),MasterEq::updatedirty-checked,WaveformDisplay::pushO(n) → O(1) ring buffer,KickEngine::steal_voiceextracted as a helper. - CI workflow added: build + smoke + clippy hygiene gate on every push.
- Dev hygiene: dropped the
spectrum_disableddebug flag, test WAV dumps now route throughstd::env::temp_dir().
MasterRow::draw()restructured soinstrument()calls land before the stored painter borrow — enables the GR section, LIM chip, and display resize handle without&mut uiborrow conflicts.draw_chromesignature changed from&Uito&mut Uito support layout-editor wrapping of header text.
This release rebrands the plugin from Slammer to Niner (a nod to the TR-909 lineage). The rename was driven by a trademark conflict with another product called Slammer.
What you need to know:
- Plugin IDs changed. New
CLAP_ID = "com.hyperfocusdsp.niner"and newVST3_CLASS_ID. Existing DAW projects saved with the old Slammer plugin will show the track as missing/disabled and need to be re-wired to Niner manually. Project parameter values themselves are unaffected — only the host-side identity changed. - Data directory auto-migrates on first launch. The old
~/.local/share/slammer(or platform equivalent) is renamed to~/.local/share/niner, so user presets, hidden-preset filters, and UI scale settings carry over without intervention. - Log filename:
slammer.log→niner.log. Old log files inside the migrated directory remain on disk as harmless artifacts. - Bundle / binary names:
slammer.vst3→niner.vst3,slammer.clap→niner.clap,slammer-standalone→niner-standalone. - Env var:
SLAMMER_DISABLE_SPECTRUM→NINER_DISABLE_SPECTRUM. - Default bounce folder name:
Slammer Bounces→Niner Bounces(in the user's Music folder). - AUR package:
slammer→niner. The oldslammerAUR entry will not auto-update; users on Arch shouldyay -R slammer && yay -S niner. - GitHub repo:
hyperfocusdsp/slammer→hyperfocusdsp/niner.
DSP, parameters, presets, and UI behavior are unchanged from v0.6.0.
-
Per-voice soft-clip — new
src/dsp/voice_clip.rsmodule with three modes (Tanh,Diode,Cubic) plus pass-throughOff. The shaper sits before each layer's amp envelope insideKickVoice::tick, matching the analog 909'sVCO → soft-clip → VCAtopology — so harmonics are dense at attack and thin out as the layer envelope closes, rather than being applied to the already-decayed mix. Distinct from the existing master-bus saturation, which still runs post-envelope on the summed voices. -
Two new params:
kick_clip_mode(0 Off / 1 Tanh / 2 Diode / 3 Cubic) andkick_clip_drive(0..1). Both default to 0 / 0, so every v0.5.x preset loads bit-identical (applyshort-circuits to pass-through when drive ≤ 0). Persisted via the existingParamSnapshotround-trip with#[serde(default)], so old preset JSON files deserialize cleanly. -
Factory
909preset now ships withkick_clip_mode = 1.0(Tanh) andkick_clip_drive = 0.15. Closes the largest perceptual gap surfaced by the 2026-04-26 TR-909 BD audit (item 3 — per-voice waveshaper before mix). -
MID noise gated to attack with its own short envelope. New param
mid_noise_decay_ms(default 30 ms) runs the noise channel off a separateAmpEnvelopeinstead of riding the tone'smid_amp_env. Real 909 kicks have a short noise burst at attack (15-30 ms) layered over a longer tone tail; legacy slammer let noise sustain for as long asmid_decay_ms, which on 250-300 ms tone tails turned the noise into a hiss bed instead of a snap. Closes audit item 4. Factory909preset uses15 msfor that signature 909 click character. -
Legacy preset compatibility —
ParamSnapshotdeserialization from v0.5.x JSON files leavesmid_noise_decay_ms = 0.0. The trigger path treats anything below 1 ms as a sentinel and falls back tomid_decay_ms, so old presets keep their original sustained-noise feel until the user explicitly tunes the new knob. -
909-style Accent. Closes audit item 7. Two new mechanisms working together: a per-step accent bit on the sequencer (parallel to the existing step bits, persisted via a new
seq_accentspayload) and a host-automatableaccent_amountparam. When a step's accent bit is set ANDaccent_amount > 0, the engine multiplies that hit'samp_scaleby1 + 0.3·aanddecay_scaleby1 + 0.5·a, composing on top of the existing drift jitter. Manual triggers (button, T key, MIDI note-on) always fire un-accented. UI: shift-click a lit step to toggle its accent — accented steps are marked by a small white tick at the bottom of the pad. Clearing a step also clears its accent so the state can't outlive its host. Backward-compat: v0.5.x sessions deserializeseq_accentsto zero (no accents) andaccent_amountto 0, so no existing pattern changes character.
- SAT/EQ row restructured to surface every v0.6.0 audio param. The left half of the row is now a 2×4 stacked cluster: top sub-row carries the existing SAT MODE LCD selector (compact variant) plus small SAT DRIVE / SAT MIX knobs; bottom sub-row carries a new CLIP MODE LCD selector (compact, mirroring the SAT MODE shape) plus the three new v0.6.0 controls — CLIP DRIVE, MID NOISE DECAY, ACCENT — all rendered in the comp-cluster small-knob style (18 px diameter). The EQ cluster on the right keeps its full-size 32 px knobs and vertical alignment unchanged. Row height grew by 22 px to fit the stack; STEP grid and DICE shift down to follow.
lcd_selectorwidget refactored inseven_seg.rsto take a uniqueid_source, amodesslice, and acompact: boolflag. The compact variant uses 14×18 arrows + 44×16 LCD and drops the trailing 8 px gap, fitting cleanly inside the new sub-row height. Two selectors with distinct id sources can now coexist in the same frame without egui Id collision. The CLIP MODE LCD displaysOFF / tAnH / dIOdE / CUbIC(lowercasenbecause the 7-segment glyph table doesn't define uppercase N).
- Intermittent bitcrush-style audio glitches root-caused. Across
five new offline-render audit tests covering default Init, the 909
preset (drift + Diode sat + Tanh kick-clip), heavy 32nd-note
retriggering, and comp+limiter+drive+warmth all engaged, the full
per-sample DSP chain produces bit-clean output (max sample-to-sample
delta in the natural attack-ramp range, no NaN, no FFT-period
correlation). The remaining real-time-only artifact under load was
isolated to the audio-thread spectrum FFT — Slammer's spectrum
analyzer runs
realfft::process_with_scratchonce per 1024 samples on the RT thread (Autokit on the same machine has no audio-thread FFT and is glitch-free, the smoking-gun cross-plugin delta).
SLAMMER_DISABLE_SPECTRUMenv var. When set to a non-empty, non-0value at plugin/standalone init, the audio thread skips the spectrum FFT entirely. The OUTPUT display's bars freeze, but CPU pressure on the RT thread drops to Autokit-equivalent. Cached in aboolfield, branch-predictor-friendly. v0.6.1 will replace this with a click-cycle tri-state OUTPUT toggle (Normal / Bars / None) and move the FFT to a worker thread so the default path also no longer occupies the audio thread.
- 5 new offline-render audit tests in
plugin.rs::teststhat mirror the full per-sample chain (engine + master_bus + dj_filter- tube + soft-clip + spectrum FFT feed) over multi-second renders,
write WAVs to
/tmp/slammer_offline_*.wavfor visual inspection, and assert no audible artifacts at the sample-delta level. Total test count 137 → 142.
- tube + soft-clip + spectrum FFT feed) over multi-second renders,
write WAVs to
- Per-trigger amplitude + decay drift. The
Driftknob now perturbs more than pitch on each hit. A newDrift::sample_envelopereturns oneDriftSampleper trigger withamp_scale(±2.5%) anddecay_scale(±5%), both gated bydrift_amount.amp_scalemultiplies the voice's tick output uniformly across SUB / MID / TOP layers;decay_scalemultiplies every amp-envelope'sdecay_msbefore triggering. Atdrift_amount = 0both factors are exactly 1.0 — deterministic v0.5.x behavior is preserved. - Analog envelope-tau quantization.
analog_quantize_tau(tau, drift_amount)snaps an envelope time-constant to ~16 levels per decade, lerped in bydrift_amount. Approximates the cap-array stepping of analog envelope-generator chips: with drift engaged, two slightly-differentdecay_mssettings can audibly snap to the same decay because they round to the same chip step. Inaudible at drift = 0; subtle character from drift ≈ 0.3 upward. Wired into everyAmpEnvelope::triggercall across the kick voice.
- Factory
909preset rewritten against a TR-909 BD audit (reference:BPB Cassette 909/clean/bd01.wav). Old values were stale (decay_ms = 934 ms,sub_fstart = 90.8 Hz). New values:decay_ms = 200,sub_fstart = 65,sub_fend = 50,top_gain = 0.15,mid_noise_gain = 0.1,sat_mode = 2.0(Diode),sat_drive = 0.1,drift_amount = 0.2. The factory909-ishand909oldpresets are unchanged for now.
- Preset dropdown scrolls cleanly past ~12 entries. Rewrote
src/ui/preset_bar.rsso the dropdown popup constrains paint to its bounding rect, only iterates the visible window, supports mouse-wheel scroll, draws a thin red-LED scrollbar thumb on the right edge, and auto-scrolls so the currently-selected preset is visible on open. Required because user preset count grew past the original fixed-height window's capacity and the old painter-style dropdown overflowed onto the knob panel below. - Pointer cursor stays on the dropdown. The knob drag-to-change
widget calls
set_cursor_icon(ResizeVertical)and was winning the last-write-of-the-frame race against the dropdown's PointingHand, so user-preset rows showed up/down resize arrows. The fix stashes the dropdown's bounding rect duringPresetBar::renderand re-applies the cursor in a newapply_late_cursormethod called fromeditor.rsafter every knob-panel draw.
- Windows standalone audio-thread underruns on managed / corporate setups. The WASAPI probe's period-size default moved from 2048 (~43 ms) to 4096 (~85 ms). On machines where Defender / Intune / WMI or similar background services steal cycles from the audio thread in bursts, 43 ms of buffer wasn't enough to absorb the stall and random clicks landed in the output. 85 ms handles the worst case observed on a managed W11 box without perceptibly affecting a sequencer-driven kick drum. Sample rate probe (previous fix in v0.4.4) is unchanged.
- WASAPI-probe startup log line.
src/windows_standalone.rsnow emitsWASAPI probe: device=..., sample_rate=..., buffer_size_range=..., chosen_period=...at launch. Makes future underrun reports evidence-driven — if a machine clicks even at 4096, the log tells us whether the driver is advertising an unusually high minimum (bump the constant) or the underrun is somewhere else entirely.
- Output safety clipper prevents DAC crackling on loud presets. At
default plugin state the master-bus chain — including the brickwall
limiter — was bypassed (comp off, drive off, limiter-toggle off), so
the engine's natural peak (~+0.6 dBFS on default params, higher with
low-boost EQ or master-volume above unity) went straight to the audio
device unclipped. On managed Windows / WASAPI setups this manifested
as hit-to-hit crackling that disappeared when the master volume was
trimmed below -0.5 dB. A final per-sample soft-clip stage now runs
after master volume with a
tanh-asymptoted ceiling at -0.009 dBFS: signals below -1.4 dBFS pass through bit-identical, signals above smoothly roll off and never reach full-scale. Loud presets still sound loud; they just don't hard-clip the converter anymore.
- Sub / mid oscillators now phase-lock on every trigger. Previously the
Driftknob added ±8.6° of random phase offset to the SUB and MID oscillators at trigger time, causing identical MIDI hits to produce audibly inconsistent low-end level (constructive vs destructive summing against decaying tails and kick-bass interactions in a mix). Trigger phase is now deterministic, matching the gated-VCO behaviour of analog kick circuits. TheDriftknob still controls ±14 cents of analog pitch drift — that's the part that makes a kick sound analog.
-
Spectrum-analyzer view on the OUTPUT display. Click the display to toggle between the rolling waveform and a 64-band log-frequency spectrum (20 Hz → 20 kHz, -60 → 0 dB). The label swaps
OUTPUT ↔ SPECTRUMso you always know which mode you're in. Peak-hold dots per band decay over ~500 ms, so transients stay readable after they pass — useful when tuning a kick's harmonic content. Mode is sticky across widget rebuilds within a DAW session (eguiMemory) and resets on full project reopen.Under the hood: a 1024-point Hann-windowed real FFT (
realfft 3.5) runs on the audio thread every ~21 ms at 48 kHz. All buffers are pre-allocated ininitialize();process()allocates nothing new (verified underassert_process_allocs). Bin magnitudes publish to the GUI via a lock-free[AtomicU32; 64], mirroring the existingMeterSharedpattern — no mutex, no drop-path allocations to worry about.
- Waveform now fills the OUTPUT display vertically. Previous scaling capped at 42% of display height so a full-scale kick looked mid-volume; bumped to 95% of available height, proportional throughout. Quieter signals still scale proportionally smaller (the OUTPUT display is an honest meter, not auto-normalized).
- Windows WASAPI auto-probe (
src/windows_standalone.rs) and macOS standalone period-size workaround (scripts/slammer-macos.sh) are untouched. The spectrum feed is per-sample inside the existing process loop, so buffer size and backend negotiation are unchanged.
- Windows standalone keyboard shortcuts now work. T (trigger) and
Space (sequencer play/stop) were dead on Windows because nih-plug's
standalone wrapper opens an outer baseview window whose
WindowHandler::on_eventreturnsEventStatus::Ignoredfor every event — includingEvent::Keyboard. Windows routesWM_KEYDOWNto that outer hwnd, so the egui child window never saw any key events. Fixed with a Windows-onlyGetAsyncKeyStatepoll for T and Space, gated by a foreground-window-thread check so background presses in other apps can't trigger Slammer. Linux, macOS, and plugin-host paths are unchanged. - BOUNCE no longer crashes on Windows. Clicking BOUNCE triggered a
non-unwinding panic inside
wglSwapLayerBuffers— the synchronousrfd::FileDialog::save_file()pumps a nested Win32 message loop, which re-entered the egui paint while OpenGL was mid-frame. The dialog now runs on a dedicatedslammer-bounceworker thread; the editor polls the result on the next paint. - Baseview updated to include RustAudio/baseview#212 (keyboard event hook on Windows), so plugin hosts that intercept keyboard messages (Ableton etc.) deliver them to Slammer.
- Crash-safe logging. Every panic — including non-unwinding ones —
now writes location, payload, and backtrace to
%APPDATA%\Slammer\slammer\data\logs\slammer.logand stderr, so a closing Windows console no longer swallows the diagnostic.
- Windows standalone now produces audio automatically. The standalone
binary used to fall back to the dummy backend on many Windows setups
because nih-plug's defaults (48 kHz sample rate, 512-sample period)
mismatched the device's WASAPI mix format or minimum buffer size.
run_standalone()now probes the default output device on Windows and forwards matching--sample-rate/--period-sizeto nih-plug vianih_export_standalone_with_args. Linux and macOS paths are unchanged. User-supplied-r/-p/-bflags still win.
- Factory preset library expanded. Eleven new factory presets baked in
alongside the original three —
808,909,909old,clap,hh,Init,overdose,psy,sd1,sd2,tight— covering sub, snare, hat, clap, and psytrance tonal territory. Fourteen factory presets total. - Master volume is now part of a preset. Saved as
master_volumeinParamSnapshotand applied on preset load, so presets that vary in loudness audition at their intended monitoring level. Backward-compatible: older preset files without the field load without touching the current master. Most new factories ship with tuned master levels; a handful of older ones (909,sd2,tight) intentionally leave master as-is.
- Cluster label alignment. PRECISE (ATK/REL/KNE), CLAP (LVL/FREQ/TAIL), and FILTER (FILT/RES) mini-cluster captions now render below their knob rows instead of above. This lines up each cluster's small knob centers with the adjacent big-knob row, eliminating the vertical offset that made the right side of the panel look misregistered. Click targets for the CLAP and PRE/POST LEDs move with their captions and behave identically.
- Hyperfocus DSP transition. Plugin vendor, URL, email, and CLAP ID all
updated from the legacy
REXISTidentity toHyperfocus DSP(https://hyperfocusdsp.com,[email protected],com.hyperfocusdsp.slammer). VST3 class ID is unchanged to preserve DAW-project compatibility for existing 0.4.x users. - Footer mark. Full
hyperfocus DSPwordmark (with small-caps suffix and ring-as-O from the canonical brand master) rendered in the footer strip. Hover tooltip: "Made by Hyperfocus DSP".
- UI scale. Click-to-cycle badge in the header (1× / 1.5× / 2×) that
persists the chosen scale both inside the DAW project (nih-plug
#[persist]) and to a sidecar file so the standalone launcher forwards it via--dpi-scaleon the next launch. Scaling itself is delegated to baseview'sWindowScalePolicy— no in-editorset_pixels_per_pointfighting baseview. - Knob cluster tightening. AMT / RCT / DRV (macro compressor) now use the same 18 px knob / 28 px cell as the PRECISE strip directly below, so the two rows read as visually paired instead of one wider than the other.
- BOUNCE alignment. The BOUNCE button's right edge is now anchored to the cluster-column right edge, so it lines up exactly with KNE, TAIL, POST, and the DICE LED row (previously off by 2–4 px).
- DICE row moved below the STEP separator line with proper spacing (was touching it).
- BOUNCE button repositioned to just above the footer groove, narrowed from 56 to 48 px.
- DJ Filter — bipolar master HP/LP state-variable filter (12 dB/oct, zero-delay feedback SVF). The FILT knob is bipolar: center = off, left sweeps a high-pass from 800 Hz down to 20 Hz, right sweeps a low-pass from 20 kHz down to 200 Hz. RES knob (0–1). PRE/POST LED toggle places the filter before or after the master bus (compressor + transformer + limiter + tube warmth).
- METAL — ring modulation on the TOP click voice using the 909 hat partial ratio (1 + √2 ≈ 2.414). Adds inharmonic metallic overtones to the click transient without affecting the SUB or MID layers. Bit-identical bypass at zero.
- DICE — randomize button with six per-section lock LEDs (S M T X E C for SUB / MID / TOP / SAT / EQ / COMP). Roll all unlocked sections at once; values are range-safe via the parameter API. Global envelope params (DECAY, DRIFT) always randomize regardless of locks.
- Logo — Slammer wordmark in the plugin header.
- Knob double-click reset now works in all hosts/platforms.
response.double_clicked()is unreliable under baseview (raw mouse events, no synthesised egui input). Replaced with manual per-widget timestamp tracking — delta < 0.35 s triggers the reset. - DECAY + DRIFT now always randomize with DICE. Previously they were gated behind the SUB lock even though they affect all three layers.
- Duplicate randomization step removed —
mid_phase_offsetwas being randomized twice per DICE roll (second call overwrote the first with a different value). - Saturation clip LP coefficient is now precomputed at initialisation rather than recomputed on every sample. No audible change; measurable CPU reduction in Clip mode.
First public release.
- Three-layer kick engine — SUB sine + pitch envelope, MID sine+noise blend, TOP bandpass-filtered click transient. Per-layer amp + pitch envelopes, drift, and tuning.
- Five-voice distortion palette — split-band rational clip, asymmetric diode, hysteresis tape, transformer drive (master bus), and auto tube warmth (post-bus). Each curve is genuinely distinct in harmonic content, symmetry, and frequency response.
- Master bus — RMS compressor with 3 macros (amount, reaction, drive), transformer drive, and a brickwall limiter with LED indicator.
- Master EQ — tilt, low shelf, and variable-Q notch.
- 680 × 444 single-panel editor with rack chrome, aspect-ratio-locked scaling (resize the DAW window freely; the layout scales without distortion).
- Header-integrated preset bar — 7-segment LED display, dropdown browser, inline rename, save, delete. Up/Down arrows cycle presets globally. Factory presets are delete-protected.
- 16-step pattern sequencer with click/drag paint, four-on-the-floor default, and a PLAY/STOP button in standalone mode.
- Host transport sync — the sequencer locks to host position and tempo inside a DAW. In standalone mode the sequencer runs on an internal transport and the tempo readout becomes interactive.
- Interactive tempo entry (standalone only) — single-click the BPM text to arm, vertical drag to scrub at 2 px/BPM, double-click to type a value directly (3-digit, digits only), Left/Right arrows for ±10 BPM (Shift for ±1). Host-synced mode keeps the readout read-only.
- Live output waveform scope with gain-reduction meter.
- Keyboard test trigger — press
Tanywhere in the editor to fire the engine without MIDI.
- Factory + user presets stored as forward-compatible JSON
(
#[serde(default)]on every field, so future versions can add parameters without breaking old files). - Last-preset recall in standalone: the editor remembers the last loaded preset and restores it on next launch.
- Full DAW state persistence for parameters and sequencer patterns inside project files.
- Linux x86_64 — VST3, CLAP, Standalone
- macOS ARM (Apple Silicon) — VST3, CLAP, Standalone
(standalone uses
slammer-macos.shlaunch script with--period-size 4096to work around nih-plug#266) - macOS Intel — VST3, CLAP, Standalone
- Windows x86_64 — VST3, CLAP, Standalone
- No Audio Unit (AU) support — nih-plug framework limitation.
- Standalone on macOS Apple Silicon needs the included launch script (adds slight latency; DAW plugins are unaffected).
- Window is aspect-ratio locked but not free-form resizable.