Ξ-W2-T1: the resample bake chain — instrument renders, extension banks, one click re-points and resets
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user