docs: close out wave Theta-W4, and defer the deck layout rework to TODO

This commit is contained in:
2026-07-31 20:31:53 -04:00
parent a32ab4b5b2
commit f115904e4f
3 changed files with 98 additions and 77 deletions
+44
View File
@@ -460,3 +460,47 @@ bumped to **v11** (`kParamsLoopVersion`), appended at the tail; slot 12 is reser
smooth on real material, whether the top-strip tab is discoverable, and the LICE
rendering of the tab and crossfade fill. Also open: whether the crossfade default
should stay 0 (a smooth seam becomes opt-in).
### Θ-W4-T2 — velocity-deck-and-bipolar-curves
Gives the three velocity-curve popups (amp, pitch, filter) one home — a new deck group
labelled VELOCITY — and makes the pitch and filter transfer curves bipolar. No
velocity-curve button remains in MASTER, PITCH, or Filter. Pitch and filter curves now
run y range [1, 1], default flat at 0, so velocity modulation of pitch and filter is off
until the user draws a curve; amp stays unipolar [0, 1] with its flat-unity default
unchanged. The domain is modelled as a `CurveDomain { Unipolar, Bipolar }` field on
`VelocityCurve`, with `curveYMin`/`curveNeutral` deriving from it; `VelocityPoint::amp`
was renamed to `value`. A velocity→pitch transfer curve is new — it did not previously
exist. Full scale is `kVelocityPitchRangeSemitones = 24.0`, now the single constant the
shell's pitch-depth control also consumes; it folds into `baseRatio_` once at note-on, so
`process()` gains no per-frame work. The preview button's text is replaced by a drawn
play triangle — `previewGlyph()` returns three vertices from the pure layer, the shell
passes them to `LICE_FillTriangle`, which was already in the build: no new dependency, no
asset. Params payload is **v12** (`kParamsVelocityVersion = 12`), appending the
velocity→pitch curve after Θ-W4-T1's loop block.
**Daniel's ruling — the depth knob stays.** The implementation initially *removed*
`FilterParams::velAmount` and the `kFilterVel` depth knob, arguing a bipolar curve is
both shape and amount. Daniel rejected that: the knob scalar AND the curve both apply.
The depth control was restored, and the filter's velocity contribution is
`velAmount × curve.eval(v)` with the curve bipolar. Consequence: with `velAmount`
surviving, the pre-v12 migration became a **pure domain re-tag** — a pre-v12 unipolar
curve's y values already sit inside [1, +1], so `velAmount` and every knot carry
forward bit-identically, with no scaling transform and no version branch in the reader.
The earlier fold-and-rescale approach (and its degree-1-homogeneity argument, which was
only exact to within double rounding) was removed entirely.
**`kFilterVel` also crossed from non-live to live** — a user-visible contract change
beyond simple restoration. Rationale: it is a depth over a latched value, the same shape
as `kFilterKeyTrack`, live since Θ-W3; the note latches `curve.eval(velocity)` and the
depth multiply happens in `applyLive` at block boundaries, gliding through the existing
cutoff ramp at zero per-sample cost.
**Deviations from spec / code review:** Code review ran on two surfaces
(engine/persistence, UI/editor) and found one Critical plus two actionable Majors and ten
Minors, all remediated. The Critical: `editedCurve()`'s `kNone` fallback let
Esc-during-a-curve-node-drag write the pitch or filter curve — bipolar domain and all —
over the amp gain curve and persist it. Fixed on both routes (the popup close now
cancels the drag; the mutable accessor refuses `kNone`). It has **no automated
regression pin** — `src/shell/instrument/` has no test target, and the bug is shell
state-machine coupling with no pure-layer equivalent.