diff --git a/docs/COMPLETED.md b/docs/COMPLETED.md index 827e5d5..12aafa2 100644 --- a/docs/COMPLETED.md +++ b/docs/COMPLETED.md @@ -1049,3 +1049,87 @@ excess 18.52 %, pitch-synchronous excess 0.00 %** — PSOLA eliminates that corn than regressing it, with every splice at n = 1 landing exactly one source period away. **These figures are a one-machine, Debug-build measurement against the named control arm, not a general performance claim.** + +### Γ-W2-T1 — pitch-rate-deck + +PITCH became PITCH/RATE: three knobs (`Key Trk | Rate | Pitch`) under the existing +Varisp|Presrv toggle, both new controls wired through the engine. Rate is 50–200 % on a +taper linear in semitones over ±12 (the stated exception to the centre-expansion law), +note-on latched; Pitch is a ±24 st baseline offset, live. Keytrack × rate × pitch-offset +compound into a single read-increment multiply — the per-sample path gained nothing. +Merged as `9dbb8b8`. Spent the phase's second payload rung, v16, as a strict suffix; a +v15 blob lifts to rate 100 % / pitch 0 st. + +**`isLiveDeckParam` was renamed `deckParamCommit`** and now returns a three-state +`LiveCommit` (`Live` / `NoteOnLatched` / `Reload`) rather than a bool — one predicate +widened, not a second mechanism. **Γ-W4-T1 derives the VST3 exposed parameter set from +this predicate**, so the classification is load-bearing two waves out. + +**The clamp question the plan left open at `[propose at review]` was resolved as one +clamp:** `clampStretchRate` at the stretcher, with the taper taking its bounds as +parameters and `deck_values` aliasing them from `engine::kStretchRateMin`/`Max`. + +**Code review found one Critical, fixed before merge:** the resample bake derived its +frame window without the two new fields while rendering *with* them, so a bake at any +non-unity Rate — or, under Varispeed, a downward Pitch — wrote a truncated file into the +bank. Fixed by deriving the window from the rate the voice actually reads at; the +regression test judges against a measured reference render rather than a recomputed +formula, and was proved non-vacuous by reverting the fix (every non-unity case fails). + +**A ruling folded in during remediation:** Pitch was an uncompensated stage-time coupling +under Varispeed — a Trigger AHD's wall-clock attack was invariant under Rate but scaled +with Pitch. Pitch is now compensated; key-track and velocity remain deliberately +uncompensated, because those are shipped sounds whose compensation would break +bit-identity at non-root notes. + +**A Varispeed golden hash was added**, honestly labelled: unlike the Preserve constants +(witnessed against pre-track commit `0a7778b`), it was captured from the remediation +commit itself, so it stands as a witness for the *next* track rather than proof of this +one. + +### Γ-W2-T2 — loop-crossfade-ux + +An explicit loop enable, a legible mark grammar, and the crossfade painted where it is +heard. **No format change** — no `ComponentState` version moved, no new persisted field, +`resolveLoop` untouched, audio unchanged. The enable **is** `SampleLoop::hasLoop`, whose +provenance changes from marker-gesture-derived to user-owned, with the gestures as +shortcuts onto it. Merged as `a8e30a9`. A new pure module, +`core/instrument/ui/loop_marks`, holds the state machine (`resolveLoopMarks`/ +`applyLoopMarks`); the four marks (START/LOOP/END/XFADE) get one grammar — line + shaped +cap + label, the cap being the grip — with cap/label/suppression geometry pure and +unit-tested. + +**The crossfade moved to `[loopEnd − crossfade, loopEnd)`** and draws as a +top-and-bottom edge wedge, never a second fill; the loop fill's peak alpha stays exactly +0.20. + +**START draws in `overlay/trace`, NOT `accent/primary` as the spec table states** — +because `accent/primary` *is* the waveform fill, so a primary START would measure 1:1 +against the material it marks. `overlay/trace` measures 3.071:1 against the fill and +3.065:1 against `bg/base`, clearing the 3:1 non-text floor on both. **This is a +deliberate deviation from `docs/product/instrument-control-surface.md` §6.3's table**, +and the spec is what is wrong. + +**Code review found three Majors, all fixed before merge:** a parked-vs-off state leak +where dragging START on a fresh capture silently converted "never set" into "LOOP OFF"; +three text draws landing on the lime waveform fill at 1.58:1 and 1.10:1 (fixed with a +`bg/base` scrim at alpha 0.90, solved for the binding case rather than chosen by eye — it +lands at 4.666:1 / 8.091:1 and is pinned in `test_theme.cpp`); and a per-mouse-move +bridge read plus bank parse in the hover path, now memoized against the existing key +struct. + +**`docs/TODO.md`'s "Pre-existing staged-envelope-node shadow at zero-attack" entry was +resolved incidentally** — giving START a cap is what closed it — and rewritten in place +with the recorded outcome by the track itself. + +**One thing is deliberately NOT settled and is recorded here as open, not as accepted:** +the audible wedge draws `accent/secondary` against the `overlay/trace` envelope trace at +**1.60:1** against a 3:1 floor. The 0.20 fill-alpha constraint is met and the +pre-existing accepted 2.25:1 pair is unmoved, but the under-floor *extent* inside the +loop span grows from two 2 px marker columns to two `crossfadeWidth × 10 px` bands. No +alpha fixes it — the pair is intrinsic to teal-on-violet. **Daniel has this: he is +judging it visually in the DAW and has not yet ruled.** + +**Neither track has been verified in a running DAW; both are asserted in CTest only.** +The full test suite passes on the merged result — **99/99, Debug config, on one +machine** — not a general cross-platform or Release-config claim.