docs: close Phase Ξ — the popup abandoned by ruling, the bake window derives itself, and Γ's rung ladder shifts to v15

This commit is contained in:
2026-08-01 21:44:05 -04:00
parent 178cca3c25
commit 4bdbbe0517
2 changed files with 111 additions and 72 deletions
+59
View File
@@ -754,3 +754,62 @@ bank-format change under the current deserializer, which fails the whole bank bl
an out-of-range value — it wants its own decision); and `instrument_bake` copies the
interleaved render buffer into a `std::vector<double>` for the WAV build, roughly
doubling peak memory for a large bake.
### Ξ-W3-T1 — capture-signal-popup (spec abandoned by ruling; shipped as derived bake window)
Phase Ξ's final track, and Phase Ξ is now complete. What `docs/PLAN.md` specified was a
popup menu letting the user hand-program the capture signal: note length as a
musical-division picker (1/64 to 64/1, dotted and triplet), start/end offsets editable in
both ms and beats, velocity, and a preview trigger auditioning the programmed note, under
the acceptance criterion "preview and bake cannot diverge."
**What shipped instead, and why — the spec and the landed code diverge substantially and
deliberately, by Daniel's ruling, not by shortfall.** The popup was built (~1500 lines)
and then abandoned unmerged. Daniel, verbatim: *"I didn't realize you had already derived
a usable window. The manual stuff for baking a specific midi length was just an idea, if
we have a smarter, fewer-clicks way of doing it, that is ideal. I just don't want to lose
anything when we bake. We can abandon the whole parameterized bake window if we can safely
derive the window in gate and trigger modes."* An audit then established, with executable
tests (`tests/test_bake_window.cpp`), that the window derives losslessly everywhere except
one irreducible case. What actually shipped, in `src/core/instrument/bake/`:
- **The bake window derives itself.** Trigger derives from the play span; Gate *without*
an active sustain loop derives from source exhaustion + release; Gate *with* an active
loop takes one user value, because a loop sounds for as long as it is held and no
derivation can supply a duration.
- **One control: "Hold,"** a musical-division picker in the chrome row, visible and
settable only when Gate + an active sustain loop. `bakeWindowNeedsHold` is the predicate
and it reads the ENGINE's loop fold (`resolveLoop`) rather than the loop fields.
- **Velocity comes from the instance's persisted preview velocity**, not a hard-coded 100
— three velocity curves are live, so the velocity is a property of the sound being
printed.
- **No preview trigger, and no popup at all.** The chrome-row play button stays a pure
MIDI trigger; bake parameters are their own thing. Daniel's ruling: *"play button is
pure MIDI trigger, Bake parameters are their own thing."* So the plan's acceptance
criterion 2 ("preview and bake cannot diverge") and its preview-trigger behavior bullet
are **retired by ruling.**
- **Three truncation bugs that pre-existed on `dev` were found and fixed:** a drawn EG
plus a stale stored `%`-length lost up to the whole take; the Preserve pitch engine's
window closed on the exact frame the terminal declick ramp began, ending files on a
full-scale hard cut; and the Gate hold length quantized onto a musical ladder that
**saturated at 384 beats**, cutting any source past it mid-sound (at 120 BPM, anything
from 192 s up — a full-mix bounce).
- **An invariant was deliberately amended:** `note/CLAUDE.md`'s "note length stays
musical-division-only" is superseded — a note length now carries EITHER an exact
duration (every derived path) or a musical division (the Hold picker only).
Quantizing a derived length is what caused the saturation truncation.
- Two undefined-behaviour paths closed as fallout: a NaN `keyTrack` from a corrupt payload
reached a narrowing cast on the per-sample audio path, and a misaligned payload tail
could fabricate a value rather than degrade to absent.
- Payload rung **v14** consumed (the Hold division).
**Not verified in a live REAPER session** — worth carrying forward as owed: the "Bake
Hold" label fitting its 56 px cell, the Hold knob's duration-ordered travel, and the
control's appearance/disappearance on the 500 ms sync tick.
**Deferred, not done — logged to `docs/TODO.md`:** the loop intrinsic is folded twice
(the editor's `pickedMarkers` resolves it from the live bank blob first, the processor's
`reloadInstrument` resolves it from the instance ref via `resolveCapture`), so the two can
disagree whenever a bank blob's loop for a capture differs from the copy in the instance's
own refs table. Pre-existing — `bakeWindowNeedsHold` is only a new *consumer* of
`pickedMarkers`, not the origin of the divergence.