Ξ-W2-T1 remediation: print master gain into the bake, derive the window from the dialed sound, reset play mode to Trigger

This commit is contained in:
2026-08-01 17:05:28 -04:00
parent 60308a3655
commit 39c2d1cdb4
31 changed files with 679 additions and 201 deletions
+27 -12
View File
@@ -18,26 +18,35 @@ decision about what the render made obsolete.
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 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 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 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 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_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
@@ -45,7 +54,13 @@ decision about what the render made obsolete.
## 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.
- **`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.