# 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::renderFrames()` 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 whole signal chain is printed, master gain included.** `renderBake` scales its output by the dialed post-mixer gain, because `resetAfterBake` hands that control back at unity. A render that summed voices alone would return every iteration shifted by 1/gain, and a gain dialed to silence would come back at full level. - **A degenerate or unholdable window is refused, not rendered.** `planBake` returns nullopt for a collapsed window, a non-positive rate, a window that rounds to no frames, and one past `kMaxBakeFrames` — an unbounded window is a `bad_alloc` inside a UI tick, and the seconds→frames narrowing is undefined long before the allocation would fail. - **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. - **Play mode resets to TRIGGER, not to the value struct's Gate default** — the one classification this track made against the ratified rule rather than reading off it. The bake's product is a finished one-shot, and Trigger is the mode that plays a finished one-shot verbatim; Gate would re-gate the printed release tail and each iteration would truncate the previous one's. "Neutral" here means "adds no processing", not "the struct's own default". `bake_reset.cpp` carries the argument at the assignment. ## Modules - `bake_plan` — `defaultBakeProgram` (the program a bake uses until the capture-signal popup ships; its end offset is DERIVED from the dialed sound, never constant), `BakePlan` (the render window, the captured slice of it, and the two event frames), `kMaxBakeFrames`, 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 - **`BakePlan` speaks two frame domains** — the captured file's and the render's, which are offset from each other whenever the note and the capture window do not start together. `bake_plan.h` says which field is in which; do not read them as one clock. - **`defaultBakeProgram`'s Trigger window bounds the Varispeed read stretch, it does not model it.** A downward pitch offset makes the read head take longer to cross the play span, so the window is scaled by the deepest downward offset the voice can reach — an upper bound, so a shallower excursion leaves trailing silence in the file. The capture-signal popup is where a user sets the window exactly. - 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.