docs: record Ξ-W2-T1, the resample bake chain, and file its two deferrals

This commit is contained in:
2026-08-01 18:11:46 -04:00
parent 7ecb3aa470
commit 9f17df1420
3 changed files with 155 additions and 174 deletions
+85
View File
@@ -669,3 +669,88 @@ both corrected the initial reading of each.
All visual outcomes remain **pending Daniel's by-eye sign-off on `dev`** — nothing was
verified in a live REAPER window; all measurement was against an offscreen bitmap in a
standalone harness. Not recorded as accepted.
### Ξ-W2-T1 — resample-bake-chain
The one-click in-sampler resample: dial → bake → dial-again, run without leaving the
sampler. A single click renders the dialed sound through the instrument's own voice
path, banks the result, re-points the instance at it, and hands the parameter set back
neutral — with the recapture's superseded predecessor never deleted, only retired to
prune's reclaim pool.
**The architecture decision was this track's first deliverable, and Daniel ratified
both halves of it.** Decision 1 (how the click crosses to the extension) is **(1b)**:
the editor invokes the extension's bake action directly over the VST-host bridge —
`NamedCommandLookup` on `"_" + channelCommandId(...)`, then `Main_OnCommandEx` — so
there is no request poller, no nonce, and no cross-process handshake. This dissolves
the S13 DEGRADED verdict rather than re-litigating it. Decision 2 (what renders the
audio) is **(2c)**: the instrument renders in-process and the extension banks the
file — taken **over the plan's leaning toward (2a)** on an engine-version-skew
argument: under (2a) the extension's own copy of the voice engine would render audio
the user heard through the VST3's separately-installed copy, and the format ladders
do not catch a behavioral divergence between the two. (2c) also leaves the
extension's link graph untouched, preserving `component_state_io`'s split-out purpose
of keeping engine object code out of the extension.
**What shipped:**
- New pure modules: `src/core/instrument/bake/` — a fifth peer of `engine/`/`map/`/
`note/`/`ui/` under `core/instrument/`, holding `bake_plan`, `bake_render`, and
`bake_reset` — plus `core/model/resample_name` and `core/wire/bake_wire`.
- New shells: `shell/instrument/instrument_bake` (the instrument's half: render,
stage the WAV outside the bank, publish one `rsbake_<guid>` request, invoke the
extension's action synchronously, read the outcome back, adopt + reset) and
`shell/capture/bake_land` (the extension's half: scans every open project tab for
pending requests, lands the ones belonging to the loaded project, refuses the rest).
- One new `ActionTableRow`, `RESAMPLE_BAKE`, registered through `main.cpp`'s existing
data-driven table.
- The instrument's guarded ext-state write surface grew from one prefix (`rsusage_`)
to two (`+ rsbake_`); the read-only-**bank** invariant holds because a bake
request key is not bank state, and the structural prefix guard still refuses
`banks`/`view`/`tail`/`assign`.
**Reset-scope classifications made at review, against Daniel's ratified rule** — these
parameters were absent from both ratified lists, so the classification itself is this
track's durable output:
- **Play mode → RESET, to Trigger.** The bake's product is a finished one-shot
carrying its own attack, span, and release; Gate would re-gate it and re-truncate
the printed tail on every iteration, breaking "iteration composes indefinitely." The
user-visible consequence: after a bake the instance is in Trigger, and a sustained
instrument needs Gate re-dialed by hand.
- **Start point → RESET.**
- **Channel mode and preview velocity → SURVIVE.**
- `resetAfterBake` defaults everything and copies back only the survivors, so a
parameter added later resets by default.
**Two behaviors worth recording because they are user-visible:**
- A bake fired from an instance in a **background project tab refuses** with
`BakeStatus::WrongProject` rather than risking a write into the wrong project's
bank — landing requires three-way agreement between the request's tab, the
session's loaded project, and the focused tab.
- A crash-stranded bake request is **cleared, not landed**, past a 30-second
staleness window (`kMaxRequestAgeSeconds`).
- Extension presence: the resample affordance reads **cleanly unavailable, not
silently lossy**, when the extension is not loaded — `bakeAvailable` gates the
editor's paint state and `runBake` refuses up front with "resample needs the
ReaSampler extension loaded" if asked anyway.
**Not demonstrated.** Nothing was verified in a live REAPER session. The
audible-and-faithful, iteration-composes, save/reload, and arrange-untouched
acceptance criteria are structural in the code and untested in a DAW. Three facts
remain DAW-unverifiable and are handled defensively rather than asserted:
`NamedCommandLookup`'s return on an absent command, `Main_OnCommandEx`'s `flag`
semantics, and whether a `WM_TIMER`-issued invoke is honoured. Daniel's manual
verification is still owed.
**Naming and lineage — proposed, not ratified, and still open jointly with
Ξ-W1-T1's lineage-record question.** This track's proposal: `Kick``Kick r2`
`Kick r3`, incrementing rather than stacking; a replace keeps the source's name;
machine-readable lineage rides `OriginRecord::parentSampleId`, written at birth
(landed by Ξ-W1-T1). The proposal is implemented (`core/model/resample_name`) but not
itself a ratified decision.
**Deferred, not done — logged to `docs/TODO.md`:** `Sample::sourceMode` has no value
meaning "produced by the instrument" (appending one is a forward-incompatible
bank-format change under the current deserializer, which fails the whole bank blob on
an out-of-range value — it wants its own decision); and `instrument_bake` copies the
interleaved render buffer into a `std::vector<double>` for the WAV build, roughly
doubling peak memory for a large bake.