docs: close out Θ-W5, and record two spline-overlay UX deferrals
Collapse Θ-W5 to wave-level in PLAN.md, append its narrative to COMPLETED.md, fix a stale resolvePlay reference in core/instrument/CLAUDE.md, and file the drag-off-margin and AttackEnd/Origin shadow warts in TODO.md.
This commit is contained in:
+23
-105
@@ -218,108 +218,24 @@ curve; and for the Gate-mode loop (W4-T1), since "Gate is unavailable in Spline
|
||||
only a real, testable rule once Gate has something to be unavailable *for*. It also
|
||||
depends on W3's radio switch, which is how a spline contour reaches the overlay at all.
|
||||
|
||||
**One track.** The spline algorithm is one implementation with several consumers; a
|
||||
second concurrent writer to it collides by construction.
|
||||
|
||||
#### Θ-W5-T1 — `spline-egs`
|
||||
|
||||
**Goal.** Offer a free-drawn alternative to every staged envelope: switch any EG from
|
||||
Staged to Spline and draw the contour directly — with the monotone-spline machinery
|
||||
already proven by the velocity curve, enhanced so sharp corners are possible rather than
|
||||
everything smoothed.
|
||||
|
||||
**Consolidates item 3.**
|
||||
|
||||
**Surface boundary — owns:** `core/instrument/engine/velocity_curve` (the shared spline
|
||||
implementation — hard points), the spline evaluator on the voice-render path,
|
||||
`core/instrument/ui/curve_popup` + the overlay's spline mode
|
||||
(`envelope_overlay`/`envelope_edit`), `core/instrument/map/component_state_io` (dual
|
||||
state persistence), and the deck band's **disabled-knob rendering only**.
|
||||
|
||||
**Behavior.**
|
||||
- **Mode toggle per EG.** Every processor that has an envelope (pitch, filter, amp) can
|
||||
switch its Staged EG to a **Spline EG**.
|
||||
- **Dual state — save but inactive.** Both the Staged and the Spline state are persisted;
|
||||
switching modes keeps the inactive one **saved but inactive**. No conversion, no
|
||||
discard — round-tripping Staged↔Spline restores the other mode's shape untouched.
|
||||
- **Gate unavailable in Spline mode.** Gate mode is **not available while a Spline EG is
|
||||
active**; the spline **always covers the full sample length** — a pure time function
|
||||
over the sample, i.e. the Trigger/one-shot playback model.
|
||||
- **Time axis: normalized, visually 1:1.** The contour is normalized to the full sample
|
||||
length and the overlay represents **the time axis of the actual sample visually 1:1**.
|
||||
Consequently a different-length capture rescales the stored contour to its own length,
|
||||
shape preserved proportionally.
|
||||
- **Point-editing grammar. Left-click adds a point; right-click deletes a point;
|
||||
control-click toggles a point hard/smooth.** (The source doc's second follow-up said
|
||||
alt-click delete; the third **supersedes** it with right-click — matching the
|
||||
velocity-curve popup's already-shipped right-click node delete, giving **one
|
||||
point-editing grammar across both spline consumers, with no migration on either side**.)
|
||||
- **Point-count ceiling: 128.** Floor: the two endpoints implied by full-length coverage.
|
||||
Daniel floated 64 and raised it to 128 explicitly so the cap does not limit **long
|
||||
rhythmic phrases, which require high resolution to be interesting** — at roughly two
|
||||
points per articulation event, 64 is about two bars of 16ths and 128 about four. **The
|
||||
ceiling is a musical bound, not a performance one** (segment lookup is logarithmic; the
|
||||
editor's 8 px minimum node separation is the practical density limit anyway). **An
|
||||
engineer tempted to lower this number must read that motivation first.**
|
||||
- **Staged controls disabled while Spline is active.** That envelope's staged segment
|
||||
knobs are **disabled — inert, not merely inaudible** — including their inner curve dials
|
||||
(the dial is part of the knob). The dormant staged state is edited only by switching
|
||||
back to Staged.
|
||||
- **Not globally monotone.** Contours don't have to rise and fall and are not globally
|
||||
monotone; the monotone guarantee is **per-segment** — all soft points between any hard
|
||||
points are smooth/monotone, with no overshoot between adjacent points.
|
||||
- **Default contour: a smooth y = 1 − x** — a smooth downward slope over the full sample
|
||||
length. (The Spline-EG default; not a change to the velocity→amp curve's existing
|
||||
default.)
|
||||
- **Hard points — the algorithm enhancement.** The one spline implementation is enhanced
|
||||
to support **multiple segments that don't minimally smooth**, so hard points are
|
||||
possible: the contour is defined by **one or more monotone spline functions** which
|
||||
together form the full time function — e.g. the first three points form a curved segment
|
||||
connecting **at a sharp angle** to the next three, finishing the contour over the full
|
||||
sample length. A hard point does no curve smoothing on either adjacent segment; the
|
||||
natural sharp angle stands instead of a continuous derivative.
|
||||
- **Shared across consumers.** The enhancement applies to the one implementation and flows
|
||||
to every consumer: the **velocity→amp transfer curve** gains hard-point support by the
|
||||
same change, as do the pitch, filter, and amp EGs in spline mode.
|
||||
- **Hot-path constraint.** Spline evaluation runs per-voice per-sample. It is an indexed
|
||||
binary search over a point array plus a polynomial evaluation — **not** a polymorphic
|
||||
curve object, and not a per-sample allocation. With a 128-point ceiling the search is
|
||||
≤7 steps; cache the segment index across samples where the read position is monotone.
|
||||
|
||||
**Acceptance criteria.**
|
||||
- Each of the pitch, filter, and amp EGs offers a Staged/Spline mode switch; in Spline
|
||||
mode the overlay (via the W3 radio switch) shows and edits the drawn contour, and played
|
||||
notes audibly follow it.
|
||||
- Left-click on the contour adds a point; right-click on a point deletes it; control-click
|
||||
toggles it hard/smooth. Points are smooth by default; a hard point renders a **visible
|
||||
sharp angle with no smoothing on either adjacent segment**, and the discontinuous slope
|
||||
is audible where the modulation target makes it so (e.g. a pitch EG corner).
|
||||
- A contour accepts points up to the **128-point ceiling**; attempting to add beyond it is
|
||||
refused **without disturbing the existing contour**. The two endpoints cannot be deleted.
|
||||
- A contour of several segments joined at hard points plays back over the full sample
|
||||
length exactly as drawn — including contours that rise and fall freely (no
|
||||
globally-monotone restriction), with **no overshoot between any adjacent pair of
|
||||
points**.
|
||||
- A freshly created Spline EG shows the smooth y = 1 − x default contour.
|
||||
- While a Spline EG is active, **Gate mode is not selectable**; the spline plays as a pure
|
||||
time function over the full sample length.
|
||||
- The overlay contour spans the full displayed sample width, 1:1 with the sample's time
|
||||
axis; loading a different-length capture rescales the contour to the new length with the
|
||||
drawn shape preserved proportionally.
|
||||
- While a Spline EG is active, that envelope's staged segment knobs **and their inner
|
||||
curve dials** render disabled and reject edits; switching back to Staged re-enables them
|
||||
with values exactly as left.
|
||||
- **Staged↔Spline round-trip preserves both states**: switch to Spline, draw, switch back
|
||||
— staged values exactly as left; switch forward again — the contour exactly as drawn.
|
||||
Both survive save/reload.
|
||||
- The velocity→amp transfer-curve editor supports the same control-click hard/smooth
|
||||
toggle with identical rendering behavior, and its existing right-click node delete
|
||||
matches the Spline EG's delete gesture unchanged — **one point-editing grammar across
|
||||
both consumers.**
|
||||
- Spline evaluation adds no allocation and no virtual dispatch to `process()`.
|
||||
|
||||
**Open questions.** None — item 3's two remaining questions (point-count bounds; gesture
|
||||
convergence) closed in the source doc's third follow-up round.
|
||||
**Θ-W5-T1 has landed** — `spline-egs` — see `docs/COMPLETED.md` for the full narrative.
|
||||
It shipped a free-drawn alternative to every staged envelope: pitch, filter, and amp
|
||||
EGs can switch Staged → Spline and have their contour drawn directly on the waveform
|
||||
overlay. The one shared monotone-spline implementation gained hard points — sharp
|
||||
corners, no smoothing on either adjacent segment — flowing to every consumer including
|
||||
the existing velocity→amp transfer curve, no fork. Both Staged and Spline state persist
|
||||
simultaneously (saved-but-inactive, lossless round-trip); params payload reached v13,
|
||||
and v12 projects still load. Gate is unavailable while a Spline EG is active; the
|
||||
contour is a pure time function over the full sample length, normalized and drawn 1:1
|
||||
with the sample's time axis. Point grammar converged on left-click add / right-click
|
||||
delete / control-click hard-smooth toggle, one grammar across both spline consumers.
|
||||
Staged segment knobs and their inner curve dials render disabled and reject edits while
|
||||
Spline is active. Point-count ceiling: 128, a musical bound rather than a performance
|
||||
one. A follow-on change in the same track reworked deck cell width: `-1` in `cellIds`
|
||||
now means one cell's width, reserved and redistributed, rather than a blank cell holding
|
||||
geometry — a Trigger face that drops Sustain and Release gets wider cells instead of
|
||||
dead slots; group widths, row packing, deck height, and Gate-mode cell widths are
|
||||
unchanged.
|
||||
|
||||
---
|
||||
|
||||
@@ -328,9 +244,11 @@ convergence) closed in the source doc's third follow-up round.
|
||||
**Depends on W5 for:** the last change to a drawn surface. Item 13 is an audit whose
|
||||
output is a disposition list over "every class of drawn surface," and item 10's sizing
|
||||
pass is judged by eye over the finished layout — running either while the spline contour,
|
||||
the disabled-knob state, or the deck inventory is still moving means auditing and then
|
||||
re-auditing. The source doc names this sequencing as an observation (item 13 after the
|
||||
layout/knob work); this plan adopts it as the boundary.
|
||||
the disabled-knob state, or the deck inventory was still moving would have meant
|
||||
auditing and then re-auditing. Θ-W5-T1 has landed (see `docs/COMPLETED.md`), so that
|
||||
surface has stopped moving and this dependency is satisfied. The source doc names this
|
||||
sequencing as an observation (item 13 after the layout/knob work); this plan adopts it
|
||||
as the boundary.
|
||||
|
||||
**One track.** Both items repaint essentially every surface in the editor; concurrent
|
||||
tracks would collide everywhere.
|
||||
|
||||
Reference in New Issue
Block a user