feat(gui): egui editor with live step grid + clickable toggles - #3
Conversation
Adds a custom nih_plug_egui editor restoring the Stage-1 UX: a 16-step grid with a live playhead and click-to-toggle cells, plus sliders for the latent vector, per-step pitches, timing, and tuning. Training UI from the original is dropped (no runtime training; regeneration is reactive on latent change). - src/shared.rs: lock-free SharedState (AtomicU16 step mask + AtomicUsize playhead) bridging audio thread and editor. - src/lib.rs: maybe_regen writes the decoded pattern into shared.steps (now the playback source of truth); process reads on/off from the mask, publishes the playhead, and clears it when stopped; editor() wired up. - src/params.rs: persisted EguiState (600x640). - Pin nih_plug + nih_plug_egui to the same rev f36931f7; an unpinned top-level nih_plug pulled a second copy and broke the Params derive. cargo test 16 pass; clap-validator 18 passed/0 failed/3 skipped.
Code Review — PR #3 (feat: egui editor with live step grid)Reviewed post-merge at the current OverviewAdds a What's good
IssuesMedium — stale scaffolding in
Low/Medium — Low — stale/misleading latent-range comment ( Low — Low — playhead is last-write-wins within a block ( Cosmetic — doc drift.
Test coverage
|
… note Loaded the VST3 in carla-single on a live X display: grid, panels and param values render correctly (screenshot in docs/img/). Logs a new observation — the Carla host window opens far larger than the 600x520 EguiState, egui content top-left, rest black; the VST3 wrapper isn't constraining the host window to the editor size. Interactive click/playhead and a headless snapshot test remain open.
Stage 3 — GUI port
Adds a custom
nih_plug_eguieditor restoring the Stage-1 openFrameworks UX (DS-UI.png): a 16-step grid with a live playhead and click-to-toggle cells, plus sliders for the latent vector, per-step pitches, timing, and tuning. The original training UI (GENERATE / Train / Make Dataset / Open Corpus / Epochs / Loss) is dropped — no runtime training, regeneration is reactive on latent change.What changed
src/shared.rs(new) — lock-freeSharedState { steps: AtomicU16, current_step: AtomicUsize }bridging the audio thread and the editor (Relaxed; no cross-variable invariant).src/lib.rs—maybe_regenwrites the decoded pattern intoshared.steps(now the playback source of truth, replacing the oldself.stepsarray);processreads on/off from the mask, publishes the playhead, clears it when stopped;editor()wired up.src/editor.rs(new) —create_egui_editor;ParamSliderwidgets + a custom painter-drawn grid (fill = on, red outline = playhead, click =shared.toggle(i)); repaints each frame so the playhead animates.src/params.rs— persistedEguiState(600×640).nih_plugandnih_plug_eguito the same revf36931f7; leaving the top-levelnih_plugunpinned pulled a second newer copy and broke theParamsderive.Trade-offs
Verification
cargo test— 16 pass (existing sequencer/decoder + newSharedStatebit tests)cargo build --release+cargo xtask bundle deepsteps-plugin --release— cleanclap-validator validate— 18 passed / 0 failed / 3 skipped (unchanged)Design doc:
docs/plans/2026-06-14-stage3-egui-editor-design.md