diff --git a/docs/COMPLETED.md b/docs/COMPLETED.md index 3423133..59b4e89 100644 --- a/docs/COMPLETED.md +++ b/docs/COMPLETED.md @@ -18,3 +18,69 @@ intentional exception (Daniel-approved) is two user-facing error strings in findings — all content cut that should have survived — and all ten were remediated and re-gated before merge. Driver: Daniel's instruction — *"Brief concise engineering comments. A little why, and maybe context, never WHAT."* + +### Θ-W1-T1 — zone-retirement + +ReaSampler 9000's zone-mapping system is retired: one loaded capture, one parameter +set, playing across the full keyboard repitched from root with key-tracking — no zones, +no per-zone divergence, no keymap of captures. The dedicated zone-editing face and its +authoring affordances (add/delete zone, per-zone parameter panel, Low/High/Root zone +legend) are gone; the root note survives as a first-class parameter. `sampler_core` +split along the note-routing/per-voice-render responsibility seam (no virtual `tick()` +on the per-voice path), and the Sample face split into chrome/waveform/decks bands with +a shared band-stack allocator, discharging the wave's two structural deliverables. +Migration adopts a saved multi-zone instance's first zone; single-zone instances lift +losslessly. + +**Deviations from spec:** +- The key-range open question (**[propose]**) is answered outright rather than left + open: no key-range concept survives at all — `KeyZone`/`lowNote`/`highNote` are gone + from the engine, the write format, and the strip. A low/high pair remains re-addable + later as two ordinary parameters. Θ-W2-T3 consumes this decision. +- **"Which zone is first" (**[verify]**) confirmed:** `PerformanceMap::zones` was an + ordered vector and `Keymap::resolve` was first-match-in-order, so index 0 was the + audible zone. Migration adopts index 0, and that zone's `sampleId` supersedes the + envelope's stored `selectionId`. +- The control row (root strip, preview, velocity knob, curve button, Mono|Stereo) moved + into the chrome band, directly under the title rather than above the deck — + user-visible and deliberate; it's what makes Θ-W2's band-disjointness real. +- Loading a capture now clears the three capture-anchored overrides (root, loop span, + start frame) while keeping the shaping parameters — strictly less destructive than the + previous whole-zone drop. +- The embed strip became a read-only readout; it lost its click handler since with no + zones there is nothing to select. +- **Migration side-effect:** a previously-zoned instance with implicit channel mode and + a stereo capture persisted as Mono will reopen as **Stereo**. It converges on the + documented rule and is reachable only for old blobs, but "sounds identical" was an + acceptance bar, so it's a real deviation. +- `sampler_core.{h,cpp}`'s 956-line documented hot-path exception is retired, not + relocated — the tree now carries no over-ceiling exception at all. +- `note_entry` was deleted as dead code (its only consumer was the removed zone editor). +- **Still unverified:** a real pre-change project reopening through REAPER's `setState` + has not been exercised in the DAW; migration is proven only in the pure domain against + hand-laid legacy bytes. + +### Θ-W1-T2 — capture-handoff-bugs + +Fixed both extension-side capture-handoff defects: drag-out now delivers the capture's +audio at the drop target every time (previously intermittent, retry-fixable); dropping a +capture onto an FX container now loads the instrument with the capture, matching the +FX-button drop path. + +**Deviations from spec:** +- Item 5's root cause was three defects, not one: non-atomic COM refcounts racing a drop + target's background copy; an unverified assumption that REAPER had already called + `OleInitialize` on the calling thread; and a teardown-before-payload-check ordering bug + that let an unresolvable payload consume the gesture. +- Item 6's fix rests on an unconfirmed hypothesis — that a bare `instantiate = -1` left + placement to REAPER's ambient FX-chain insert point, which a container-focused chain + window moves. It is now pinned to an explicit top-level position; nobody could confirm + the mechanism without the DAW. +- The opportunistic rider was partly taken: `src/ingest.{h,cpp}` re-homed to + `src/shell/actions/`. `ext_keys.h` was declined (most consumers sit in another track's + exclusive surface); `resource.h` was declined (it's a build input paired with + `src/resource.rc` and the SWELL resgen step). +- **Neither acceptance criterion has actually been met yet.** Item 5's gate is + explicitly a soak ("a single pass is not a gate") and item 6 needs a live container + drop; both require REAPER and are outstanding. The code is merged; the acceptance + gates are not closed. diff --git a/docs/PLAN.md b/docs/PLAN.md index 328c069..851afe7 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -140,144 +140,13 @@ collision is real and the serialization is the correct answer. ### Θ-W1 — Collapse and re-seam -**Three tracks, in priority order.** This wave removes a whole subsystem, re-seams the -two files everything downstream must edit, and lands the filter DSP as a standalone -pure module — so that from W2 on, tracks fill bands and call modules rather than -fighting over god-TUs. - -#### Θ-W1-T1 — `zone-retirement` - -**Goal.** Retire the zone mapping system: ReaSampler 9000 becomes one capture = one -parameter set — and, in the space that deletion clears, land the two responsibility -seams the rest of the phase depends on. - -**Consolidates item 16.** (Also discharges the superseded per-zone storage language in -items 2, 9, 11, 12 before those items are built.) - -**Surface boundary — owns:** -- `src/core/instrument/map/` — `sample_map` (zone payload → one parameter set), - `component_state_io` (zones-payload version bump + migration). -- `src/core/instrument/engine/` — `zone_params.h`, `sampler_core.{h,cpp}` (the - responsibility seam). -- `src/core/instrument/ui/` — `editor_geometry` (Zone-face layout removal + the band - seam), `keyboard_strip` (zone-highlight geometry removal). -- `src/shell/instrument/` — all eight editor face-axis TUs, `editor_internal.h`, - `processor_state.cpp`, `reasampler_processor.{h,cpp}`. -- `CMakeLists.txt` target list (shared, low-conflict). - -**Behavior.** -- **One capture = one parameter set.** The instrument holds one loaded capture and one - set of playback parameters governing it. No zones, no per-zone divergence, no keymap - of captures. Every playback parameter edits in exactly one place; no gesture can - express per-zone divergence. -- **The zone-mapping surface goes away** — the dedicated zone-editing face and its - authoring affordances (add/delete zone, the per-zone parameter panel, the Low/High/ - Root zone legend). **The root note survives as a first-class parameter of the one - set** — only its zone-legend housing goes. -- **The per-zone/per-instance storage distinction collapses.** There is nothing for - VOICE and MASTER to be exceptions to and no second surface to hold in parity. -- **Migration: adopt the first zone's capture and parameters.** A saved multi-zone - instance lifts by adopting its first zone's capture and that zone's parameters; the - remaining zones drop from the instance. Dropping a zone touches no file and no bank - entry. The sounds-identical bar is **deliberately relaxed** for a genuinely - multi-zone instance — Daniel has no projects with zones in use, so it has no real - referents. Single-zone instances lift losslessly. -- **The single-capture experience is unchanged** — a loaded capture still plays across - the keyboard repitched from its root, key-tracking still applies. -- **No effect on the bank or the extension.** Captures, banks, capture/placement - separation, and the prune safety model are untouched. - -**Structural deliverables (this wave's real output for everything downstream).** -- **The `sampler_core` responsibility seam.** With zone resolution gone, what remains - divides cleanly: note routing / allocation / stealing / mono stack / panic (per-note, - cold) versus the per-voice per-sample render (hot). Split along that seam. Envelope - evaluation comes out as a concrete, header-inline evaluator — **never** a virtual - `tick()` on the per-voice path (structural heuristic 3 names this blowout by name). - The exact seam is [propose]; the constraint is not. -- **The Sample-face band seam.** Split `editor_paint_sample` / `editor_input_sample` / - the Sample-face half of `editor_geometry` into **three bands — chrome, waveform, - decks** — with one pure band-stack allocator owning the vertical inventory. Land the - band inventory the phase needs (chrome band, waveform band sized for two stacked - lanes, deck row) so W2's three tracks *fill* bands rather than re-allocate them. The - band allocator is a shared read-only surface from W2 on; no later track re-owns it - without saying so. -- Both TUs are at the ~600 ceiling before this work; neither may exit it over. - -**Acceptance criteria.** -- The editor exposes no zone-mapping surface anywhere: no zone view or button, no - add/delete-zone affordance, no per-zone parameter panel, no Low/High zone legend. The - root-note control survives. -- Every playback parameter edits in exactly one place and governs the instrument as a - whole; no gesture can express per-zone divergence. -- A single-capture instance saved before this change reopens sounding identical — same - keyboard response, same parameters, same root. -- A multi-zone instance saved before this change reopens holding its first zone's - capture with that zone's parameters — no error, no file touched, no bank entry - disturbed. -- The one parameter set round-trips save/reload intact. -- Bank, capture, placement, and prune behavior are unchanged. -- `sampler_core` and the Sample-face TUs are split by responsibility; every resulting TU - is under ~600 lines or carries an explicit hot-path justification. No virtual dispatch - is introduced on the per-voice per-sample path — verify by inspection of the split. - -**Open questions.** -- **Does any key-range concept survive? [propose]** With zones gone, does the capture - respond across the entire keyboard (repitched from root), or does a user-settable - low/high playable range survive as a plain parameter of the one set? Lean, consistent - with the simplification motive: **no range concept — full-keyboard response** — with - a low/high pair re-addable later as ordinary parameters if missed. **Θ-W2-T3 consumes - this answer** for the piano strip's contents; answer it here. -- **Which zone is "first"? [verify]** The instance's existing zone ordering. No - real-world stakes given the migration rationale. -- **The two responsibility seams [propose]** — see Structural deliverables. - ---- - -#### Θ-W1-T2 — `capture-handoff-bugs` - -**Goal.** Fix the two extension-side defects that break getting a capture out of the -bank and into a target — one intermittent, one deterministic. - -**Consolidates items 5, 6.** - -**Surface boundary — owns:** -- `src/shell/actions/drag_out_win.{h,cpp}`, `src/shell/actions/instrument_drop_win.{h,cpp}` -- `src/core/ui/drag_out`, `src/core/wire/instrument_drop` -- `src/shell/panel/panel_drag.cpp` -- `src/ingest.{h,cpp}` (physically at `src/` root — see rider below) - -Fully disjoint from the instrument; runs concurrently with T1 and T3 without contact. - -**Behavior.** -- **Item 5 — drag-out sometimes lands without audio.** Extension side, not the VST. - Dragging a capture out of the pool or a named bank *sometimes* produces a drop that - does not contain the audio; retrying works. Both details are load-bearing: the failure - is intermittent and a retry succeeds. Expected: every completed drag-out delivers the - capture's audio at the drop target, first try, every time. -- **Item 6 — drop onto FX container loads the instrument without the capture.** - Dropping a capture onto an FX *container* loads a ReaSampler 9000 instance but without - the capture; dropping the same capture onto the FX *button* works as expected. The - FX-button path is the reference; the container path must match it. - -**Acceptance criteria.** -- **Item 5 (soak gate — the failure is intermittent, so a single pass is not a gate):** - across a sustained session of varied drag-outs — pool and bank sources, including the - first drag after other bank activity — every drop yields a playable file containing - the audio, with no retry ever needed. -- **Item 6:** a capture dropped onto an FX container yields an instrument instance with - that capture loaded and immediately playable, indistinguishable (apart from where the - FX sits) from the FX-button drop. The FX-button path remains unregressed. -- `instrument_drop`'s all-or-nothing contract holds on the container path too: any - failure rolls back via `TrackFX_Delete`, leaving no orphaned instance. - -**Open questions.** None carried from the source items (both are bug reports recorded -without root-cause analysis, per the no-code-reads constraint they were written under). - -**Opportunistic rider (not required; propose at review).** `ingest.cpp` / `ingest.h`, -`ext_keys.h`, and `resource.h` physically live at `src/` root rather than under -`shell/actions/` — a documented structural wart Phase Q's reorg did not close. This -track is the only one in the phase that opens `ingest`; re-homing it here is cheap and -otherwise needs its own dispatch. Take it or explicitly decline it. +**Θ-W1-T1 (`zone-retirement`) and Θ-W1-T2 (`capture-handoff-bugs`) have landed** — see +`docs/COMPLETED.md`. Between them: the zone subsystem is retired and the wave's two +responsibility seams (the `sampler_core` split, the Sample-face band split) are in place; +the two extension-side capture-handoff defects are fixed. **One track remains:** +Θ-W1-T3 (`filter-dsp-port`), which lands the filter DSP as a standalone pure module — +so that from W2 on, tracks fill bands and call modules rather than fighting over +god-TUs. ---