From 3b9b78b82c50fb56d84fa8ef568abe727d90f417 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Mon, 27 Jul 2026 12:57:08 -0400 Subject: [PATCH] docs(phase-s): settle S-VIEW velocity persistence + draggable nodes, single-pass on L3 F1 -> preview velocity persists via VST3 component state (v5->v6). F2 -> envelope nodes draggable via new pure envelope_edit module. Drop the two-pass L3 restyle framing; build on the merged L3 LnF. F3 remains open. --- CONTEXT.md | 127 +++++++++++++++++++++++++++------- PLAN.md | 44 ++++++++---- docs/product/midi-playback.md | 54 ++++++++++----- 3 files changed, 170 insertions(+), 55 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 76f5c6e..20ecc00 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -2277,11 +2277,32 @@ view wholesale, with two changes: A **performance choice**, never written to `Sample` or the bank. Back-compat: an absent field on an older blob → 100% (bit-identical playback). - **Preview velocity** — a **utility** setting for the Sample view's preview-trigger button, not a - musical parameter of the capture. **Lean: transient editor state** (like a metronome level) — - it does not need to persist in component state, so it adds no version bump and no bank-fact risk. - *(Fork S-VIEW-F1, Daniel's call: transient vs. a small persisted editor convenience. Lean - transient — simplest, no compat surface, and a preview level is not something users expect to - survive a reload.)* + musical parameter of the capture. **SETTLED (S-VIEW-F1, Daniel 2026-07-27): it PERSISTS across + reloads.** The seam is **VST3 component state**, not the extension's `persist` ext-state module. + This distinction is load-bearing and was verified against the existing VST source, not recalled: + - **Wrong seam — the extension's `persist` module.** `persist` writes REAPER *project* ext-state + (`SetProjExtState`, namespace `"reasampler"`) and is owned by the **extension**, not the + instrument. Preview velocity is a **per-instance** instrument-editor setting; putting it in + project ext-state would (a) make it project-global rather than per-instance (two ReaSampler 9000 + instances would share one preview level), (b) route an instrument-owned setting through a seam + the instrument only *reads* from (bank state), violating the read-only-over-bank contract, and + (c) leak an instrument concern into the extension's key space. Rejected. + - **Right seam — the instrument's own VST3 component state**, the same blob the processor already + round-trips via `ReaSamplerProcessor::getState`/`setState` over `IBStream`, whose format is the + **envelope-versioned `ComponentState`** in `src/vst/sample_map.h` (currently **v5**: version tag + + channel-mode byte + last-consumed-assignment marker + selection-id + zones payload). Preview + velocity is a **top-level instance concern** — a sibling of `channelMode` and + `lastConsumedAssignGeneration`, **not** a per-`PerformanceZone` field (it is one setting per + instance, not per zone). It therefore lands as a **new field on `ComponentState`** added by + bumping the **envelope version to v6** (a new `float previewVelocity` after the assignment + marker, before the selection-id), leaving the **zones payload untouched** — exactly the + independent-version-axes composition the header already documents (envelope grows a field; the + zone-record payload version does not move). Round-trip: `serializeComponentState` appends it, + `deserializeComponentState` reads it; a v6 blob restores it directly, and **every older blob + (v5 and down) lifts to a sensible default** (e.g. 100/127 ≈ 0.79 or a chosen mid level), so + already-saved instances are unchanged and no compat surface is broken. This is the same additive + envelope-bump discipline S7 (v4→v5) already used — a well-trodden move in this codebase, not new + machinery. ### The envelope overlay — visual component (new) @@ -2293,9 +2314,51 @@ drawn shape lines up with the waveform under it. **Pure geometry:** an `envelope (mirror of `waveform_view` / `param_slider`) maps the AHDSR/Trigger params + the sample's frame-length to a polyline in the waveform rect (`param↔pixel` at the shared time base), unit-tested against known param sets; the shell traces it via kit line draws in an accent hue. -Decorative + informative — it never captures input (the sliders remain the edit surface); dragging -the envelope *nodes* directly is a deferred stretch (fork S-VIEW-F2, lean defer — sliders first, -node-drag is polish once the overlay reads). + +**The overlay is directly editable — DRAGGABLE NODES (SETTLED, S-VIEW-F2, Daniel 2026-07-27).** +The envelope is not a read-only informative curve — its breakpoints are **draggable handles** that +set the envelope parameters directly. Dragging a node and the existing sliders are **two surfaces +onto one model**: the sliders stay as the precise numeric-entry surface, node-drag is the direct- +manipulation surface, and **both read and write the same `PerformanceZone` envelope fields** — a +drag updates the params, the sliders reflect them live, and a slider edit re-lays the nodes. There +is exactly one source of truth (the zone's envelope params); the two surfaces never diverge. This +is the same one-source-multiple-views instinct the whole system runs on. + +- **Which nodes, and what each axis means.** The AHDSR shape (Gate) exposes handles at the segment + breakpoints — **attack-end** (top of the attack ramp), **hold-end**, **decay-end / sustain-level** + (the corner where decay settles to the sustain plateau), and **release-end**. The Trigger shape + exposes **fade-in-end**, **%-length** (the `playEnd` anchor), and **fade-out-end**. For each node, + **the horizontal axis maps to time** (the segment duration — attack/hold/decay/release seconds, or + fade/length fractions) and **the vertical axis maps to level** where the node is a level breakpoint + (the sustain node's vertical drag sets sustain 0..1; the peak nodes sit at unity). Time-only nodes + (attack-end, hold-end, release-end) drag horizontally; the sustain node drags on **both** axes + (its X sets decay time, its Y sets sustain level) — the standard ADSR-editor grammar (Ableton + Simpler, Phase Plant, Serum all use exactly this). +- **Constraints.** Nodes are **monotonic in time** — a node cannot be dragged left of its + predecessor or right of its successor (attack-end can't pass hold-end, etc.); each segment stays + ≥ 0. Level drags clamp to **0..1** (sustain) / unity (peaks). Times clamp to the same per-param + min/max the sliders already enforce (so node-drag can never produce a param the slider couldn't). + The `playEnd`/%-length node additionally clamps to the sample's frame-length. **No snapping** by + default (continuous drag, matching the sliders' resolution); a fine-drag modifier (drag with a + held modifier for reduced sensitivity) is an optional polish, not required. +- **Pure module owns the geometry/hit-test math (house pattern).** A pure REAPER/LICE-free module — + name it **`envelope_edit`** (sibling to `envelope_overlay`; mirror of `card_drag` / `mode_switch` + / `param_slider`) — owns **node hit-testing** (point → which node, with a pick radius) and the + **drag→param mapping** (a pixel delta on a given node → the resulting clamped envelope param set, + respecting the monotonic + range constraints above). It is **unit-tested**: a known node + a known + pixel delta asserts the expected param delta and the clamp/monotonic behavior at the boundaries. + `envelope_overlay` keeps the params→polyline forward map (draw); `envelope_edit` owns the + pixel→params inverse map (edit) + hit-test. The shell (`reasampler_editor.cpp`) does the LICE + handle draw (small node markers at each breakpoint, hover/drag-lit via kit states) and routes + mouse events through `envelope_edit`, then commits the resulting params to the zone through the + same off-audio-thread path the sliders use (no new RT surface — a node-drag is a param edit, same + as a slider drag). Zones and the single-capture Sample face share this one edit surface (one + storage site, S15-F2). +- **Sync with sliders (load-bearing).** Because both surfaces write the same `PerformanceZone` + envelope fields, keeping them in sync is **structural, not a listener chain**: the editor re-reads + the zone's params every paint, so a slider edit re-lays the nodes and a node-drag re-positions the + sliders with no explicit cross-wiring. The single source of truth makes divergence impossible by + construction. ### The real piano-key pattern — visual component (new) @@ -2357,23 +2420,33 @@ face without scrolling, and cannot be resized below the constraint floor. ### Module architecture (preserve the pure/shell split) - **Pure (new/extended):** `envelope_overlay` (AHDSR/Trigger params + frame-length → polyline in a - rect; unit-tested); `keyboard_strip` extended with the natural/accidental predicate; the sampler - core extended with the **key-track scalar** in the repitch math (unit-tested against known + rect; unit-tested); `envelope_edit` (NEW — node hit-test + pixel-delta→clamped-param inverse map + for the draggable envelope handles; mirror of `card_drag`; unit-tested at the monotonic/clamp + boundaries); `keyboard_strip` extended with the natural/accidental predicate; the sampler core + extended with the **key-track scalar** in the repitch math (unit-tested against known note/root/keyTrack → ratio); `sample_map` (`PerformanceZone`) extended with the additive - `keyTrack` field + component-state version bump + back-compat default. + `keyTrack` field, plus the **envelope-v6 `previewVelocity` field on `ComponentState`** (S-VIEW-F1) + — both additive, component-state version bumped, back-compat defaults on read. - **Shell (`reasampler_editor.cpp`):** re-partition the paint/hit-test into the three views (Sample face, Browse modal overlay, Zone surface) replacing the two-view toggle; add the preview-trigger button + velocity knob wired to an off-audio-thread preview note through the - voice engine; draw the envelope overlay + piano-key overlay via the kit; set the larger default - `ViewRect` + `checkSizeConstraint`. All layout/hit-test math stays in the pure geometry modules. + voice engine (the velocity **persists** via the envelope-v6 `ComponentState` field, S-VIEW-F1); + draw the envelope overlay + **its draggable node handles** (routing mouse events through the pure + `envelope_edit` module and committing params via the same off-thread path the sliders use, + S-VIEW-F2) + piano-key overlay via the kit; set the larger default `ViewRect` + + `checkSizeConstraint`. All layout/hit-test math stays in the pure geometry modules. ### Precision / invariant implications -- **Read-only bank consumer (unchanged).** Key-tracking, preview velocity, and every marker/mode - control are the instrument's **performance map** (D-B) — never written to `Sample` or the bank. -- **Additive, back-compat component state.** `keyTrack` is a new optional field defaulting to 100%; - an older blob (absent field) restores to 100% → **playback bit-identical** under the same engine. - No existing field changes. +- **Read-only bank consumer (unchanged).** Key-tracking, preview velocity, envelope-node edits, and + every marker/mode control are the instrument's **performance map / editor state** (D-B) — held in + the instrument's own VST3 component state, **never written to `Sample` or the bank.** +- **Additive, back-compat component state.** Two additive fields land: `keyTrack` (per- + `PerformanceZone`, default 100%) inside the zones payload, and `previewVelocity` (per-instance) + as a new top-level `ComponentState` field via an **envelope bump to v6** — the zones-payload + version axis is untouched (independent version axes). Every older blob lifts on read (absent + `keyTrack` → 100%, absent `previewVelocity` → the chosen mid default), so already-saved instances + restore **playback bit-identical** under the same engine. No existing field changes. - **RT discipline (unchanged).** The preview-trigger fires a note through the existing voice engine via the off-audio-thread commit path (`commitMapAndReload` idiom); no new `process`-thread work, no allocation on the audio thread. @@ -2384,14 +2457,13 @@ face without scrolling, and cannot be resized below the constraint floor. ### Open questions / forks (Daniel / Phase S team) -- **S-VIEW-F1 — preview velocity persistence.** Transient editor state (lean) vs. a small persisted - editor convenience. Lean transient — no compat surface, no version bump. -- **S-VIEW-F2 — envelope-overlay interactivity.** Read-only informative overlay + sliders as the - edit surface (lean, ship first) vs. draggable envelope nodes on the overlay (deferred stretch — - polish once the overlay reads). +*(S-VIEW-F1 and S-VIEW-F2 are SETTLED — Daniel 2026-07-27 — and folded into the spec above: +preview velocity **persists** via envelope-v6 `ComponentState`; the envelope overlay's nodes are +**draggable** via the pure `envelope_edit` module. They are no longer open questions.)* + - **S-VIEW-F3 — Browse modal presentation.** Full-window overlay (lean — the modal-picker feel) vs. a large centered sheet with a dimmed Sample behind. Presentation detail; either satisfies "easy - to summon and dismiss." + to summon and dismiss." **Still Daniel's call** — the only S-VIEW fork left open. ### Must-verify before build (S-VIEW) @@ -2404,6 +2476,13 @@ face without scrolling, and cannot be resized below the constraint floor. - **Preview note through the voice engine off-thread** — confirm the existing `commitMapAndReload` / off-thread reload idiom is the right seam to fire a one-shot preview note without touching `process` on the UI thread; no torn state on the atomic voice-engine pointer. +- **Preview-velocity persistence seam (S-VIEW-F1)** — the envelope-v6 `ComponentState` field is the + prescribed seam (verified against `src/vst/sample_map.h` + `reasampler_processor.cpp` + getState/setState this pass); confirm at build that appending a `float previewVelocity` after the + assignment marker keeps the v5→v6 lift clean and the zones-payload byte offsets unchanged. +- **Envelope node-edit inverse map (S-VIEW-F2)** — confirm the pure `envelope_edit` pixel→param + inverse map produces params the sliders' own min/max already permit (so the two surfaces can + never diverge), and that the monotonic time-node constraint holds at the segment boundaries. - **Envelope-overlay time base** — confirm the seconds→frames resolution the overlay draws against matches the voice engine's live-rate resolution so the drawn shape lines up with the waveform. diff --git a/PLAN.md b/PLAN.md index a67b461..10fcc28 100644 --- a/PLAN.md +++ b/PLAN.md @@ -190,10 +190,14 @@ is **ReaSampler 9000**. Framing + propagation surfaces: > §Addendum r9. When a point lands, doc-keeper moves it to `COMPLETED.md`. > > **Depends on Phase S being on dev** (this redesigns Phase S editor shells — -> `reasampler_editor.cpp` + the pure `src/vst/` geometry modules). The Phase L L3 restyle and -> this S-VIEW redesign both touch the editor; sequence so they do not collide (S-VIEW is a -> layout/param change, L3 is a palette/kit change — best landed as one editor pass or S-VIEW then -> L3-restyle-the-new-layout). +> `reasampler_editor.cpp` + the pure `src/vst/` geometry modules). **Phase L L3 is FINISHED and +> merged** (2026-07-27, commit `c53683e` — `reasampler_editor.cpp` + `reasampler_embed.cpp` +> restyled through the L1 kit). S-VIEW builds the new three-view layout **directly on the current +> L3 look-and-feel as its baseline — one implementation pass, styled correctly from the start.** +> There is **no two-pass "land S-VIEW then restyle through L3"** — L3 already happened; every new +> S-VIEW surface (Sample face, Browse modal, Zone surface, envelope overlay + node handles, +> piano-key strip, preview cluster) is drawn through the L1 kit at build time, inheriting the L3 +> palette and component-draw grammar. Restyle-after is not a concern. **Goal:** A three-view editor that makes "pick a capture, tune it, play it" fast, easy, and fun — Sample is home, Browse is an easy-to-summon modal picker, Zone is the deliberate keymap surface. @@ -212,15 +216,26 @@ piano-key pattern work; drop-a-capture-onto-FX instantiates a playing instance. Pitch engine / AHDSR|Trigger / AD pitch env — moved from Zone's param panel, single-capture one-zone storage per S15-F2). Reference grammar: Simpler / Phase Plant (labelled value-strip under a hero waveform). -- [ ] **S-VIEW-3 — envelope overlay on the hero waveform (new visual).** Draw the amp envelope - (AHDSR for Gate, fade/%-length for Trigger) as a curve over the Sample waveform at accurate - wall-clock time. New pure `envelope_overlay` module (params + frame-length → polyline; unit- - tested); shell traces it in an accent hue. Informative + decorative — sliders stay the edit - surface (node-drag deferred, fork S-VIEW-F2). +- [ ] **S-VIEW-3 — envelope overlay + DRAGGABLE NODES on the hero waveform (new visual + edit + surface).** Draw the amp envelope (AHDSR for Gate, fade/%-length for Trigger) as a curve over the + Sample waveform at accurate wall-clock time. New pure `envelope_overlay` module (params + frame- + length → polyline; unit-tested); shell traces it in an accent hue. **The breakpoints are draggable + handles** (S-VIEW-F2 SETTLED — in scope, not deferred): X → segment time, Y → level on level- + breakpoint nodes (sustain drags both axes), monotonic-in-time + range-clamped so a drag can never + produce a param the slider couldn't. New pure `envelope_edit` module (node hit-test + pixel-delta→ + clamped-param inverse map; mirror of `card_drag`; unit-tested at the clamp/monotonic boundaries). + Sliders stay the precise numeric surface; **both surfaces read/write the same `PerformanceZone` + envelope fields** — one source of truth, kept in sync by re-reading params each paint (no listener + chain). Node-drag commits via the same off-audio-thread path a slider edit uses (no new RT + surface). - [ ] **S-VIEW-4 — preview-trigger + velocity knob (new, Sample view).** A button firing the sampler at the loaded capture's root note through the live voice engine (off the audio-thread - commit path — no MIDI controller needed) + an adjacent velocity knob. Preview velocity leans - **transient editor state** (fork S-VIEW-F1). RT discipline: no `process`-thread work. + commit path — no MIDI controller needed) + an adjacent velocity knob. **Preview velocity PERSISTS** + (S-VIEW-F1 SETTLED): it lands as a new top-level `previewVelocity` field on `ComponentState` via an + **envelope bump to v6** (`src/vst/sample_map.h`), round-tripped through the processor's existing + `getState`/`setState` over `IBStream` — **not** the extension's project-scoped `persist` ext-state + (wrong owner + wrong scope; see CONTEXT.md §S-VIEW). Zones payload untouched (independent version + axes); older blobs lift to a mid default. RT discipline: no `process`-thread work. - [ ] **S-VIEW-5 — Browse reduced to *choosing* (modal over Sample).** Keep search + bank tabs + captures grid + scroll + selection; add **confirm/cancel** (double-click loads). **Remove** the large waveform preview, Mono/Stereo toggle, root keyboard-strip (all moved to Sample), and the @@ -262,10 +277,9 @@ piano-key pattern work; drop-a-capture-onto-FX instantiates a playing instance. the new default showing the full Sample face without scroll; cannot shrink below the floor. ### Phase S editor redesign — forks (Daniel's to call) -- **S-VIEW-F1 — preview velocity persistence:** transient editor state (**lean**) vs. a small - persisted convenience. Lean transient — no compat surface, no version bump. -- **S-VIEW-F2 — envelope-overlay interactivity:** read-only overlay + sliders edit (**lean**, ship - first) vs. draggable envelope nodes (deferred stretch). +*(S-VIEW-F1 and S-VIEW-F2 SETTLED 2026-07-27 — folded into S-VIEW-4 / S-VIEW-3 above. F1: preview +velocity persists via envelope-v6 `ComponentState`. F2: envelope nodes are draggable via the pure +`envelope_edit` module. Only F3 remains open.)* - **S-VIEW-F3 — Browse modal presentation:** full-window overlay (**lean**) vs. a centered sheet over a dimmed Sample. Presentation detail. diff --git a/docs/product/midi-playback.md b/docs/product/midi-playback.md index f9942be..0814fc7 100644 --- a/docs/product/midi-playback.md +++ b/docs/product/midi-playback.md @@ -7,8 +7,15 @@ the **product framing behind a scoped phase**. Its build roadmap lives in **PLAN *why* (the plugin-format reasoning, the bare-VST3-vs-JUCE assessment, the settled decision record). -Status: framed by product-designer (2026-07-26), **revised 2026-07-26 (r8)**. r8 records -Daniel's **duration-preserving correction** (2026-07-26, verbatim: *"isn't that ratio stuff +Status: framed by product-designer (2026-07-26), **revised 2026-07-27 (r9)**. r9 records the +**three-view editor redesign** (Sample / Browse / Zone) and its follow-up settlements: the +S-VIEW-F1 and S-VIEW-F2 forks are now **SETTLED** (2026-07-27) — **preview velocity persists** via +the instrument's own VST3 component state (envelope bump to v6, *not* the extension's project +ext-state), and the **envelope overlay's nodes are draggable** via a new pure `envelope_edit` +module — and S-VIEW builds **directly on the finished, merged Phase L L3 look-and-feel** in a +**single implementation pass** (no two-pass restyle-after; L3 landed 2026-07-27, commit `c53683e`). +Only S-VIEW-F3 (Browse modal presentation) remains open. Full r9 record in the Addendum below. r8 +records Daniel's **duration-preserving correction** (2026-07-26, verbatim: *"isn't that ratio stuff going to change the playback rate? I want duration-preserving repitching"*): the ratio path is **varispeed** (pitch/duration coupled), so S16 is reshaped from "pitch envelope only" into a **pitch-engine mode — Varispeed vs Preserve — plus the pitch envelope** riding either engine. @@ -1043,20 +1050,33 @@ grammar, and it is the grammar the redesign adopts: higher:* the useful musical range is 0–200%; past 200% it is noise, and a bounded slider reads cleaner than an open one. - **Preview velocity (Sample view).** The velocity level the preview-trigger button fires at, - set by an adjacent knob. Not persisted as a musical parameter of the sample — it is a - *utility* setting for the audition button (like a metronome level), so it can live as - transient editor state or a small persisted convenience; the spec leans transient (see - §S-VIEW). This is what makes the preview button *fun*: tap it hard or soft without reaching for - a controller. + set by an adjacent knob. It is a *utility* setting for the audition button (like a metronome + level), not a musical parameter of the sample — but **it PERSISTS across reloads** (S-VIEW-F1 + SETTLED, Daniel 2026-07-27). The seam matters and was verified against the existing VST source, + not recalled: it lives in the **instrument's own VST3 component state** (the envelope-versioned + `ComponentState` blob the processor round-trips through `getState`/`setState`, bumped to v6 for a + new top-level `previewVelocity` field), **not** the extension's `persist` project ext-state — + that module is REAPER-project-scoped and extension-owned, so it would make the level + project-global instead of per-instance and route an instrument concern through a bank-read-only + seam. See CONTEXT.md §S-VIEW for the round-trip and back-compat lift. This is what makes the + preview button *fun*: tap it hard or soft without reaching for a controller — and it remembers. **Two visual components the redesign commits to:** -- **The envelope overlay** on the Sample view's hero waveform: the amp envelope (AHDSR for Gate, - the fade/%-length shape for Trigger) drawn as a curve *at accurate wall-clock time* across the - waveform, the way Simpler draws its amp envelope over the sample and Phase Plant traces it in - bright blue. This is the single biggest "reads like a real sampler, not a spreadsheet" move — - the envelope stops being four abstract sliders and becomes a *shape you see over the sound*. - The sliders stay (precise entry), but the picture leads. +- **The envelope overlay — with DRAGGABLE nodes** — on the Sample view's hero waveform: the amp + envelope (AHDSR for Gate, the fade/%-length shape for Trigger) drawn as a curve *at accurate + wall-clock time* across the waveform, the way Simpler draws its amp envelope over the sample and + Phase Plant traces it in bright blue. This is the single biggest "reads like a real sampler, not + a spreadsheet" move — the envelope stops being four abstract sliders and becomes a *shape you see + over the sound*. And it is **directly editable** (S-VIEW-F2 SETTLED, Daniel 2026-07-27 — in + scope, not deferred): the breakpoints are **draggable handles** (X → segment time, Y → level on + the sustain node; monotonic-in-time, range-clamped), the exact ADSR-editor grammar Simpler / + Phase Plant / Serum all use. The sliders stay as the precise numeric-entry surface, and **both + surfaces read/write the same envelope params** — one source of truth, so a drag moves the sliders + and a slider edit re-lays the nodes with no divergence. The geometry/hit-test math lives in a new + pure `envelope_edit` module (mirror of `card_drag`, unit-tested at the clamp/monotonic + boundaries); the shell only draws the handles and routes the mouse. This is the affordance that + makes envelope-shaping *feel* like a sampler instead of a form. - **The real piano-keyboard pattern** on the Zone view's keyboard strip. Today's spectral strip is pretty but does not *read as a keyboard* — Daniel's note. The redesign keeps the pastel spectral hue but overlays the **actual alternating white/black key pattern** (bright/dark per @@ -1069,9 +1089,11 @@ consumer** — every new control (key-tracking, preview velocity, the loop/start inherits) is *performance map*, never written to `Sample` or the bank (D-B). LICE/SWELL drawing only, all layout/hit-test in pure geometry modules (D-A). RT discipline untouched — the preview-trigger fires a note through the same voice engine off the audio-thread commit path, no -new audio-thread work. The VST3 class UID is unchanged — a view reorganization is not a compat -event. And this is an *editor* redesign: the S3 voice engine, the keymap resolution, the -component-state format (extended additively for key-tracking) are the same load-bearing core. +new audio-thread work. The VST3 class UID is unchanged — a view reorganization plus additive +persisted fields is not a compat event; saved instances rebind and restore. And this is an +*editor* redesign: the S3 voice engine, the keymap resolution, and the component-state format +(extended additively — `keyTrack` per-zone, `previewVelocity` per-instance via an envelope bump to +v6, both with back-compat defaults on read) are the same load-bearing core. **Where the spec lives:** CONTEXT.md §Phase S — editor view-model redesign (S-VIEW); PLAN.md §Phase S — editor view-model redesign. This Addendum is the *why*; those are the *what/how*.