Ξ-W2-T1: the resample bake chain — instrument renders, extension banks, one click re-points and resets

This commit is contained in:
2026-08-01 16:26:28 -04:00
parent 6c982cd617
commit 60308a3655
52 changed files with 2212 additions and 55 deletions
+51
View File
@@ -0,0 +1,51 @@
# src/core/instrument/bake — the resample bake's pure half
## Scope
The offline pass that turns the dialed instrument into a file, and the reset that hands the
instrument back neutral afterwards. A fifth peer of `engine/` / `map/` / `note/` / `ui/`
under `core/instrument/`, pure by the same rule — no REAPER types, no VST3 types, no host.
It is neither engine (it owns no voice), mapping (it resolves no capture), nor note (it
holds no program): it is the *composition* of the three into one render, plus the one
decision about what the render made obsolete.
## Invariants
- **The bake renders on its OWN engine, never the live one.** `renderBake` takes its
`SampleData` BY VALUE and detaches `SampleData::live` before constructing a `VoiceEngine`
for the render alone. Two consequences, both load-bearing: the audio thread's live block
can neither be observed nor disturbed by a bake, and a repeated bake of one dialed sound
is byte-identical because nothing outside the passed value can vary between runs.
- **The window bounds the render; the envelope does not.** Termination is structural — the
loop runs to `BakePlan::totalFrames` and stops. That is why a Gate bake with a sustain
loop active terminates: the gate is released at `noteOffFrame` so the tail is real, but
even a pathological envelope cannot run past the window.
- **The block size is fixed here, not taken from the host.** A block boundary is where the
engine re-observes state, so pinning it is part of what makes two bakes on two hosts
produce the same bytes.
- **A degenerate window is refused, not rendered.** `planBake` returns nullopt for a
collapsed window, a non-positive rate, or a window that rounds to no frames.
- **The reset's survive list is written out; everything else defaults.** `resetAfterBake`
starts from a default-constructed parameter set and copies back only the mapping facts.
A parameter added later therefore resets by default — the safe direction, since
under-resetting applies the same processing twice while over-resetting costs a re-dial.
A new mapping fact must be added to the copy list explicitly.
## Modules
- `bake_plan``defaultBakeProgram` (the program a bake uses until the capture-signal
popup ships; its release tail exists so the bake is not truncated at note-off),
`BakePlan` (the frame window plus its two event frames), and `planBake`, the one
`ResolvedNote` + rate -> frames resolution.
- `bake_render``BakeAudio` and `renderBake`: the programmed note through the sample's
own voice path, summed into an interleaved buffer at the source's own channel count.
- `bake_reset``BakeReset` and `resetAfterBake`: the ratified reset scope, answered for
both the parameter set and the post-mixer master gain.
## Gotchas
- Frame 0 of the render is the start of the CAPTURED FILE, not note-on. A capture that
opens before the note has `noteOnFrame > 0` and silence ahead of it.
- The render's channel count is the loaded `SampleData`'s, which is already the instance's
channel-mode decision — a mono-mode instance bakes mono, and that is faithful, not a fold.