1f24c4b095
Split root CLAUDE.md into 19 per-directory files scoped to their source area. Roll v0 history into docs/ARCHIVE.md; retire CONTEXT.md, CONTEXT-ARCHIVE.md, PLAN.md, COMPLETED.md. Move plan docs under docs/. Rescue 9 live deferrals into docs/TODO.md.
1066 lines
89 KiB
Markdown
1066 lines
89 KiB
Markdown
# ARCHIVE.md — ReaSampler pre-1.0 history
|
||
|
||
Rarely-read backup of completed version-0 work, rolled here when 0 → 1 closed (2026-07-29). Not a source of context for current work: current architecture lives in the per-directory `src/**/CLAUDE.md` files, current plans in `docs/`. Full uncompressed history is in git.
|
||
|
||
## Part 1 — Capture roadmap, Design View, versioning, multi-bank, reclaim
|
||
|
||
### Milestone 0 — Transition scaffold: reaper_mpeview → ReaSampler
|
||
Retired the MPE scaffold (`mpe_model`/`mpe_view`) and stood up the sampler's pure core (`bank_model`, `peaks`) in its place, preserving the pure-core/REAPER-shell split; extension renamed `reaper_reasampler`; forever-stable action-id prefix chosen for the sampler family.
|
||
|
||
### Milestone 1 — bank_model + JSON round-trip (pure)
|
||
`Sample` metadata struct + `BankIndex` (add/remove/query/tier moves/dedup-by-hash) with lossless JSON round-trip. Relative-paths-only enforced at the model boundary (absolute paths rejected).
|
||
|
||
### Milestone 2 — peaks (pure)
|
||
Waveform min/max bin computation from raw PCM, dependency-free of REAPER's own peak API; multi-channel envelopes preserved (no silent fold); remainder-bin and short-buffer edge cases covered.
|
||
|
||
### Milestone 3 — Offline capture spike (REAPER shell)
|
||
`OfflineRenderBackend` renders the time selection to a bank-relative 32-bit float WAV via `Main_OnCommand(42230)` (REAPER always shows its render-progress window — no headless path exists); populates a `Sample` into the in-memory `BankIndex`; non-destructive; unsaved-project state gates on Save-As (no default-location fallback).
|
||
|
||
### Milestone 4 — persist (index ↔ project ext state)
|
||
`SetProjExtState`/`GetProjExtState` under namespace `"reasampler"`, keys `bank_index` + `project_guid`. **Decision:** project identity is **GUID-primary** with `ReaProject*` as a secondary disambiguator only — this replaced two earlier iterations (GUID-only mis-detected forks sharing a copied GUID; pointer-primary mis-detected reopen/new-project on address recycling). Save-As is **copy** semantics — old project's bank stays intact.
|
||
|
||
### Milestone 5 — bank_panel (docked grid)
|
||
Docked LICE grid; thumbnails from `peaks` bins, in-memory cache keyed by `(sampleId, drawWidth, bankGeneration)` — **not persisted**, discarded on bank change. Audition via stock `PlayPreview`/`StopPreview`, single-stop-funnel for leak-free lifecycle (read-only, never inserts into arrange). Multi-select + keyboard nav.
|
||
|
||
### Milestone 6 — insert (placement)
|
||
`InsertMedia` at the edit cursor onto the **currently selected track(s)** (Daniel's directive — not a new track), for multiple selected tracks placing on each then restoring original selection/cursor; one undo block. Conform-to-project-tempo is an explicit separate flag (`&8`) — the stretch-to-time-selection bit (`&4`) is never set on the default path (pure-tested). Non-destructive to the bank.
|
||
|
||
### D1 — view_mode_model (pure)
|
||
N-mode registry (Arrange + Design seeded) + GUID-keyed membership + folder-tree-aware visibility derivation + park/restore planner (restore returns captured values, never a hardcoded "on") + JSON round-trip.
|
||
|
||
### D2 — view shell
|
||
Drives REAPER flags per the D1 planner: `B_SHOWINTCP`/`B_SHOWINMIXER`/`B_MAINSEND`/`I_FXEN` + per-FX offline, snapshot-before-park via `GetMediaTrackInfo_Value`. Master and `B_MUTE`/`I_SOLO` never touched (review-gated).
|
||
|
||
### D3 — persist slice (view state ↔ project ext state)
|
||
View section (membership + active mode + snapshots) serialized under `"reasampler"` alongside the bank; active mode reapplied on project open, including saved-while-parked restore from persisted snapshots.
|
||
|
||
### D4 — actions
|
||
Bindable Design View action family (`src/actions.{h,cpp}`): toggle/activate mode, tag/untag selected tracks, show-both toggle. New `src/track_guid.{h,cpp}` shared GUID formatter. Saved active mode reapplied on load via a load-signal seam in `persist` (drained by `main.cpp`'s timer).
|
||
|
||
### D5 — in-window toggle affordance (UI)
|
||
Segmented `[ Arrange | Design ]` mode switch in the bank_panel header via new pure module `mode_switch` (mirror of `bank_grid`); per-mode membership count shown. **Not built:** the offlined-FX re-init tooltip caveat — no trace in source, did not survive the later Phase L panel redesign.
|
||
|
||
### D2-W1 — view_mode_model lane extension (pure)
|
||
Extended D1's planner to item level: lane↔mode mapping, managed-vs-manual lane-ownership index, managed-only lane ops, the auto-tag decision (manual-lane items exempt; pre-existing content ⇒ Arrange), JSON round-trip of the lane index.
|
||
|
||
### D2-W2 — shell: lane application + new-content detection
|
||
Applies managed-lane ops (`I_FREEMODE`/`I_FIXEDLANE`/`C_LANEPLAYS`) in the DAW, manual lanes never touched. New pure modules `guid_diff` (poll-to-poll GUID diffing) and `lane_keys` (durable `P_LANENAME`-based lane identity, resolving the `I_FIXEDLANE` renumber-fragility risk). New-content auto-tag runs on the bank_panel timer with a first-poll-after-open guard. **Noted limitation at landing:** item-lane show/hide was structurally correct but a provable no-op on real projects until D2-W3 minted `reasampler:`-prefixed named lanes; end-to-end DAW verification was sequenced after D2-W3 for that reason.
|
||
|
||
### Milestone 7 — capture action family
|
||
Bindable capture actions, source resolvers (master mix/selected tracks/selected items/razor area). **Wet-only decision (Daniel):** REAPER offline render has no true pre-FX dry bit, so approximate-dry action variants were dropped; `CaptureRequest.wetDry` retained as a seam for true dry (later Milestone 10). **Superseded post-landing:** the four wet source-mode actions were replaced by **three FX-scope actions** (item/track/master, later item/track only) with range inferred orthogonally (razor-else-time-selection) — this fixed item captures rendering through the parent FX chain. FX scope: item = item/take FX only; track = item FX + the track's own FX; the out-of-scope chain (ancestors + master) is neutralized to unity via the reusable `FxBypassGuard` RAII (snapshot → neutralize → render → restore). **Master scope was later removed entirely** — capture is item and track only; to capture the master, render a track instead.
|
||
|
||
### Milestone 8 — RealtimeRecordBackend
|
||
Track-scope tap via `CreateTrackSend` into a hidden temp track (post-fader, `B_MAINSEND=0`) — captures each source track's own post-FX/fader output, chain-independent by construction; no `FxBypassGuard` needed (fixed the spike's master→temp feedback-loop silent-file bug). Timer-driven async `begin`/`tick`/`abort` state machine, project-scoped transport reads, idempotent latched `restore()`. **Decisions:** track scope only this increment (item realtime deferred — needs per-item take isolation); project-close guarded via `ValidatePtr2` (`dropWithoutRestore()` for an already-closed project); no undo block (transient scaffold, fully reversed by `restore()`). Master scope removed entirely (mirrors Milestone 7).
|
||
|
||
### T1 — offline tail: auto (default) + manual override
|
||
Three-state `TailMode {None, Auto, Manual}`. Auto: 8s-capped tail render + surgical `RENDER_NORMALIZE` (trim-end-only bit, `32768`) trimmed to -72 dB via a derived `RENDER_TRIMEND` ratio — deterministic (bit-identical repeats hold because only trailing silence is trimmed, nothing scaled/faded). Manual: fixed tail clamped to 8s, no trim. None: byte-identical to pre-tail exact-bounds capture. Exposed as a docked-panel **footer toggle** (cycles Off→Auto→Manual) rather than per-action tail variants (the "…with tail" action variants were dropped).
|
||
|
||
### T2 — realtime tail (follow-on to T1)
|
||
Parallel tail path for the realtime backend (which doesn't drive `RENDER_*`): records an 8s-capped tail window past the range end, then trims via a PCM backward decay-scan to the -72 dB point (new pure module `wav_trim` for header-aware WAV truncation; `peaks` gained `lastFrameAboveThreshold`). Manual records a fixed tail and skips the scan. **Realtime tail is documented non-deterministic** — bit-identical repeats are not asserted for this path (by design, not a defect).
|
||
|
||
### T1-followons — Manual fine-adjust UI + per-project tail persistence
|
||
Scroll-wheel fine-adjust of Manual tail length in 250ms steps (`kManualStepMs`), clamped [0, 8s]; label format `"Tail: Manual X.Xs"`. Tail setting (mode + Manual length) promoted into `ReaSamplerSession` and persisted under forever-stable ext-state key `"tail_setting"`; absent key falls back to Off / 2s default.
|
||
|
||
### D2-W3-A — lane minting + item→lane assignment + persist round-trip
|
||
Pure `planLaneMinting`: a track with ≥2 modes' worth of managed-eligible content gets one durable `reasampler:<mode>`-named lane minted per involved mode and every managed item (including pre-existing) assigned to its mode's lane; manual-lane items exempt at the source. Shell `applyMintPlan`/`mintManagedLanes` grows `I_NUMFIXEDLANES` (never shrinks user lanes), one undo block, triggered off the auto-tag detection tick. `reconcileManagedLanes` rebuilds the lane-ownership index from durable `P_LANENAME` on project load. Lane-ownership index rides inside the existing `view_state` blob (no new persistence key). **At landing, two behaviors were flagged as REAPER-runtime-only / DAW-verification-pending:** whether lane names stick when written the same tick a track flips to fixed-lane mode, and whether the leftover empty default lane 0 is silent.
|
||
|
||
### D2-W3-B — item-level mode actions + W3-A polish
|
||
Item-level "move to Design"/"move to Arrange"/"untag" actions mirroring the track-level family, each re-driving mint/apply, manual-lane items exempt, one undo block each. Plus three carried polish items (redundant `I_NUMFIXEDLANES` re-read removed, shared `item_read` seam extracted, reconcile guard for unregistered mode ids). **Deferred (Daniel's decision):** a per-track lane-split UI indicator in the panel — no natural cheap home found; preserved as a backlog note in PLAN.md Phase D2, not silently dropped.
|
||
|
||
### Phase V — Versioning & release
|
||
|
||
#### V1/V3 — app_version module: version constant, ext-state stamp, show-version action
|
||
Pure `app_version` module: CMake-sourced semver (`REASAMPLER_VERSION`) via `configure_file` → `version_generated.h`; ext-state writing-version stamp (numeric triple only, no channel suffix) under key `"version"` written on every save; absent stamp classifies as silent `PreVersioning`; on-demand "show version" action (no startup print).
|
||
|
||
#### V4 — beta-in-isolation: fully isolated coexisting binary via compile-time channel flag
|
||
`-DREASAMPLER_CHANNEL=beta` produces a fully isolated `reaper_reasampler_beta` binary: its own ext-state namespace (`reasampler_beta`), forever-stable command-id prefix (`CEREBELLUM_REASAMPLER_BETA_`), `"ReaSampler beta: "` action names, channel-qualified dock ident, `-beta` version-display suffix. All channel identity derives from one `REASAMPLER_CHANNEL_IS_BETA` bit via `channelCommandId`/`channelActionName` composition helpers — no scattered `#ifdef`s in shells. Stable build is byte-identical to pre-V4 identity. **Accepted isolation semantics:** a channel reads/writes only its own namespace (no cross-channel migration/fallback); the `reasampler:` lane-name prefix is deliberately NOT channel-qualified.
|
||
|
||
### Phase B — Multi-bank
|
||
|
||
#### B1 — bank_book (pure)
|
||
REAPER-free bank registry wrapping N `BankIndex` instances (pool seeded + privileged: un-deletable/un-renamable/un-evacuable, never zero banks); create/rename/reorder/delete named banks; active-bank id; move/copy a sample between banks (destination collapse-by-hash observed); evacuate; JSON round-trip with legacy `bank_index` migration into the pool. `BankIndex` itself untouched (additive). **Fork R-B, settled 2026-07-24 (Phase-B-wide, retro-touches B1–B4):** every bank index verb — action and panel gesture alike — wraps its mutation in a batched REAPER undo point (`Undo_BeginBlock2`/`EndBlock2`, `UNDO_STATE_MISCCFG`, SDK-verified to participate in undo), with a `projectconfig` hook triggering deferred session reload so Ctrl-Z/redo visibly restores state in-session.
|
||
|
||
#### B-cap — owned-file manifest seam
|
||
Capture records each file it creates into an owned-file manifest (pure `owned_manifest` module), persisted under a sibling `"owned_files"` ext-state key, decoupled from `bank_book` (tracks creation, not index membership). Landed early — deferred to Phase R would have meant retroactively reconstructing the manifest, a backfill cliff (fork R-D).
|
||
|
||
#### B2 — persist slice (banks ↔ project ext state)
|
||
Book serialized under `banks` key (pool folded in as bank-zero), distinct from `view_state`; legacy `bank_index`-only projects migrate to pool + zero named banks on first load, with `banks` authoritative and no `bank_index` written thereafter.
|
||
|
||
#### B3 — actions
|
||
Bindable multi-bank action set: create/rename/delete/evacuate bank, activate (direct + cycle), move/copy selected samples to a bank, pool/banks full-height toggles.
|
||
|
||
#### B4 — bank_panel vertical split (UI)
|
||
Pool grid (top) + named-banks tab-page region (bottom); LICE-drawn tab strip (new pure module `tab_strip`, mirror of `mode_switch`) with overflow/scroll (fork 5a); both a "move to bank" menu and drag-between-regions for sample moves, copy as secondary act on the menu (fork 5b), with drop-target highlighting during drag. Delete confirms on non-empty banks, naming evacuate as the alternative. Active-bank indicator resolved at build time.
|
||
|
||
#### B5 — sample-remove (the missing sample-level verb)
|
||
Surfaces `BankIndex::remove` through `bank_book`: drops a sample's index entry from a bank/the pool, index-only, non-destructive to the file (orphaned until prune); pool contents removable while pool-container privileges hold; no confirm dialog (recoverable via the R-B batched undo). **Fork R-A, settled 2026-07-24:** remove scope is **this-bank only** by default and only surfaced affordance; an `all-banks` scope stays a latent, unshipped parameter in the action signature.
|
||
|
||
#### Milestone 10 — provenance (re-capture from source)
|
||
Reshaped from an earlier, broader M10. **Cut (Daniel):** the null-test verification action and the true pre-FX-dry mechanism — both dropped (`docs/product/provenance.md` §What was cut). **Kept, forks P1=a/P2=a settled 2026-07-23** (P3/P4 moot under P2=a): `Sample.provenance` populated on resample-from-sample with parent id + a **thin `rsprov1` capture-recipe fingerprint** (scope, exact range, tail, rate/channels, track GUIDs, FX-chain identity — a drift/repro fingerprint, explicitly NOT a serialized chain to restore); "re-capture from source" action regenerates the file in place from the source's current state (bank-only, never places into the timeline), reporting drift if the source changed. Because re-capture is bank-only, provenance stays pure per-sample bank metadata and `bank_model`/`view_mode_model` remain decoupled — dual-canvas compliance needed no new coupling.
|
||
|
||
#### Phase B open questions — all resolved
|
||
All five forks settled by Daniel 2026-07-23 (persistence-key fold, delete-drops-members + evacuate verb, move-as-default gesture, distinct active-bank/shown-tab with an indicator, LICE tabs + both move affordances); B5 forks R-A/R-B settled 2026-07-24. Recorded in `docs/product/removal-and-prune.md` §Fork R-A/R-B.
|
||
|
||
### Phase R — Reclaim (file lifecycle: the prune path)
|
||
|
||
> **Boundary (load-bearing):** remove creates orphans; prune reclaims them. No operation other than prune deletes a file; prune deletes only files no index references.
|
||
|
||
#### R1 — prune-reconcile core (pure)
|
||
`pruneOrphans(present, referenced, owned) → (owned ∩ present) − referenced`, filesystem-free; referenced-set unioned across the whole book so a file kept alive by any bank (including via a copy) is never an orphan; a present-but-unowned (hand-dropped) file is never reclaimed.
|
||
|
||
#### R2 — prune shell + persist wiring (filesystem I/O, thin)
|
||
Enumerates the resolved current bank folder, unions `book().referencedPaths()` + the owned-file manifest, feeds the pure core, produces a dry-run report (count/bytes/file list). No deletion this wave. Forever-stable `BANK_PRUNE_FOLDER` action registered (dry-run only in R2).
|
||
|
||
#### R3 — deletion + action (the destructive step, guarded)
|
||
Dry-run → `ShowMessageBox` confirm → staleness-intersect (`pruneDeletePlan`: confirmed ∩ fresh pure-core output) → delete exactly that plan; zero ext-state writes, no undo point (file deletion is not REAPER-undoable). `bank_panel` prune button dispatches the same registered action (no duplicate logic). **Forks settled 2026-07-24:** R-C (deletion mechanism) — trash-preferred (Windows `SHFileOperationW`+`FOF_ALLOWUNDO`), unlink fallback where no portable trash surface exists (macOS/Linux); R-D (orphan attribution) — owned-file manifest, seam landed early at B-cap rather than reconstructed at prune time; R-E (trigger) — manual action + panel button only, dry-run-first, no background sweep, no "delete-and-prune-now" convenience shipped.
|
||
|
||
#### Milestone 11 — polish (wave 1)
|
||
Batch capture (`batch_capture` pure module, one sample per selected item/razor area, mixed-result aggregation, single persist per batch); action trigger buttons + keybinding-help labels (`action_buttons` pure module, live-binding labels via `kbd_getTextFromCmd`); conform-on-insert closed as already-shipped (verified-extant, no new code). **Cut (Daniel, 2026-07-26):** resample-and-mute-source — superseded by the Design View mode projection (park/hide inactive-mode content), which made a mute-after-capture workflow redundant.
|
||
|
||
#### Milestone 11 — polish (wave 2 / completion)
|
||
Native OS drag-out: pure `drag_out` module (gesture-boundary decision at panel-edge, path-list assembly with dedupe/missing-file skip) + `drag_out_win` shell (Windows OLE `DoDragDrop`/`CF_HDROP`; macOS/Linux via `SWELL_InitiateDragDropOfFileList`). Structurally copy-only — `DROPEFFECT_MOVE` never offered, no source-deletion path exists; prune remains the sole file-deletion authority. Completes Milestone 11.
|
||
|
||
## Part 2 — Look-and-feel, and the ReaSampler 9000 instrument
|
||
|
||
### Phase L — Look-and-feel (system-wide visual design language)
|
||
Own lettered namespace (`L`), orthogonal to and ungated by the M/D/B/R/V/S pillars — a
|
||
parallel team's whole-system look-and-feel effort answering Daniel's post-DAW-test verdict
|
||
("this looks like temple os… does Cockos have a toolkit?"). The then-authoritative spec was
|
||
CONTEXT.md §Phase L. Product framing + settled decisions: `docs/product/visual-design-language.md`.
|
||
**Settled decisions (Daniel, 2026-07-26):** **DS-1** — toolkit is LICE + WDL free game, no
|
||
external frameworks (iPlug2/JUCE/VSTGUI rejected); reuse a WDL/vwnd piece where it beats
|
||
re-deriving; keep hit-test geometry in pure CTest-covered modules. **DS-2** — visual
|
||
direction Direction B ("Neon Console") + Direction C's spectral keyboard strip, **REVISED
|
||
2026-07-26 (palette-only):** neutral ladder moved from near-black into REAPER's mid-grey
|
||
family (`bg/base #2b2b2b` / `bg/panel #333333` / `bg/cell #3a3a3a` / `line/hairline #4a4a4a`
|
||
/ `text/primary #dcdcdc` / `text/dim #a8a8a8`), and a single electric-cyan accent replaced by
|
||
a **three-accent pastel system** (`accent/primary` lime `#B0E098` = live/active/selected,
|
||
`accent/secondary` teal `#84D6D0`, `accent/tertiary` purple `#C2AAE8` = categorical
|
||
distinctions); a bundled-font upgrade was considered and **declined** (no font
|
||
redistribution). **DS-3** — dock-panel scope is a thorough layout redesign, not a light
|
||
re-skin; sequenced after M11 merged. **Phase L is complete: L1–L7 and L3 all landed.**
|
||
|
||
#### L1 — shared LICE drawing kit (the foundation)
|
||
`theme`/palette module (role→color, the DS-2-revised grey ladder + three pastel accents,
|
||
WCAG contrast-floor helpers/tests, `theme_tests`); `component_geometry` (button/slider/
|
||
list-row geometry + hover hit-test, `component_geometry_tests`); `draw_kit` shell
|
||
(`fillSurface`, `drawButton`/`drawSlider`/`drawListRow`/`drawWaveform`, cached-font `text()`
|
||
over four `LICE_CachedFont`s, full interaction-state model, double-buffer preserved). GDI
|
||
`DrawText` retired from `bank_panel` — the single biggest "temple os → modern" lever.
|
||
|
||
#### L2 — dock-panel layout redesign
|
||
`action_bar` pure module (task-grouped clusters: Capture/Placement/Maintenance, label +
|
||
keybinding sub-rects, overflow, hit-test; `action_bar_tests`). `bank_panel` redesigned with
|
||
the full M11-aware button inventory placed by cluster, prune kept footer-set-apart +
|
||
`warn`-colored, entire panel drawn through the L1 kit with a single `KitColor→LICE_pixel`
|
||
boundary (`draw_kit`'s `toLice`).
|
||
|
||
#### L4 — dock-panel button layout enhancement
|
||
Three-zone re-home: top toolbar (capture/placement/maintenance — **Maintenance restored to
|
||
the top toolbar per Daniel's directive during build**, revising the original "capture +
|
||
placement only" spec), bottom toolbar (Design View tagging/switching), footer (narrow
|
||
`[Arrange|Design]` toggle · Tail button as a proper kit button · Prune set apart). New pure
|
||
`footer_bar` module (`footer_bar_tests`); `action_bar` gained `ActionCluster::Tagging`/
|
||
`Switching`. Pure re-home of existing actions — no new actions, no capture/placement change.
|
||
|
||
#### L5 — dock-panel button refinements
|
||
Rare capture variants (Batch Items/Batch Razor/Capture RT) pulled into a right-anchored
|
||
More (⋯) overflow menu (`overflow_menu` pure module + `TrackPopupMenu`); short button faces
|
||
with the `ReaSampler:` prefix dropped; a **custom LICE-kit hover-delay tooltip** (`tooltip`
|
||
pure module, sourced from the registered action phrase, not `kbd_getTextFromCmd`) chosen
|
||
over attaching a SWELL tooltip to non-child LICE rects; four Item/Track × Arrange/Design tag
|
||
buttons with opposite-mode-only enablement (`mode_enable` pure module); Activate-Arrange/
|
||
Activate-Design/Toggle buttons removed (footer toggle is the sole mode-switch affordance;
|
||
Show Both stays); cluster-gap spacing widened 16→24px.
|
||
|
||
#### L6 — toolbar polish (in-DAW feedback pass on L5)
|
||
Single-row button faces (keybinding sub-row removed, toolbar height 40→28px); keybinding
|
||
folded into the hover tooltip (`"phrase — binding"`); Cancel RT moved into the overflow menu;
|
||
visible top-bar cluster order tidied (Capture → Maintenance → Placement). Icons considered
|
||
and deferred.
|
||
|
||
#### L7 — capture ordering, card metadata, and selection styling
|
||
Persisted deterministic capture order via a per-`Bank` id→slot `SlotMap` in `bank_book`
|
||
(gap-preserving, JSON rides inside the existing `banks` blob, pre-L7 migration seeds dense
|
||
order); `BankBook` gains `reorderSample`/`replaceSample`/`orderedSampleIds`/`reconcileSlots`.
|
||
One sanctioned `Sample` change: `captureTimeSigNum`/`captureTimeSigDenom` stamped via
|
||
`TimeMap_GetTimeSigAtTime`. `card_drag` pure module (gesture precedence: leave-client→OS
|
||
drag-out, other-bank→move/copy, same-bank→reorder/Alt-replace; SWELL stock cursors per
|
||
gesture) and `card_meta` pure module (bars.beats.subdivisions + s.ms, blank when unstamped).
|
||
Sparse-grid render with gap cells; selection restyle drops inverted-fill for a normal cell +
|
||
`accent/tertiary` purple border. **M9** (capture-to-slot-N MIDI-bindable capture) stays
|
||
explicitly deferred — L7 eases a future revival but ships no slot-numbered actions or MIDI
|
||
bindings.
|
||
|
||
#### L3 — VST editor + embed-strip restyle
|
||
Brought `reasampler_editor.cpp` + `reasampler_embed.cpp` onto the L1 kit: cached-font
|
||
`text()` replacing raw GDI `DrawTextA`; the shells' **local pre-L1 palette** (forest-green
|
||
`kColBackground`/`kColCardBg`/`kColThumb`/… constants, arrived off-kit — the "born in the
|
||
kit" coordination contract did not occur) retired in favor of the L1 `theme` roles; capture
|
||
browser/toggles/ADSR+pitch sliders/zone bars/list rows/waveform all restyled through the kit;
|
||
pastel spectral keyboard strip + zone bars (active zone lifts to `accent/primary` + a static
|
||
glow, no animation). Beta title band stays a **textual-only** distinction (no channel-
|
||
specific accent color — closed the one open L3-readiness fork). VST3 class UID unchanged (a
|
||
visual refresh is not a compat event); all `src/vst/` geometry modules stayed pure — L3
|
||
touched only the two draw shells. **L3 was the last Phase L point — Phase L is complete.**
|
||
|
||
### Phase S — MIDI-playback instrument (native VST3 sampler; a second build artifact)
|
||
Landed on `dev` (merged 2026-07-27); **DAW verification pending Daniel's smoke test** at the
|
||
time of this record. The then-authoritative spec was CONTEXT.md §MIDI-playback instrument
|
||
(Phase S). Product framing: `docs/product/midi-playback.md`.
|
||
|
||
#### S3 — pure sampler core (voice engine / envelope / keymap / repitch)
|
||
The REAPER-free and VST3-free core — the heart of the phase, mirroring `bank_model`/`peaks`/
|
||
`view_mode_model`. Polyphonic voice allocation with bounded stealing; ADSR envelope math
|
||
asserted against a known signal; repitch/interpolation from root note with loop-point-aware
|
||
sustain; keymap model (key ranges/zones, (note,velocity)→sample query, chromatic-from-root
|
||
as the degenerate Tier-0 case). Core boundary enforced plain-data-only (test target links
|
||
neither SDK).
|
||
|
||
#### S4 — Tier 0: "the bank plays" (single sample, chromatic)
|
||
Wired the S3 core into the VST3 shell: `process` marshals MIDI note-on/off/velocity, drives
|
||
the core, writes to the output bus (block-granular timing at this tier). Live-state seam
|
||
reads the bank + selected sample's root note off the bridge, resolving WAV paths the same
|
||
project-relative way `persist` does (shared `capture_paths::projectDirOfRpp`; bank JSON
|
||
parsed via the shared `bank_book` path, not a spike string-scan). Minimal LICE sample-picker
|
||
list in the editor; the pick is instance-owned component state, never written to the bank.
|
||
Chromatic-from-root, 16-voice polyphony, amp envelope, velocity→volume; load/decode off the
|
||
audio thread via lock-free atomic pointer swap (graveyard-reclaim) — `process` never
|
||
allocates.
|
||
|
||
#### S5 — Tier 1: "a keymap" (zoned multisamples, per-sample root notes)
|
||
Keymap editor in the LICE editor assigns bank samples to key ranges, each with its own root
|
||
note (from S2 intrinsics, overridable in the performance map). Zoned playback resolution —
|
||
one sample per key-region, repitched from its own root. Keymap persisted as the instrument's
|
||
own VST3 component state (D-B split: bank carries file-fact defaults, instrument owns the
|
||
performance choice); the live bank seam stays read-only.
|
||
|
||
#### S6 — embedded TCP/MCP UI (D-D)
|
||
`IReaperUIEmbedInterface` implemented so a compact keymap/level strip draws inline in the
|
||
TCP/MCP, reusing the same LICE surface as the main editor; clean open/close/resize lifecycle.
|
||
Scheduled (not deferred) per D-D, sequenced last as polish over the Tier-0 need.
|
||
|
||
#### S7 — stereo channel mode (mono | stereo)
|
||
Per-instance `ChannelMode {Mono, Stereo}` as an S3-core extension (not a shell hack):
|
||
`SampleData` carries 1- or 2-channel decoded PCM, `Voice::renderFrameStereo` +
|
||
`VoiceEngine::render(left,right,n)` share one read head/envelope tick; mono stays the exact
|
||
degenerate case (byte-identical). Cross-mode decode policy: mono source + stereo mode →
|
||
dual-mono; stereo source + mono mode → downmix. Toggle lived in component state v4 (v3 +
|
||
one byte), never written to the bank. `setBusArrangements` accepted only the mode's
|
||
arrangement, with `restartComponent(kIoChanged)` on a runtime mode change — verified against
|
||
the vendored Steinberg SDK. **Superseded later:** the GA post-launch pass (recorded in Part 3
|
||
scope) deleted the dynamic mono↔stereo bus renegotiation in favor of a permanently stereo
|
||
output bus with `ChannelMode` becoming decode-only — do not read S7 as the final channel-mode
|
||
shape.
|
||
|
||
#### S8 — ingest through the bank (one gesture: capture/import into bank + assign)
|
||
Goal: the extension owns ingest (arrange/Media-Explorer/drop-target access); the instrument
|
||
stays a read-only bank consumer. Recorded **honest SDK limits** at spec time: Media Explorer
|
||
exposes only `OpenMediaExplorer` + `MediaExplorerGetLastPlayedFileInfo` — no enumerate-
|
||
selected-files, no drop-handler registration — so ME import is single-file, pull-on-action,
|
||
not push/drop; REAPER exposes no drag-drop registration API, so panel drop handling rides
|
||
ReaSampler's own HWND (`WM_DROPFILES`/`IDropTarget`), and a drop onto the VST3 editor window
|
||
was flagged as an unproven cross-artifact spike (see S13). **All four checklist items are
|
||
recorded unchecked (`[ ]`) in the source** despite the Phase S banner asserting S1–S18 landed
|
||
on dev — no confirming text elsewhere in this range resolves the discrepancy (see Open
|
||
questions).
|
||
|
||
#### S9 — bank-generation change-detection
|
||
A monotonic bank-generation counter (new `ext_keys.h` constant, forever-stable spelling)
|
||
stamped into `"reasampler"` ext-state, bumped on any bank-content mutation that changes
|
||
instance playback (capture add, recapture-in-place, remove, move/copy). The instrument polls
|
||
it off the audio thread on a UI/timer cadence and calls `reloadFromBank()` on change, reusing
|
||
S4's atomic pointer-swap handoff (no `process`-thread work, no glitch). Pre-S9 projects
|
||
default to generation 0.
|
||
|
||
#### S10 — capture-first editor: browser + guided single-capture setup
|
||
**Policy reversal (revising S4):** the "first sample plays" fallback is removed — a fresh
|
||
instance with no stored selection plays **nothing** and the editor shows an explicit
|
||
"pick a capture" empty state; loading only happens on an explicit pick (or S13 drop / S8
|
||
ingest). Default editor face reframed (Daniel, 2026-07-26) as a **capture browser**
|
||
(peak-thumbnail cards, name, root/key badge, bank filter) feeding a guided single-capture
|
||
setup (root note, play-mode basics, level), demoting the full keymap editor to an opt-in
|
||
**S10-Z Zones panel**. New pure `keyboard_strip` + `capture_browser` modules planned (mirrors
|
||
of `mode_switch`/`editor_geometry`); drag-state machine on the LICE `IPlugView`. **All
|
||
checklist items for S10 (and its S10-Z sub-section) are recorded unchecked (`[ ]`) in the
|
||
source** — see Open questions; the ±1 nudge-button row and `zoneHitTest` were slated for
|
||
retirement here regardless of check-state.
|
||
|
||
#### S11 — waveform view with draggable loop points
|
||
Selecting a zone/capture shows its waveform (via the existing `peaks` module over the
|
||
already-decoded PCM); loop-start/loop-end markers drag to set the sustain loop, snapping to
|
||
the nearest zero-crossing; a sample with no loop shows a "no loop" state. Loop points are a
|
||
performance-map override on the zone, seeded from the S2 bank intrinsic (D-B split — the
|
||
bank fact is never written back). **Boundary note (S10 reframe):** this waveform draw is the
|
||
same one S10's picked-capture view uses, built once. **All S11 checklist items are recorded
|
||
unchecked (`[ ]`) in the source** — see Open questions.
|
||
|
||
#### S12 — editor scale + ergonomics
|
||
**Boundary note:** the "sample list" S12 was to scroll/search became S10's capture browser;
|
||
S12 narrowed to (a) scroll + type-to-filter search layered over that browser and (b) numeric
|
||
entry + ADSR, and additionally absorbed the S15/S16 control surfaces (deferred to here per
|
||
spec). **Landed (all three checklist items `[x]`):** pure `browser_scroll` module (scroll/
|
||
search geometry, `browser_scroll_tests`); pure `note_entry` module (`parseNoteEntry` accepts
|
||
decimal or note-name input, clamps to [0,127], `note_entry_tests`); pure `param_slider`
|
||
module (control-panel stack/toggle/slider geometry + point→control routing,
|
||
`param_slider_tests`) driving AHDSR (hold-stage addition) plus the S15/S16 controls (Gate/
|
||
Trigger toggle, Trigger %-length/fades, Varispeed/Preserve toggle, AD pitch-envelope
|
||
enable/attack/decay/depth) on the selected zone's `ZonePlayParams`, committed via
|
||
`commitAndReload`. **Standing ruling (Daniel), enforced here:** wall-clock envelope times are
|
||
stored as rate-free **seconds** (zones payload v5), resolved to frames at keymap build
|
||
against the live project rate — no hardcoded sample rates in `src/`.
|
||
|
||
#### S13 — drop-to-load (partial landing; relay deferred)
|
||
**Spike verdict (ps-w12, 2026-07-27): DEGRADED.** The cross-artifact relay would require a
|
||
new instrument WRITE seam into ext-state plus an extension-side timer-poller/claim-nonce
|
||
handshake — the same race the S17 spec rejected — so it was explicitly deferred (remains in
|
||
`PLAN.md` §S13); the shipped ingest gesture stays drop-onto-docked-panel (S8). **Landed:**
|
||
the editor child window accepts `WM_DROPFILES`/`IDropTarget` (Windows-only, D5) and, since no
|
||
relay exists, shows a transient banner + persistent empty-state affordance directing the user
|
||
to drop onto the ReaSampler bank panel instead — no file is ever ingested from the editor
|
||
drop, and no timeline item is ever inserted.
|
||
|
||
#### S15 — sampling modes: Trigger vs Gate
|
||
Per-sample/per-zone performance choice (D-B), an S3-core extension. **Gate** grows ADSR to
|
||
AHDSR (`holdFrames`, `==0` is exactly pre-S15 behavior — back-compat, no type rename),
|
||
sustain loop points apply. **Trigger** is a one-shot: plays `[start, start+lengthFraction·
|
||
(frames−start))` with equal-power fade-in/fade-out, ignores note-off entirely, no sustain
|
||
loop; choke-on-note-off explicitly out of scope (fork S15-F1, held). Both modes gain a
|
||
modifiable **start point** (playback offset). Mode + params live in the performance map;
|
||
truncated/older blobs default to Gate/hold=0/start=0 (today's behavior unchanged). Editor
|
||
control surface deferred to S12 (spec-sanctioned).
|
||
|
||
#### S16 — pitch engine modes (Varispeed vs Preserve) + pitch envelope
|
||
Per-zone `PitchEngine {Varispeed, Preserve}`. Varispeed = today's coupled pitch/duration
|
||
path. Preserve = duration-preserving repitch via a hand-rolled pure OLA `pitch_shift` module
|
||
(house pattern, `pitch_shift_tests`; `WDL_SimplePitchShifter` excluded by an include-chain
|
||
conflict with `windows.h`, held as a future quality/latency swap behind the same contract). A
|
||
per-voice AD pitch envelope (`enabled=false` default) multiplies the Varispeed ratio or adds
|
||
to the Preserve shift amount. **S16-F1: default engine is Preserve** (Daniel: "I want
|
||
duration-preserving repitching"), per-zone togglable back to Varispeed. Editor exposure
|
||
deferred to S12.
|
||
|
||
#### S17 — drop-and-load: drag a capture onto a track's FX button
|
||
Dragging a single capture over a track's TCP FX button lights it as a drop zone; dropping
|
||
instantiates ReaSampler 9000 on that track with the capture already loaded and selected.
|
||
Third `drag_out` gesture `InstrumentDrop` (only arms for a **single** capture — multi-payload
|
||
over REAPER UI falls through to `OsDrag`, per a resolved open question). Mechanism:
|
||
`TrackFX_AddByName` + a Steinberg-format `.vstpreset` built by the new pure `instrument_drop`
|
||
module from the instrument's own `sample_map::serializeComponentState` (one serializer, two
|
||
artifacts — the byte format cannot drift), applied via `TrackFX_SetPreset`. **The originally
|
||
planned `TrackFX_SetNamedConfigParm` "vst_chunk" write proved silently unappliable for VST3**
|
||
and was replaced by the `.vstpreset` + `TrackFX_SetPreset` path (GA DAW-fix pass, 2026-07-28).
|
||
FX-button hotspot resolved via `GetThingFromPoint` (`"fx_chain"`/`"fx_N"` info strings), not
|
||
home-grown geometry. Batched into one undo point; never inserts a timeline item.
|
||
**VERIFIED (GA DAW-fix pass, 2026-07-28).**
|
||
|
||
#### S18 — VST3 channel isolation (beta ReaSampler 9000 pairs only with beta extension)
|
||
Extends V4's beta/stable split to the VST3 instrument: a second FOREVER-STABLE class UID
|
||
(`REASAMPLER_PROC_UID_BETA_1..4`) alongside the existing stable UID, selected at compile time
|
||
by the single `REASAMPLER_CHANNEL_IS_BETA` bit (one class per binary, both UIDs frozen
|
||
forever); binary name forks via CMake `OUTPUT_NAME` (`reasampler_9000` / `reasampler_9000_beta`);
|
||
display name/version-string channel-aware via `app_version::vstPluginName()`. Pairing-surface
|
||
invariant recorded structurally (all wire keys live under the channel-derived
|
||
`kProjExtNamespace()`, so a key that forgets to isolate is impossible by construction) rather
|
||
than newly coded.
|
||
|
||
#### S-VIEW-BUG-1 — drop-to-FX bug fix (Wave 1)
|
||
Root cause: the pure `instrument_drop` FX-hotspot classifier matched only `fx_*` strings, so
|
||
a drop on the TCP (`tcp.fx`) or MCP (`mcp.fx`) FX button fell through to `OsDrag` (arrange-
|
||
as-audio) instead of instantiating the instrument. Fix: predicate widened to match `fx_*` /
|
||
`tcp.fx` / `mcp.fx`, unit-tested at each hotspot string; shell unchanged. **DAW-confirmation
|
||
recorded as pending** Daniel's post-merge smoke test at the time of this entry.
|
||
|
||
#### S-VIEW-SIZE-1 — 1080p default window size (Wave 1, interim)
|
||
Default editor `ViewRect` bumped from 560×400 to **840×560** with a `checkSizeConstraint`
|
||
minimum floor, verified against the vendored `pluginview.h` mechanism. **Explicitly interim**
|
||
— recorded as the correct starting point, not the final tuned value; final tuning deferred to
|
||
a later wave (T-SHELL) once the three concrete Sample-face band heights exist.
|
||
|
||
---
|
||
|
||
## Part 3 — Editor redesign, GA fixes, and the Phase Q structural reorganization
|
||
|
||
### Phase S — editor view-model redesign (three views: Sample / Browse / Zone)
|
||
Additive Phase S sub-phase (S-VIEW; Daniel, 2026-07-27, r9). Retired the flat Browser|Zones
|
||
toggle for a three-view model — Sample (home/default), Browse (full-window modal overlay over
|
||
Sample, S-VIEW-F3 settled), Zone (dedicated surface, own button). Landed across three waves
|
||
(pure modules → shell wiring → velocity-curve editor UI); **S-VIEW complete — all ten points
|
||
landed**, integrated suite green.
|
||
|
||
#### S-VIEW-1 — three-view navigation model
|
||
Flat toggle retired; three-view model landed. Empty state surfaces Browse as the dominant
|
||
call-to-action; fresh instance stays silent (S10 reversal preserved). S-VIEW-F3 settled and
|
||
implemented: Browse is a full-window modal overlay over Sample.
|
||
|
||
#### S-VIEW-2 — Sample view (the new main face)
|
||
Composed the home face: hero waveform with the S11 markers (moved from Browse), a fenced root
|
||
affordance, the Mono/Stereo toggle (moved from Browse), and the "modes-and-down" control strip
|
||
(Mode / Pitch engine / AHDSR|Trigger / AD pitch env, moved from Zone's param panel — single-
|
||
capture one-zone storage per S15-F2). Reference grammar: Simpler / Phase Plant.
|
||
|
||
#### S-VIEW-3 — envelope overlay + draggable nodes
|
||
Amp envelope (AHDSR for Gate, fade/%-length for Trigger) drawn as a curve over the Sample
|
||
waveform at accurate wall-clock time. New pure `envelope_overlay` (params+frame-length→polyline)
|
||
and `envelope_edit` (node hit-test + pixel-delta→clamped-param inverse map) modules, both
|
||
unit-tested. Breakpoints are draggable (S-VIEW-F2 settled): X→segment time, Y→level on
|
||
level-breakpoint nodes (sustain drags both axes), monotonic-in-time + range-clamped. The
|
||
Trigger frames↔fraction conversion was extracted to a new pure `trigger_seam` module.
|
||
|
||
#### S-VIEW-4 — preview-trigger + velocity knob
|
||
A button fires the sampler at the loaded capture's root note through the live voice engine
|
||
(off the audio-thread commit path) plus an adjacent velocity knob. Preview velocity PERSISTS
|
||
(S-VIEW-F1 settled): `previewVelocity` on `ComponentState`, envelope v5→v6, round-tripped via
|
||
`getState`/`setState`; zones payload untouched, older blobs lift to a mid default.
|
||
|
||
#### S-VIEW-5 — Browse reduced to choosing (modal over Sample)
|
||
Search + bank tabs + captures grid + scroll + selection retained; confirm/cancel added
|
||
(double-click loads). Large waveform preview, Mono/Stereo toggle, root keyboard-strip (all
|
||
moved to Sample), and loop-point labels + track-root message (cut) removed. Renders as a
|
||
full-window modal overlay (S-VIEW-F3 settled).
|
||
|
||
#### S-VIEW-6 — key-tracking parameter
|
||
Per-`PerformanceZone` scalar on keyboard pitch tracking around the root (100% = 12-tone-ET, 0%
|
||
= no tracking, 200% = double); additive, defaults 100% (bit-identical). Key-track math lives in
|
||
the pure sampler core, applied in both Varispeed and Preserve repitch; zones payload v6.
|
||
Surfaces on the Zone param panel + Sample control strip.
|
||
|
||
#### S-VIEW-7 — piano-key pattern on the keyboard strip
|
||
Alternating white/black (bright/dark per palette) key pattern overlaid on the pastel spectral
|
||
fill via a pure `isNaturalKey` predicate (12-tone, unit-tested) on `keyboard_strip`. Shared by
|
||
the Zone strip and Sample root affordance.
|
||
|
||
#### S-VIEW-8 — Zone view retained + wired
|
||
+Add Zone / Delete, the per-zone keyboard strip (piano pattern), the Low/High/Root numeric-entry
|
||
legend, and the per-zone param panel all retained; key-tracking control added. Nothing from the
|
||
prior Zones view dropped.
|
||
|
||
#### S-VIEW-9 — velocity→amp transfer curve (pure core + engine application)
|
||
New pure `velocity_curve` module: eval via a Fritsch–Carlson monotone cubic Hermite spline (no
|
||
overshoot outside [0,1]) + control-point editing (add/move/delete x-ordered + box-clamped) +
|
||
hit-test + pixel-delta→clamped-point inverse map. Additive `velocityCurve` field on
|
||
`PerformanceZone`, zones-payload v7; default = **flat y=1** (R10-F1 settled, Option A, Daniel
|
||
2026-07-27: "any velocity plays at full level"); older ≤v6 blobs lift to flat y=1. Applied at
|
||
`Voice::start()`, replacing `velocityGain_ = velocity/127.0` with `curve.eval(velocity)`.
|
||
**Default is a deliberate non-back-compat behavior change** — existing zones' soft hits play at
|
||
full level after upgrade; flagged and accepted by Daniel.
|
||
|
||
#### S-VIEW-10 — velocity-curve editor UI (shell, Sample + Zone views)
|
||
Draggable transfer-curve editor rendered through the L1 kit in both the Sample view (curve box
|
||
beside the hero band) and the Zone per-zone param panel: `eval` polyline over a 0–127 × 0–1 box
|
||
with draggable control points — add on empty-click, move, Alt-click delete interior, drag-off-box
|
||
delete with a warn-state affordance. Coordinate math added to `velocity_curve` (`pixelFromPoint`/
|
||
`pointFromPixel`); reads/writes the existing `velocityCurve` field — zones payload stays v7, no
|
||
schema change. Mirrors the S-VIEW-3 envelope-node interaction grammar (snapshot-at-grab →
|
||
off-audio-thread commit).
|
||
|
||
#### Phase S — product name (ReaSampler 9000)
|
||
The MIDI-playback instrument's product name is **ReaSampler 9000** (Daniel, 2026-07-26, on
|
||
DAW-testing the S1–S6 instrument); the extension remains **ReaSampler**. Propagated across the
|
||
VST3 class display-name string, the `IPlugView` editor title band, the S6 embed-strip label, and
|
||
Phase S docs. **Do not change the VST3 class UID** — already-saved-project instances key off it.
|
||
**S-NAME-1 settled (Daniel 2026-07-26):** the built binary filename (CMake `OUTPUT_NAME`, e.g.
|
||
`reasampler_9000.vst3`) was renamed alongside the display strings so the on-disk name matches;
|
||
the VST3 class UID stayed unchanged as the compat anchor.
|
||
|
||
S-VIEW-1 through S-VIEW-10 rolled up as fully landed (Wave 1 core + Wave 2 shell + Wave 3
|
||
velocity-curve editor UI).
|
||
|
||
### Phase D2 — Two-canvas (item-level mode projection; additive to D1)
|
||
Extends D1's track-level mode projection to **item level** via REAPER 7 fixed lanes: on a track
|
||
present in both stances, each mode owns a fixed lane — the active mode's lane shows/plays, the
|
||
inactive mode's is hidden/silenced — so a Design take and an Arrange take share a track/time
|
||
position without colliding on the view. Nothing in D1 changes; runtime floor rises to **REAPER
|
||
7** for this sub-phase (no version-gate branch — simply unavailable below v7). D2-W1 (pure lane
|
||
extension), D2-W2 (shell: lane application + new-content detection), D2-W3-A (lane minting +
|
||
item→lane assignment + persist round-trip), and D2-W3-B (item-level mode actions + polish) have
|
||
all landed — **Phase D2 is functionally complete.** **Deferred, not dropped:** a per-track
|
||
lane-split marker in the panel UI — the mode switch already shows the active mode; no natural
|
||
cheap home for a per-track indicator was found.
|
||
|
||
### Milestone 9 — slots (MPC-style)
|
||
**Abandoned (Daniel, 2026-07-27) — will not be built.** Goal had been "capture to slot N" /
|
||
"insert slot N", MIDI-bindable, with slot state persisted via the index.
|
||
|
||
### Post-S-VIEW DAW-fix pass + voice-system redesign (2026-07-27)
|
||
Merged to dev; integrated suite 52/52 green. Found during Daniel's post-S-VIEW DAW testing
|
||
(fixes) and a subsequent voice-redesign directive — not PLAN-tracked.
|
||
|
||
- **Envelope node editing:** every Gate stage (A/H/D/S/R incl. a visible in-bounds Release) and
|
||
Trigger's zero-fade-out node are now fully grabbable in both modes; `envelope_edit` uses
|
||
param-domain schematic scaling (8px minimum node separation enforced by the forward map in
|
||
`envelope_overlay`), all nodes clamped in-canvas. Previously only a subset was editable.
|
||
- **Gap-free waveform render:** `columnMinMax` moved to `peaks`; `waveformColumnCount` moved to
|
||
`component_geometry`; dock panel and VST editor now share one gap-free per-column min/max
|
||
algorithm via `draw_kit::drawWaveform`.
|
||
- **Radial Knob primitive:** `param_slider` gained a `Knob` control kind (7→5 o'clock arc, needle,
|
||
vertical-drag) — foundation for the later Wave B control deck.
|
||
- **Zone-bleed fix 3a:** `reconcileSingleCaptureZones` added to `sample_map`, fixing stale
|
||
full-range zone shadowing so the engine plays the zone the editor draws; called on load
|
||
(`setState`), bank-assign, and capture-confirm.
|
||
- **Voice-system redesign (Daniel directive):** `sampler_core` gains user-parameterized voice
|
||
count (1–32, default 16), `VoiceMode` Poly/Mono (last-note held-note stack, `MonoTrigger`
|
||
Retrigger/Legato toggle), an isolated `PreviewCard` (dedicated preview voice, never steals
|
||
from/into the MIDI pool), two-tier panic (CC123 release / CC120 immediate hard-stop incl.
|
||
Trigger one-shots). Processor sums `PreviewCard` alongside engine+drain; `retireIdleDrain()`
|
||
retires fully-idle drain snapshots on the UI-timer cadence; voice-param setters rebuild the
|
||
engine via the drain-slot swap (no bank re-read, no WAV re-decode, ringing tails not cut).
|
||
`ComponentState` envelope bumped **v6→v7** (voiceCount/voiceMode/monoTrigger bytes); pre-v7
|
||
blobs lift to `{16, Poly, Retrigger}` — reproduces pre-Phase-S behavior exactly.
|
||
|
||
### FB1 — Sample-view recomposition + master gain (r11; 2026-07-27)
|
||
Merged to dev; integrated suite 55/55 green. Closes S-VIEW-11 + S-VIEW-12 from the Wave B plan
|
||
(S-VIEW-13 stays open, deferred to FB2). Recomposed the Sample face after Daniel's post-landing
|
||
DAW pass. Three new pure modules: `knob_deck` (group-box/caption-row/compact-toggle/knob-cell
|
||
geometry, deterministic whole-group wrap), `curve_popup` (centered-sheet geometry, width/height
|
||
clamps, outside-sheet dismissal test), `master_gain` (dB↔linear taper math for a new −∞…+24dB
|
||
post-mixer master gain, norm 0 = true silence, unity ≈0.714 normalized — shared by the editor
|
||
knob and the processor multiply so they cannot drift). Sample control strip rebuilt as five
|
||
fenced groups: AMP ENVELOPE (Gate|Trigger toggle), PITCH (Varisp|Preserve + Key Track), PITCH ENV
|
||
(Off|On), VOICE (Voices/Poly|Mono/Retrig|Legato), MASTER (Gain). Hero waveform now runs
|
||
full-width/elastic (840×620 default preserved, floor 150px — **R11-F1 settled** at build, no
|
||
window-size change required). New 28×28 curve preview button opens a `curve_popup`-geometry
|
||
centered sheet; right-click on a node deletes it (endpoint-guarded). New persisted
|
||
`masterGainLinear` field, `ComponentState` **v7→v8**, pre-v8 blobs lift to unity gain; processor
|
||
applies it as a per-sample ramp (no zipper noise).
|
||
|
||
**Decision:** persistence was **NOT zero-change**, contrary to the original r11 spec description
|
||
of a pure view recomposition — CONTEXT.md's "zero component-state change" clause was corrected to
|
||
record the v8 bump and the master gain field.
|
||
|
||
### FB2 — Zone-panel parity (r11; 2026-07-28)
|
||
Merged to dev; integrated suite 55/55 green. Closes S-VIEW-13 / R11-F2. Zone param panel rebuilt
|
||
on the same `knob_deck` + `curve_popup` grammar as the Sample face (FB1); `param_slider`'s linear
|
||
slider rows retired on the Zone panel — the `Knob` primitive is now the only live consumer of
|
||
that half of `param_slider`. Zone-authoring affordances (+Add Zone/Delete, per-zone piano-key
|
||
strip, Low/High/Root legend) preserved unchanged; VOICE and MASTER stay Sample-only
|
||
(per-instance). No new pure modules required. **This completes the r11 editor recomposition
|
||
(Phase S Wave B)** — S-VIEW-11/12/13 all landed, no open r11 or Wave B items remain in PLAN.md.
|
||
|
||
### GA post-launch DAW-fix pass (2026-07-28)
|
||
Merged to dev; integrated suite 55/55 green. Six fixes found during GA DAW testing, not
|
||
PLAN-tracked.
|
||
|
||
- **Preserve pitch engine rewritten to SOLA:** `pitch_shift`'s dual-tap OLA (taps hard-locked half
|
||
a window apart — fixed relative phase caused anti-phase cancellation on many source
|
||
frequencies, spectral garbage on repitched notes) replaced by **correlation-aligned SOLA
|
||
splices**: one active read tap chases the write head at the shift ratio, each splice jump
|
||
refined by a cross-correlation search so the new read point is waveform-aligned, then old and
|
||
new taps crossfaded with a raised-cosine amplitude-complementary fade. Clean pitch shift past
|
||
+24st; a repitched pure sine stays a single tone.
|
||
- **Voice takeover declick:** `Voice::start` gained a `takeoverDeclick` parameter (gated on the
|
||
envelope complement) removing the click at mono retrig/fallback and poly at-cap steal
|
||
boundaries. The "steal-all" DAW symptom was diagnosed as no-loop sample exhaustion under
|
||
Preserve (voices playing to silence, not being stolen) — the engine steals exactly one voice
|
||
per note-on as designed.
|
||
- **Stereo bus pin + channel-mode auto-default:** output bus made **permanently stereo** — the
|
||
dynamic mono↔stereo bus renegotiation (which hard-panned dual-mono) is deleted. `ChannelMode`
|
||
becomes decode-only (downmix vs dual-mono on WAV decode); channel mode auto-defaults from the
|
||
loaded capture's channel count via pure `channelModeFor()` unless the user has explicitly
|
||
toggled it (explicit toggle latches the preference). `ComponentState` **v8→v9**:
|
||
`channelModeExplicit` bool added; pre-v9 blobs treat the stored mode byte as an explicit
|
||
preference (no auto-override).
|
||
- **Drop-to-FX injection fix:** injection switched from `TrackFX_SetNamedConfigParm(...,
|
||
"vst_chunk", <base64>)` (silently unappliable for VST3 — REAPER's wrapper cannot accept
|
||
unframed component-state bytes) to a Steinberg-format `.vstpreset` file whose 'Comp' chunk is
|
||
the serialized component state, applied via `TrackFX_SetPreset`. FX hotspot now prefix-matches
|
||
`tcp.fx*`/`mcp.fx*`/`fx_*` (embed strip tokens excluded). `reasampler_uid.h` split out of
|
||
`reasampler_vst.h` as an SDK-free header so `instrument_drop` can derive the class-ID hex string
|
||
without the VST3 SDK.
|
||
- **Drag-out arm fix:** `SetCapture` moved to the drag-arm branch of `handleClick` in `bank_panel`
|
||
so a first straight-out drag (pointer leaving the client rect before a second click) correctly
|
||
arms and fires the OS drag-out.
|
||
- **Panel mode-toggle repaint:** the Design/Arrange mode-toggle actions now call
|
||
`bankPanelInvalidate()` after applying the mode switch, so the panel footer reflects a mode
|
||
change fired from the Actions list or a keybinding, not only a button click.
|
||
|
||
### GA2 fix pass (2026-07-28)
|
||
Merged to dev; integrated suite 55/55 green. Two fixes found during GA DAW testing, not
|
||
PLAN-tracked.
|
||
|
||
- **Preserve pitch engine — ring prime:** `pitch_shift` now primes its ring buffer with the
|
||
actual upcoming source at note-on (was zero-filled): gap-free frame-0 onset (the ~25ms Preserve
|
||
onset latency is gone — Preserve now speaks on frame 0, matching Varispeed), clean repitch
|
||
across the full C1–C8 range, and a real-content-bounded tail (the prior last-window
|
||
tail-truncation that clipped the decay is gone).
|
||
- **Takeover declick rev-2/3 — bounded blend, supersedes the GA fix:** the declick mechanism is
|
||
now a bounded blend (`out*(1-w) + ref*w`, w decaying from 1.0 over the blend window) applied at
|
||
every takeover boundary — mono retrig/fallback, poly at-cap steal, and **preview re-trigger**.
|
||
This supersedes the GA pass's `(1-amp)` envelope-complement gate, which zeroed the compensation
|
||
on Trigger and zero-attack restarts, leaving an audible click. The preview card is now covered
|
||
by the same mechanism.
|
||
|
||
### Preview via real MIDI note path + self-contained playback (pS; 2026-07-28)
|
||
Merged to dev; integrated suite 55/55 green. Architecture corrections from DAW testing, not
|
||
PLAN-tracked.
|
||
|
||
- **`PreviewCard` retired.** Preview now injects a synthetic note-on at the loaded capture's root
|
||
note into the main `VoiceEngine` (the same path host MIDI uses), so preview obeys
|
||
polyphony/mono/voice-stealing/envelopes. `sampler_core` no longer has a `PreviewCard`; the
|
||
processor no longer sums a separate preview voice. The unity-Varispeed-bypass demotion (added
|
||
in GA2 for the PreviewCard) is removed — GA2's primed shifter speaks on frame 0 anyway.
|
||
- **Self-contained playback.** ReaSampler 9000 no longer depends on the extension being loaded to
|
||
play. `ComponentState` bumped v9→**v10** with a `SampleRefs` table: per referenced sample, the
|
||
instance owns a project-relative path + decode intrinsics (root note, loop points, channels,
|
||
displayName) — NOT a copy of the audio. `reloadInstrument()` decodes directly from those refs
|
||
(bank-free — plays with the extension absent). The bank/bridge is now a browser source only:
|
||
loading a capture copies its reference into the instance's `SampleRefs`. The reopen-heal
|
||
apparatus (retry timer, editor-gated poll-to-play) is removed. Pre-v10 blobs lift to empty refs
|
||
and re-save self-contained; a bounded legacy lift covers migration.
|
||
|
||
### Q-W0 fix-now remediations — closes Q-W0 (2026-07-28)
|
||
Merged to `phase-q` (commit `546927e`). Landed six Daniel-approved fix-now remediations from the
|
||
Q-W0 pre-restructure functional + DSP quality audit — T1-01 (linked-lag stereo SOLA + follower
|
||
self-heal fallback), T1-03 (playable-span prime bound), T1-09 (declick dead-state removal),
|
||
T2-01a (provenance wire-cursor hardening backport), T3-01 (rate-derived gain ramp), T3-03
|
||
(rate-derived fade ceiling) — plus seven review riders. **Closes Q-W0 entirely** (audit, triage,
|
||
sign-off, and remediation all complete) and opens Q-W1.
|
||
|
||
### Phase Q — 1.0 structural reorganization
|
||
Reorganized all of `src/` into `core/` (pure, subsystem-namespaced: `model`/`view`/`capture`/
|
||
`audio`/`ui`/`reclaim`/`version`/`json`/`util`/`wire`/`instrument/{engine,map,ui}`), `shell/`
|
||
(REAPER/host-facing: `capture`/`panel`/`view`/`persist`/`actions`/`instrument`/`bank_ops`), and
|
||
`app/` (`main.cpp`), splitting several god-modules along the way under a soft ~600-line-per-TU
|
||
ceiling. Suite reached 60/60 at Q-W1, 61/61 for the remaining waves.
|
||
|
||
#### Q-W1 — safe opener: `core/json` extraction + directory/namespace layout (2026-07-29)
|
||
Merged to `phase-q`, 60/60 green. Extracted pure `core/json` (`Reader`/`Writer`), deleting the
|
||
five hand-rolled JSON decoders (`bank_model`/`bank_book`/`view_mode_model`/`owned_manifest`/
|
||
`tail_control`) — round-trip byte-identical to before. Collapsed the wire-`Cursor` family into
|
||
`core/wire`; added a shared `readFileBytes` helper; extracted `slot_map` from `bank_book`.
|
||
Relocated ~50 clean modules into the `core/{model,view,capture,audio,ui,reclaim,version,json,
|
||
util,wire}` / `core/instrument/{engine,map,ui}` / `shell/{capture,panel,view,persist,actions,
|
||
instrument}` layout; `main.cpp` moved to `app/`. Unified `ui::Rect` + `contains()` (the
|
||
XYWH-vs-LTRB fork retired, `footer_bar.h`'s "NAME NOTE" collision workaround gone); `clamp01`
|
||
deduplicated. Naming riders: survivor parser minted as `json::Reader`/`Writer`; `BankIndex`
|
||
renamed `BankModel`. `reasampler_uid.h` relocated to `core/wire/`. Interim `core/namespaces.h`
|
||
shim added for six not-yet-split god TUs (each downstream split wave retires its own includes).
|
||
**Riders explicitly skipped/deferred:** T4-22 (`hitIndex` hit-test template), T4-06
|
||
(`view_mode_model` planner split), T4-09 (`view_lanes` split). **Open residual:** `bank_book.cpp`
|
||
still 737 LOC — the serialize/deserialize seam identified but blocked on a `nameKey` linkage
|
||
design decision, escalated to Daniel and pending as of 2026-07-29 (resolved in Q-W5).
|
||
|
||
#### Q-W2 — split `bank_panel.cpp` (2026-07-29)
|
||
Merged (`pq-w2-panel`), 61/61 green, reviewed-approved. Split the largest extension god-module
|
||
(3459 LOC at the Q-W0 census, 8+ responsibilities) into eight TUs under `shell/panel/`:
|
||
`panel_render`, `panel_thumbnails`, `panel_audition` (direct call-through, never virtual —
|
||
preview idle path unchanged), `panel_input`, `panel_bank_ops`, `panel_window`, plus two new seams
|
||
(Q-5 settled reshape, Daniel 2026-07-28) `panel_layout` (toolbar/footer/menu rects + row/cluster
|
||
builders + region geometry) and `panel_drag` (the card-drag/hover state machine, mirroring the
|
||
pure `card_drag`) — without which `panel_render` (~700) and `panel_input` (~800) would have
|
||
shipped over the ceiling. `bank_panel.h` split alongside (Interface Segregation).
|
||
`panel_bank_ops` becomes the single home for bank-CRUD verbs that Q-W4 dedupes `actions.cpp`
|
||
against. **Recorded ceiling overages (reviewer-endorsed, comment-volume driven, non-comment lines
|
||
~322–369):** `panel_input.cpp` 636, `panel_render.cpp` 613, `panel_state.h` 608 — no honest seam
|
||
remained, bisection rejected. **Review note for Q-W4:** `panel_bank_ops`'s verbs still embedded
|
||
prompts/panel-state nudges; Q-W4's dedupe needed promptless inner verbs, not a call-site swap.
|
||
**DAW-smoke-tested and passing (Daniel, 2026-07-29); the full panel-parity verification batch
|
||
was not executed.**
|
||
|
||
#### Q-W2v — split the VST god-modules (2026-07-29)
|
||
Merged (`pq-w2v-vst`), 61/61 green, reviewed-approved. Closed the audit's structural scope gap —
|
||
the VST artifact's god-modules had no owning wave. Split `reasampler_editor.cpp` (3065 LOC,
|
||
largest file in the repo) into eight TUs along the Sample/Browse/Zone face axis: `editor_session`,
|
||
`editor_controls`, `editor_layout` (pure-candidate hoist into the existing pure home,
|
||
`editor_geometry` — discharges T2-06's stranded-layout-math finding), `editor_paint_sample`,
|
||
`editor_paint_browse_zone`, `editor_input_sample`, `editor_input_browse_zone`, `editor_platform`.
|
||
Split `reasampler_processor.cpp` (1164 LOC) into `processor_state` / `processor_reload` /
|
||
lifecycle+`process()` (kept whole, no virtual seam added to the atomic-pointer-swap reload
|
||
pattern). Split `sample_map` into resolution core vs the `component_state_io` binary codec (+
|
||
header split, T4-13 ≡ T2-07) — the extension's preset-blob path stops linking the whole voice
|
||
engine to serialize one blob. **`sampler_core.cpp` stays whole (968 LOC) — a documented,
|
||
deliberate exception to the ~600 ceiling** (per-voice-per-sample envelope ticks need same-TU
|
||
inlining to let the compiler inline the stack; no LTO in the build; a by-class split would blow
|
||
out the dispatch); its header splits into `zone_params.h` + `sampler_core.h`. The `core/wire` LE
|
||
byte-codec template (`putLE`/`readLE`) lands here with its biggest consumer. The `src/vst/`
|
||
directory is gone — all VST sources now live under `core/instrument/` and `shell/instrument/`.
|
||
**Deferred/known:** `component_state_io.h` still transitively includes `sample_map.h`→
|
||
`sampler_core.h`; the `engine` namespace is deferred (`sampler_core` stays flat `reasampler`);
|
||
capture-side LE rewires left for the capture family. **DAW-smoke-tested and passing (Daniel, 2026-07-29); the full editor/processor-parity
|
||
verification batch was not executed.**
|
||
|
||
#### Q-W3 — split `main.cpp` (2026-07-29)
|
||
Merged (`pq-w3-main`), 61/61 green, reviewed-approved. Reduced `main.cpp` (1897 LOC at the Q-W0
|
||
census) to API pointers + `ReaperPluginEntry` + dispatch by hoisting four TUs (T4-02 reshape,
|
||
settled with Q-5, Daniel 2026-07-28 — the planned three left `capture_orchestrator` over the
|
||
ceiling): `capture_orchestrator`, `capture_batch` (fourth hoist, landing `capture_orchestrator`
|
||
~450), `scope_resolve`, `realtime_lifecycle`. `FxBypassGuard` moved out but stays a stack RAII
|
||
object (precision-critical); the realtime idle tick stays a single pointer test. Q-W0 riders
|
||
landed in this wave: **`ICaptureBackend` deleted** (T4-26) — one deriver, zero polymorphic call
|
||
sites; `OfflineRenderBackend` becomes concrete; the CLAUDE.md/CONTEXT "two backends behind one
|
||
interface" description corrected in the same commit. Shared `stampCaptureSample` capture-epilogue
|
||
dedupe (T2-09); `makeUniqueTag` fixed with a per-session monotonic counter (T1-11 — same-second
|
||
batch captures previously collided silently). Naming rider (Q-9, settled — Daniel 2026-07-28):
|
||
pure module takes the stem `capture_realtime`, shell takes the suffix (mirroring
|
||
`drag_out`/`drag_out_win`) — renamed from `realtime_record`; `capture_realtime_finalize` split in
|
||
the same surgery. WAV/RIFF consolidation rider (audit §4e, settled): one pure `wav_codec` owner
|
||
(walker+layout+build+patch) absorbing `ingest.cpp`'s WAV/PCM build helpers (ingest drops to ~500,
|
||
gains a test target). `wav_codec_tests` replaces `wav_trim_tests`; `capture_realtime_tests`
|
||
replaces `realtime_record_tests`. **Known open:** `wav_trim.h`'s transitional forwarding shim
|
||
still has three live includers (`sample_map.h`, `editor_session.cpp`, `processor_reload.cpp`);
|
||
`ingest.cpp` trimmed to 567 LOC but keeps the `namespaces.h` shim. **DAW-smoke-tested and passing (Daniel, 2026-07-29); the full
|
||
null-test/bit-identical-repeats/capture≠placement verification batch was not executed.**
|
||
|
||
#### Q-W4 — split `actions.cpp` + dedupe bank verbs against `panel_bank_ops` (2026-07-29)
|
||
Merged (`pq-w4-actions`), 61/61 green, reviewed-approved. Split the two unrelated command-id
|
||
families in `actions.cpp` (1016 LOC at the Q-W0 census — T4-03, no reshape needed) into
|
||
`design_view_actions`, `bank_actions`, `prune_action` (keeping the `doBankPruneFolder` deletion
|
||
authority contract intact, routing to `persist`'s `prune_fs` after Q-W5). Deduped
|
||
`actions.cpp`'s own `promptText`/`mintBankId` and bank verbs against the Q-W2 `panel_bank_ops`
|
||
single owner — bank verbs reshaped to **promptless inner verbs** (one mutation home, two UX
|
||
skins: panel and actions each keep their exact prior UX); `promptText` renamed
|
||
`promptBankName`; `persistBankOp`/`persistBook` gain null-session guards. `prune_action` verified
|
||
a clean deletion-authority isolate (no `Undo_*`, no ext-state writes). Command-id
|
||
suffixes/display phrases verified byte-identical in review (FOREVER-STABLE). **Review 🟡
|
||
(resolved in Q-W6):** two session pointers / a null-session-as-model-rejection misreport
|
||
(unreachable today). **DAW-smoke-tested and passing (Daniel, 2026-07-29); the full per-wave
|
||
verification batch was not executed.**
|
||
|
||
#### Q-W5 — split `persist.cpp` (isolate the single file-deletion authority into `prune_fs`) (2026-07-29)
|
||
Merged (`pq-w5-persist`), 61/61 green, reviewed-approved. Split `persist.cpp` (852 LOC at the
|
||
Q-W0 census, 5 responsibilities) into `session` (lifecycle+poll, `BeginLoadProjectState` reload
|
||
hook), `ext_state_io` (ext-state ↔ JSON serialization bridge + GUID minting + folder relocation),
|
||
and **`prune_fs`** (prune scanning + `deleteOrphanFile` via `SHFileOperationW`) — the split
|
||
**concentrates** the byte-deleting authority into one obvious module (verified tree-wide as
|
||
exactly one anonymous-namespace function), never spreading it. Q-W0 rider (T2-04, settled):
|
||
generalized the `GetProjExtState` grow-loop retry policy into a header-only template, rewiring all
|
||
three hand-rolled copies (`usage_scan`'s start cap raised 4KB→64KB, allocation-only, verified
|
||
equivalent; the grow-loop gains a defensive NUL). **Resolves the Q-W1 open residual:** the
|
||
`bank_book_json` split lands via a private static `nameKey` (Daniel-approved option a) —
|
||
`bank_book.cpp` is now ~462 LOC. `persist.h` kept as a compat umbrella across the in-flight waves
|
||
(retired in Q-W6). **DAW-smoke-tested and passing (Daniel, 2026-07-29); the full
|
||
save/load/undo-reload/relocation/prune verification batch was not executed.**
|
||
|
||
#### Q-W6 — OCP registration table + residual fat-header (I) splits (2026-07-29)
|
||
Merged to `phase-q`, 61/61 green, reviewed-approved. Replaced the ~350-line hand-written
|
||
non-table action registration blocks (isolated in `app/main.cpp` after Q-W3) with a data-driven
|
||
`ActionTableRow` registration table (flat function-pointer dispatch, no `std::function`/virtual);
|
||
unload mirror-unregisters from the same table; `main.cpp` shrinks 653→404. Capture rows derive
|
||
their suffix+phrase from the pure `captureActionTable()` (parallel-list risk gone by
|
||
construction). FOREVER-STABLE suffixes/phrases/retired-ids verified byte-identical row-by-row in
|
||
review. Split residual fat headers: `persist.h` umbrella retired (13 callers repointed);
|
||
`capture.h`'s realtime seam moved to `capture_realtime_shell.h`; the `wav_trim.h` shim + its
|
||
INTERFACE target deleted. Phase-end cleanup riders: `bankOp*` verbs + `persistBankOp` lifted to
|
||
new `shell/bank_ops` taking `ReaSamplerSession&` (dissolves the Q-W4 🟡 review note); **`core/
|
||
namespaces.h` DELETED** (the interim Q-W1 shim's contract fulfilled, ~26 includers rewired); the
|
||
grow-loop rehomed to `core/wire/ext_state_read.h`; a stale-comment sweep
|
||
(`persist.cpp`/`bank_panel.cpp` refs); CLAUDE.md's persist/bank_book/actions/wav_codec bullets
|
||
corrected in-wave. **Review-noted follow-on, not landed:** extending the table pattern to the
|
||
design_view/bank/ingest families' hand-registration; `view_mode_model.h` (748 LOC) remains the
|
||
largest header, its planner split stays optional/deferred. **DAW-smoke-tested and passing (Daniel,
|
||
2026-07-29); the full per-wave verification batch was not executed.**
|
||
|
||
**All seven Phase Q waves (Q-W0 through Q-W6) are recorded as structurally complete on
|
||
`phase-q`.** Every wave from Q-W2 onward originally carried the same recurring caveat verbatim:
|
||
in-DAW behavioral-parity verification checked off as PENDING/deferred by design. **Daniel has
|
||
since DAW-smoke-tested Phase Q and confirmed it passing (2026-07-29); the full per-wave
|
||
verification batch described above was not executed.**
|
||
|
||
---
|
||
|
||
## Part 4 — The version-0 plan of record
|
||
|
||
Distilled from `PLAN.md` (the tracking checklist itself, not `COMPLETED.md`) at the 0→1 roll.
|
||
Parts 1–3 above narrate what landed, drawn from `COMPLETED.md`; Part 4 preserves the plan-of-record
|
||
layer — open questions, forks, and settled/recommended decisions — as it stood in `PLAN.md`,
|
||
including entries that were still live deferrals rather than finished history at roll time (see
|
||
the roll report for the full enumeration of those).
|
||
|
||
### Open questions carried from CONTEXT.md (still open at the 1.0 roll)
|
||
- **`parseInt` narrowing hardening.** `bank_model.cpp`'s `parseInt` casts `int64_t → int` via
|
||
`static_cast` without a range check; integers that fit in int64 but exceed `INT_MAX` are
|
||
implementation-defined. Hardening candidate — add a bounds check before the cast when
|
||
integer-field validation is next in scope.
|
||
- **Capture send/routing isolation.** The FX-scope capture neutralizes out-of-scope FX, gain, and
|
||
pan, but NOT aux **sends** — a downstream coloring send (e.g. folder → reverb track) still
|
||
routes and blends into an item/track capture past the intended isolation point. The hard part:
|
||
distinguishing source routing that must be preserved (a MIDI send whose destination synth IS an
|
||
item's true audio source) from coloring sends that must be excluded (folder → reverb). Repro
|
||
case and a likely snapshot/mute-sends approach are recorded in `PLAN.md`; no fix has landed.
|
||
|
||
### Phase D2 — two-canvas closure
|
||
D2 (item-level mode projection, additive to D1) is functionally complete — D2-W1, D2-W2, D2-W3-A,
|
||
D2-W3-B all landed. One item is explicitly **deferred, not dropped**: a per-track lane/mode-state
|
||
panel indicator. No natural cheap home was found in the bank panel; the mode switch already shows
|
||
the active mode. Can be picked up later if wanted.
|
||
|
||
### Phase S — MIDI-playback instrument (ReaSampler 9000)
|
||
Landed on dev 2026-07-27 (S1–S18 plus the product-name/binary-rename work); `PLAN.md` records DAW
|
||
verification as pending Daniel's smoke test at that point. The then-authoritative spec was
|
||
CONTEXT.md §MIDI-playback instrument (Phase S); product framing `docs/product/midi-playback.md`.
|
||
|
||
**S13 — cross-artifact ingest relay: DEFERRED, not built.** Spike verdict (ps-w12, 2026-07-27):
|
||
DEGRADED. The instrument's REAPER bridge (`reaper_bridge`) is deliberately read-only; a relay
|
||
would need a new instrument WRITE seam into ext-state plus an extension-side timer poller
|
||
servicing a drop-ingest inbox key with a claim/clear nonce — the same cross-process handshake race
|
||
the S17 spec rejected for alternative (A). Both the read-only-instrument boundary and the new
|
||
poller are load-bearing design calls, so the relay is deferred to a future wave; the shipped
|
||
ingest gesture stays drop-onto-docked-panel (S8). The editor's degrade-path affordance ("drop
|
||
files onto the ReaSampler bank panel to add them") landed as part of Phase S.
|
||
|
||
**Product name.** ReaSampler 9000 (Daniel, 2026-07-26, on DAW-testing the S1–S6 instrument). The
|
||
extension stays ReaSampler.
|
||
|
||
**Compat verification — must-DAW-verify before shipping the rename, unchecked in `PLAN.md`.** The
|
||
working assumption is that REAPER rebinds a saved instance by VST3 class UID, not module filename,
|
||
so renaming the module with an unchanged UID keeps saved projects working. This was **not
|
||
confirmed from source** — a web check surfaced a JUCE/VST3-replace-VST2 case suggesting the
|
||
binding may be more nuanced (possible FXID involvement) — treat as to-verify, not asserted fact.
|
||
Required DAW check: save a project with a ReaSampler 9000 instance under the old filename, rename
|
||
the module, reopen, confirm rebind + state restore. Fallback if REAPER keys partly on filename:
|
||
keep the current filename (display-strings-only rename).
|
||
|
||
**Held and optional-forever (noted, not specified — no PLAN points drawn up):**
|
||
- Tier 2 "expressive" (HELD) — velocity layers, round-robin, full ADSR, per-sample tuning/gain
|
||
trim, sustain loops; the next depth increment once Tier 0–1 proves the instrument belongs.
|
||
- Tier 3 "instrument polish" (optional-forever) — filters, filter/pitch envelopes, LFOs, per-voice
|
||
pan, choke groups, a modest FX slot; a direction to leave room for, never a commitment. S16
|
||
landed the *pitch* envelope + Varispeed/Preserve pitch-engine mode early (Daniel's directive),
|
||
so the Tier-3 line now means the *filter* envelope + LFO remainder.
|
||
- Sinc Varispeed-quality upgrade (HELD — WDL_Resampler) — beats the 2-point linear interp for
|
||
Varispeed base-repitch quality; an optional per-voice quality toggle, RT-suitable but heavier;
|
||
a Varispeed-quality option only, never a Preserve engine.
|
||
- WDL_SimplePitchShifter swap (HELD — fork S16-F2 route a) — drop-in swap for `pitch_shift` if the
|
||
hand-rolled OLA onset latency or warble proves musically unacceptable; same
|
||
`PitchEngine::Preserve` contract. WDL excluded from the shipped build by include-chain
|
||
(windows.h).
|
||
- Trigger choke-on-note-off (HELD — fork S15-F1) — a future option for Trigger mode to cut on
|
||
note-off or a same-group re-trigger (hi-hat open/closed); deliberately out of S15 scope.
|
||
|
||
**Editor view-model redesign (S-VIEW, three views: Sample / Browse / Zone) — landed.**
|
||
Re-partitioned the editor from a two-view toggle into a three-view model with the loaded sample as
|
||
home (Sample default face, Browse a modal picker over Sample, Zone a dedicated keymap surface);
|
||
added key-tracking, preview velocity, and the r10 velocity→amp transfer curve, plus an envelope
|
||
overlay, real piano-key pattern, and velocity-curve editor components. Built directly on the
|
||
post-L3 look-and-feel as its baseline — no separate restyle-after pass. All r9/r10 forks settled
|
||
2026-07-27: S-VIEW-F1 (preview velocity persists via envelope-v6 `ComponentState`) and S-VIEW-F2
|
||
(envelope nodes draggable via `envelope_edit`) folded into S-VIEW-4/S-VIEW-3; R10-F1 (flat y=1
|
||
default) folded into S-VIEW-9; S-VIEW-F3 (full-window overlay) implemented as Browse rendering as
|
||
a full-window modal over Sample.
|
||
|
||
**Wave B — Sample-face recomposition (r11, 2026-07-27) — landed.** All linear sliders replaced by
|
||
radial knobs in a fenced knob deck; mode toggles compact, not full-width; the inline
|
||
velocity-curve box replaced by a miniature curve-preview button + full-size popup (right-click
|
||
deletes a node); hero waveform full-width. S-VIEW-11/12/13 landed as FB1 (knob deck + master gain
|
||
+ curve popup + full-width hero, merged 2026-07-27, suite 55/55) and FB2 (Zone-panel parity,
|
||
merged 2026-07-28, suite 55/55). Phase S editor Wave B (r11) is recorded complete in `PLAN.md`.
|
||
Forks settled: R11-F1 (hero height vs. default window) at FB1 build — elastic hero, 840×620
|
||
default kept; R11-F2 (Zone-panel parity) at FB2 build — knob deck + curve popup adopted on the
|
||
Zone panel, `param_slider` slider rows retired there.
|
||
|
||
### Phase Q — Quality (1.0 structural reorganization): the decision record
|
||
(The wave-by-wave landing narrative is recorded in Part 3 above, distilled from `COMPLETED.md`.
|
||
What follows is `PLAN.md`'s own decision/fork record — the settled and recommended calls that
|
||
shaped the reorg — kept here because it does not otherwise survive verbatim in the landed-narrative
|
||
form.)
|
||
|
||
Phase Q was named the last structural pillar — namespaced `Q` (Quality; M/D/B/R/V/S/L all already
|
||
taken) — and framed as a pure structural refactor: no feature, no behavior change, the test suite
|
||
passing unchanged as the proof of correctness. Product framing, the Vital-grounded target shape,
|
||
the grep-verified SOLID audit, and the fork record (Q-1..Q-6) live in
|
||
`docs/product/code-organization.md`.
|
||
|
||
**The gate.** Phase Q was gated on the tree being otherwise quiescent — Daniel's plain readiness
|
||
target: "when Phase S and L3 are finished." Satisfied 2026-07-27: Phase S merged; Phase L complete
|
||
(L1–L7); D2 functionally complete with its one deferred item (per-track lane indicator) explicitly
|
||
not blocking; M9 (slots) abandoned 2026-07-27 (Daniel) and will not be reactivated — named in the
|
||
gate only so that reactivating D2's deferred indicator would re-arm quiescence.
|
||
|
||
**Q-W0 (pre-restructure functional + DSP audit) ran first**, per Daniel's ask, complementary to
|
||
the grep-verified SOLID/naming audit already grounding Q-W1..Q-W6. Four parallel tracks (T1 DSP,
|
||
T2 architecture, T3 env-coupled constants, T4 sizing/placement) produced 59 findings
|
||
(`docs/product/code-quality-audit.md` + `docs/product/audit-notes/`); Daniel approved every
|
||
disposition 2026-07-28. The Q-11 question (pitch-technique replacement) was answered by the audit
|
||
itself: the SOLA pitch engine is sound — no technique replacement warranted; every pitch finding is
|
||
a bounded in-technique fix or a documented operating limit.
|
||
|
||
**Settled decisions:** Q-1 phase name/id family (`Q`, `Q-W0..Q-W6` + `Q-W2v`) settled this-doc.
|
||
Q-10 audit-report home = a committed doc (`docs/product/code-quality-audit.md`), not a tracked
|
||
issue list. Q-11 = defer to findings (a bounded OLA fix weighed before a technique replacement,
|
||
which would be a Daniel decision at triage time, not automatic). Q-5 settled to seams-by-
|
||
responsibility with the T4 seam lists adopted (`bank_panel` 6→8 seams, `capture_orchestrator`
|
||
further split with `capture_batch`); the ~600-line ceiling is an acceptance criterion on every
|
||
split wave, not a bisection target. Q-6 settled in scope, last wave. Q-8 settled both renames
|
||
(`BankIndex`→`BankModel`; JSON parser → `json::Reader`/`Writer`) plus, from the audit,
|
||
`ICaptureBackend` deleted in Q-W3 (one deriver, zero polymorphic call sites — the CLAUDE.md/
|
||
CONTEXT correction rides Q-W3 itself). Q-9 settled: align to stem `capture_realtime`, shell
|
||
suffixed, during W3. VST placement (audit §4a) settled: `core/instrument/{engine,map,ui}` +
|
||
`shell/instrument/` under the single `core/`/`shell/` top split. WAV/RIFF consolidation
|
||
(audit §4e) settled as a Q-W3 rider: one pure `wav_codec` owner (walker+layout+build+patch).
|
||
Q-W2v scheduling (audit §4f) settled: parallel with Q-W2 (different artifact, zero file overlap).
|
||
|
||
**Recommended-and-adopted (Q-2..Q-9, `docs/product/code-organization.md` §6):** Q-2 JSON
|
||
extraction in scope and first; Q-3 `core/`/`shell/`/`app/` top-split with subsystem dirs beneath
|
||
(over pure-Vital subsystem-first — makes the pure/shell invariant structural); Q-4 sub-namespace
|
||
matches sub-directory; Q-7 naming rides the relocation waves, no dedicated naming wave (forced
|
||
once Q-3/Q-4 settle).
|
||
|
||
**Hard constraint — zero runtime cost.** No added virtual dispatch, no header→TU indirection, no
|
||
changed call/inline or branch shape on the three hot paths: `peaks` envelope compute,
|
||
audition/preview, the realtime-capture idle tick. An acceptance criterion on every point.
|
||
|
||
**Structural heuristics (acceptance criteria on every wave):** (1) more directories a must, more
|
||
files good, ~600-line ceiling as the bar with the audit's named seams as method — a documented
|
||
hot-path exception (`sampler_core.cpp`) is legitimate, silent overshoot is not; (2) templates
|
||
earned for compile-time dedup (the LE byte codec) but not for name-only unification (the rect
|
||
family stays one concrete `ui::Rect`, no template); (3) SOLID is great but saved CPU is better —
|
||
no dispatch-stack blowouts anywhere, prefer static polymorphism where types are compile-time-known.
|
||
|
||
**Sequencing (as planned; all landed 2026-07-29):** GATE → Q-W0 → Q-W1 (safe opener) →
|
||
{Q-W2 → Q-W4; Q-W2v parallel with Q-W2 (zero file overlap); Q-W3 → Q-W6; Q-W5 best after Q-W4}.
|
||
Big-bang was rejected; every wave independently landable and CTest-green throughout.
|
||
|
||
**Must-verify-before-build checklist** (all satisfied by the 2026-07-29 landing): Q-W0 closed
|
||
before any structural point (triage + Daniel sign-off on every disposition); ~600-line ceiling per
|
||
split TU with `sampler_core.cpp` the sole documented exception; no dispatch-stack blowouts
|
||
anywhere; hot-path call/inline shape unchanged (`computeEnvelope` stays free-function, audition
|
||
stays direct call-through, idle tick stays a single pointer test); command-id/display-string/
|
||
ext-state-namespace/VST3-UID contracts left byte-identical by any rename; name-collision sweep
|
||
resolved before W1; naming changes zero-behavior-change and off the wire; the GATE re-confirmed
|
||
against dev before W1.
|
||
|
||
---
|
||
|
||
## Part 5 — Spec provenance (pre-build reasoning)
|
||
|
||
Distilled from `CONTEXT-ARCHIVE.md` ("ReaSampler spec provenance", 1230 lines) ahead of its
|
||
deletion. That file held build-detail sections moved verbatim out of `CONTEXT.md` once each
|
||
phase landed, in original document order — each section was the unedited original text, kept
|
||
for provenance rather than as a living reference. Most of it was pre-implementation scaffolding
|
||
whose value fully expired once the code shipped, so it is summarized rather than transcribed
|
||
here: recurring per-phase **"Module architecture (preserve the pure/shell split)"** sketches
|
||
(superseded by the nineteen per-directory `CLAUDE.md` files the shipped tree now carries),
|
||
recurring **"REAPER API surface (verify all signatures)"** / **"REAPER / Steinberg API surface"**
|
||
/ **"LICE / WDL API surface"** checklists (superseded by root `CLAUDE.md`'s standing instruction
|
||
to verify every cited API name against `vendor/reaper-sdk/sdk/reaper_plugin_functions.h`), a
|
||
**"Data model (sketch — refine in code)"** for the original single-artifact tool (the shipped
|
||
`Sample`/`BankModel` shapes are the truth now), and a numbered **"Build order"** for that same
|
||
original tool. These appeared, in the same shape, ahead of the Phase B (original capture tool),
|
||
D1/D2 (Design View), M (multi-bank), R (removal-and-prune), and D3 (VST3 spike) build-detail
|
||
sections, plus a "Kit architecture (the pure/shell split)" sketch ahead of Phase L — all dropped
|
||
for the same reason. What follows preserves only the design reasoning and settled constraints
|
||
that do not survive verbatim in `CLAUDE.md`, `CONTEXT.md`, or Parts 1–4 above.
|
||
|
||
### WDL pitch/resample surface — the S16 viability assessment
|
||
A prior sweep had dismissed `WDL_SimplePitchShifter` (`vendor/WDL/WDL/simple_pitchshift.h`) as
|
||
"wrong tool" because it is duration-preserving — but Daniel's directive made duration-preserving
|
||
*the requirement* for the Preserve pitch mode, so the dismissal was replaced with a real
|
||
viability read of the header. Findings: the vendored WDL tree's *entire* pitch/resample surface
|
||
is exactly two headers, `resample.h` (`WDL_Resampler`, a sinc/linear resampler — held as an
|
||
optional Varispeed-quality upgrade only, since a resampler couples duration) and
|
||
`simple_pitchshift.h` (`WDL_SimplePitchShifter`, time-domain OLA, duration-preserving — the S16
|
||
Preserve-engine candidate under fork S16-F2 route (a)); there is no elastique-class
|
||
formant-preserving shifter anywhere in the tree, and REAPER's own elastique is a separate
|
||
zplane license unavailable to the project without a new third-party dependency (JUCE/rubberband/
|
||
signalsmith), which was not proposed. `WDL_SimplePitchShifter` was assessed RT-viable *with a
|
||
pre-warm discipline* (run silence through once at voice-allocation so its OLA ring and queue
|
||
reach steady state before the audio thread ever calls it) but with a real cost: an inherent
|
||
~half-window onset latency (~25 ms at a 50 ms window) and basic ("SimpleWindowed"-class) quality
|
||
with audible warble on large transpositions and no formant preservation. The recommendation was
|
||
route (a) (`WDL_SimplePitchShifter`, low-cost) or route (b) (a hand-rolled pure `pitch_shift`
|
||
module) as the Preserve engine, with the pitch-envelope modulation hand-rolled over whichever
|
||
engine won. **What shipped:** a hand-rolled correlation-aligned SOLA `pitch_shift` (route (b)) —
|
||
see `CLAUDE.md`'s GA post-launch DAW-fix pass — with the WDL swap recorded as a still-HELD
|
||
fallback in `PLAN.md`'s carried-forward list (Part 4 above) should the hand-rolled engine's
|
||
latency/warble ever prove unacceptable.
|
||
|
||
### ReaSampler 9000 — the UX overhaul (S10–S13) findings
|
||
Daniel DAW-tested the S1–S6 instrument and the verdict was that it *worked* but the UX was
|
||
unacceptable — "this is supposed to be better than ReaSamplOMatic5000" (RS5K). The S1–S6 editor
|
||
was spike-grade: a clickable sample list, zone rows each carrying seven tiny ±1 nudge/delete
|
||
mini-buttons, text-only labels, no keyboard visualization, no waveform, no drag interaction, no
|
||
scrolling for long lists — setting a zone from C1 to C4 by clicking "+" thirty-six times was the
|
||
headline catastrophe. The overhaul was then **reframed around the actual workflow, not a
|
||
keymap** (a revision superseding the original keymap-first S10 plan): most instances play a
|
||
*single* capture, so the metric became **time-to-first-note**, not zone-table completeness. The
|
||
settled hierarchy: (1) primary flow is one capture, fast; (2) a **fresh instance is silent** —
|
||
nothing auto-selected, no auto-play of sample #1 (a deliberate reversal of the earlier S4
|
||
"first sample plays" convenience, recorded as a reversal, not a regression); (3) a **capture
|
||
browser** (scannable cards with peak thumbnails, name, root/key badge, bank-filterable) replaces
|
||
the "giant list of item blocks" anti-pattern; (4) graphic, descriptive controls with a guided
|
||
single-capture setup fast path; (5) multi-zone keymap editing is **demoted to an opt-in "Zones"
|
||
panel** — "most of the time the zones won't be used." **What "better than RS5K" meant,
|
||
concretely:** match RS5K's genuine strengths (drag-a-file-on-it loading, a visual note-range
|
||
control, a draggable waveform with loop markers, ADSR sliders) while exploiting its real
|
||
weaknesses — RS5K is one-sample-per-instance (forces track sprawl) and has no multi-zone view in
|
||
a single instance, where ReaSampler 9000 is multi-zone-in-one-instrument by design with an
|
||
opt-in Zones panel RS5K structurally lacks. **Sequencing rationale:** S10 (the browser + guided
|
||
setup + silent-by-default fix) was recommended first because it carries the *entire* felt UX
|
||
wound and the time-to-first-note metric, ahead of both S11/S12 (which lean on S10's browser) and
|
||
ahead of S7 (stereo capture) — "it works, the UX is awful" points at the editor as the live
|
||
wound, not the engine, even though no hard dependency runs either way.
|
||
|
||
### Drop-and-load — dragging a capture onto a track's FX button (S17 spec)
|
||
The gesture: while a capture is dragged out of the bank panel, a track's TCP FX button becomes a
|
||
drop zone; dropping there instantiates a ReaSampler 9000 on that track with the dragged capture
|
||
already loaded and selected — placement-of-the-player, the third integration gesture alongside
|
||
capture and placement-into-arrange. It needed a genuinely new drag mode because the existing
|
||
`CF_HDROP` OS-drag path (M11) cannot carry it — REAPER's FX button is not a native
|
||
drop-target-that-instantiates-a-plugin-with-a-file — so the extension itself has to hover-track
|
||
the pointer over REAPER's own UI and drive the insert. The `drag_out` pure module was to gain a
|
||
third `DragGesture`, `InstrumentDrop`, disambiguated from `Internal` and `OsDrag` purely by
|
||
pointer position plus a shell-supplied "is this REAPER's own UI" predicate — mirroring how the
|
||
existing gesture decision stayed pure over a shell-supplied rect. **The load-capture seam was
|
||
the hard, load-bearing part:** the Phase S spec at the time gave the instrument only a read seam
|
||
over the bank, with no entry point for an external actor to say "load *this* capture." Two
|
||
mechanisms were weighed: **(A, rejected)** a fresh-instance ext-state handshake (a "pending load"
|
||
hint keyed to the target track/FX, claimed and cleared by the new instance on init) — rejected
|
||
because the claim/clear race needs a cross-process handshake to get right; **(B, settled)**
|
||
direct VST3 component-state injection immediately after `TrackFX_AddByName` returns the new FX
|
||
index, writing the instance's own serialized chunk (with the capture pre-selected) via
|
||
`TrackFX_SetNamedConfigParm(track, fx, "vst_chunk", <base64 blob>)` — deterministic, no shared
|
||
state, no handshake, at the cost of making the component-state blob format a hard cross-artifact
|
||
contract between the extension and the instrument. **Deviation from the landed code:** per root
|
||
`CLAUDE.md`'s `instrument_drop_win` entry, the shipped mechanism is not the spec's settled (B) —
|
||
the `TrackFX_SetNamedConfigParm` "vst_chunk" write was found **silently unappliable for VST3** at
|
||
build time, so the shipped shell instead applies the dragged capture's state via a transient
|
||
`.vstpreset` image + `TrackFX_SetPreset`, all-or-nothing with a `TrackFX_Delete` rollback on
|
||
failure. The gesture-disambiguation contract (position, not a mode toggle; M11's OS-drag and the
|
||
internal bank-to-bank drag byte-for-byte unchanged) and the invariant framing (a deliberate
|
||
placement act, no auto-capture, no timeline insert, no private sample copy — the instrument
|
||
reads the one authoritative bank by reference) carried through unchanged.
|
||
|
||
### L2/L4/L5 dock-panel layout contracts — the reasoning
|
||
**L2** was scoped as a *layout design*, not a skin pass, because M11 added a real button
|
||
inventory (action-trigger buttons + keybinding-help labels) that had to be placed without
|
||
crowding the grid — the mandate was to group by *task* (capture / organize / reclaim / view),
|
||
not by build phase, and to sequence L2 after M11 merged so it inventoried the buttons actually
|
||
landed. **L4** re-homed that inventory around *frequency and intent*, shipping no new action and
|
||
changing no capture/placement behavior: capture + placement + maintenance moved to a **top**
|
||
toolbar (the eye's first landing, matching the tool's purpose); Design View tagging/switching
|
||
took the vacated **bottom** toolbar; the footer got a narrowed `[Arrange|Design]` toggle, a
|
||
proper Tail button, and Prune — ordered left (benign/frequent: view toggle, tail length) to right
|
||
(destructive/rare: Prune, isolated and `warn`-colored) so a mis-click near the left is cheap and
|
||
the one destructive control stays spatially and chromatically distinct. **L5** was a third
|
||
refinement pass, again shipping no new action: the rarer capture variants (Batch Items/Razor,
|
||
Capture RT, Cancel RT) moved into a right-anchored "⋯ More" overflow menu; button *faces* stayed
|
||
short labels with a hover tooltip carrying the full action name (`ReaSampler:` prefix stripped
|
||
for display, not for the registered gaccel name) and its keybinding; the Tag Design/Untag pair
|
||
became **four opposite-mode-only** buttons (Item/Track × Arrange/Design) over the *already
|
||
existing* `doTag`/`doUntag`/`doMoveItems` action families — pure layout + enablement wiring, not
|
||
new feature work — with a pure enablement rule (a button is live iff its target mode differs
|
||
from the active mode, read from the same source the footer toggle uses); `VIEW_TOGGLE_MODE`,
|
||
`VIEW_ACTIVATE_ARRANGE`, and `VIEW_ACTIVATE_DESIGN` were dropped from the bottom toolbar (actions
|
||
stay registered, FOREVER-STABLE ids unchanged) since the footer toggle already covers switching,
|
||
while `VIEW_SHOW_BOTH` was kept as the cross-mode "pin visible everywhere" escape hatch the
|
||
toggle doesn't cover.
|
||
|
||
### The L3 gate + Phase S coordination contract
|
||
The originally-anticipated coordination contract had two branches for how the VST3 editor/embed
|
||
shells would pick up the L1 kit: "born in the kit" (built against it from the start) or "L3
|
||
restyles them" (built plain, then brought onto the kit). In practice Phase S's editor and embed
|
||
shells arrived on `dev` drawing flat `LICE_FillRect` blocks and raw GDI `DrawTextA` off a local
|
||
pre-L1 forest-green palette — the "born in the kit" branch did not occur — so L3 performed a full
|
||
restyle of both shells through the L1 kit instead, landing the settled-and-revised
|
||
grey-neutral-plus-three-accent-pastel treatment (with a pastel spectral keyboard strip as the
|
||
signature surface) and routing text through the kit's cached-font `text()`. This resolved the
|
||
Phase Q gate condition ("Phase S + L3 merged to dev") and closed out Phase L (L1–L7 complete).
|
||
The VST3 class UID was left unchanged — a visual restyle is explicitly not a compat event.
|
||
|
||
### Open questions the source left unresolved
|
||
Two items were still open at the point their sections were written and are not known to have
|
||
been revisited since: **multi-capture drag over an FX button** (S17) — reject, or load the
|
||
first / a keymap of all — and whether the drop zone has to be the **FX button specifically** vs.
|
||
anywhere on the target track's TCP. Neither is known to block anything currently planned; noted
|
||
here rather than silently dropped.
|