docs: close out Θ-W3-T2 into COMPLETED, collapse the W3 wave, and record the overlay contrast wart as open
This commit is contained in:
+15
-213
@@ -172,219 +172,21 @@ exist before it can receive a corner radio switch and inner curve dials. T1 addi
|
||||
depends on W2-T1 for the filter itself: the filter is the first control set where the
|
||||
latched-at-note-on delivery model fails audibly, and it is what made the defect visible.
|
||||
|
||||
**Two tracks, and they are SERIAL — T1 before T2. This is not a priority hint.** Θ-W4's
|
||||
two tracks are disjoint and merely ordered; these two are not. T1
|
||||
(`live-parameter-delivery`) changes how every playback parameter *reaches* a sounding
|
||||
voice; T2 (`staged-envelope-curves`) changes what *shape* the three envelopes have. Both
|
||||
land inside `envelopes.h`'s stage math: T1 has to define what happens to a stage's
|
||||
counters when its duration changes mid-flight, and T2 rewrites the same level
|
||||
computation to apply a per-segment curve exponent and to grow the Trigger AHD. Two
|
||||
writers on those forty lines is a merge fight over the phase's hottest code.
|
||||
|
||||
The ordering is T1-first for one structural reason: **T2 adds parameters, T1 defines how
|
||||
parameters are delivered.** Authoring T2's curve exponents, Hold fraction, and Trigger
|
||||
AHD fields under the latching model means adding them, then re-plumbing every one of them
|
||||
through the live path afterwards — a backfill cliff, and the exact shape of avoidable
|
||||
rework. T1 first means every parameter T2 introduces is authored into the delivery
|
||||
mechanism from the start. The cost of the ordering is honest and worth stating: T2 is the
|
||||
wave's user-visible payload and T1 delays it by one track.
|
||||
|
||||
T1 has landed and specified its mid-stage rule in terms that survive T2: **hold
|
||||
normalized stage position** — φ = elapsed/duration held fixed across a duration change,
|
||||
then advancing at 1/newDuration (Daniel's pick among the candidates T1 laid out; see
|
||||
`docs/COMPLETED.md`). It is expressed over *normalized stage position* rather than over
|
||||
output level specifically so T2's exponent is a pure map of that position and composes
|
||||
with the rule rather than invalidating it.
|
||||
|
||||
Beyond the ordering, the W3 collision is the phase's densest, which is why neither track
|
||||
splits further: the envelope parameter model, the per-sample envelope evaluation, the
|
||||
deck-band inner dials, and the waveform-band overlay are one system with one source of
|
||||
truth (the settled S-VIEW rule: node-drag and slider-edit read/write the same model,
|
||||
structurally, not via a listener chain). Splitting T2 into model and UI tracks would put
|
||||
two writers on `play_params.h` + `component_state_io` and two writers on the overlay's
|
||||
forward/inverse map pair. The serialization is the correct answer.
|
||||
|
||||
#### Θ-W3-T1 — `live-parameter-delivery`
|
||||
|
||||
**Θ-W3-T1 has landed** — see `docs/COMPLETED.md` for the full narrative. Continuous
|
||||
playback controls (filter cutoff/Q/morph/drive/mod amount/key-track; every stage time
|
||||
and level on all three envelopes) are now delivered live to sounding voices via a
|
||||
seqlock-published `LiveValues` block owned at processor-instance scope, above
|
||||
`LoadedInstrument`, so a drain-slot voice keeps tracking the knob (the desired
|
||||
behavior). `foldLive(const PlayParams&)` is the single derivation from the value type.
|
||||
Both of Daniel's ruling questions are answered and implemented: **reload tier =
|
||||
Grouping B** (root note, loop span, and start frame still reload; everything else
|
||||
continuous goes live), and **mid-stage rule = candidate (iv), hold normalized stage
|
||||
position** (φ = elapsed/duration held fixed across a duration change, then advancing
|
||||
at 1/newDuration) — expressed over normalized position specifically so T2's
|
||||
per-segment curve exponent, below, composes with it. **Trigger's %-length and fades
|
||||
are NOT live** — they are baked into `SampleData` at build, so a Trigger-mode instance
|
||||
gets zero live amp delivery until T2 folds the fade pair into the AHD. The five
|
||||
non-live exclusions (`kKeyTrack`, `kFilterVel`, `kTrigLength`, `kTrigFadeIn`,
|
||||
`kTrigFadeOut`) are documented in `src/core/instrument/ui/deck_groups.h`, now their
|
||||
single home.
|
||||
|
||||
#### Θ-W3-T2 — `staged-envelope-curves`
|
||||
|
||||
**Prerequisite: Θ-W3-T1 has landed** — see `docs/COMPLETED.md`. Both tracks write
|
||||
`envelopes.h`'s stage math, and this track's curve exponent must be authored as a map of
|
||||
the normalized stage position T1's mid-stage rule holds fixed (φ = elapsed/duration,
|
||||
candidate (iv)).
|
||||
|
||||
**Goal.** Grow the envelope-overlay editor from an amp-only fixture into the shared
|
||||
graphical surface for every envelope, give every envelope shapeable segments, fix the
|
||||
overlay's layout and contrast failures, consolidate the Trigger amp fade pair into the
|
||||
AHD — and, in the same region, kill the Trigger×Preserve end-of-sample click.
|
||||
|
||||
**Consolidates items 1, 8, 14, 4.**
|
||||
|
||||
**Surface boundary — owns:**
|
||||
- `core/instrument/engine/` — the envelope evaluator (from W1's seam), `zone_params.h`,
|
||||
`pitch_shift.{h,cpp}` (item 4's tail), the voice-render envelope and tail path.
|
||||
- `core/instrument/map/` — `trigger_seam`, `component_state_io` (curve exponents, the
|
||||
pitch Hold stage, the Trigger AHD, migration).
|
||||
- `core/instrument/ui/` — `envelope_overlay`, `envelope_edit`, `knob_deck` (the inner
|
||||
dial).
|
||||
- `src/shell/instrument/` — the deck band (inner dials, radio switches) and the
|
||||
waveform band's **overlay layer only** (T2's lane geometry from W2 is consumed, not
|
||||
re-owned).
|
||||
|
||||
**Behavior — the overlay surface (item 1).**
|
||||
- **Radio switch per envelope deck.** Each envelope knob-deck group (AMP ENVELOPE,
|
||||
PITCH ENV, and now Filter) gains a radio switch in the corner of its deck. Selecting a
|
||||
deck's radio makes *that* envelope the one displayed and editable in the graphic
|
||||
waveform overlay — replacing today's always-on Amp AHDSR. The switch is exclusive:
|
||||
**one overlay-active envelope at a time, or none** — no-envelope-shown is a valid
|
||||
state, not an error.
|
||||
- **Default overlay: none.** The editor opens with **no envelope selected**.
|
||||
- **Segment curve values on all envelopes.** Amp AHDSR, Pitch AHD, and Filter AHDSR all
|
||||
gain an editable curve value per *sloped* segment. The curve is exponential; the
|
||||
per-segment parameter is the exponent scalar, range **0.1 to 10**. **1.0 is the linear
|
||||
neutral** (y = x^1.0 is linear).
|
||||
- **Which segments are sloped: every stage except Hold and Sustain.** For an AHDSR that
|
||||
is Attack, Decay, Release; for the Pitch AHD, Attack and Decay. Hold is flat
|
||||
everywhere and carries no curve dial.
|
||||
- **Inner dial on curvable-segment knobs.** Every knob controlling a sloped, curvable
|
||||
segment gains an **inner dial**: its own inner arc, its own hover accent (tertiary
|
||||
purple), its own needle, its own numerical label. The inner dial controls that
|
||||
segment's curve exponent; the outer knob keeps controlling the segment's time/level
|
||||
value as today.
|
||||
- **Curve editing in the overlay.** Dragging on a segment **adds a round mid-segment
|
||||
knot** whose drag adjusts that segment's curve — the overlay is a curve-edit surface
|
||||
in its own right, alongside (not instead of) the inner dial. Same one-model rule as
|
||||
the existing draggable nodes: knot-drag and inner dial cannot diverge.
|
||||
- **Overlay recolor.** Envelope segments change from secondary blue to **tertiary
|
||||
purple** — secondary blue contrasts poorly against the primary green waveform behind
|
||||
it. Re-verify the tertiary-purple-on-green pair against `theme`'s contrast-floor
|
||||
assertions.
|
||||
- **Pre-existing instances load at exponent 1.0** — the linear neutral — so their
|
||||
audible envelope behavior is unchanged.
|
||||
|
||||
**Behavior — layout policy and the pitch AHD (item 8).**
|
||||
- **The release segment anchors to the right edge** of the overlay. Release is dragged
|
||||
from its **top node** (the node joining sustain to release) instead of the bottom
|
||||
corner; the bottom corner — the envelope's end point — becomes **fixed/anchored**, not
|
||||
draggable. Today's failure is that with little or no release the sustain portion
|
||||
occupies only a small stretch and the whole figure reads off-center.
|
||||
- **Spec change: the pitch envelope becomes AHD** — Attack → Hold → Decay. **Hold ranges
|
||||
0 to 100% of the time remaining after Attack and Decay** (100% of
|
||||
`sample length − (attack + decay)`). At 100% Hold fills all remaining time; at 0% it
|
||||
takes none.
|
||||
- **The A + H + D ≤ sample-length bound holds by construction, not by a clamp.** Hold is
|
||||
expressed as a fraction of what is left, so the sum cannot overflow. Do not add a
|
||||
separate clamp; none is needed.
|
||||
- **Scope rule — split on the sustain stage.** The 1:1-overlay property and the
|
||||
combined-time bound apply to **all envelopes that do NOT have a sustain stage**;
|
||||
envelopes **with** a sustain stage get the right-anchored-release policy instead. The
|
||||
two policies **coexist rather than merge**. Daniel's rationale: the 1:1 mapping only
|
||||
makes sense for trigger, not gated envelopes. **The pitch envelope's D is not a
|
||||
release, so it is not right-anchored.**
|
||||
|
||||
**Behavior — mode-driven envelope shape (item 14).**
|
||||
- **Trigger mode: fades out, AHD in.** In Trigger mode the amp envelope becomes an AHD
|
||||
per item 8's definition. The fade-in-length and fade-out-length controls **go away in
|
||||
Trigger mode**; Attack and Decay carry those roles. Daniel's motive is stated:
|
||||
reducing code for the same job — one staged-envelope design covering what is currently
|
||||
two mechanisms.
|
||||
- **The Trigger AHD's sloped segments get the full curve treatment** — exponent 0.1–10,
|
||||
inner dials, mid-segment overlay knots, tertiary-purple rendering.
|
||||
- **Gate mode unchanged** — the amp envelope stays the AHDSR with the right-anchored
|
||||
release.
|
||||
- **The filter envelope follows the same rule.** Stated once, governing all three:
|
||||
**pitch is always AHD (1:1 overlay); amp and filter are AHDSR in Gate
|
||||
(right-anchored release) and AHD in Trigger (1:1 overlay).** The rule selects by the
|
||||
envelope's **current shape under the active playback mode**, not by which processor it
|
||||
modulates. No new rule is needed — item 8's rule already decides every case.
|
||||
- **Pre-existing instances reopen sounding identical.** A Trigger instance's prior
|
||||
fade-in/fade-out contour must be reproduced by the loaded AHD. The evident mapping
|
||||
(Attack ← fade-in, Decay ← fade-out, Hold ← the full remainder, exponents at whatever
|
||||
reproduces the prior fade shape) is a verify-at-implementation detail. **A prior zero
|
||||
fade-out is Decay = 0** — the abrupt end stays representable, so nothing the old
|
||||
controls could express is lost.
|
||||
|
||||
**Behavior — the Trigger×Preserve click (item 4).**
|
||||
- In Trigger mode — polyphonic or monophonic alike — with the pitch engine in
|
||||
**Preserve mode only**, the end of the sample produces an audible click. The scoping is
|
||||
the useful part of the report and is recorded as given: Trigger × Preserve ×
|
||||
end-of-sample; Varispeed is not implicated.
|
||||
- Expected: a Trigger one-shot in Preserve ends silently — no click or discontinuity at
|
||||
the sample end, in either voice mode.
|
||||
- **Why it is in this track and not earlier:** the defect sits in the region the
|
||||
fade-out currently governs, and this track retires that mechanism. Fixing it in a
|
||||
prior wave means fixing it, then re-verifying it under the surviving mechanism
|
||||
anyway — and the only other instrument-side track before this one owns the whole
|
||||
engine. The gate below is the *post-consolidation* gate.
|
||||
|
||||
**Acceptance criteria.**
|
||||
- Each envelope deck shows a corner radio switch; activating one puts that envelope in
|
||||
the overlay, editable there, and the overlay tracks the switch immediately. At most one
|
||||
envelope is overlay-active; with none active the overlay draws no envelope.
|
||||
- The editor opens with no envelope overlay-active.
|
||||
- Every curvable-segment knob shows the inner dial (inner arc, tertiary-purple hover
|
||||
accent, needle, numeric label); sweeping it 0.1 → 10 visibly reshapes the overlay
|
||||
segment and **audibly** reshapes the envelope on played notes. Hold and Sustain knobs
|
||||
carry no inner dial.
|
||||
- Dragging on an overlay segment adds a round mid-segment knot; dragging the knot adjusts
|
||||
that segment's curve, and the segment's inner dial reflects the same value.
|
||||
- Overlay envelope segments render in tertiary purple and are clearly legible against the
|
||||
primary green waveform.
|
||||
- With release at zero or minimum, the sustain segment extends to (near) the right edge —
|
||||
the overlay reads full-width, not bunched left. Dragging the sustain→release top node
|
||||
adjusts release; the bottom-right corner is fixed and not draggable.
|
||||
- Envelopes with a sustain stage follow that anchoring policy; the pitch envelope's Decay
|
||||
is **not** right-anchored.
|
||||
- The pitch envelope plays and displays three stages — Attack, Hold, Decay — with Hold
|
||||
spanning 0–100% of the time remaining after Attack and Decay. **No combination of A, H,
|
||||
D exceeds the sample length, and no clamp fires, because none exists.**
|
||||
- The overlay of any sustain-less envelope is 1:1 with the waveform's time axis: a stage
|
||||
boundary at N seconds sits over the waveform at N seconds.
|
||||
- In Trigger mode the amp deck shows Attack / Hold / Decay — **no fade-in or fade-out
|
||||
control anywhere in Trigger mode** — with curve inner dials on Attack and Decay and
|
||||
none on Hold.
|
||||
- Switching between Gate and Trigger switches the amp *and* filter envelope surfaces
|
||||
(deck and overlay) between AHDSR and AHD accordingly.
|
||||
- The Trigger amp AHD behaves stage-for-stage like the pitch AHD — Hold semantics, curve
|
||||
treatment, and overlay mapping match. (The observable proxy for the consolidation
|
||||
motive: one staged-envelope design, several consumers.)
|
||||
- **Migration:** a project saved before this change reopens sounding identical — every
|
||||
curve at exponent 1.0, and a Trigger instance's prior fade contour (including a zero
|
||||
fade-out's abrupt end) audibly reproduced by the loaded AHD.
|
||||
- **Item 4's gate, under the surviving mechanism:** a Trigger-mode note played in
|
||||
Preserve, in both Poly and Mono, ends with no audible click at the sample end —
|
||||
verified by ear *and* by inspecting the rendered output for a terminal discontinuity.
|
||||
Varispeed playback is unchanged.
|
||||
|
||||
**Open questions.**
|
||||
- **Stage-value state across the Gate/Trigger switch — shared or per-mode? [propose]**
|
||||
The Gate AHDSR and Trigger AHD share stage names (A, H, D); whether they share *values*
|
||||
(one envelope whose S and R fall away in Trigger) or keep per-mode state (item 3's
|
||||
"save but inactive" dual-state precedent) is unspecified — and the question covers the
|
||||
**filter envelope too**. Migration leans per-mode for the amp: an old instance carries
|
||||
both its AHDSR values and its Trigger fade values, and a shared-value model cannot
|
||||
preserve both modes' prior sound at once. The filter is new and carries no migration
|
||||
weight either way; matching the amp's answer is the natural default. Resolve with a
|
||||
proposal at review — the sound-identical gate must hold for whichever mode a saved
|
||||
instance plays in.
|
||||
**Both tracks have landed** — Θ-W3-T1 (`live-parameter-delivery`) and Θ-W3-T2
|
||||
(`staged-envelope-curves`) — see `docs/COMPLETED.md` for the full narrative of each.
|
||||
Between them: every continuous playback control (filter cutoff/Q/morph/drive/mod
|
||||
amount/key-track, every stage time and level on all three envelopes) now reaches a
|
||||
sounding voice live instead of latching at note-on, and the envelope-overlay editor
|
||||
grew from an amp-only fixture into the shared graphical surface for all three
|
||||
envelopes — a corner radio switch per deck (none active by default), curve-shapeable
|
||||
segments on every sloped stage (0.1–10 exponent, an inner dial paired with an overlay
|
||||
knot), the release-right-anchored AHDSR layout against the pitch envelope's 1:1 AHD,
|
||||
and the Trigger amp/filter fade pair folded into a Trigger AHD, consolidating what
|
||||
were two staged-shape mechanisms into one. The Trigger × Preserve end-of-sample click
|
||||
is fixed; the landed fix is wider than scoped, also ringing out Gate × Preserve ×
|
||||
source-exhaustion, previously a hard cut. The ordering was deliberately serial — T1's
|
||||
live-delivery mechanism landed first so T2's new curve exponents and Trigger AHD
|
||||
fields were authored directly into it rather than backfilled afterward.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user