docs: record Ξ-W2-T1, the resample bake chain, and file its two deferrals
This commit is contained in:
+16
-174
@@ -435,180 +435,22 @@ head governs every step after it.
|
||||
|
||||
#### Ξ-W2-T1 — `resample-bake-chain`
|
||||
|
||||
**Goal.** One click bakes the dialed sound into a bank capture, re-points the instance at
|
||||
it, and hands the instrument back at neutral — the dial → bake → dial-again loop, run
|
||||
without leaving the sampler.
|
||||
|
||||
**Consolidates item 15 (the chain; the popup UI is Ξ-W3-T1).**
|
||||
|
||||
**Surface boundary — owns:** whichever seam the architecture decision selects (see below),
|
||||
plus `shell/capture/capture_orchestrator` (a resample entry point alongside
|
||||
`captureAndIndexOne`), `core/model/bank_model` + `owned_manifest` at the add boundary,
|
||||
`shell/actions` (a new action, if the decision needs one), and the instrument-side bake
|
||||
trigger. **Does not own** the capture-signal popup's UI — that is Ξ-W3-T1.
|
||||
|
||||
##### The architecture decision — this track's first deliverable, before any chain work
|
||||
|
||||
Item 15 requires a click **inside the VST3 editor** to cause a **bank write**, and the
|
||||
instrument is a read-only bank consumer by invariant. The one prior attempt at this
|
||||
crossing (S13) closed **DEGRADED** and was deferred. Resolve this before building the
|
||||
chain. Two orthogonal choices:
|
||||
|
||||
**Decision 1 — how the click crosses to the extension.**
|
||||
- **(1a) Ext-state request key + extension-side timer poller**, with a claim/clear nonce —
|
||||
the shape S13 spiked and deferred. It needs a new instrument WRITE seam into ext-state,
|
||||
though the `rsusage_`-prefixed guarded write already establishes the precedent that such
|
||||
a seam can exist without weakening the read-only-*bank* invariant. Known cost: the
|
||||
cross-process handshake race the S17 spec rejected.
|
||||
- **(1b) Direct action invocation over the VST-host bridge — the lead candidate.** A
|
||||
REAPER-hosted VST3 already resolves REAPER API functions **by name** over the host
|
||||
callback (that is how the instrument reads `GetProjExtState`), and can fetch its own
|
||||
host context — the track/take/project it was instantiated in. If `NamedCommandLookup`
|
||||
and `Main_OnCommand` resolve the same way, the editor can invoke the extension's bake
|
||||
action **synchronously**, with no request key, no poller, and no nonce — which dissolves
|
||||
the S13 problem for this case rather than re-litigating it. **[verify]** against
|
||||
`vendor/reaper-sdk/sdk/` (`reaper_plugin.h`, `video_processor.h`,
|
||||
`reaper_plugin_functions.h`) that both resolve over the callback, and that calling them
|
||||
from the editor's UI thread is safe. This is a verification task, not an assertion — do
|
||||
not build on it until confirmed.
|
||||
- **(1c) No crossing — a bindable extension-side action only.** Fallback. It abandons
|
||||
Daniel's "from directly inside the ReaSampler 9000" framing, so it is the answer only if
|
||||
(1a) and (1b) both fail. If it is taken, say so explicitly rather than quietly shipping
|
||||
a panel button.
|
||||
|
||||
**Decision 2 — what actually renders the audio.**
|
||||
- **(2a) Headless voice-engine render, extension-side.** The extension reads the instance's
|
||||
component state, reconstructs the engine from the shared pure core, renders the
|
||||
programmed note, and banks the result through the normal capture add-path. Exact and
|
||||
fully deterministic. **Trade-off:** the extension currently links `component_state_io`
|
||||
but deliberately **not** `sampler_core`/`pitch_shift` — `component_state_io` was split out
|
||||
precisely to avoid pulling the voice engine into the extension. This reverses that.
|
||||
- **(2b) REAPER offline render of the hosting track**, with the programmed note. Reuses
|
||||
`OfflineRenderBackend` and with it the whole precision-invariant apparatus — exact
|
||||
bounds, bit-identical repeats, relative paths, tail control — for free. **Trade-offs:**
|
||||
it needs a *temporary* arrange mutation to program the note (the realtime backend's
|
||||
temp-track-created-and-removed-cleanly is the precedent), and the existing scope model
|
||||
has no "this one FX only" scope, so post-instrument track FX would be baked in unless a
|
||||
new bypass scope is added. The arrange must be byte-identical afterward.
|
||||
- **(2c) The instrument renders, the extension banks it.** The instrument produces the
|
||||
audio (it already owns the engine) into a temp file or shared buffer; the extension
|
||||
moves it into the bank and indexes it. Splits the work along the existing ownership
|
||||
line — but adds a handoff artifact and its cleanup.
|
||||
|
||||
**Recommended sequence for the decision:** verify (1b) first — if it holds, the crossing
|
||||
is nearly free. Then pick between (2a) and (2b) on the strength of the
|
||||
"only-the-instrument, not the track chain" requirement: Daniel's ask says "send a trigger
|
||||
or gate **through the sampler** offline," which favours (2a) or (2c) unless (2b) gains a
|
||||
single-FX bypass scope. Record the verdict in this track's review, and if the verdict is
|
||||
DEGRADED, escalate rather than improvise — the fallback is (1c) + (2a).
|
||||
|
||||
**Behavior — the chain.**
|
||||
- **One gesture, whole chain.** A single click performs: offline pass → capture → bank
|
||||
update (replace or add-distinct) → instance re-point → parameter reset. **One action
|
||||
from the user's side, not a wizard.**
|
||||
- **Offline pass through the instrument's own processing.** The audio is produced by
|
||||
sending a trigger or gate through the sampler **offline** — the instrument's own voice
|
||||
path, with filtering, pitching, and amp exactly as dialed. The recapture is of that
|
||||
processed output, not of the raw source.
|
||||
- **The recapture is a bank capture like any other** — project-relative, indexed,
|
||||
browsable from any surface that browses the bank, recorded in the owned-file manifest,
|
||||
and governed by the same safety rules as every file the system creates.
|
||||
- **The note is the capture's root.** The rendered note is the capture's root note — which
|
||||
is exactly why **the root-note parameter is not reset by resampling**: capturing at root
|
||||
is what makes root survivable, and resetting it would detune every subsequent iteration.
|
||||
- **Gate's hold and tail come from the programmed window.** The programmed note length is
|
||||
the Gate hold bound — the gate holds for the note length, then releases; a Gate loop
|
||||
cycles within the held span and the render still terminates. The **end offset** is the
|
||||
home of the tail policy: captured time past the note's end is where the release rings.
|
||||
- **Replace, or add distinct.** When nothing else references the source capture, the
|
||||
recapture **replaces** it as the bank entry; when other references exist, the original
|
||||
entry stays and the recapture is **added as a new distinct capture**. "Other reference"
|
||||
means **any usage of the source capture tied to it by the provenance/recapture system**
|
||||
— Ξ-W1-T1's records. Read plainly: the reference universe is the resample system's own
|
||||
lineage records, **not** the prune-protection universe. Bank multi-membership, items
|
||||
placed in the arrange, and a plain hold by another instance outside any recapture
|
||||
lineage do **not** force add-distinct, and need not for safety — the superseded file
|
||||
survives until prune, and prune's protection universe is unchanged and broader.
|
||||
- **Replacement never destroys audio bytes.** "Replace" means the bank entry now denotes
|
||||
the recapture; the superseded file is not deleted. Prune remains the system's only
|
||||
file-deletion authority — resample writes a new file and retires the old one to
|
||||
reclaimable-by-prune status; it never overwrites or deletes it. Until a prune reclaims
|
||||
it, the pre-bake audio survives on disk — **the iterate loop's built-in recovery floor.**
|
||||
- **The instance re-points** at the new/corrected bank capture and plays the baked sound.
|
||||
- **Parameters reinitialize to default** — destructive to the dialed settings **by
|
||||
design**: the processing now lives in the recaptured audio, and neutral controls are the
|
||||
starting point for the next iteration.
|
||||
- **Reset scope: only what the bake baked in.** Ratified by Daniel, not derived:
|
||||
- **Reset** (their effect is in the audio): the envelope contours — staged and spline
|
||||
alike — the filter parameters, master gain, the pitch envelope/engine settings, the
|
||||
velocity transfer curves (their effect at the programmed velocity is in the audio), and
|
||||
the loop points (they shaped the render, and old positions are meaningless against new
|
||||
audio).
|
||||
- **Survive** (mapping facts, not present in the audio): the **root note**, whatever
|
||||
remains of key mapping (key-tracking; any key-range concept Θ-W1-T1 settled), and the
|
||||
VOICE group (polyphony behavior leaves no trace in a single rendered note).
|
||||
- **No timeline item, ever.** Resampling is a capture act: it writes a file to the bank and
|
||||
updates the index; nothing is placed in the arrange view. Any framing that auto-inserts
|
||||
the recapture is invalid — and if architecture (2b) is chosen, its temporary programming
|
||||
item must leave the arrange byte-identical.
|
||||
- **Undo/recovery: a plus, not a requirement.** Welcome if it falls out cheaply; the
|
||||
feature ships without it. The guaranteed recovery path is the superseded file surviving
|
||||
on disk until a prune reclaims it.
|
||||
|
||||
**Acceptance criteria.**
|
||||
- On a dialed-in instrument, one click yields all of: a recapture in the bank, the instance
|
||||
holding that recapture, and the baked-in audio parameters at their defaults — with the
|
||||
root note and the other surviving mapping parameters untouched.
|
||||
- **The bake is audible and faithful:** after the click, playing the programmed capture
|
||||
note (root, at the programmed length, offsets, and velocity, in the active mode) through
|
||||
the now-neutral controls sounds as the dialed instrument sounded just before the click —
|
||||
the processing has moved from the controls into the audio.
|
||||
- **A Gate-mode bake terminates on its own:** the gate holds for the programmed note
|
||||
length, then releases — even with loop-sustain active, the render ends. No indefinite
|
||||
capture.
|
||||
- **After the bake the root note is unchanged** — iteration never detunes; the next bake
|
||||
plays the same root.
|
||||
- **Sole-reference case:** the bank afterwards shows the recapture where the source
|
||||
capture's entry was; no other bank entry is disturbed. **Other-references case**
|
||||
(provenance-tied usage of the original exists, computed by Ξ-W1-T1): the original entry
|
||||
is untouched, a distinct new entry appears, and **every other holder of the original
|
||||
sounds exactly as before.**
|
||||
- **The click deletes no file:** the superseded audio file still exists on disk afterwards,
|
||||
and only a later prune — under the settled orphan rules, only when nothing references it
|
||||
— can reclaim it.
|
||||
- **The arrange timeline is untouched:** no item appears anywhere, on any track, and if a
|
||||
temporary programming item was used it is gone and the arrange is byte-identical.
|
||||
- **Iteration composes:** dial → click → dial → click bakes the second pass onto the
|
||||
first's result, repeatable indefinitely.
|
||||
- **Save/reload:** an instance holding a recapture reloads and plays it exactly like any
|
||||
other loaded capture.
|
||||
- The bake adds nothing to `process()` — the offline pass runs off the audio thread.
|
||||
|
||||
**Open questions.**
|
||||
- **The architecture decision [propose, with a verification step].** See above. The (1b)
|
||||
bridge verification is **[verify]**; the choice among the candidates is **[propose]**.
|
||||
- **Extension presence [propose].** The instrument plays self-contained with the extension
|
||||
absent, but the bank is the extension's surface and resampling mutates the bank. The
|
||||
natural answer is that resample **requires the extension present and is cleanly
|
||||
unavailable — not silently lossy — without it**; propose the exact behavior at review.
|
||||
- **Reset-scope edge cases [verify].** The rule is settled and the per-parameter
|
||||
classification is ratified. Only a genuinely new parameter — one arriving with a Θ track
|
||||
and absent from the ratified lists — is classified **against the rule** and surfaced at
|
||||
review. Not a new Daniel call. (Θ adds: curve exponents → reset; spline contours →
|
||||
reset, already named; the filter's velocity/key-tracking mod → reset with the filter;
|
||||
loop crossfade → reset with the loop points; the Staged/Spline mode flag → classify.)
|
||||
**Phase Γ's additions are NOT this track's** — this track ran ahead of Γ, so the values Γ
|
||||
introduces (rate, pitch offset, the limiter flag, the loop enable) are amended in by
|
||||
**Γ-W3-T2 `bake-reset-amendment`** afterwards, against what this track actually shipped.
|
||||
Γ-W4-T1 then adds the host-notification obligation over the completed list. Nothing here
|
||||
needs to anticipate either.
|
||||
- **Naming and lineage [propose, jointly with Ξ-W1-T1's lineage-record question].** When
|
||||
add-distinct fires, the new capture needs a display name (derived from the original?),
|
||||
and the bank some way to read iteration lineage across repeated bakes. One proposal,
|
||||
both tracks.
|
||||
- **Provenance of the recapture — homed in item 17**, i.e. Ξ-W1-T1. A resample's recipe is
|
||||
the instrument's own settings, not a track's chain. Nothing to decide here beyond
|
||||
consuming that answer.
|
||||
**Landed** — see `docs/COMPLETED.md` for the full narrative. The architecture decision
|
||||
(this track's first deliverable) is ratified: **Decision 1 = (1b)**, the editor invokes
|
||||
the extension's bake action directly over the VST-host bridge
|
||||
(`NamedCommandLookup`/`Main_OnCommandEx`), no poller/nonce, dissolving the S13 DEGRADED
|
||||
verdict; **Decision 2 = (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. **Extension presence** resolved to cleanly-unavailable, not silently lossy —
|
||||
the affordance refuses up front when the extension is not loaded. **Reset-scope edge
|
||||
cases** classified against the ratified rule: play mode → RESET to Trigger, start point
|
||||
→ RESET, channel mode and preview velocity → SURVIVE. **Phase Γ's additions remain
|
||||
NOT this track's** — Γ-W3-T2 `bake-reset-amendment` still amends the reset list for
|
||||
Γ's own new values, unaffected by this landing. **Naming and lineage** is proposed
|
||||
(`Kick` → `Kick r2` → `Kick r3`) but not itself ratified — still open jointly with
|
||||
Ξ-W1-T1's lineage-record question, which Ξ-W1-T1 answered on its own side
|
||||
(`OriginRecord::parentSampleId`). Nothing was verified in a live REAPER session;
|
||||
Daniel's manual verification is still owed.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user