docs: spec live-parameter delivery as Theta-W3-T1
Note-on latching rejected for continuous controls. New serial track ahead of staged-envelope-curves (renumbered T2); edit-model and mid-stage-rule questions left open for Daniel. Records two deferred filter follow-ups.
This commit is contained in:
+310
-22
@@ -28,11 +28,21 @@ transliterate: **Θ → `th`**, **Ξ → `xi`**. So Θ-W1-T1 dispatches into
|
||||
|
||||
## Decision state
|
||||
|
||||
**Nothing in this plan awaits a Daniel decision.** Every question carried forward from
|
||||
`TODO-1.0.md` is classified **[verify]** (answerable by reading code or running the DAW)
|
||||
or **[propose]** (a design call made at implementation review with a proposal, not a
|
||||
Daniel call). That classification is preserved per question, attached to the track that
|
||||
will answer it.
|
||||
**Two questions in this plan await a Daniel decision, and both are in Θ-W3-T1.**
|
||||
Everything carried forward from `TODO-1.0.md` is still classified **[verify]**
|
||||
(answerable by reading code or running the DAW) or **[propose]** (a design call made at
|
||||
implementation review with a proposal, not a Daniel call); that classification is
|
||||
preserved per question, attached to the track that will answer it.
|
||||
|
||||
Θ-W3-T1 (`live-parameter-delivery`) is the exception, and it is an honest one: it is not
|
||||
from `TODO-1.0.md` — it arose from Θ-W2-T1's implementation review — and it carries two
|
||||
genuine **[Daniel]** questions that no amount of code-reading answers. **Which edits stop
|
||||
triggering an instrument reload** is an edit-model call about the product's shape, and
|
||||
**what a stage-time change does to a voice already inside that stage** is sound-defining.
|
||||
Both are stated in that track with candidates laid out and neither pre-picked. The track
|
||||
should not be dispatched at full scope until they are answered; a reduced-scope fallback
|
||||
is named there so the track is not hard-blocked if Daniel would rather rule after seeing
|
||||
the mechanism work.
|
||||
|
||||
### Flagged for awareness — not blocking, but decision-grade
|
||||
|
||||
@@ -169,21 +179,287 @@ the chrome band — and none re-allocated the band stack.
|
||||
|
||||
---
|
||||
|
||||
### Θ-W3 — The staged envelope system
|
||||
### Θ-W3 — Live parameters, then the staged envelope system
|
||||
|
||||
**Depends on W2 for:** the filter envelope's existence — items 1, 8, and 14 govern
|
||||
*three* envelopes, and the filter is the third; and for the Filter deck, which must
|
||||
exist before it can receive a corner radio switch and inner curve dials.
|
||||
exist before it can receive a corner radio switch and inner curve dials. T1 additionally
|
||||
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.
|
||||
|
||||
**One track.** This is the phase's densest collision: 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
|
||||
it into model and UI tracks would put two writers on `zone_params.h` +
|
||||
`component_state_io` and two writers on the overlay's forward/inverse map pair. The
|
||||
serialization is the correct answer.
|
||||
**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.
|
||||
|
||||
#### Θ-W3-T1 — `staged-envelope-curves`
|
||||
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 must specify its mid-stage rule in terms that survive T2 — see that track's open
|
||||
question 2, whose candidates are deliberately expressed over *normalized stage position*
|
||||
rather than over output level, because T2's exponent is a pure map of that position and
|
||||
must compose with the rule rather than invalidate 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`
|
||||
|
||||
**Goal.** Retire note-on latching as the delivery model for continuous playback controls:
|
||||
a knob moved while a note is sounding changes *that* note, not merely the next one.
|
||||
Establishes the live-parameter block and its publish path, splits the editor's commit
|
||||
routing so a parameter edit stops rebuilding the instrument, and makes reachable a
|
||||
property the filter DSP was deliberately built with and that nothing currently exercises.
|
||||
|
||||
**Consolidates:** nothing from `TODO-1.0.md`. This track carries no item number — it
|
||||
arose from Θ-W2-T1's implementation review. See "Work in this plan that is not one of the
|
||||
seventeen" below the traceability table.
|
||||
|
||||
**Origin — Daniel's ruling, recorded verbatim** (2026-07-30, on the filter's latched
|
||||
cutoff):
|
||||
|
||||
> *"hell no, I was going to bring that up for the other envelopes. We must live compute,
|
||||
> latching the parameters at note on is not acceptable. long term these will be
|
||||
> automatable parameters."*
|
||||
|
||||
The ruling rejects the **precedent, not one instance of it.** Every playback parameter is
|
||||
latched into the `Voice` at note-on today — the filter (Θ-W2-T1), the amp AHDSR, and the
|
||||
pitch envelope alike. It also silently defeats a property that was built on purpose:
|
||||
`src/core/instrument/engine/filter/CLAUDE.md` records that `prepare()` does **not** clear
|
||||
state precisely so a live parameter move glides rather than clicks. Nothing reaches it.
|
||||
|
||||
**The architectural finding — read this before scoping the track.** A staff-engineer
|
||||
attempted the live-cutoff fix inside Θ-W2-T1 and correctly stopped. The fix is **not
|
||||
local to the engine:**
|
||||
|
||||
- An editor knob release calls `commitAndReload()` → `reloadInstrument()`, which re-reads
|
||||
the bridge, **re-decodes the WAV from disk**, builds a fresh `SampleData` +
|
||||
`VoiceEngine`, and atomic-swaps it into `live_`.
|
||||
- The displaced instrument moves to `draining_`, where already-sounding voices keep
|
||||
rendering **their own frozen `SampleData`**. New note-ons route only to `live_`.
|
||||
- Therefore a `Voice` that read `sample_->play` per frame instead of latching would
|
||||
**still** not move a sustaining note: that note lives in a different snapshot, and that
|
||||
snapshot is by design never updated again.
|
||||
|
||||
Two consequences bind the whole track. The live-parameter block's ownership must sit
|
||||
**above** `LoadedInstrument` so `live_` and `draining_` observe the same one — a block
|
||||
owned by a snapshot reproduces the defect exactly. And the set of edits that trigger a
|
||||
reload has to shrink; that is the open product question below.
|
||||
|
||||
**Three commit tiers already exist. The work is reassigning edits across them, not
|
||||
inventing a taxonomy.**
|
||||
|
||||
1. **Full reload** — `commitAndReload()` → `reloadInstrument()`: bridge read, WAV
|
||||
re-decode, fresh engine, snapshot swap. Today: every parameter edit.
|
||||
2. **Engine rebuild** — `rebuildVoiceEngine()`: rebuilds around the already-decoded
|
||||
`SampleData`, no disk, same drain-slot swap. Today: voice count, voice mode, mono
|
||||
trigger.
|
||||
3. **Live** — `masterGain_`: a lock-free atomic the audio thread reads per block and
|
||||
ramps toward per sample (`gainCurrent_` / `gainRampStep_`), no rebuild, no snapshot.
|
||||
Today: master gain alone.
|
||||
|
||||
Tier 3 is the shape this track generalizes, and it is a **house precedent already
|
||||
shipped and already zipper-free** — not a pattern imported from elsewhere. Read it before
|
||||
designing the block.
|
||||
|
||||
**Behavior — what must become live.** Every continuous user-facing playback control:
|
||||
|
||||
- **Filter** — cutoff, Q, morph, drive, mod amount, key-track.
|
||||
- **All three envelopes** — every stage time and every stage level, on the amp AHDSR, the
|
||||
pitch envelope, and the filter AHDSR alike.
|
||||
|
||||
**Behavior — what stays latched per note, where making it live is a bug.** These are
|
||||
facts about the note event, not controls, and a design that "generalizes" them into the
|
||||
live block has misread the ruling:
|
||||
|
||||
- **Velocity**, and everything derived from it — in particular the velocity-curve
|
||||
evaluation result (`velocityGain_`, `filterVelOffset_`), which is evaluated once in
|
||||
`Voice::start` on purpose.
|
||||
- **The note number**, and the pitch ratio derived from it against the root note
|
||||
(`baseRatio_`). Moving a root-note or key-track control must not retune a sounding
|
||||
note; the legato `retune()` path is the only sanctioned mid-note pitch move.
|
||||
- **The sample/capture identity and its decoded PCM.** Loading a different capture is a
|
||||
new sound, not a parameter change.
|
||||
|
||||
**Behavior — the live block.** Requirements, stated as constraints rather than as a
|
||||
chosen design:
|
||||
|
||||
- **Ownership outlives every `LoadedInstrument`.** One block per processor instance,
|
||||
observed identically by `live_` and `draining_`. A drain-slot voice therefore keeps
|
||||
tracking the knob — that is the *desired* behavior (it is the note the user is
|
||||
hearing), and it should be stated so nobody later "fixes" it.
|
||||
- **`PlayParams` stays a plain copyable value type.** `component_state_io`, `sample_map`,
|
||||
and the editor all pass it by value; atomics cannot simply be pushed into it.
|
||||
- **No lock on the audio path**, and **no torn read**: the editor writes on the UI
|
||||
thread while `process()` reads on the audio thread, so observing half of one edit and
|
||||
half of another mid-block is a real hazard the design must close, not a theoretical one.
|
||||
- **No constructor growth, and no dependency grab-bag.** An `IServiceProvider`-shaped
|
||||
parameter object threaded into `VoiceEngine`/`Voice` constructors is a smell and is
|
||||
rejected. One candidate that satisfies both: a single `const LiveParams*` field on
|
||||
`SampleData`, defaulting to `nullptr` so the bare core behaves exactly as today
|
||||
(latched, byte-identical, existing tests untouched) and the shell sets it on the path
|
||||
`SampleData` already travels. That is a [propose]-class mechanism note, not a mandate.
|
||||
|
||||
**Behavior — the per-frame constraint (Daniel's standing non-negotiable).** Root
|
||||
`CLAUDE.md`'s performance guardrails and structural heuristic 3 both bind here:
|
||||
|
||||
- **No allocation and no virtual dispatch on the per-voice-per-sample path.** A per-frame
|
||||
copy of a params struct is a violation; so is an added header→TU indirection.
|
||||
`envelopes.h`'s evaluators and `Voice::advanceFrame` are header-inline by RT
|
||||
constraint (no LTO configured) — concrete, no common base, no virtual `tick()`.
|
||||
- **Observation happens at block boundaries, not per frame.** The voice already caches
|
||||
its filter parameters in members (`filterCutoffNorm_`, `filterModAmount_`,
|
||||
`filterKeyTrack_`, …); the minimal live implementation refreshes those cached members
|
||||
at a block boundary instead of only at note-on, leaving the per-sample shape unchanged.
|
||||
At 512 frames / 48 kHz that is ~93 Hz of control resolution — ample for a hand on a
|
||||
knob, and the coarse floor for automation later.
|
||||
- **Smoothing is required, and it is what makes the block-rate step inaudible.** A
|
||||
block-rate jump in a base value is a step; `masterGain_`'s per-sample linear ramp
|
||||
toward the target is the in-house answer. **The ramp must terminate exactly**, not
|
||||
asymptotically: `tickFilterCutoff`'s two exact skips compare the value itself, so a
|
||||
never-quite-arriving one-pole would pin the filter on the always-re-solve path forever.
|
||||
Cost while a cutoff ramp is running is known and affordable — 15.5 ns/frame/voice
|
||||
measured in `engine/filter/CLAUDE.md`, ~1.2% of one core at 16 voices — and it returns
|
||||
to the skip path when the ramp completes.
|
||||
|
||||
**Acceptance criteria.**
|
||||
|
||||
- With a note held, sweeping filter cutoff, Q, morph, drive, mod amount, or key-track
|
||||
audibly moves *that* note. Same for every stage time and stage level on all three
|
||||
envelopes.
|
||||
- A knob move on a live parameter performs **no** WAV re-decode and **no** snapshot
|
||||
rebuild — verified against `reloadInstrument`, not by ear alone.
|
||||
- A note sounding out of the **drain slot** responds to a live parameter move identically
|
||||
to a `live_` voice.
|
||||
- **No click, no step, no zipper** on any live parameter move, at any block size —
|
||||
including a full-range cutoff sweep at maximum Q, and a sustain-level change on a note
|
||||
held in Sustain.
|
||||
- **The per-sample path costs nothing at rest.** With no live value changed, the render
|
||||
is unchanged in call/inline shape from today; all live observation is at block
|
||||
boundaries. No allocation, no lock, no virtual call is added on the audio thread.
|
||||
- The audio thread never observes a partially-applied edit within one block.
|
||||
- `PlayParams` is still a plain copyable value type, still passed by value by
|
||||
`component_state_io`, `sample_map`, and the editor.
|
||||
- **Velocity, the velocity-curve result, the note number, the pitch ratio, and the
|
||||
decoded PCM are still latched at note-on.** Dragging a velocity-curve point does not
|
||||
retune or re-gain a sounding note.
|
||||
- **With no live block attached the core is byte-identical to today** — `sampler_core`'s
|
||||
existing regression tests pass unchanged, including the bare-engine baselines.
|
||||
- **Migration:** a project saved before this change reopens sounding identical. Persisted
|
||||
values are unchanged; `ComponentState` gains no version bump unless the chosen
|
||||
representation forces one, and if it does, the bump is additive and pre-existing blobs
|
||||
lift with no audible change.
|
||||
- The filter DSP's glide property is finally exercised by a test: a cutoff move across a
|
||||
`prepare()` without a `reset()` produces no output discontinuity.
|
||||
|
||||
**Open questions.**
|
||||
|
||||
1. **Which edits stop triggering a reload? [Daniel]** Sample/capture selection genuinely
|
||||
needs a reload; filter cutoff plainly does not. The line between them is an edit-model
|
||||
decision, not an implementation one. Three candidate groupings, in widening order:
|
||||
- **Grouping C — continuous controls only.** Only knob-valued continuous controls go
|
||||
live (filter cutoff/Q/morph/drive/mod/key-track, envelope times and levels). Every
|
||||
discrete toggle — play mode, pitch engine, filter law, filter enable, channel mode,
|
||||
velocity curve — keeps reloading. Narrowest blast radius, smallest verification
|
||||
surface, and it fully satisfies the ruling as stated.
|
||||
- **Grouping B — capture-anchored edits reload.** C, plus: the three capture-anchored
|
||||
overrides (root note, loop span, start frame) also reload, because they name
|
||||
positions in the decoded PCM and `loadSelection` already treats them as one family.
|
||||
Everything else goes live or drops to the tier-2 rebuild.
|
||||
- **Grouping A — only the sound source reloads.** Reload if and only if the identity
|
||||
of the decoded audio changes: capture selection and channel mode (a decode policy).
|
||||
Everything else is live or a rebuild. Widest live surface, largest verification
|
||||
surface, and the one that most nearly matches "these will be automatable
|
||||
parameters."
|
||||
No pick is made here. **Proposed fallback if Daniel would rather rule after seeing the
|
||||
mechanism run:** build the mechanism and ship Grouping C, leaving B and A reachable as
|
||||
later reassignments rather than rework — the tier a given edit sits in is a routing
|
||||
decision at the editor's commit site, not a property of the block. That is a scope
|
||||
proposal, not a decision taken.
|
||||
2. **What does a stage-time change do to a voice already inside that stage? [Daniel]**
|
||||
Sound-defining, and there is no single right answer. Bounded by one non-negotiable:
|
||||
**it must not click.** A discontinuity in output level on a parameter move is a
|
||||
defect, and a sustain level changed while a voice is held must glide, not step. The
|
||||
candidates, with the click property of each named:
|
||||
- **(i) Jump / recompute from absolute elapsed.** Keep `framesInStage_`, divide by the
|
||||
new duration. This is what today's code would do if params were simply swapped —
|
||||
`level_ = framesInStage_ / attackFrames`. Steps the level discontinuously. Fails the
|
||||
non-negotiable unaided.
|
||||
- **(ii) Clamp.** As (i), but a stage whose new duration is already exceeded completes
|
||||
immediately. Still steps.
|
||||
- **(iii) Re-derive rate from the current level.** Hold the level, recompute the
|
||||
per-frame advance so the stage completes at the new duration measured *from now*.
|
||||
Continuous by construction. Costs: the stage's total time becomes elapsed +
|
||||
remaining rather than the dialed value, and it needs a rate representation. This is
|
||||
what a hardware EG with a rate DAC does.
|
||||
- **(iv) Hold normalized stage position.** Keep phase φ = elapsed / duration fixed
|
||||
across the change, then advance at 1/newDuration. Continuous in level (φ unchanged →
|
||||
level unchanged) and the remaining stage takes its share of the dialed duration.
|
||||
- **(v) Track absolute elapsed against the new duration.** Identical to (i); listed so
|
||||
it is visibly not a distinct third option.
|
||||
- **(vi) Keep (i) and smooth the envelope output.** Accept the recompute and put a
|
||||
short declick ramp on the envelope's output level — the codebase already owns a
|
||||
bounded-blend declick primitive (`kDeclickDecay`, `seedDeclick`) built for exactly
|
||||
this class of step.
|
||||
Only **(iii)** and **(iv)** are continuous without added machinery, and both compose
|
||||
with T2's curve exponent because both are expressed over normalized position, which
|
||||
the exponent is a pure map of. **(vi)** is the option that makes the cheap rule
|
||||
acceptable, at the cost of a second smoother. Sustain is a separate sub-case under
|
||||
every candidate: it is a *level*, not a timed stage, so a live sustain edit is a direct
|
||||
level step and needs the ramp regardless of which rule wins.
|
||||
3. **Representation: atomics in the value type, or a second live representation?
|
||||
[propose]** Two forks, both with real costs:
|
||||
- **F1 — atomics in the value type.** Push atomics into the live-relevant fields of
|
||||
`PlayParams` / `FilterParams` / `AdsrParams`. One model, no mapping to drift.
|
||||
Blast radius is wide and probably disqualifying: atomics are non-copyable, and
|
||||
`PlayParams` is passed by value by `component_state_io`, `sample_map`, and the
|
||||
editor — the plain-copyable-value-type constraint above is a hard bound on how far
|
||||
this fork can go.
|
||||
- **F2 — a second live-parameter representation.** A parallel block the audio thread
|
||||
reads, published by the UI thread. Keeps the value type plain; the cost is that the
|
||||
model exists twice and the mapping between them can drift. Mitigation to require if
|
||||
this fork wins: derive the live block from `PlayParams` through exactly **one**
|
||||
explicit fold function, so there is a single writer and a single site to keep in
|
||||
step. Three publication mechanisms sit under F2, and the choice among them is the
|
||||
torn-read answer: a **seqlock** (odd/even generation counter, audio thread copies
|
||||
the block once per block and retries on a torn read — the standard single-writer
|
||||
lock-free pattern); **per-parameter atomics** (simplest, exactly what `masterGain_`
|
||||
does today, but offers no coherence *across* parameters — a set like an envelope's
|
||||
A/H/D/S/R can be observed mid-edit); or a **double-buffered block published by
|
||||
atomic pointer swap** (coherent, no retry, needs a reclaim rule — and the processor
|
||||
already owns that muscle in `live_`/`draining_`/`graveyard_`).
|
||||
4. **Does the block need sample-accurate (sub-block) resolution now? [propose]** Daniel's
|
||||
ruling names automation as the long-term destination, and VST3 delivers parameter
|
||||
changes on the audio thread with sample offsets inside `ProcessData` — so per-block is
|
||||
the coarse floor, not the ceiling. Building sub-block splitting now is speculative;
|
||||
*foreclosing* it is the failure mode. The requirement this track must carry either
|
||||
way: **the block's writer interface must not assume a UI thread**, so the audio
|
||||
thread's own parameter-change queue can drive it later without a redesign.
|
||||
5. **Does a live edit still persist immediately? [verify]** Today `commitAndReload` also
|
||||
writes the edited set into `params_`, which is what `getState` serializes. A live path
|
||||
must keep making that UI-thread write so a saved project carries the edit — but that
|
||||
write is no longer the audio thread's source. Confirm by reading that nothing else
|
||||
depends on the reload as its persistence trigger.
|
||||
|
||||
#### Θ-W3-T2 — `staged-envelope-curves`
|
||||
|
||||
**Prerequisite: Θ-W3-T1 must land first** — see the wave preamble. Both tracks write
|
||||
`envelopes.h`'s stage math, and this track's curve exponent must be authored as a map of
|
||||
whatever normalized stage position T1's mid-stage rule establishes.
|
||||
|
||||
**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
|
||||
@@ -1108,24 +1384,35 @@ The check that nothing was dropped. Every row points at a track that exists abov
|
||||
|
||||
| # | Item (short) | Phase-Wave-Track | Worktree slug |
|
||||
|---|---|---|---|
|
||||
| 1 | Envelope editor: radio switch, curve dials, overlay recolor | Θ-W3-T1 | `pth-w3-t1-staged-envelope-curves` |
|
||||
| 1 | Envelope editor: radio switch, curve dials, overlay recolor | Θ-W3-T2 | `pth-w3-t2-staged-envelope-curves` |
|
||||
| 2 | MM preamp Filter — resonant HP/LP stage | Θ-W1-T3 (DSP) **+** Θ-W2-T1 (integration) | `pth-w1-t3-filter-dsp-port`, `pth-w2-t1-filter-voice-path` |
|
||||
| 3 | Alternative Spline EGs | Θ-W5-T1 | `pth-w5-t1-spline-egs` |
|
||||
| 4 | Bug: end-of-sample click, Trigger × Preserve | Θ-W3-T1 | `pth-w3-t1-staged-envelope-curves` |
|
||||
| 4 | Bug: end-of-sample click, Trigger × Preserve | Θ-W3-T2 | `pth-w3-t2-staged-envelope-curves` |
|
||||
| 5 | Bug: drag-out sometimes lands without audio | Θ-W1-T2 | `pth-w1-t2-capture-handoff-bugs` |
|
||||
| 6 | Bug: FX-container drop loses the capture | Θ-W1-T2 | `pth-w1-t2-capture-handoff-bugs` |
|
||||
| 7 | Stereo waveform shows both channels | Θ-W2-T2 | `pth-w2-t2-stereo-waveform-lanes` |
|
||||
| 8 | Release anchoring; the Pitch AD becomes AHD | Θ-W3-T1 | `pth-w3-t1-staged-envelope-curves` |
|
||||
| 8 | Release anchoring; the Pitch AD becomes AHD | Θ-W3-T2 | `pth-w3-t2-staged-envelope-curves` |
|
||||
| 9 | Loop points — regression + Gate loop-sustain | Θ-W4-T1 | `pth-w4-t1-gate-loop-sustain` |
|
||||
| 10 | Knob/label sizing, ms units, double-click reset | Θ-W6-T1 | `pth-w6-t1-legibility-and-antialiasing` |
|
||||
| 11 | Preview glyph; VELOCITY deck; bipolar curves | Θ-W4-T2 | `pth-w4-t2-velocity-deck-and-bipolar-curves` |
|
||||
| 12 | Toolbar cleanup; full-width piano strip; tooltips | Θ-W2-T3 | `pth-w2-t3-toolbar-and-piano-strip` |
|
||||
| 13 | Antialiased rendering audit for high-DPI | Θ-W6-T1 | `pth-w6-t1-legibility-and-antialiasing` |
|
||||
| 14 | Trigger amp/filter fade → AHD consolidation | Θ-W3-T1 | `pth-w3-t1-staged-envelope-curves` |
|
||||
| 14 | Trigger amp/filter fade → AHD consolidation | Θ-W3-T2 | `pth-w3-t2-staged-envelope-curves` |
|
||||
| 15 | One-click in-sampler resample | Ξ-W1-T2 (note model) **+** Ξ-W2-T1 (bake chain) **+** Ξ-W3-T1 (popup) | `pxi-w1-t2-note-program-model`, `pxi-w2-t1-resample-bake-chain`, `pxi-w3-t1-capture-signal-popup` |
|
||||
| 16 | Retire the zone mapping system | Θ-W1-T1 | `pth-w1-t1-zone-retirement` |
|
||||
| 17 | Consolidate provenance/usage tracking | Ξ-W1-T1 | `pxi-w1-t1-tracking-consolidation` |
|
||||
|
||||
### Work in this plan that is not one of the seventeen
|
||||
|
||||
The table above is a completeness proof over `TODO-1.0.md` — every row points at a track,
|
||||
so nothing from the source was dropped. It is deliberately **not** an index of the plan:
|
||||
work that did not come from the source doc has no row, and inventing one would weaken the
|
||||
proof it exists to give.
|
||||
|
||||
- **Θ-W3-T1 — `live-parameter-delivery`** (`pth-w3-t1-live-parameter-delivery`). Arose
|
||||
from Θ-W2-T1's implementation review, not from `TODO-1.0.md`. It is the only such track
|
||||
in this plan today; if others appear, they belong on this list rather than in the table.
|
||||
|
||||
### Deliberate compressions
|
||||
|
||||
Recorded so a reader of `TODO-1.0.md` can see what this plan did to the source, rather
|
||||
@@ -1137,7 +1424,7 @@ than discovering it later:
|
||||
acceptance criteria are split accordingly — the range/resonance assertions land in
|
||||
W1-T3's tests, the audible/pipeline/deck criteria in W2-T1.
|
||||
- **Item 2's filter envelope ships twice.** W2-T1 ships it in the existing staged AHDSR
|
||||
shape; W3-T1 gives it the curve treatment and the mode-driven Gate→AHDSR /
|
||||
shape; W3-T2 gives it the curve treatment and the mode-driven Gate→AHDSR /
|
||||
Trigger→AHD shape. This is deliberate: waiting would put the filter behind the whole
|
||||
envelope system.
|
||||
- **Item 4 lands in Θ-W3, not Θ-W1.** Its fix region is the region item 14 retires, and
|
||||
@@ -1169,8 +1456,9 @@ Phase Θ — ReaSampler 9000: one parameter set, filter, shapeable envelopes, le
|
||||
T1 filter-voice-path ....................... 2 (integration)
|
||||
T2 stereo-waveform-lanes ................... 7
|
||||
T3 toolbar-and-piano-strip ................. 12
|
||||
W3 The staged envelope system
|
||||
T1 staged-envelope-curves .................. 1, 8, 14, 4
|
||||
W3 Live parameters, then the staged envelope system [T1 before T2 — serial]
|
||||
T1 live-parameter-delivery ................. (not one of the seventeen)
|
||||
T2 staged-envelope-curves .................. 1, 8, 14, 4
|
||||
W4 Loop sustain and the velocity deck
|
||||
T1 gate-loop-sustain ....................... 9
|
||||
T2 velocity-deck-and-bipolar-curves ........ 11
|
||||
|
||||
Reference in New Issue
Block a user