docs: 1.0 documentation restructure
Split root CLAUDE.md into 19 per-directory files scoped to their source area. Roll v0 history into docs/ARCHIVE.md; retire CONTEXT.md, CONTEXT-ARCHIVE.md, PLAN.md, COMPLETED.md. Move plan docs under docs/. Rescue 9 live deferrals into docs/TODO.md.
This commit is contained in:
-3258
File diff suppressed because it is too large
Load Diff
-1230
File diff suppressed because it is too large
Load Diff
-2779
File diff suppressed because it is too large
Load Diff
@@ -1,619 +0,0 @@
|
|||||||
# PLAN.md — ReaSampler milestone roadmap
|
|
||||||
|
|
||||||
Living milestone roadmap for ReaSampler. Derived from CONTEXT.md's 11-step build
|
|
||||||
order; CONTEXT.md remains the authoritative spec — this file is the tickable
|
|
||||||
checklist, not a re-statement of the spec. When a point lands, doc-keeper removes
|
|
||||||
it here and appends it to `COMPLETED.md`.
|
|
||||||
|
|
||||||
**Conventions**
|
|
||||||
- One checkbox `- [ ]` = one discrete, independently-landable point.
|
|
||||||
- Each milestone opens with a **Goal** (one line) and a **Verify** criterion
|
|
||||||
(the acceptance gate; precision invariants pulled in where one applies).
|
|
||||||
- Verify-in-DAW points require a manual REAPER run; pure points are gated by CTest.
|
|
||||||
- "See CONTEXT.md §…" points at the authoritative detail — do not duplicate it here.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Open questions to resolve during build
|
|
||||||
Carried from CONTEXT.md §Open questions — keep visible until each is closed by a
|
|
||||||
landed milestone.
|
|
||||||
|
|
||||||
- **`parseInt` narrowing hardening:** `src/bank_model.cpp` `parseInt` casts
|
|
||||||
`int64_t → int` via `static_cast` without a range check; integers that fit
|
|
||||||
in int64 but exceed `INT_MAX` are implementation-defined. Hardening candidate
|
|
||||||
— add bounds check before the cast when integer-field validation is in scope.
|
|
||||||
- **Capture send/routing isolation (TODO):** The FX-scope capture neutralizes out-of-scope FX, gain, and pan — but NOT aux **sends**. So a downstream coloring send (e.g. a folder → reverb-track send) still routes and blends the reverb into an item/track capture, past the intended isolation point. A true item-level capture should be taken at the isolated graph point — the target scope's output before out-of-scope track FX/gain/pan **and** before out-of-scope aux/parallel sends. The hard part: distinguish **source routing that must be preserved** (e.g. a MIDI send T1→T2 where T2's synth is where a MIDI item's audio is actually produced — the "item level" for that MIDI item is T2's synth output) from **coloring sends that must be excluded** (folder→reverb). Repro: folder F1; T1 (MIDI) sends MIDI to T2 (synth); T1+T2 → F1; F1 sends to reverb T3; capturing the MIDI item on T1 currently includes the reverb, should be isolated to T2's synth output pre-F1 with the MIDI send preserved and the reverb send excluded. Likely approach: snapshot + mute out-of-scope tracks' aux sends during the render while preserving the main/source signal path — needs a rule for which sends are load-bearing.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Phase D2 — Two-canvas (item-level mode projection; additive to D1)
|
|
||||||
|
|
||||||
> **Phase D2 is functionally complete** — D2-W1, D2-W2, D2-W3-A, D2-W3-B all landed; see `COMPLETED.md`.
|
|
||||||
>
|
|
||||||
> **Deferred:** panel UI indicator for per-track lane/mode state (a per-track
|
|
||||||
> lane-split marker). The mode switch already shows the active mode; no natural
|
|
||||||
> cheap home for a per-track indicator was found in the bank panel. Explicitly
|
|
||||||
> deferred — not silently dropped. Can be picked up later if wanted.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Phase S — MIDI-playback instrument (native VST3 sampler; a second build artifact)
|
|
||||||
|
|
||||||
> **Landed on dev (merged 2026-07-27); DAW verification pending Daniel's smoke test.**
|
|
||||||
> S1–S18 and the product-name/binary-rename work are all on dev. The cross-artifact
|
|
||||||
> ingest relay (S13 bullet) was explicitly DEGRADED and remains deferred — see below.
|
|
||||||
> Completed material archived in `COMPLETED.md`. Authoritative spec: **CONTEXT.md
|
|
||||||
> §MIDI-playback instrument — additive phase spec (Phase S)**. Product framing:
|
|
||||||
> `docs/product/midi-playback.md`.
|
|
||||||
>
|
|
||||||
## S13 — cross-artifact ingest relay (deferred)
|
|
||||||
|
|
||||||
> **SPIKE VERDICT (ps-w12, 2026-07-27): DEGRADED — relay deferred.** The instrument's
|
|
||||||
> REAPER bridge (`reaper_bridge`) is deliberately READ-ONLY; a relay would need a new
|
|
||||||
> instrument WRITE seam into ext-state and an extension-side timer poller servicing a
|
|
||||||
> drop-ingest inbox key with a claim/clear nonce — the same cross-process handshake race
|
|
||||||
> the S17 spec rejected for alternative (A). Both the read-only-instrument boundary and
|
|
||||||
> the new poller are load-bearing design calls, so the relay is deferred to a future wave.
|
|
||||||
> The shipped ingest gesture stays drop-onto-docked-panel (S8). The degrade path (editor
|
|
||||||
> shows a "drop files onto the ReaSampler bank panel to add them" affordance) landed as
|
|
||||||
> part of Phase S. See `COMPLETED.md` §S13 for full context.
|
|
||||||
|
|
||||||
- [ ] Cross-artifact ingest relay: the editor hands the dropped path + this instance's
|
|
||||||
identity to the extension as a bank-ingest request over an agreed seam. DEFERRED —
|
|
||||||
relay mechanism proved load-bearing to redesign; drop-onto-panel (S8) is the shipped
|
|
||||||
ingest path. Requires (a) a new instrument WRITE seam into ext-state and (b) an
|
|
||||||
extension-side timer poller + claim/clear nonce. A future wave when the design is ready.
|
|
||||||
|
|
||||||
## Phase S — product name (ReaSampler 9000)
|
|
||||||
The MIDI-playback instrument's product name is **ReaSampler 9000** (Daniel, 2026-07-26,
|
|
||||||
on DAW-testing the S1–S6 instrument). The extension remains **ReaSampler**; the instrument
|
|
||||||
is **ReaSampler 9000**. Framing + propagation surfaces:
|
|
||||||
`docs/product/midi-playback.md` §Product name.
|
|
||||||
|
|
||||||
- [ ] **Compat verification (must-DAW-verify before shipping the rename):** the working
|
|
||||||
assumption is that REAPER **rebinds a saved instance by its VST3 class UID, not by the
|
|
||||||
module filename**, so a filename rename with an unchanged UID keeps saved projects working
|
|
||||||
(existing instances still resolve). **This is not yet confirmed from source** — a web
|
|
||||||
check surfaced a JUCE/VST3-replace-VST2 case suggesting REAPER's binding is more nuanced
|
|
||||||
than "UID only" (it can involve an FXID match), so treat UID-rebind as **to-verify, not
|
|
||||||
asserted fact**. **DAW-verify:** save a project with a ReaSampler 9000 instance under the
|
|
||||||
old filename, rename the module, reopen — confirm the instance rebinds and restores its
|
|
||||||
state. If REAPER does key partly on filename, fall back to keeping the current filename
|
|
||||||
(display-strings-only) and record that as the shipped choice.
|
|
||||||
|
|
||||||
## Phase S — held and optional-forever (noted, not specified)
|
|
||||||
|
|
||||||
- **Tier 2 — "expressive" (HELD).** Velocity layers, round-robin (anti-machine-gun),
|
|
||||||
full ADSR, per-sample tuning/gain trim, sustain loops. The next depth increment once
|
|
||||||
Tier 0–1 proves the instrument belongs — **its points are not drawn up here.**
|
|
||||||
- **Tier 3 — "instrument polish" (optional-forever).** Filters, filter/pitch
|
|
||||||
envelopes, LFOs, per-voice pan, choke groups, a modest FX slot. A direction to leave
|
|
||||||
room for, never a commitment. **Note:** S16 lands the *pitch* envelope + the Varispeed/
|
|
||||||
Preserve pitch-engine mode early (Daniel's directive) — the Tier-3 "filter/pitch envelopes"
|
|
||||||
line now means the *filter* envelope + LFOs remainder.
|
|
||||||
- **Sinc Varispeed-quality upgrade (HELD — WDL_Resampler).** `WDL_Resampler`'s sinc mode
|
|
||||||
beats the core's 2-point linear interp for **Varispeed** base-repitch quality (see the S16
|
|
||||||
WDL finding). An optional per-voice quality toggle (linear default / sinc), RT-suitable but
|
|
||||||
heavier. Held as a Tier-2/3 quality option — not needed for S15/S16, not scheduled. (A
|
|
||||||
resampler couples duration, so it is a Varispeed-quality option only, **not** a Preserve
|
|
||||||
engine.)
|
|
||||||
- **WDL_SimplePitchShifter swap (HELD — fork S16-F2 route a).** `WDL_SimplePitchShifter`
|
|
||||||
as a drop-in swap for the `pitch_shift` pure module if the hand-rolled OLA onset latency
|
|
||||||
or warble proves musically unacceptable. Same `PitchEngine::Preserve` contract behind the
|
|
||||||
seam. WDL excluded from the shipped build by include-chain (windows.h); held as the
|
|
||||||
quality/latency alternative.
|
|
||||||
- **Trigger choke-on-note-off (HELD — fork S15-F1).** A future option for Trigger mode to
|
|
||||||
*cut* (choke) on note-off or on a same-group re-trigger (hi-hat open/closed). Deliberately
|
|
||||||
out of S15 scope (Trigger ignores note-off entirely there); a Tier-3 choke-group direction.
|
|
||||||
|
|
||||||
## Phase S — editor view-model redesign (three views: Sample / Browse / Zone)
|
|
||||||
|
|
||||||
> **Additive Phase S sub-phase (S-VIEW; Daniel, 2026-07-27, r9).** Re-partitions the ReaSampler
|
|
||||||
> 9000 editor from a two-view toggle into a **three-view model where the loaded sample is the
|
|
||||||
> home** — Sample (default face), Browse (modal picker over Sample), Zone (dedicated keymap
|
|
||||||
> surface). Adds three performance parameters (key-tracking, preview velocity, and the r10
|
|
||||||
> velocity→amp transfer curve) and three visual components (envelope overlay, real piano-key
|
|
||||||
> pattern, and the r10 velocity-curve editor), and frames two engineering prerequisites
|
|
||||||
> (drop-to-FX bug, default window size). An **editor** redesign — the S3 voice engine, keymap
|
|
||||||
> resolution, and read-only-over-bank contract are **unchanged**; component state extends
|
|
||||||
> additively for key-tracking; VST3 class UID unchanged. Authoritative spec: **CONTEXT.md §Phase
|
|
||||||
> S — editor view-model redesign (S-VIEW)**. Product framing: `docs/product/midi-playback.md`
|
|
||||||
> §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). **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.
|
|
||||||
**Verify (in DAW):** open on a 1080p screen → full Sample face (hero waveform + envelope overlay +
|
|
||||||
fenced root + preview-trigger + control strip) with no scroll; Browse opens as a modal over
|
|
||||||
Sample, select+confirm loads a new capture; Zone opens on its own button, key-tracking + the
|
|
||||||
piano-key pattern work; drop-a-capture-onto-FX instantiates a playing instance.
|
|
||||||
|
|
||||||
### Phase S editor Wave B — Sample-face recomposition (r11; Daniel, 2026-07-27)
|
|
||||||
|
|
||||||
> Daniel's post-landing DAW pass on the S-VIEW editor: **all linear sliders → small radial
|
|
||||||
> knobs** grouped into a fenced knob deck (envelope controls grouped as a unit); the **mode
|
|
||||||
> toggles compact**, not full-width; the **inline velocity-curve box → a miniature curve
|
|
||||||
> preview button + full-size popup editor** (right-click deletes a node in the popup); the
|
|
||||||
> **hero waveform full-width**. Authoritative layout spec: **CONTEXT.md §S-VIEW → "The
|
|
||||||
> Sample-face recomposition (r11)"** (band order, group taxonomy, cell metrics, popup
|
|
||||||
> geometry, inventory contract — nothing silently dropped). Product framing:
|
|
||||||
> `docs/product/midi-playback.md` §Addendum r11. All drawing through the L1 kit; all
|
|
||||||
> layout/hit-test in pure geometry modules.
|
|
||||||
>
|
|
||||||
> **S-VIEW-11, S-VIEW-12, and S-VIEW-13 have all landed** — FB1 (merged 2026-07-27, suite
|
|
||||||
> 55/55) brought the knob deck + master gain + curve popup + full-width hero; FB2 (merged
|
|
||||||
> 2026-07-28, suite 55/55) brought Zone-panel parity. **Phase S editor Wave B (r11) is
|
|
||||||
> complete.** See `COMPLETED.md` for both FB entries.
|
|
||||||
|
|
||||||
**Open forks** — **R11-F1** (hero height vs. default window) SETTLED at FB1 build: elastic
|
|
||||||
hero, 840×620 default kept. **R11-F2** (Zone-panel parity) SETTLED at FB2 build: knob deck
|
|
||||||
+ curve popup adopted on the Zone panel; `param_slider` slider rows retired on that surface.
|
|
||||||
|
|
||||||
### Phase S editor redesign — forks (Daniel's to call)
|
|
||||||
*(r9/r10 forks all settled: S-VIEW-F1 and S-VIEW-F2 SETTLED 2026-07-27 — folded into S-VIEW-4 /
|
|
||||||
S-VIEW-3. F1: preview velocity persists via envelope-v6 `ComponentState`. F2: envelope nodes are
|
|
||||||
draggable via the pure `envelope_edit` module. R10-F1 SETTLED 2026-07-27 — Option A flat y=1,
|
|
||||||
folded into S-VIEW-9. S-VIEW-F3 SETTLED — full-window overlay, implemented as Browse renders as a
|
|
||||||
full-window modal over Sample (landed in S-VIEW-1/S-VIEW-5). **r11 forks all settled: R11-F1
|
|
||||||
SETTLED at FB1 build (elastic hero, 840×620 default kept); R11-F2 SETTLED at FB2 build (Zone
|
|
||||||
panel adopts knob deck + curve popup, `param_slider` slider rows retired on that surface).**)*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Phase Q — Quality (structural reorganization; zero-runtime-cost)
|
|
||||||
|
|
||||||
> **New pillar, own lettered namespace, and — uniquely — the LAST structural pillar.** Phase
|
|
||||||
> Q is a **pure structural refactor**: it reorganizes `src/` into a healthier shape (more
|
|
||||||
> encapsulation, granular namespaces, `core/`/`shell/`/`app/` subdirectories) against a stated
|
|
||||||
> quality bar — *"mtytel Vital is my code reference for quality"* — to bring the codebase
|
|
||||||
> "into the realm of something I can stand to look at." It ships **no feature and changes no
|
|
||||||
> behavior**: the test suite passing unchanged is the proof of correctness. Namespaced **`Q`
|
|
||||||
> (Quality)** — M/D/B/R/V/S/L are all taken; `Q` names the *end* (the quality bar), the reorg
|
|
||||||
> being the *means*. Authoritative spec: **CONTEXT.md §Phase Q — structural reorganization
|
|
||||||
> (reorg spec)**. Product framing, the Vital-grounded target shape, the grep-verified SOLID
|
|
||||||
> audit that is the evidence base, and the settled/recommended fork record (Q-1..Q-6):
|
|
||||||
> `docs/product/code-organization.md`. When a point lands, doc-keeper moves it to
|
|
||||||
> `COMPLETED.md`.
|
|
||||||
>
|
|
||||||
> **THE GATE (load-bearing — state first; reconciled to reality 2026-07-27).** Phase Q is
|
|
||||||
> **gated on the tree being otherwise quiescent.** Daniel's plain readiness target: **"when
|
|
||||||
> Phase S and L3 are finished."** **As of 2026-07-27 both gate conditions are satisfied:**
|
|
||||||
> Phase S merged to dev (2026-07-27); Phase L L3 (the VST restyle) merged to dev
|
|
||||||
> (2026-07-27, commit `c53683e`) — **Phase L is complete** (L1/L2/L3/L4/L5/L6/L7 all
|
|
||||||
> landed, see `COMPLETED.md`). **D2** is functionally complete (D2-W1..W3-B landed; the
|
|
||||||
> lone open item, a per-track lane-split panel indicator, is *explicitly deferred*, not a
|
|
||||||
> blocking residual). **M9** (slots) is **abandoned** (Daniel, 2026-07-27) — will not be
|
|
||||||
> built. D2 is named in the gate only so that *reactivating* its deferred panel indicator
|
|
||||||
> re-arms the quiescence condition; neither D2 nor M9 blocks the gate today. *Why the gate:* Phase Q
|
|
||||||
> touches **nearly every file in `src/`** (relocate into subdirectories, re-namespace every
|
|
||||||
> header, split the four largest TUs, plus the §2b renames). Every large in-flight branch
|
|
||||||
> (Phase S on its worktree, and L3 once it lands) is diffed against the *current flat
|
|
||||||
> layout*; landing a rename-and-relocate-everything reorg mid-flight forces every open
|
|
||||||
> branch through the worst conflict class (every hunk moved, every qualified reference
|
|
||||||
> changed) — a combinatorial re-resolution, not a linear one. Phase Q is *last* precisely
|
|
||||||
> because it reshapes the ground every other pillar stands on. Landing it early taxes every
|
|
||||||
> subsequent phase; landing it last taxes nothing. **The gate is now satisfied — Phase Q
|
|
||||||
> may begin, entering at the Q-W0 audit wave** (the structural waves Q-W1+ do not begin until
|
|
||||||
> Q-W0's triage closes and Daniel signs off — see the Q-W0 sub-gate above).
|
|
||||||
>
|
|
||||||
> **M9 disposition — resolved (Daniel, 2026-07-27): abandoned.** M9 is out; it will not be
|
|
||||||
> reactivated. The gate remains satisfied; no re-arm condition applies.
|
|
||||||
>
|
|
||||||
> **PRE-RESTRUCTURE AUDIT WAVE (Q-W0 — added 2026-07-27, Daniel's ask; runs FIRST).** Before any
|
|
||||||
> structural point (Q-W1+) begins, Phase Q now opens with a **functional + DSP quality audit**
|
|
||||||
> (`Q-W0`) — a thorough static analysis of the code from a *functional-correctness* and
|
|
||||||
> *algorithm-quality* point of view, complementary to (not a repeat of) the grep-verified
|
|
||||||
> SOLID/naming audit that already grounds Q-W1..Q-W6. Q-W0 hunts DSP smells (close eye on the pitch
|
|
||||||
> engine), reinvented wheels, duplicate code, poor-quality algorithms, leaky pure/shell boundaries,
|
|
||||||
> and domain-modeling smells around env-coupled constants; it produces a written, triaged findings
|
|
||||||
> report. **The gate to begin Q-W1 is that Q-W0's triage is complete and Daniel has signed off on
|
|
||||||
> each finding's disposition** (fix-now vs. document-and-defer). Q-W0's findings may add or reshape
|
|
||||||
> downstream Q-W1..Q-W6 points; fixes that Q-W0 classifies fix-now are remediated in Q-W0 (or folded
|
|
||||||
> into the wave that already touches the file), **not** deferred silently into the structural waves.
|
|
||||||
>
|
|
||||||
> **Q-W0 SIGN-OFF: COMPLETE (Daniel, 2026-07-28).** The audit ran as four parallel tracks (T1 DSP,
|
|
||||||
> T2 architecture, T3 env-coupled constants, T4 sizing/placement — **59 findings**; report
|
|
||||||
> `docs/product/code-quality-audit.md`, appendices `docs/product/audit-notes/`), and **all 59
|
|
||||||
> findings' dispositions are approved as proposed.** The Q-W1 sub-gate is satisfied **once the six
|
|
||||||
> approved fix-now remediations land** (in flight on branch `pq-w0-fixes`, Q-W0-scoped): T1-01,
|
|
||||||
> T1-03, T1-09, T2-01(a), T3-01, T3-03. The audit's §3 plan reshape is **folded into the waves
|
|
||||||
> below** (Q-W2 6→8 seams; NEW wave **Q-W2v** parallel with Q-W2; Q-W3 3→4 hoists + riders; Q-W5
|
|
||||||
> + the ext-state-loop dedupe), and its §4 decision list is settled — see the settlement block
|
|
||||||
> below. The Q-11 question is answered by the audit: the SOLA pitch engine is **sound — no
|
|
||||||
> technique replacement warranted**; every pitch finding is a bounded in-technique fix or a
|
|
||||||
> documented operating limit.
|
|
||||||
>
|
|
||||||
> **Settled (Q-1, this-doc):** the phase is **`Q` (Quality)**; point-id family `Q1..Qn`, wave
|
|
||||||
> prefixes `Q-W0` (the pre-restructure audit) then `Q-W1..Q-W6` (the structural reorg; **+
|
|
||||||
> `Q-W2v`**, the VST god-module wave added at the Q-W0 sign-off, 2026-07-28).
|
|
||||||
> **Settled (Q-10/Q-11, Daniel 2026-07-27):** Q-10 audit-report home = a **committed doc**
|
|
||||||
> (`docs/product/code-quality-audit.md`, not a tracked issue list); Q-11 pitch-remediation depth =
|
|
||||||
> **defer to findings** (default document-and-defer; weigh a bounded OLA fix before a technique
|
|
||||||
> replacement; a technique replacement is a Daniel decision at triage time, not an automatic Q-W0
|
|
||||||
> action).
|
|
||||||
> **Recommended, Daniel's to call (Q-2..Q-9, see
|
|
||||||
> `docs/product/code-organization.md` §6):** Q-2 JSON extraction in scope + first (rec: yes);
|
|
||||||
> Q-3 directory shape `core/`/`shell/`/`app/` top-split with subsystem dirs beneath (rec: this
|
|
||||||
> over pure-Vital subsystem-first — it makes the pure/shell invariant *structural*); Q-4
|
|
||||||
> sub-namespace to match sub-directory (rec: both); Q-5 split god-modules to the audit's named
|
|
||||||
> seams, no finer (rec: yes); Q-6 OCP registration-table as the final wave (rec: in, last);
|
|
||||||
> **Q-7 naming rides the relocation waves, no dedicated naming wave (rec: yes — forced once
|
|
||||||
> Q-3/Q-4 settle); Q-8 class/module renames — fix the two that actively mislead (`BankIndex`→
|
|
||||||
> `BankModel`; the JSON `Parser`→`json::Reader`/`Writer`), leave the merely-quirky (rec);
|
|
||||||
> Q-9 align the `capture_realtime`/`realtime_record` shell↔core word order during W3 (rec: yes).**
|
|
||||||
>
|
|
||||||
> **SETTLED (Daniel, 2026-07-28 — with the Q-W0 sign-off; the REC record above kept as history):**
|
|
||||||
> **Q-5 SETTLED** — split to **seams-by-responsibility with the T4 seam lists adopted**
|
|
||||||
> (`bank_panel` 6→8 seams adding `panel_layout` + `panel_drag`, T4-01; `capture_orchestrator`
|
|
||||||
> further split with `capture_batch`, T4-02), and the **~600-line file ceiling is an acceptance
|
|
||||||
> criterion on every split wave** — seams are the method, the ceiling is the bar; arbitrary
|
|
||||||
> bisection to hit the number is rejected. **Q-6 SETTLED: in scope, last wave, as planned.**
|
|
||||||
> **Q-8 SETTLED: both renames** — `BankIndex`→`BankModel` (W1) and the JSON parser minted as
|
|
||||||
> `json::Reader`/`json::Writer` (W1); additionally from the audit, **`ICaptureBackend` is deleted
|
|
||||||
> in Q-W3** (T4-26 — one deriver, zero polymorphic call sites; the CLAUDE.md/CONTEXT "two
|
|
||||||
> backends behind one interface" correction **rides Q-W3 itself**, recorded as a rider — the docs
|
|
||||||
> are not edited before that wave). **Q-9 SETTLED: yes** — align to stem `capture_realtime`,
|
|
||||||
> shell suffixed, during W3. **VST placement (audit §4a) SETTLED: T4-18** — `src/vst/` integrates
|
|
||||||
> into the single `core/`/`shell/` top split as `core/instrument/{engine,map,ui}` +
|
|
||||||
> `shell/instrument/` (Q-3 directory map updated in CONTEXT.md §Phase Q). **WAV/RIFF
|
|
||||||
> consolidation (audit §4e) SETTLED:** a named rider on **Q-W3** — one pure **`wav_codec`** owner
|
|
||||||
> (walker + layout + build + patch), absorbing the T4-10 ingest extraction. **Q-W2v scheduling
|
|
||||||
> (audit §4f) SETTLED: parallel with Q-W2** (different artifact, zero file overlap; the serial
|
|
||||||
> "Q-W7" alternative set aside).
|
|
||||||
>
|
|
||||||
> **HARD CONSTRAINT — performance (see CONTEXT.md §Phase Q, `docs/product/code-organization.md`
|
|
||||||
> §3).** The reorg must cost **zero runtime.** On the three hot paths — `peaks` envelope
|
|
||||||
> compute, audition/preview, the realtime-capture tick — **no added virtual dispatch, no
|
|
||||||
> header→TU indirection, no changed call/inline or branch shape.** `computeEnvelope` stays a
|
|
||||||
> free function on `const std::vector<float>&`; audition split stays a direct call-through;
|
|
||||||
> the realtime idle tick stays a single pointer test; `FxBypassGuard` stays stack RAII. This is
|
|
||||||
> an acceptance criterion on every point: *a split that would add a hot-path indirection is out
|
|
||||||
> of scope — rework it or drop it.*
|
|
||||||
>
|
|
||||||
> **STRUCTURAL HEURISTICS (Daniel, 2026-07-28 — acceptance criteria on every wave; these
|
|
||||||
> *generalize* the three-hot-path guardrail above, they do not replace it):**
|
|
||||||
> (1) **More directories is a must, more files is good, ~600-line file ceiling** — SRP applies to
|
|
||||||
> namespaces, encapsulation, and file organization alike. The ceiling is the *bar*, the audit's
|
|
||||||
> named seams are the *method*: a file landing over ~600 needs a responsibility seam, not an
|
|
||||||
> arbitrary bisection; a documented hot-path exception (`sampler_core.cpp`, T4-14/T4-27) is
|
|
||||||
> legitimate, silent overshoot is not.
|
|
||||||
> (2) **Templates are the right tool for compile-time dedup — use them where earned** (the LE
|
|
||||||
> byte codec `putLE`/`readLE`, T4-20), not for name-only unification (the rect family is one
|
|
||||||
> **concrete** `ui::Rect`, NOT a template — T4-21's ruling).
|
|
||||||
> (3) **SOLID is great but saved CPU is better** — no dispatch-stack blowouts *anywhere*, not
|
|
||||||
> just the three named hot paths; prefer static polymorphism where types are compile-time-known
|
|
||||||
> (T4-27's warning is the canonical case: a by-class `sampler_core` split would put virtual
|
|
||||||
> envelope `tick()`s on the per-voice-per-sample path).
|
|
||||||
>
|
|
||||||
> **NAMING dimension (added 2026-07-27; grep-verified audit in `docs/product/code-organization.md`
|
|
||||||
> §2b).** Beyond giving symbols a directory + namespace *home* (Q-3/Q-4), Phase Q also gives
|
|
||||||
> poorly/inconsistently-named symbols a consistent *name*, against the same Vital bar. The audit
|
|
||||||
> found: four `class Parser` copies collapsing to one `json::Parser` (Q-W1); shared pure-UI rect
|
|
||||||
> types (`FooterRect`/`ButtonRect`) that the codebase already hand-checks for collision
|
|
||||||
> (`footer_bar.h`'s "NAME NOTE") — resolved by the Q-4 sub-namespaces for free; the
|
|
||||||
> `bank_model`/`BankIndex` file↔class word-mismatch (Q-8); and the `capture_realtime`/
|
|
||||||
> `realtime_record` shell↔core word-order inversion (Q-9). **Renames ride the wave that already
|
|
||||||
> relocates/splits the file — no dedicated naming wave (Q-7);** the geometry-mirror
|
|
||||||
> `compute*`/`hitTest*` verb vocabulary and the `_tests` suffix are already consistent and are
|
|
||||||
> preserved verbatim. Naming changes are zero-behavior-change like the rest of Phase Q, and the
|
|
||||||
> FOREVER-STABLE contract strings (command ids, action names, ext-state namespace/keys, VST3 UID)
|
|
||||||
> are **not** C++ symbols and are never renamed.
|
|
||||||
>
|
|
||||||
> **Every point is independently landable and CTest-green at EVERY step.** The CMake
|
|
||||||
> per-module static-lib + per-module test-executable seams already draw the module boundaries;
|
|
||||||
> a file move + namespace change is mechanically verifiable — `ctest --test-dir build` is green
|
|
||||||
> or it isn't. **Green-CTest-at-every-point is an acceptance criterion.** Big-bang is rejected;
|
|
||||||
> the reorg is risk-ordered waves (W1 safe opener → W2/W2v–W5 god-module splits → W6 OCP finish).
|
|
||||||
>
|
|
||||||
> **PHASE STATUS (2026-07-29): all seven waves (Q-W0..Q-W6 incl. Q-W2v) are structurally
|
|
||||||
> COMPLETE.** Remaining before the phase closes and merges to `dev`: (1) Daniel's in-DAW
|
|
||||||
> verification batch — the full deferred list across all waves (panel parity, editor/processor
|
|
||||||
> parity, stereo Preserve listening, null test, bit-identical repeats, capture flows, action
|
|
||||||
> families, one-op-one-Ctrl-Z, prune fail-safes, save/load/relocation) — now unblocked since the
|
|
||||||
> tree is stable; (2) the phase-close CLAUDE.md architecture refresh (module map still describes
|
|
||||||
> some pre-Q homes); (3) the phase-q → dev merge on Daniel's sign-off. See `COMPLETED.md` for
|
|
||||||
> each wave's full landed narrative.
|
|
||||||
|
|
||||||
## Q-W0 — pre-restructure functional + DSP quality audit (runs FIRST; gates Q-W1)
|
|
||||||
**STATUS (2026-07-29): audit COMPLETE, triage COMPLETE, sign-off COMPLETE, fix-now
|
|
||||||
remediations LANDED — Q-W0 is fully closed.** The findings report is committed
|
|
||||||
(`docs/product/code-quality-audit.md`; track appendices in `docs/product/audit-notes/` — T1
|
|
||||||
DSP, T2 architecture, T3 env-constants, T4 sizing/placement; 59 findings). Daniel approved
|
|
||||||
every disposition 2026-07-28. The six approved fix-now remediations plus seven review riders
|
|
||||||
landed 2026-07-28 (merge `546927e`) — see `COMPLETED.md`. **Q-W1 has since landed on top of
|
|
||||||
this closure** (see `COMPLETED.md`).
|
|
||||||
**Goal:** Before a single structural point moves, perform a **thorough static/functional audit** of
|
|
||||||
the codebase and produce a **written, triaged findings report**. This is the *functional-correctness
|
|
||||||
and algorithm-quality* complement to the grep-verified SOLID/naming audit that already grounds
|
|
||||||
Q-W1..Q-W6 (§2/§2b of `docs/product/code-organization.md`) — it goes deeper on *does the code do the
|
|
||||||
right thing well*, not *where does the responsibility live*. It hunts, across the whole `src/` tree:
|
|
||||||
duplicate code, reinvented wheels, poor-quality algorithms (**close eye on the pitch engine**),
|
|
||||||
numerical-robustness and artifact hazards in the DSP paths, leaky pure/shell boundaries, and
|
|
||||||
**domain-modeling smells around env-coupled constants** (values stored in a frame/rate/DPI/tick-coupled
|
|
||||||
domain that should be stored rate-free and resolved at use). Every finding is **classified for
|
|
||||||
disposition**: *eliminate-before-restructure* (fix-now) vs. *document-and-defer* (with rationale).
|
|
||||||
Nothing structural (Q-W1+) begins until this wave closes and Daniel signs off on the disposition of
|
|
||||||
every finding. CONTEXT.md §Phase Q (Q-W0 audit scope + triage). See
|
|
||||||
`docs/product/code-organization.md` §2c.
|
|
||||||
**Verify:** A findings report exists and is complete over the named audit surfaces (DSP/audio incl.
|
|
||||||
pitch; architecture smells; env-coupled-constant domain-modeling smells). **Every finding is
|
|
||||||
triaged** (fix-now vs. documented-and-deferred, each with a one-line rationale). Fix-now findings are
|
|
||||||
either remediated in this wave or explicitly assigned to the downstream wave that already touches the
|
|
||||||
file (recorded per finding); no fix-now finding is left implicit. Any behavior-changing remediation
|
|
||||||
lands with its module's CTest executable green and (where a DSP path changes audibly) a stated
|
|
||||||
before/after listening or null check. **The gate to Q-W1 is: triage complete + Daniel signed off.**
|
|
||||||
**Depends on:** the GATE (tree quiescent). Precedes every other Q wave — Q-W1 depends on Q-W0.
|
|
||||||
|
|
||||||
- [ ] **DSP / audio audit — close eye on pitch.** Assess *algorithm quality* (correctness,
|
|
||||||
artifacts, numerical robustness, interpolation quality, reinvented-wheel vs. established DSP
|
|
||||||
technique) across: `src/vst/pitch_shift` (hand-rolled OLA pitch-preserve — window/overlap choice,
|
|
||||||
phase handling, transient/formant behavior, buffer edges), `sampler_core` (repitch ratio math,
|
|
||||||
interpolation order/quality, loop-point crossfade, voice-stealing correctness/click behavior),
|
|
||||||
`peaks` (envelope min/max binning), `wav_trim` (decay-scan threshold + truncate plan), and the
|
|
||||||
capture/tail paths. **Pitch-remediation depth (Q-11 SETTLED, Daniel 2026-07-27): defer to
|
|
||||||
findings** — default document-and-defer; only if the audit surfaces artifacts that matter, weigh a
|
|
||||||
bounded OLA fix (window/overlap/edge tuning) **before** a technique replacement. A technique
|
|
||||||
replacement (phase-vocoder / WSOLA) reshapes `pitch_shift`, spills a downstream Q-wave point, and is
|
|
||||||
a **Daniel decision at triage time, not an automatic Q-W0 action** — Q-W0 surfaces the
|
|
||||||
recommendation, Daniel calls it.
|
|
||||||
- [ ] **Architecture-smell audit.** Duplicate code, reinvented wheels, poor abstractions, and any
|
|
||||||
leaky pure/shell boundary (a `core/` module reaching a REAPER/host type, or geometry/algorithm
|
|
||||||
math living untestable in a shell). Cross-check against — but do not merely restate — the §2/§2b
|
|
||||||
SOLID/naming findings; Q-W0's contribution is the *functional* smells those audits didn't target.
|
|
||||||
- [ ] **Env-coupled-constant domain-modeling audit (explicit smell category).** Flag ANY value
|
|
||||||
stored in a frame/rate/DPI/tick-coupled domain that should be stored **rate-free and resolved at
|
|
||||||
the point of use** — per the load-bearing invariant that wall-clock times are rate-free SECONDS
|
|
||||||
resolved against the live project rate (`sample_map`), with NO hardcoded sample rates in `src/`.
|
|
||||||
Treat this as **domain-modeling** (store rate-free, resolve at use), **not** "rescale by rate."
|
|
||||||
There was a prior incident here — envelope times stored in the frame domain — so this is a
|
|
||||||
first-class category, not a footnote. Sweep envelope times, loop points, fade lengths, tail
|
|
||||||
lengths, and any UI geometry constant that silently assumes a DPI/rate.
|
|
||||||
- [ ] **Triage + report.** Write the findings report as a **committed doc,
|
|
||||||
`docs/product/code-quality-audit.md`** (Q-10 SETTLED, Daniel 2026-07-27 — beside the SOLID/naming
|
|
||||||
audit, not a tracked issue list); classify each finding fix-now vs. document-and-defer with a
|
|
||||||
one-line rationale; for each fix-now, note whether it is remediated in Q-W0 or assigned to the
|
|
||||||
downstream wave that already opens the file. Deferred findings carry a documented rationale so they
|
|
||||||
are a decision, not an omission.
|
|
||||||
- [ ] **Sign-off gate.** Daniel reviews the triage and signs off on each disposition. Q-W1 does not
|
|
||||||
begin until this is done; fold any new/reshaped downstream points the audit surfaces into
|
|
||||||
Q-W1..Q-W6 before starting them. **DONE (Daniel, 2026-07-28): all 59 dispositions approved as
|
|
||||||
proposed; the §3 plan reshape and §4 decisions are folded into Q-W1..Q-W6 + Q-W2v below.**
|
|
||||||
|
|
||||||
## Q-W1 — safe opener: extract `core/json` + impose the directory/namespace layout on clean modules
|
|
||||||
|
|
||||||
> **Landed on `phase-q` (2026-07-29). Integrated suite 60/60 green.** `core/json`
|
|
||||||
> (`json::Reader`/`json::Writer`) extracted; the five hand-rolled JSON decoders (incl.
|
|
||||||
> `tail_control`'s) deleted; the wire-`Cursor` family collapsed into `core/wire`; the shared
|
|
||||||
> `readFileBytes` helper added; ~50 clean modules relocated into `core/{model,view,capture,
|
|
||||||
> audio,ui,reclaim,version,json,util,wire}/`, `core/instrument/{engine,map,ui}/`,
|
|
||||||
> `shell/{capture,panel,view,persist,actions,instrument}/`, `app/main.cpp`; sub-namespaces
|
|
||||||
> applied; one concrete `ui::Rect` + aliases (LTRB fork + `footer_bar` NAME NOTE retired);
|
|
||||||
> `slot_map` extracted from `bank_book`; `clamp01` deduped; `BankIndex`→`BankModel`;
|
|
||||||
> `reasampler_uid.h` relocated to `core/wire/`. See `COMPLETED.md` for the full narrative.
|
|
||||||
>
|
|
||||||
> **Skipped/deferred riders:** T4-22 (`hitIndex` hit-test template) — not trivial, deferred as
|
|
||||||
> an opportunistic follow-on once the rect unification is in use downstream; T4-06
|
|
||||||
> (`view_mode_model` planner split) — optional, deferred; T4-09 (`view_lanes` split) —
|
|
||||||
> deferred (in scope only if a later wave touches `view.cpp` anyway).
|
|
||||||
>
|
|
||||||
> **Open residual — `bank_book.cpp` still 737 LOC.** The serialize/deserialize seam is
|
|
||||||
> identified but blocked on a `nameKey` linkage design decision, escalated to Daniel and
|
|
||||||
> **pending** as of 2026-07-29. Downstream waves touching `bank_book` should check this
|
|
||||||
> residual before assuming the split is finished.
|
|
||||||
>
|
|
||||||
> An interim `core/namespaces.h` shim covers the six not-yet-split god TUs; each downstream
|
|
||||||
> split wave (Q-W2 onward) retires its own includes of it as that module splits.
|
|
||||||
|
|
||||||
## Q-W2 — split `bank_panel.cpp` (the biggest extension god-module — 3459 LOC at the Q-W0 census)
|
|
||||||
|
|
||||||
> **Landed on `phase-q` (2026-07-29, merge of `pq-w2-panel`). Integrated suite 61/61 green,
|
|
||||||
> reviewed-approved.** `bank_panel.cpp` (3459 LOC) split into eight TUs under `shell/panel/`:
|
|
||||||
> `panel_render` / `panel_thumbnails` / `panel_audition` / `panel_input` / `panel_bank_ops` /
|
|
||||||
> `panel_window` / `panel_layout` / `panel_drag`, plus per-seam public headers and internal
|
|
||||||
> `panel_state.h`; audition stays a direct call-through; the one-bank-op-one-undo invariant is
|
|
||||||
> preserved; ~50 TU-private helpers wrapped in anonymous namespaces (a review follow-up). See
|
|
||||||
> `COMPLETED.md` for the full narrative.
|
|
||||||
>
|
|
||||||
> **Recorded ceiling overages (reviewer-endorsed, preserved as a durable record per CONTEXT's
|
|
||||||
> "silent overshoot is not legitimate" rule):** `panel_input.cpp` 636, `panel_render.cpp` 613,
|
|
||||||
> `panel_state.h` 608 — the overage is comment volume; non-comment lines are ~322–369 per file;
|
|
||||||
> no honest seam remains; bisection was rejected.
|
|
||||||
>
|
|
||||||
> **Review note for the Q-W4 planning record:** `panel_bank_ops`'s verbs still embed
|
|
||||||
> prompts/panel-state nudges — Q-W4's dedupe needs promptless inner verbs (`renameBank(id,name)`
|
|
||||||
> etc.), not a call-site swap; `promptText`/`mintBankId` are byte-identical twins with
|
|
||||||
> `actions.cpp` and are the cheapest first dedupe.
|
|
||||||
>
|
|
||||||
> **In-DAW verification (panel parity) is PENDING on `phase-q`** — deferred by design, not yet
|
|
||||||
> performed.
|
|
||||||
|
|
||||||
## Q-W2v — split the VST god-modules (NEW wave — Q-W0 T4 §1.5; runs parallel with Q-W2)
|
|
||||||
|
|
||||||
> **Landed on `phase-q` (2026-07-29, merge of `pq-w2v-vst`). Integrated suite 61/61 green,
|
|
||||||
> reviewed-approved.** `reasampler_editor.cpp` (3084 LOC, the largest file in the repo) split
|
|
||||||
> into eight face-axis TUs under `shell/instrument/`, with pure layout hoisted into
|
|
||||||
> `core/instrument/ui/editor_geometry` (discharges T2-06, newly tested); `reasampler_processor.cpp`
|
|
||||||
> split into `processor_state` / `processor_reload` / lifecycle+`process()` kept whole (no
|
|
||||||
> virtual seam, T4-29); `sample_map` split into a resolution core + `component_state_io` codec
|
|
||||||
> (the extension preset path no longer links the voice engine — link-proven; T4-13 ≡ T2-07);
|
|
||||||
> `sampler_core.cpp` stays whole with the documented hot-path exception comment (T4-14/T4-27);
|
|
||||||
> `zone_params.h` split out; `core/wire/bytes.h` (`putLE`/`ByteReader`) lands (T4-20);
|
|
||||||
> `ThumbnailKey` adopted (T2-10); a golden full-blob v11 fixture pins the codec bytes. The
|
|
||||||
> `src/vst/` directory is gone. See `COMPLETED.md` for the full narrative.
|
|
||||||
>
|
|
||||||
> **Deferred/known:** `component_state_io.h` still includes `sample_map.h`→`sampler_core.h`
|
|
||||||
> transitively (T2-07's header half — future work); the `engine` namespace is deferred
|
|
||||||
> (`sampler_core` stays flat `reasampler`); capture-side LE rewires are left for the capture
|
|
||||||
> family.
|
|
||||||
>
|
|
||||||
> **In-DAW verification (editor/processor parity) is PENDING on `phase-q`** — deferred by
|
|
||||||
> design, not yet performed.
|
|
||||||
|
|
||||||
## Q-W3 — split `main.cpp` (hoist orchestration; leave main = pointers + entry + dispatch)
|
|
||||||
|
|
||||||
> **Landed on `phase-q` (2026-07-29, merge of `pq-w3-main`). Integrated suite 61/61 green,
|
|
||||||
> reviewed-approved.** `app/main.cpp` reduced 1897 → 653 LOC (pointers + entry + dispatch; the
|
|
||||||
> remaining bulk is the registration residue Q-W6 dissolves) via four hoists into
|
|
||||||
> `shell/capture/`: `capture_orchestrator`, `capture_batch`, `scope_resolve`,
|
|
||||||
> `realtime_lifecycle`; `FxBypassGuard` moved intact as a stack RAII object; the realtime idle
|
|
||||||
> tick stays a single pointer test; `ICaptureBackend` deleted (T4-26) with the
|
|
||||||
> CLAUDE.md/CONTEXT-ARCHIVE corrections landed in the same commit; the Q-9 rename done (pure
|
|
||||||
> `core/capture/capture_realtime`, shell `capture_realtime_shell` + `capture_realtime_finalize`
|
|
||||||
> split, T4-08); `stampCaptureSample` dedupe (T2-09, divergent time-sig behavior preserved via
|
|
||||||
> caller arg); `makeUniqueTag` gains a per-session monotonic counter (T1-11 behavior fix — stems
|
|
||||||
> now `<epoch>-<n>` / `rt-<epoch>-<n>`; the per-process residual is documented in-code); one pure
|
|
||||||
> `wav_codec` RIFF owner absorbs `wav_trim` + `ingest`'s WAV build + content hashes, with golden
|
|
||||||
> hash literals pinned (`wav_codec_tests` replaces `wav_trim_tests`; `capture_realtime_tests`
|
|
||||||
> replaces `realtime_record_tests`). See `COMPLETED.md` for the full narrative.
|
|
||||||
>
|
|
||||||
> **Known open:** `wav_trim.h`'s transitional forwarding shim still has three live includers
|
|
||||||
> (`sample_map.h`, `editor_session.cpp`, `processor_reload.cpp`) — repoint-and-retire is a named
|
|
||||||
> follow-up; `ingest.cpp` is trimmed to 567 LOC but keeps the `namespaces.h` shim (`ingest` +
|
|
||||||
> `view` remain the shim's unowned consumers).
|
|
||||||
>
|
|
||||||
> **In-DAW verification (null test, bit-identical repeats) is PENDING on `phase-q`** — deferred
|
|
||||||
> by design, not yet performed.
|
|
||||||
|
|
||||||
## Q-W4 — split `actions.cpp` + dedupe bank verbs against `panel_bank_ops`
|
|
||||||
|
|
||||||
> **Landed on `phase-q` (2026-07-29, merge of `pq-w4-actions`). Integrated suite 61/61 green,
|
|
||||||
> reviewed-approved.** `actions.cpp` (1019 LOC) split into `design_view_actions` / `bank_actions`
|
|
||||||
> / `prune_action`, plus a fourth shared `action_registry` TU, all under `shell/actions/`;
|
|
||||||
> `promptText`/`mintBankId` deduped against `panel_bank_ops`; bank verbs reshaped to promptless
|
|
||||||
> inner verbs (one mutation home, two UX skins — panel and actions each keep their exact prior
|
|
||||||
> UX); command-id suffixes/display phrases verified byte-identical in review; `prune_action`
|
|
||||||
> stays a clean deletion-authority isolate (no `Undo_*`, no ext-state writes);
|
|
||||||
> `persistBankOp`/`persistBook` gain null-session guards; `promptText` renamed `promptBankName`.
|
|
||||||
> See `COMPLETED.md` for the full narrative.
|
|
||||||
>
|
|
||||||
> **Review note (🟡, resolved in Q-W6):** two session pointers / a null-session-as-model-rejection
|
|
||||||
> misreport (unreachable today) was resolved by Q-W6's `bank_ops` lift.
|
|
||||||
>
|
|
||||||
> **In-DAW verification (action families, one-op-one-Ctrl-Z, prune fail-safes) is PENDING on
|
|
||||||
> `phase-q`** — deferred by design, not yet performed.
|
|
||||||
|
|
||||||
## Q-W5 — split `persist.cpp` (isolate the single file-deletion authority into `prune_fs`)
|
|
||||||
|
|
||||||
> **Landed on `phase-q` (2026-07-29, merge of `pq-w5-persist`). Integrated suite 61/61 green,
|
|
||||||
> reviewed-approved.** `persist.cpp` (853 LOC) split into `session` / `ext_state_io` / `prune_fs`
|
|
||||||
> under `shell/persist/` + `persist_internal.h`; the file-deletion authority is concentrated —
|
|
||||||
> `SHFileOperationW`/orphan-remove lives in exactly one anonymous-namespace function in
|
|
||||||
> `prune_fs.cpp`, verified tree-wide; the prune fail-safe chain stays byte-intact. T2-04's
|
|
||||||
> `GetProjExtState` grow-loop is unified as a header-only template, with all three hand-rolled
|
|
||||||
> copies rewired (`usage_scan`'s start cap raised 4KB→64KB, allocation-only, verified
|
|
||||||
> equivalent). The Q-W1 `bank_book_json` residual lands via a private static `nameKey`
|
|
||||||
> (Daniel-approved option a) — `bank_book.cpp` is now ~462 LOC. `persist.h` is kept as a compat
|
|
||||||
> umbrella for parallel safety (retired in Q-W6); deletion-authority wording is scoped precisely
|
|
||||||
> in headers; the grow-loop gains a defensive NUL. See `COMPLETED.md` for the full narrative.
|
|
||||||
>
|
|
||||||
> **In-DAW verification (save/load/undo-reload, ext-state round-trip, folder relocation, prune
|
|
||||||
> deletion) is PENDING on `phase-q`** — deferred by design, not yet performed.
|
|
||||||
|
|
||||||
## Q-W6 — OCP registration-table + residual fat-header (I) splits
|
|
||||||
|
|
||||||
> **Landed on `phase-q` (2026-07-29). Integrated suite 61/61 green, reviewed-approved.** Action
|
|
||||||
> registration/gaccel/hookcommand-dispatch/mirror-unregister all iterate one `ActionTableRow`
|
|
||||||
> table (flat function-pointer dispatch, no `std::function`/virtual); adding a new action now
|
|
||||||
> touches one table row only; `main.cpp` shrinks 653→404. FOREVER-STABLE suffixes/phrases/
|
|
||||||
> retired-ids verified byte-identical row-by-row in review; capture rows derive their
|
|
||||||
> suffix+phrase from the pure `captureActionTable()` (the parallel-list risk is gone by
|
|
||||||
> construction). See `COMPLETED.md` for the full narrative.
|
|
||||||
>
|
|
||||||
> **Phase-end cleanup riders (landed in this wave):** `bankOp*` verbs + `persistBankOp` lifted to
|
|
||||||
> new `shell/bank_ops` taking `ReaSamplerSession&` (dissolves the Q-W4 🟡 review note);
|
|
||||||
> `persist.h` umbrella retired (13 callers repointed); `capture.h`'s realtime seam moved to
|
|
||||||
> `capture_realtime_shell.h`; the `wav_trim.h` shim + its INTERFACE target deleted;
|
|
||||||
> **`core/namespaces.h` DELETED** (the interim Q-W1 shim's contract fulfilled — ~26 includers
|
|
||||||
> rewired); the grow-loop rehomed to `core/wire/ext_state_read.h`; a stale-comment sweep
|
|
||||||
> (`persist.cpp`/`bank_panel.cpp` refs); CLAUDE.md's persist/bank_book/actions/wav_codec bullets
|
|
||||||
> corrected in-wave.
|
|
||||||
>
|
|
||||||
> **Review-noted follow-on (not landed, deferred):** extending the table pattern to the
|
|
||||||
> design_view/bank/ingest families' hand-registration; `channelIdFor`'s shared string-store scan
|
|
||||||
> is correct-by-prefix-disjointness — a suffix-keyed map would make it structural, but isn't
|
|
||||||
> required; `view_mode_model.h` (748 LOC) remains the largest header (T4-06's planner split
|
|
||||||
> stays optional/deferred).
|
|
||||||
>
|
|
||||||
> **In-DAW verification (all action families, registration/fire/unregister parity) is PENDING on
|
|
||||||
> `phase-q`** — deferred by design, not yet performed.
|
|
||||||
|
|
||||||
## Phase Q — sequencing
|
|
||||||
```
|
|
||||||
GATE: Phase S + Phase L L3 merged to dev (D2 complete, M9 abandoned) — tree quiescent
|
|
||||||
("when Phase S and L3 are finished" — L1/L2/L3/L4–L7 all landed — GATE SATISFIED)
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
Q-W0 (audit + triage + report — COMPLETE; all 59 dispositions signed off 2026-07-28;
|
|
||||||
│ fix-now remediations LANDED 2026-07-28)
|
|
||||||
▼
|
|
||||||
Q-W1 (safe opener: core/json ×5 + wire codec + rect unification + relocation incl. ~20 VST
|
|
||||||
│ pure libs under core/instrument/{engine,map,ui} + riders — LANDED 2026-07-29)
|
|
||||||
├─► Q-W2 (split bank_panel — 8 seams — LANDED 2026-07-29)
|
|
||||||
│ └─► Q-W4 (split actions + dedupe vs panel_bank_ops — LANDED 2026-07-29)
|
|
||||||
├─► Q-W2v (VST god-modules — editor 8 TUs / processor 3 TUs / component_state_io;
|
|
||||||
│ sampler_core TU whole — documented exception — LANDED 2026-07-29)
|
|
||||||
│ [parallel with Q-W2: zero overlap]
|
|
||||||
├─► Q-W3 (split main — 4 hoists incl. capture_batch; + wav_codec, ICaptureBackend deletion,
|
|
||||||
│ stamp dedupe, T1-11, capture_realtime_finalize — LANDED 2026-07-29)
|
|
||||||
│ └─► Q-W6 (OCP registration-table — LANDED 2026-07-29)
|
|
||||||
└─► Q-W5 (split persist; + ext-state-loop dedupe — LANDED 2026-07-29) [best after Q-W4]
|
|
||||||
|
|
||||||
STATUS (2026-07-29): all seven waves (Q-W0..Q-W6 incl. Q-W2v) structurally COMPLETE, 61/61
|
|
||||||
integrated suite green. Remaining: Daniel's in-DAW verification batch, the phase-close
|
|
||||||
CLAUDE.md architecture refresh, and the phase-q → dev merge on sign-off.
|
|
||||||
```
|
|
||||||
Q-W0 ran and closed 2026-07-28 (its six fix-now remediations landed the same day). W1 was the
|
|
||||||
safe, high-leverage structural opener (all later waves assumed the layout — including the T4-18
|
|
||||||
`instrument/` placement — it establishes). The god-module splits (W2, W2v, W3, W5) were
|
|
||||||
risk-ordered and mostly parallel-safe; **Q-W2v ran parallel with Q-W2** (different artifact, zero
|
|
||||||
file overlap — audit §4f SETTLED); W4 depended on W2's `panel_bank_ops`, W6 depended on W3's
|
|
||||||
isolated registration code. Big-bang was rejected — every wave landed independently,
|
|
||||||
CTest-green throughout. **All seven waves landed on `phase-q` by 2026-07-29 — Phase Q is
|
|
||||||
structurally complete** (see the phase preamble's PHASE STATUS block for what remains before the
|
|
||||||
phase closes and merges to `dev`).
|
|
||||||
|
|
||||||
## Phase Q — must-verify-before-build
|
|
||||||
- **Q-W0 closed before any structural point** — the functional/DSP audit's findings report exists,
|
|
||||||
every finding is triaged (fix-now vs. document-and-defer, each with rationale), fix-now findings
|
|
||||||
are remediated or assigned to the wave that opens their file, and **Daniel has signed off on every
|
|
||||||
disposition.** Q-W1 does not begin otherwise. (CONTEXT.md §Phase Q Q-W0; naming/DSP smell
|
|
||||||
categories §2c of `docs/product/code-organization.md`.) **Status 2026-07-28: triage + sign-off
|
|
||||||
COMPLETE (all 59 dispositions); the sub-gate closes when the six fix-now remediations land
|
|
||||||
(`pq-w0-fixes`).**
|
|
||||||
- **~600-line ceiling on every split wave** — every TU a split wave ships lands under ~600 LOC,
|
|
||||||
with `sampler_core.cpp` the single documented exception (T4-14/T4-27). Seams are the method,
|
|
||||||
the ceiling is the bar; arbitrary bisection to hit the number is rejected (Q-5 settlement,
|
|
||||||
2026-07-28).
|
|
||||||
- **No dispatch-stack blowouts anywhere** — heuristic (3) generalizes the hot-path guardrail
|
|
||||||
beyond the three named paths: prefer static polymorphism where types are compile-time-known;
|
|
||||||
templates only where earned for compile-time dedup (T4-20 yes; T4-21's rect NO-template
|
|
||||||
ruling).
|
|
||||||
- **Hot-path call/inline shape** — before landing each split, confirm no virtual dispatch and no
|
|
||||||
header→TU indirection was added on `peaks` envelope compute, audition/preview, or the realtime
|
|
||||||
tick. `computeEnvelope` stays a free function on `const std::vector<float>&`;
|
|
||||||
audition stays a direct call-through; the idle tick stays a single pointer test. (CONTEXT.md
|
|
||||||
§Phase Q, `docs/product/code-organization.md` §3.)
|
|
||||||
- **Command-id + display strings are FOREVER-STABLE** — a reorg must not change a shipped
|
|
||||||
`command_id` string, action display name, ext-state namespace, or VST3 class UID. Re-namespacing
|
|
||||||
C++ symbols is orthogonal to these on-the-wire/on-disk contract strings; keep them byte-identical
|
|
||||||
(per-channel, per the Phase V V4 contract). (CONTEXT.md §Phase Q.)
|
|
||||||
- **Name-collision sweep on sub-namespacing** — `Sample` (model) vs `AudioSample` (audio) vs the
|
|
||||||
unified `Parser` (json), plus the shared pure-UI rect types `FooterRect`/`ButtonRect` (already
|
|
||||||
hand-collision-checked in `footer_bar.h`), and any other cross-lib name that collides once
|
|
||||||
flattened into granular namespaces. Resolve by each symbol's new subsystem home before landing
|
|
||||||
W1. (naming audit §2b.2; `docs/product/code-organization.md` §6 Q-4/Q-8.)
|
|
||||||
- **Naming stays zero-behavior-change and off the wire** — the §2b renames touch C++ symbols only;
|
|
||||||
no `command_id` string, action display name, ext-state namespace/key, `reasampler:` lane prefix,
|
|
||||||
or VST3 class UID is renamed (naming audit §2b.5). A rename that would alter a shipped contract
|
|
||||||
literal is out of scope, full stop.
|
|
||||||
- **The GATE** — do not begin any Q point until **Phase S + Phase L L3** are merged to dev and the
|
|
||||||
tree is quiescent (D2 complete, M9 abandoned). Re-confirm quiescence against dev before W1.
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# TODO
|
|
||||||
|
|
||||||
Forward-looking follow-ups. Deferred by decision, not oversight — each entry records why it was deferred and what "done" looks like.
|
|
||||||
|
|
||||||
## Persist ReaSampler 9000 instance identity to let prune reclaim de-referenced captures after reopen
|
|
||||||
|
|
||||||
**Context (what shipped — Phase S usage-detection).** Each ReaSampler 9000 instance publishes the captures it holds to project ext-state (`rsusage_<guid>` keys, ComponentState v11). The extension's prune reads those records and unions every live instance's held captures into the referenced-set, so a capture any live instance holds can never be pruned. Fail-safe: unreadable/ambiguous usage state aborts prune (deletes nothing). Airtight on safety.
|
|
||||||
|
|
||||||
**The wart.** The per-instance identity token is minted fresh each incarnation and is NOT persisted. After save→reopen, an instance cannot recognize its OWN prior-session usage record — it looks foreign, so the instance defensively unions and marks the record append-only (poisoned). Net effect: after any reopen, prune stops reclaiming captures an instance once loaded but no longer uses. Safe (never deletes a used capture), but the bank folder grows without bound.
|
|
||||||
|
|
||||||
**Intended fix.** Persist the instance identity in ComponentState so an instance recognizes its own last-session record and does a clean-replace instead of union/poison → prune reclaims de-referenced instance-touched captures normally.
|
|
||||||
|
|
||||||
**The constraint the fix MUST handle (why deferred).** VST3 provides no stable per-instance identity, and Ctrl+D / in-place FX duplication clones plugin state. A persisted identity is inherited by an in-place duplicate → two live instances in one project share one `rsusage_<guid>` key. Harmless while both hold the same capture; the risk is a divergent clone — the copies load DIFFERENT captures, and last-writer-wins drops the other's held capture from the record, exposing it to prune. The fix must detect a genuine live same-identity collision and protect the union in that case, WITHOUT reintroducing the sibling-drop bug the fresh-per-session token was originally added to prevent. (Whole-project copies are a non-issue — bank files are cloned with the project and ext-state is per-project.)
|
|
||||||
|
|
||||||
**Priority / risk.** Low / deferred. Current behavior is safe; the only cost is unbounded bank-folder growth after reopens. Decided 2026-07-28 to ship the safe version and defer this.
|
|
||||||
|
|
||||||
**Done looks like.** Save → reopen → de-reference a capture from an instance → prune reclaims it. And: in-place-duplicate + diverge + delete-from-bank never deletes a capture a live instance holds.
|
|
||||||
+1065
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
|||||||
|
# COMPLETED.md — ReaSampler landed milestones
|
||||||
|
|
||||||
|
Completed milestone entries removed from `PLAN.md`. Each entry preserves its
|
||||||
|
original Goal, Verify, and checklist points with boxes marked done.
|
||||||
|
|
||||||
|
This file holds the current (1.x) cycle's landed milestones only. Nothing has
|
||||||
|
landed in the 1.x cycle yet. For all pre-1.0 (version-0) history, see
|
||||||
|
`docs/ARCHIVE.md`.
|
||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
# TODO
|
||||||
|
|
||||||
|
Forward-looking follow-ups. Deferred by decision, not oversight — each entry records why it was deferred and what "done" looks like.
|
||||||
|
|
||||||
|
## Persist ReaSampler 9000 instance identity to let prune reclaim de-referenced captures after reopen
|
||||||
|
|
||||||
|
**Context (what shipped — Phase S usage-detection).** Each ReaSampler 9000 instance publishes the captures it holds to project ext-state (`rsusage_<guid>` keys, ComponentState v11). The extension's prune reads those records and unions every live instance's held captures into the referenced-set, so a capture any live instance holds can never be pruned. Fail-safe: unreadable/ambiguous usage state aborts prune (deletes nothing). Airtight on safety.
|
||||||
|
|
||||||
|
**The wart.** The per-instance identity token is minted fresh each incarnation and is NOT persisted. After save→reopen, an instance cannot recognize its OWN prior-session usage record — it looks foreign, so the instance defensively unions and marks the record append-only (poisoned). Net effect: after any reopen, prune stops reclaiming captures an instance once loaded but no longer uses. Safe (never deletes a used capture), but the bank folder grows without bound.
|
||||||
|
|
||||||
|
**Intended fix.** Persist the instance identity in ComponentState so an instance recognizes its own last-session record and does a clean-replace instead of union/poison → prune reclaims de-referenced instance-touched captures normally.
|
||||||
|
|
||||||
|
**The constraint the fix MUST handle (why deferred).** VST3 provides no stable per-instance identity, and Ctrl+D / in-place FX duplication clones plugin state. A persisted identity is inherited by an in-place duplicate → two live instances in one project share one `rsusage_<guid>` key. Harmless while both hold the same capture; the risk is a divergent clone — the copies load DIFFERENT captures, and last-writer-wins drops the other's held capture from the record, exposing it to prune. The fix must detect a genuine live same-identity collision and protect the union in that case, WITHOUT reintroducing the sibling-drop bug the fresh-per-session token was originally added to prevent. (Whole-project copies are a non-issue — bank files are cloned with the project and ext-state is per-project.)
|
||||||
|
|
||||||
|
**Priority / risk.** Low / deferred. Current behavior is safe; the only cost is unbounded bank-folder growth after reopens. Decided 2026-07-28 to ship the safe version and defer this.
|
||||||
|
|
||||||
|
**Done looks like.** Save → reopen → de-reference a capture from an instance → prune reclaims it. And: in-place-duplicate + diverge + delete-from-bank never deletes a capture a live instance holds.
|
||||||
|
|
||||||
|
## Isolate capture from out-of-scope aux/parallel sends, not just FX/gain/pan
|
||||||
|
|
||||||
|
**Context (carried from PLAN.md's "Open questions to resolve during build").** The FX-scope capture neutralizes out-of-scope FX, gain, and pan for both item and track scope — root `CLAUDE.md`'s "Capture FX scope" precision invariant states this precisely: the out-of-scope chain (ancestors + master track, plus the item's own track for item scope) has its FX, gain, and pan/width/pan-law/mode neutralized to unity. **Aux/parallel sends are conspicuously absent from that enumerated list** — the invariant as currently written does not cover them, which is the gap this item exists to close.
|
||||||
|
|
||||||
|
**The wart.** A downstream coloring send (e.g. a folder → reverb-track send) still routes and blends into an item/track capture, past the intended isolation point. Repro from PLAN.md: folder F1; T1 (MIDI) sends MIDI to T2 (synth); T1+T2 → F1; F1 sends to reverb T3; capturing the MIDI item on T1 currently includes the reverb, but should be isolated to T2's synth output pre-F1 with the MIDI send preserved and the reverb send excluded.
|
||||||
|
|
||||||
|
**Intended fix.** Likely approach (PLAN.md): snapshot + mute out-of-scope tracks' aux sends during the render while preserving the main/source signal path.
|
||||||
|
|
||||||
|
**The constraint the fix MUST handle.** Distinguish **source routing that must be preserved** (e.g. a MIDI send T1→T2 where T2's synth is where a MIDI item's audio is actually produced — the "item level" for that MIDI item is T2's synth output) from **coloring sends that must be excluded** (folder→reverb). PLAN.md notes this is "the hard part" and that a rule is needed for which sends are load-bearing.
|
||||||
|
|
||||||
|
**Settled rule (Daniel, 2026-07-29).** The classification rule for which sends are load-bearing: (1) **ancestor sends are excluded** from the capture — the folder parent is *up* the folder tree but *downstream* in signal flow, so this item deliberately says "ancestor," not "upstream," which would read backwards to anyone applying signal-flow convention; (2) **sibling sends are preserved and captured** — a sibling is a track sharing the capture scope's parent; (3) **if the send's destination has a different parent, ignore it in the capture** — it isn't a sibling, so it isn't captured. Applied to the repro above: the T1→T2 MIDI send is a sibling send (T1 and T2 share parent F1) and is preserved — T2's synth is where the item's audio is actually produced; the F1→T3 reverb send is an ancestor send and is excluded. This resolves the repro.
|
||||||
|
|
||||||
|
**Still open.** The rule above settles *which sends to mute*, but the repro is only fully resolved together with a second, unspecified lever — *where the capture taps*. PLAN.md's own framing of this open question says a true item-level capture should be taken "at the isolated graph point — the target scope's output before out-of-scope track FX/gain/pan and before out-of-scope aux/parallel sends." How the mute-rule above and the tap-point interact is not yet specified.
|
||||||
|
|
||||||
|
**Priority / risk.** Not stated in PLAN.md (recorded there as an open `(TODO)` question, not yet triaged to a priority). Flagged here as mattering more than the other four carried-over items because it exposes a live gap in a stated precision invariant, not just a deferred feature.
|
||||||
|
|
||||||
|
**Done looks like.** Capturing the MIDI item on T1 in the repro above is isolated to T2's synth output pre-F1 — the T1→T2 MIDI send is preserved and the F1→reverb send is excluded — and the CLAUDE.md "Capture FX scope" invariant's neutralization list is extended to name sends explicitly.
|
||||||
|
|
||||||
|
## Confirm REAPER's VST3 UID-vs-filename instance rebind behavior (Phase S compat verification)
|
||||||
|
|
||||||
|
**Context.** PLAN.md, under "Phase S — product name (ReaSampler 9000)": the working assumption is that REAPER rebinds a saved instance by its VST3 class UID, not by the module filename, so a filename rename with an unchanged UID keeps saved projects working (existing instances still resolve).
|
||||||
|
|
||||||
|
**The wart.** This is not yet confirmed from source — PLAN.md records that a web check surfaced a JUCE/VST3-replace-VST2 case suggesting REAPER's binding is more nuanced than "UID only" (it can involve an FXID match), so UID-rebind is to-verify, not asserted fact.
|
||||||
|
|
||||||
|
**Intended fix / required check.** DAW-verify: save a project with a ReaSampler 9000 instance under the old filename, rename the module, reopen — confirm the instance rebinds and restores its state.
|
||||||
|
|
||||||
|
**The constraint the fix MUST handle.** If REAPER does key partly on filename, the fallback is to keep the current filename (display-strings-only) and record that as the shipped choice.
|
||||||
|
|
||||||
|
**Priority / risk.** Marked "must-DAW-verify before shipping the rename" in PLAN.md; no explicit priority level stated beyond that.
|
||||||
|
|
||||||
|
**Done looks like.** The save→rename→reopen DAW check is performed and its result (rebind confirmed, or filename-revert fallback taken) is recorded.
|
||||||
|
|
||||||
|
## S13 — cross-artifact ingest relay (deferred, spike verdict DEGRADED)
|
||||||
|
|
||||||
|
**Context.** PLAN.md records the ps-w12 (2026-07-27) spike verdict: DEGRADED — relay deferred. The instrument's REAPER bridge (`reaper_bridge`) is deliberately READ-ONLY; a relay would need a new instrument WRITE seam into ext-state and an extension-side timer poller servicing a drop-ingest inbox key with a claim/clear nonce — the same cross-process handshake race the S17 spec rejected for alternative (A). The shipped ingest gesture stays drop-onto-docked-panel (S8); the editor shows a "drop files onto the ReaSampler bank panel to add them" affordance as the degrade path.
|
||||||
|
|
||||||
|
**The wart.** Dropping a file directly onto the editor/instrument does not ingest it into the bank — only drop-onto-docked-panel does.
|
||||||
|
|
||||||
|
**Intended fix.** The editor hands the dropped path + this instance's identity to the extension as a bank-ingest request over an agreed seam.
|
||||||
|
|
||||||
|
**The constraint the fix MUST handle.** Requires (a) a new instrument WRITE seam into ext-state (breaking the current read-only-bridge invariant) and (b) an extension-side timer poller + claim/clear nonce — both are load-bearing design calls that need to be made deliberately, not as a call-site patch. Both the read-only-instrument boundary and the new poller were judged load-bearing enough that the relay is deferred to a future wave rather than pushed through now.
|
||||||
|
|
||||||
|
**Priority / risk.** PLAN.md marks this DEFERRED, awaiting a future wave, with no priority assigned.
|
||||||
|
|
||||||
|
**Done looks like.** Not stated in PLAN.md beyond "a future wave when the design is ready."
|
||||||
|
|
||||||
|
## Phase D2 — per-track lane/mode-state panel indicator (deferred)
|
||||||
|
|
||||||
|
**Context.** PLAN.md: Phase D2 is functionally complete (D2-W1, D2-W2, D2-W3-A, D2-W3-B all landed). One item was deferred out of that completion: a panel UI indicator for per-track lane/mode state (a per-track lane-split marker).
|
||||||
|
|
||||||
|
**The wart.** The mode switch already shows the active mode, but there is no per-track indicator; PLAN.md records that no natural cheap home for one was found in the bank panel.
|
||||||
|
|
||||||
|
**Intended fix.** Not specified in PLAN.md beyond the goal (a per-track lane-split marker in the bank panel) — the design is unresolved, which is part of why it was deferred rather than built.
|
||||||
|
|
||||||
|
**The constraint the fix MUST handle.** Finding a home for the indicator in the bank panel's existing layout, which PLAN.md notes doesn't currently have a natural cheap spot for it.
|
||||||
|
|
||||||
|
**Priority / risk.** Not stated in PLAN.md. PLAN.md's own framing: "Explicitly deferred — not silently dropped. Can be picked up later if wanted."
|
||||||
|
|
||||||
|
**Done looks like.** Not stated in PLAN.md.
|
||||||
|
|
||||||
|
## FX-GUID keying for `restoreFxOffline` (Design View park/restore)
|
||||||
|
|
||||||
|
**Context.** CONTEXT.md's "Open questions to resolve during build" (Design View section): the bulk of reconcile residuals shipped (`ViewModeModel::reconcile(liveGuids)` prunes orphaned snapshots on every toggle/load; folder restructure is self-healing because the tree is rebuilt each toggle; membership is intentionally kept so undo-delete preserves the tag). Two sub-items were left deferred out of that; this is the first.
|
||||||
|
|
||||||
|
**The wart.** `restoreFxOffline` currently restores per-FX offline state by slot index. If the FX chain is reshuffled while a track is parked, restore lands on whatever plugin now occupies that slot rather than the plugin it was originally captured from.
|
||||||
|
|
||||||
|
**Intended fix.** FX-GUID keying — key the per-FX offline snapshot entries by FX identity rather than slot index.
|
||||||
|
|
||||||
|
**The constraint the fix MUST handle.** The keying change requires a snapshot-schema migration; CONTEXT.md names this alongside the keying change as the reason the fix was deferred rather than folded into the reconcile-residuals work.
|
||||||
|
|
||||||
|
**Priority / risk.** Not stated in the source.
|
||||||
|
|
||||||
|
**Done looks like.** Not stated in the source beyond the fix description above.
|
||||||
|
|
||||||
|
## Dormant membership entries in persisted `view_state`
|
||||||
|
|
||||||
|
**Context.** CONTEXT.md's "Open questions to resolve during build" (Design View section), the second of the two sub-items left deferred after the reconcile-residuals ship described above.
|
||||||
|
|
||||||
|
**The wart.** Truly-deleted tracks accumulate stale entries in persisted `view_state`.
|
||||||
|
|
||||||
|
**Intended fix.** A future user-initiated "compact" action to remove the stale entries.
|
||||||
|
|
||||||
|
**The constraint the fix MUST handle.** Must NOT be automatic pruning — automatic pruning would reintroduce the undo-delete tag-loss that the deliberate membership-retention was designed to prevent.
|
||||||
|
|
||||||
|
**Priority / risk.** Not stated as a priority level; the source characterizes the wart itself as "harmless and bounded."
|
||||||
|
|
||||||
|
**Done looks like.** Not stated in the source beyond "a future user-initiated 'compact' action."
|
||||||
|
|
||||||
|
## Confirm no fight between Design View flags and screenset recall
|
||||||
|
|
||||||
|
**Context.** CONTEXT.md's "Open questions to resolve during build" (Design View section): Design View drives the same track flags a screenset recall would drive, and last writer wins between the two.
|
||||||
|
|
||||||
|
**The wart.** Not a defect — this is a verification task, not a code change. The open concern is confirming there is no surprising interaction between Design View's flag-driving and an active screenset recall.
|
||||||
|
|
||||||
|
**Intended fix.** N/A — no fix is proposed; the task is to confirm no surprising fight between the two mechanisms.
|
||||||
|
|
||||||
|
**The constraint the fix MUST handle.** N/A — verification only.
|
||||||
|
|
||||||
|
**Priority / risk.** Not stated in the source.
|
||||||
|
|
||||||
|
**Done looks like.** Not stated in the source beyond "confirm no surprising fight."
|
||||||
|
|
||||||
|
## Active-bank indicator placement (B4 polish)
|
||||||
|
|
||||||
|
**Context.** CONTEXT-ARCHIVE.md's "Open questions to resolve during build" (B4 panel section): forks 1–5 are all settled; one panel-polish detail remains open. Fork 4 already settled that the active-bank indicator must be "visually unmistakable" — only its placement is undecided.
|
||||||
|
|
||||||
|
**The wart.** No placement chosen yet among three candidates: per-region headers, a single header readout, or a lit-tab treatment.
|
||||||
|
|
||||||
|
**Intended fix.** Not stated in the source beyond the three candidate placements above — the choice among them is the open item. This is explicitly a panel-polish detail.
|
||||||
|
|
||||||
|
**The constraint the fix MUST handle.** Not stated in the source.
|
||||||
|
|
||||||
|
**Priority / risk.** Not stated as a priority level; the source characterizes this as a "panel-polish detail."
|
||||||
|
|
||||||
|
**Done looks like.** Not stated in the source beyond choosing one of the three placement options.
|
||||||
@@ -10,7 +10,8 @@ flatness, naming families) are **not restated**; where a finding below touches t
|
|||||||
is because the functional mechanism is new.
|
is because the functional mechanism is new.
|
||||||
|
|
||||||
Every claim below was verified by grep/read of the actual tree. Line numbers are as of this
|
Every claim below was verified by grep/read of the actual tree. Line numbers are as of this
|
||||||
audit's snapshot. Wave assignments reference PLAN.md §Q-W1..Q-W6.
|
audit's snapshot. Wave assignments reference the Q-W1..Q-W6 waves (landed history in
|
||||||
|
`docs/ARCHIVE.md`; sequencing in `docs/product/code-organization.md` §5).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
Static analysis, 2026-07-28, branch `pq-w0-audit`. Scope: any value stored in an
|
Static analysis, 2026-07-28, branch `pq-w0-audit`. Scope: any value stored in an
|
||||||
environment-coupled domain — frames, sample rate, DPI, pixels, tick cadence — that should be
|
environment-coupled domain — frames, sample rate, DPI, pixels, tick cadence — that should be
|
||||||
stored **rate-free / device-free and resolved at the point of use** (PLAN.md §Q-W0 env-coupled
|
stored **rate-free / device-free and resolved at the point of use** (`docs/product/code-organization.md`
|
||||||
bullet; `docs/product/code-organization.md` §2c.3; the load-bearing `sample_map` seconds
|
§2c.3 env-coupled bullet; the load-bearing `sample_map` seconds invariant, documented in
|
||||||
invariant). Findings are domain-modeling calls, not "rescale by rate" patches. The judgment bar
|
`src/core/instrument/CLAUDE.md`). Findings are domain-modeling calls, not "rescale by rate" patches. The judgment bar
|
||||||
applied: a finding requires (a) an env-coupled *stored* domain AND (b) an environment that can
|
applied: a finding requires (a) an env-coupled *stored* domain AND (b) an environment that can
|
||||||
actually change under it. Frame counts computed transiently from seconds at the use site are
|
actually change under it. Frame counts computed transiently from seconds at the use site are
|
||||||
correct and are not reported.
|
correct and are not reported.
|
||||||
@@ -63,7 +63,7 @@ fix-now findings in those files must be remediated in Q-W0 itself.
|
|||||||
normalize the Trigger fade-in/out knobs.
|
normalize the Trigger fade-in/out knobs.
|
||||||
- **Stored vs. correct domain:** the fade **storage** domain (int64 SOURCE frames, persisted in
|
- **Stored vs. correct domain:** the fade **storage** domain (int64 SOURCE frames, persisted in
|
||||||
the zones payload) is settled and correct — a source-timeline fact, invariant under project-
|
the zones payload) is settled and correct — a source-timeline fact, invariant under project-
|
||||||
rate change (PLAN.md §S15). The *UI ceiling*, however, encodes a wall-clock intent ("2-second
|
rate change (`docs/ARCHIVE.md` §S15). The *UI ceiling*, however, encodes a wall-clock intent ("2-second
|
||||||
max fade throw") as a frame count at an assumed 44.1 kHz source. `88200` is a rate-derived
|
max fade throw") as a frame count at an assumed 44.1 kHz source. `88200` is a rate-derived
|
||||||
literal in `src/`, brushing the no-hardcoded-rate ruling even though it never touches disk.
|
literal in `src/`, brushing the no-hardcoded-rate ruling even though it never touches disk.
|
||||||
- **What breaks when the environment shifts:** the environment here is the **source file's
|
- **What breaks when the environment shifts:** the environment here is the **source file's
|
||||||
@@ -165,7 +165,7 @@ fix-now findings in those files must be remediated in Q-W0 itself.
|
|||||||
`SampleRefs` (paths + root/loop/channels intrinsics), `instanceGuid` — all rate-free or
|
`SampleRefs` (paths + root/loop/channels intrinsics), `instanceGuid` — all rate-free or
|
||||||
file-fact domains. Clean.
|
file-fact domains. Clean.
|
||||||
- **Trigger `fadeInFrames`/`fadeOutFrames`/`startPoint`/`SampleLoop.start/end` persisted as
|
- **Trigger `fadeInFrames`/`fadeOutFrames`/`startPoint`/`SampleLoop.start/end` persisted as
|
||||||
int64 SOURCE frames:** deliberate, settled source-timeline facts (PLAN.md §S15;
|
int64 SOURCE frames:** deliberate, settled source-timeline facts (`docs/ARCHIVE.md` §S15;
|
||||||
`bank_model.h:66-72` documents the loop rationale) — frames *of the file* are invariant under
|
`bank_model.h:66-72` documents the loop rationale) — frames *of the file* are invariant under
|
||||||
project-rate change; the file's own rate is stored alongside and resolved at decode. Correct
|
project-rate change; the file's own rate is stored alongside and resolved at decode. Correct
|
||||||
domain, not a finding.
|
domain, not a finding.
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
# Capture tail — spec
|
# Capture tail — spec
|
||||||
|
|
||||||
Authoritative spec for the **capture-tail** feature: preserving reverb/release
|
Authoritative spec for the **capture-tail** feature: preserving reverb/release
|
||||||
tails that decay past the end of a capture range. The tickable milestone lives in
|
tails that decay past the end of a capture range. The tickable milestone's landed
|
||||||
`PLAN.md` (Milestone T); this doc holds the full technical detail **and** the
|
history is in `docs/ARCHIVE.md` (Milestone T); this doc holds the full technical
|
||||||
product framing.
|
detail **and** the product framing.
|
||||||
|
|
||||||
> **Why this doc carries the technical spec (not `CONTEXT.md`).** Every other
|
> **Why this doc carries the technical spec (not the architecture docs).** Every
|
||||||
> pillar (capture M0–M11, Design View, Multi-bank) keeps its authoritative
|
> other pillar (capture M0–M11, Design View, Multi-bank) keeps its authoritative
|
||||||
> technical spec as a `CONTEXT.md §` section and its *why* in a `docs/product/`
|
> technical spec as a per-directory `src/**/CLAUDE.md` section and its *why* in a
|
||||||
> note. Capture-tail is a rider on the already-shipped offline-render path
|
> `docs/product/` note. Capture-tail is a rider on the already-shipped
|
||||||
> (M3/M7), not a standalone pillar, and it is being specced without reopening
|
> offline-render path (M3/M7), not a standalone pillar, and it was specced without
|
||||||
> `CONTEXT.md`. So the authoritative detail lands **here**, house-styled to match
|
> reopening the architecture spec. So the authoritative detail lands **here**,
|
||||||
> the CONTEXT specs; when the tail work lands, doc-keeper may fold the invariant
|
> house-styled to match those specs; the landed invariant deltas are folded into
|
||||||
> deltas into `CONTEXT.md §Precision invariants` as landed history. Same standing
|
> root `CLAUDE.md` §Precision invariants and `src/core/capture/CLAUDE.md` /
|
||||||
> discipline applies: **verify every REAPER API name/flag against
|
> `src/shell/capture/CLAUDE.md` as landed history. Same standing discipline
|
||||||
|
> applies: **verify every REAPER API name/flag against
|
||||||
> `vendor/reaper-sdk/sdk/reaper_plugin_functions.h` before use** — the flag values
|
> `vendor/reaper-sdk/sdk/reaper_plugin_functions.h` before use** — the flag values
|
||||||
> below are transcribed from that header (line numbers cited) and are not guesses.
|
> below are transcribed from that header (line numbers cited) and are not guesses.
|
||||||
|
|
||||||
@@ -252,7 +253,7 @@ held to bit-identical repeats.
|
|||||||
- **Opt-in beyond the region.** The tail only ever adds audio past the range end
|
- **Opt-in beyond the region.** The tail only ever adds audio past the range end
|
||||||
when a tail is explicitly requested (Auto or Manual). This is exactly the existing
|
when a tail is explicitly requested (Auto or Manual). This is exactly the existing
|
||||||
invariant: *"no added silence unless a tail is explicitly requested"*
|
invariant: *"no added silence unless a tail is explicitly requested"*
|
||||||
(`CONTEXT.md §Precision invariants`, `CLAUDE.md §Exact bounds`). Auto-trim
|
(`CLAUDE.md §Precision invariants §Exact bounds`). Auto-trim
|
||||||
strengthens it — the tail added is decay, not silence, and the silence past the
|
strengthens it — the tail added is decay, not silence, and the silence past the
|
||||||
decay is trimmed off.
|
decay is trimmed off.
|
||||||
- **The null test uses NO tail.** The null-test / verify capture (M10) and any
|
- **The null test uses NO tail.** The null-test / verify capture (M10) and any
|
||||||
@@ -274,8 +275,8 @@ held to bit-identical repeats.
|
|||||||
the tail is **the track's own reverb/delay decay**, not the parent bus's. A track
|
the tail is **the track's own reverb/delay decay**, not the parent bus's. A track
|
||||||
with a reverb plugin captures that reverb's tail; a track feeding a folder reverb
|
with a reverb plugin captures that reverb's tail; a track feeding a folder reverb
|
||||||
does **not** capture the folder reverb's tail (that send is out of scope — and
|
does **not** capture the folder reverb's tail (that send is out of scope — and
|
||||||
note the pre-existing send-isolation caveat in `PLAN.md §Open questions`, which
|
note the pre-existing send-isolation caveat, which the tail inherits
|
||||||
the tail inherits unchanged, does not worsen). Correct and consistent.
|
unchanged, does not worsen). Correct and consistent.
|
||||||
- **Item scope:** item/take FX only, self-track + ancestors + master bypassed →
|
- **Item scope:** item/take FX only, self-track + ancestors + master bypassed →
|
||||||
the tail is the **item/take FX decay only**. An item with a take reverb captures
|
the tail is the **item/take FX decay only**. An item with a take reverb captures
|
||||||
its tail; the track's reverb does not ring into it. Correct.
|
its tail; the track's reverb does not ring into it. Correct.
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ subdirectories) **without sacrificing runtime performance**, against a stated qu
|
|||||||
>
|
>
|
||||||
> Bring the codebase "**into the realm of something I can stand to look at.**"
|
> Bring the codebase "**into the realm of something I can stand to look at.**"
|
||||||
|
|
||||||
Its build roadmap lives in **PLAN.md §Phase Q** and its authoritative spec in
|
Its build roadmap's landed history lives in **`docs/ARCHIVE.md`** ("Phase Q — Quality:
|
||||||
**CONTEXT.md §Phase Q**. This doc holds the *why* — the quality bar, the evidence base
|
the decision record") and its authoritative spec now lives in root **`CLAUDE.md`**
|
||||||
|
(§Directory and namespace layout, §Performance guardrails, §Structural heuristics)
|
||||||
|
plus the per-directory `src/**/CLAUDE.md` files. This doc holds the *why* — the quality bar, the evidence base
|
||||||
(a grep-verified SOLID audit), the target directory/namespace shape grounded in the Vital
|
(a grep-verified SOLID audit), the target directory/namespace shape grounded in the Vital
|
||||||
reference, and the numbered fork decisions.
|
reference, and the numbered fork decisions.
|
||||||
|
|
||||||
@@ -321,10 +323,10 @@ re-namespaced, and split, not carried forward untouched into a tidier tree. "Som
|
|||||||
look at" is partly shape (§2/§2b) and partly *functional soundness* (§2c).
|
look at" is partly shape (§2/§2b) and partly *functional soundness* (§2c).
|
||||||
|
|
||||||
Q-W0 is **not executed by product-designer** — this doc *defines the wave and its scope*; the audit
|
Q-W0 is **not executed by product-designer** — this doc *defines the wave and its scope*; the audit
|
||||||
itself is staff-engineer/DSP work. The authoritative wave definition lives in **PLAN.md §Q-W0** and
|
itself is staff-engineer/DSP work. The wave's landed history lives in **`docs/ARCHIVE.md`**
|
||||||
its spec framing in **CONTEXT.md §"The pre-restructure audit wave (Q-W0)"**; this section is the
|
("Q-W0 fix-now remediations" and "Phase Q — Quality: the decision record"), and its findings live in
|
||||||
evidence-doc framing (the *why* and the smell taxonomy), matching how §2/§2b frame the structural
|
**`docs/product/code-quality-audit.md`**; this section is the evidence-doc framing (the *why* and
|
||||||
audits.
|
the smell taxonomy), matching how §2/§2b frame the structural audits.
|
||||||
|
|
||||||
### 2c.1 Audit surfaces (the named targets)
|
### 2c.1 Audit surfaces (the named targets)
|
||||||
|
|
||||||
@@ -451,7 +453,7 @@ namespace of every header, splitting the four largest TUs). Meanwhile:
|
|||||||
- **Phase L** has **L3** (VST editor + embed-strip restyle, gated on Phase S landing on dev)
|
- **Phase L** has **L3** (VST editor + embed-strip restyle, gated on Phase S landing on dev)
|
||||||
still to land — it touches the Phase S draw shells (`reasampler_editor` / `reasampler_embed`),
|
still to land — it touches the Phase S draw shells (`reasampler_editor` / `reasampler_embed`),
|
||||||
which arrive on dev with Phase S. (L1/L2/L4/L5/L6/L7 have **already landed** — see
|
which arrive on dev with Phase S. (L1/L2/L4/L5/L6/L7 have **already landed** — see
|
||||||
`COMPLETED.md`; the once-listed "L2 pending" is stale and has been corrected here.)
|
`docs/ARCHIVE.md`; the once-listed "L2 pending" is stale and has been corrected here.)
|
||||||
- **D2** is **functionally complete** (D2-W1..W3-B landed; the only open item — a per-track
|
- **D2** is **functionally complete** (D2-W1..W3-B landed; the only open item — a per-track
|
||||||
lane-split panel indicator — is *explicitly deferred*, not a blocking residual). **M9** (slots)
|
lane-split panel indicator — is *explicitly deferred*, not a blocking residual). **M9** (slots)
|
||||||
is **abandoned** (Daniel, 2026-07-27) — will not be built. D2 is named in the gate only so a
|
is **abandoned** (Daniel, 2026-07-27) — will not be built. D2 is named in the gate only so a
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
Date: 2026-07-28 · Branch: `pq-w0-audit` · Static analysis only; no code changed by the audit.
|
Date: 2026-07-28 · Branch: `pq-w0-audit` · Static analysis only; no code changed by the audit.
|
||||||
|
|
||||||
This is the committed Q-W0 findings report (Q-10 SETTLED: a committed doc beside the SOLID/naming
|
This is the committed Q-W0 findings report (Q-10 SETTLED: a committed doc beside the SOLID/naming
|
||||||
audit — `docs/product/code-organization.md` §2c.3; deliverable contract in PLAN.md §Q-W0). It
|
audit — `docs/product/code-organization.md` §2c.3; deliverable contract landed per `docs/ARCHIVE.md`
|
||||||
|
§Phase Q). It
|
||||||
synthesizes four parallel audit tracks; the full track notes remain in the tree as appendices and
|
synthesizes four parallel audit tracks; the full track notes remain in the tree as appendices and
|
||||||
are the evidence base for every claim here — this report cites finding IDs and does not restate
|
are the evidence base for every claim here — this report cites finding IDs and does not restate
|
||||||
mechanisms in full:
|
mechanisms in full:
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# Design View — product notes
|
# Design View — product notes
|
||||||
|
|
||||||
Framing, rationale, and design-direction calls behind the **Design View** phase.
|
Framing, rationale, and design-direction calls behind the **Design View** phase.
|
||||||
The tickable spec lives in `PLAN.md` (Phase D) and the authoritative technical
|
The tickable spec's landed history lives in `docs/ARCHIVE.md` (Phase D) and the
|
||||||
detail in `CONTEXT.md` (§Design View). This doc holds the *why* — the workflow
|
architecture detail in `src/core/view/CLAUDE.md` + `src/shell/view/CLAUDE.md`
|
||||||
|
(§Design View). This doc holds the *why* — the workflow
|
||||||
narrative, the N-mode reasoning, the screenset differentiation, and the
|
narrative, the N-mode reasoning, the screenset differentiation, and the
|
||||||
design-direction recommendations — so those don't clutter the build docs.
|
design-direction recommendations — so those don't clutter the build docs.
|
||||||
|
|
||||||
@@ -339,8 +340,7 @@ tooltip on the switch.
|
|||||||
`docs/design/`), say so and it moves. Flagging because establishing the
|
`docs/design/`), say so and it moves. Flagging because establishing the
|
||||||
directory is a project convention, not mine to assume silently.
|
directory is a project convention, not mine to assume silently.
|
||||||
2. **Phase namespace.** Proposed **Phase D** (a lettered namespace) rather than
|
2. **Phase namespace.** Proposed **Phase D** (a lettered namespace) rather than
|
||||||
M12 — see PLAN.md rationale. Confirm you're happy with letters for parallel,
|
M12. Confirm you're happy with letters for parallel, non-capture phases.
|
||||||
non-capture phases.
|
|
||||||
3. **SDK name correction.** The brief cited `B_SHOWINMCP`; the verified SDK flag
|
3. **SDK name correction.** The brief cited `B_SHOWINMCP`; the verified SDK flag
|
||||||
for mixer-panel visibility is **`B_SHOWINMIXER`** (SDK header line 2235). The
|
for mixer-panel visibility is **`B_SHOWINMIXER`** (SDK header line 2235). The
|
||||||
spec uses the verified name. Just flagging the discrepancy so it isn't a
|
spec uses the verified name. Just flagging the discrepancy so it isn't a
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
Framing for a **MIDI-triggered audio sampler** that plays back ReaSampler's captured
|
Framing for a **MIDI-triggered audio sampler** that plays back ReaSampler's captured
|
||||||
banks. This began as a discussion-shaping doc; with all forks now settled it has become
|
banks. This began as a discussion-shaping doc; with all forks now settled it has become
|
||||||
the **product framing behind a scoped phase**. Its build roadmap lives in **PLAN.md
|
the **product framing behind a scoped phase**. Its build roadmap lives in
|
||||||
§Phase S** and its authoritative spec in **CONTEXT.md §Phase S** — this doc holds the
|
**`docs/ARCHIVE.md` §Phase S** and its authoritative spec in
|
||||||
*why* (the plugin-format reasoning, the bare-VST3-vs-JUCE assessment, the settled
|
**`src/core/instrument/CLAUDE.md`** and **`src/shell/instrument/CLAUDE.md`** — this
|
||||||
decision record).
|
doc holds the *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-27 (r11)**. r11 records the
|
Status: framed by product-designer (2026-07-26), **revised 2026-07-27 (r11)**. r11 records the
|
||||||
**Sample-face recomposition** (Daniel's post-landing DAW pass, 2026-07-27): all linear sliders →
|
**Sample-face recomposition** (Daniel's post-landing DAW pass, 2026-07-27): all linear sliders →
|
||||||
@@ -42,10 +43,11 @@ demoted to an opt-in Zones panel — see the r6 Addendum in §4. r6 also settles
|
|||||||
S1–S6 instrument: the product name **ReaSampler 9000** and the **"better than RS5K" UX
|
S1–S6 instrument: the product name **ReaSampler 9000** and the **"better than RS5K" UX
|
||||||
overhaul** (Phase S points S10–S13) — see the r5 Addendum in §4. r4 (below) settled the four
|
overhaul** (Phase S points S10–S13) — see the r5 Addendum in §4. r4 (below) settled the four
|
||||||
residual forks D-A..D-D. The
|
residual forks D-A..D-D. The
|
||||||
"no PLAN.md footprint" era is **over** — with D-A through D-D settled (below), the
|
"no landed-roadmap footprint" era is **over** — with D-A through D-D settled (below), the
|
||||||
instrument was scoped into **Phase S** (codename Daniel's: "S" for Sampler, because "D"
|
instrument was scoped into **Phase S** (codename Daniel's: "S" for Sampler, because "D"
|
||||||
collides with the existing Design View phase). **PLAN.md §Phase S is now the
|
collides with the existing Design View phase). **`docs/ARCHIVE.md` §Phase S now records
|
||||||
authoritative roadmap; CONTEXT.md §Phase S is the authoritative spec.** This doc is the
|
the landed roadmap; `src/core/instrument/CLAUDE.md` and `src/shell/instrument/CLAUDE.md`
|
||||||
|
are the authoritative spec.** This doc is the
|
||||||
framing/decision record they point back to. Prior revisions (a) established that a REAPER
|
framing/decision record they point back to. Prior revisions (a) established that a REAPER
|
||||||
*extension* cannot be a MIDI instrument, (b) corrected a material omission — REAPER's
|
*extension* cannot be a MIDI instrument, (b) corrected a material omission — REAPER's
|
||||||
**VST-host bridge**, which lets a VST3 plugin *hosted inside REAPER* call back into
|
**VST-host bridge**, which lets a VST3 plugin *hosted inside REAPER* call back into
|
||||||
@@ -410,8 +412,8 @@ it doesn't carry" — is unchanged. What the bridge settles is *where that mappi
|
|||||||
between extension and instrument as **live shared `"reasampler"` state**, not a file one
|
between extension and instrument as **live shared `"reasampler"` state**, not a file one
|
||||||
writes and the other re-parses.
|
writes and the other re-parses.
|
||||||
|
|
||||||
**What the current index carries** (from `bank_model`'s `Sample`, per CONTEXT.md §Data
|
**What the current index carries** (from `bank_model`'s `Sample`): id, display name,
|
||||||
model): id, display name, relative path, source range, channel count, sample rate,
|
relative path, source range, channel count, sample rate,
|
||||||
length, capture tempo, an **optional key**, peak/RMS/LUFS, content hash, tier,
|
length, capture tempo, an **optional key**, peak/RMS/LUFS, content hash, tier,
|
||||||
provenance, timestamp. Notably it *already* has an optional key field and capture
|
provenance, timestamp. Notably it *already* has an optional key field and capture
|
||||||
tempo — the seeds of pitch-mapping are there.
|
tempo — the seeds of pitch-mapping are there.
|
||||||
@@ -426,8 +428,8 @@ tempo — the seeds of pitch-mapping are there.
|
|||||||
to velocity zones).
|
to velocity zones).
|
||||||
- **Round-robin groups** (cycle through N samples on repeated same-note hits).
|
- **Round-robin groups** (cycle through N samples on repeated same-note hits).
|
||||||
- **Loop points** (sustain loop start/end for held notes; sample-accurate,
|
- **Loop points** (sustain loop start/end for held notes; sample-accurate,
|
||||||
zero-crossing-aware — CONTEXT already flags loop/zero-crossing handling as
|
zero-crossing-aware — loop/zero-crossing handling is day-one-relevant for
|
||||||
day-one-relevant for wavetable material).
|
wavetable material).
|
||||||
- **Amplitude envelope** (ADSR) and optionally filter/pitch envelopes.
|
- **Amplitude envelope** (ADSR) and optionally filter/pitch envelopes.
|
||||||
- **Tuning/gain trim** per sample.
|
- **Tuning/gain trim** per sample.
|
||||||
|
|
||||||
@@ -559,7 +561,7 @@ ReaSampler-native way to build it and it's assumed, not debated, going forward.
|
|||||||
|
|
||||||
All four residual decisions are now called. Each is marked **SETTLED** with Daniel's
|
All four residual decisions are now called. Each is marked **SETTLED** with Daniel's
|
||||||
choice and the reasoning kept as the record of *why* — do not re-litigate. They are
|
choice and the reasoning kept as the record of *why* — do not re-litigate. They are
|
||||||
scoped into **PLAN.md §Phase S** / **CONTEXT.md §Phase S**.
|
scoped into **`docs/ARCHIVE.md` §Phase S** / **`src/core/instrument/CLAUDE.md`**.
|
||||||
|
|
||||||
**D-A — SETTLED: bare Steinberg VST3 SDK + LICE editor (no JUCE).** *(The central fork.
|
**D-A — SETTLED: bare Steinberg VST3 SDK + LICE editor (no JUCE).** *(The central fork.
|
||||||
§1a is the assessment that fed it. The sub-question — who draws the editor? — was the
|
§1a is the assessment that fed it. The sub-question — who draws the editor? — was the
|
||||||
@@ -649,7 +651,7 @@ After Phase S was scoped (D-A..D-D), Daniel set two further directions. These ar
|
|||||||
**settled directions**, not open forks — specced as new Phase S points (S7–S9), not
|
**settled directions**, not open forks — specced as new Phase S points (S7–S9), not
|
||||||
re-litigated. Recorded here per the doc's settled-decisions convention.
|
re-litigated. Recorded here per the doc's settled-decisions convention.
|
||||||
|
|
||||||
**D-E — Channel mode: mono | stereo, per-instance, bus-negotiated (→ PLAN.md S7).**
|
**D-E — Channel mode: mono | stereo, per-instance, bus-negotiated (→ `docs/ARCHIVE.md` §S7).**
|
||||||
Captures are often stereo; the current mono downmix is a Tier-0 simplification. The
|
Captures are often stereo; the current mono downmix is a Tier-0 simplification. The
|
||||||
engine gets a **per-instance channel-mode toggle (1 mono / 2 stereo)** that "works with
|
engine gets a **per-instance channel-mode toggle (1 mono / 2 stereo)** that "works with
|
||||||
the REAPER audio bus automatically" — the VST3 declares/negotiates its output bus
|
the REAPER audio bus automatically" — the VST3 declares/negotiates its output bus
|
||||||
@@ -662,8 +664,8 @@ Cross-mode policy: mono-source-in-stereo → dual-mono; stereo-source-in-mono
|
|||||||
never a bank fact). Sequenced **first after the editor/embed work** because it touches the
|
never a bank fact). Sequenced **first after the editor/embed work** because it touches the
|
||||||
engine Daniel smoke-tests.
|
engine Daniel smoke-tests.
|
||||||
|
|
||||||
**Ingest routes through the bank — "option 1"; the extension owns ingest (→ PLAN.md
|
**Ingest routes through the bank — "option 1"; the extension owns ingest (→
|
||||||
S8 + S9).** Loading a sample into the sampler is **one gesture**: capture/import-into-bank
|
`docs/ARCHIVE.md` §S8 + §S9).** Loading a sample into the sampler is **one gesture**: capture/import-into-bank
|
||||||
+ auto-assign to the active instance. The **extension owns ingest** (it has arrange
|
+ auto-assign to the active instance. The **extension owns ingest** (it has arrange
|
||||||
access, Media-Explorer access, and the drop-target surface on its own panels); the
|
access, Media-Explorer access, and the drop-target surface on its own panels); the
|
||||||
**instrument stays a read-only bank consumer** — it never captures or imports. Sub-parts,
|
**instrument stays a read-only bank consumer** — it never captures or imports. Sub-parts,
|
||||||
@@ -690,7 +692,7 @@ with the honest SDK reality verified against the vendored headers:
|
|||||||
|
|
||||||
*The genuine spikes flagged (not decisions Daniel owes, just build-time unknowns):* the
|
*The genuine spikes flagged (not decisions Daniel owes, just build-time unknowns):* the
|
||||||
ME merely-selected-file read (b), and the drop-onto-editor cross-artifact relay (c). Both
|
ME merely-selected-file read (b), and the drop-onto-editor cross-artifact relay (c). Both
|
||||||
are honestly-flagged as spikes in PLAN.md S8, not promised.
|
are honestly-flagged as spikes in `docs/ARCHIVE.md` §S8, not promised.
|
||||||
|
|
||||||
### Addendum — product name + UX overhaul (Daniel, 2026-07-26, post-S1–S6 DAW test)
|
### Addendum — product name + UX overhaul (Daniel, 2026-07-26, post-S1–S6 DAW test)
|
||||||
|
|
||||||
@@ -700,7 +702,7 @@ open forks (the two flagged forks below are the only calls left to Daniel).
|
|||||||
|
|
||||||
**The instrument's product name is `ReaSampler 9000`.** The extension stays **ReaSampler**
|
**The instrument's product name is `ReaSampler 9000`.** The extension stays **ReaSampler**
|
||||||
(capture + organization); the instrument is **ReaSampler 9000** (playback). Propagation is
|
(capture + organization); the instrument is **ReaSampler 9000** (playback). Propagation is
|
||||||
a checklist item (PLAN.md §Phase S — product name; CONTEXT.md §Product name): the VST3
|
recorded in `docs/ARCHIVE.md` §Phase S — product name: the VST3
|
||||||
class **display name** string, the `IPlugView` editor title band (today "ReaSampler
|
class **display name** string, the `IPlugView` editor title band (today "ReaSampler
|
||||||
Instrument"), the S6 embed-strip label, and the docs. **Compat guard (load-bearing):** the
|
Instrument"), the S6 embed-strip label, and the docs. **Compat guard (load-bearing):** the
|
||||||
**VST3 class UID must NOT change** — instances in saved projects key off it; a UID change
|
**VST3 class UID must NOT change** — instances in saved projects key off it; a UID change
|
||||||
@@ -747,8 +749,9 @@ names the editor as the wound.
|
|||||||
|
|
||||||
After the r5 UX-overhaul directive was specced (keymap-first S10), Daniel reframed the
|
After the r5 UX-overhaul directive was specced (keymap-first S10), Daniel reframed the
|
||||||
workflow before S10 was implemented. This **revises S10** and settles S-NAME-1. Settled
|
workflow before S10 was implemented. This **revises S10** and settles S-NAME-1. Settled
|
||||||
directions, not open forks — recorded here per the doc's settled-decisions convention; PLAN.md
|
directions, not open forks — recorded here per the doc's settled-decisions convention;
|
||||||
§S10 and CONTEXT.md §Phase S (workflow hierarchy) carry the spec.
|
`docs/ARCHIVE.md` §S10 records what landed and `src/core/instrument/CLAUDE.md` (the
|
||||||
|
editor `ui/` modules) documents the current architecture.
|
||||||
|
|
||||||
**The reframe, verbatim (Daniel, 2026-07-26):** *"We need to think hard about the workflow
|
**The reframe, verbatim (Daniel, 2026-07-26):** *"We need to think hard about the workflow
|
||||||
with this plugin. Have a giant list of 'item' blocks is visually useless. When the plugin is
|
with this plugin. Have a giant list of 'item' blocks is visually useless. When the plugin is
|
||||||
@@ -806,8 +809,8 @@ partly on filename, fall back to keeping the filename and record that as shipped
|
|||||||
|
|
||||||
Daniel directed a set of engine features for the sampler, specced as **new Phase S points
|
Daniel directed a set of engine features for the sampler, specced as **new Phase S points
|
||||||
S15 (Trigger vs Gate) and S16 (pitch envelope)**. **The feature set is settled** — recorded
|
S15 (Trigger vs Gate) and S16 (pitch envelope)**. **The feature set is settled** — recorded
|
||||||
here per the doc's settled-decisions convention; PLAN.md §S15/S16 and CONTEXT.md §Sampling
|
here per the doc's settled-decisions convention; `docs/ARCHIVE.md` §S15 / §S16 records what
|
||||||
modes carry the spec. Two forks are flagged with leans (S15-F1 choke, S15-F2 param
|
landed and `src/core/instrument/CLAUDE.md` §Sampling modes documents the current spec. Two forks are flagged with leans (S15-F1 choke, S15-F2 param
|
||||||
granularity); the WDL question was resolved by inspection.
|
granularity); the WDL question was resolved by inspection.
|
||||||
|
|
||||||
**Directive, verbatim (Daniel, 2026-07-26):** *"let's have product spec out some features
|
**Directive, verbatim (Daniel, 2026-07-26):** *"let's have product spec out some features
|
||||||
@@ -927,9 +930,10 @@ This reshapes S16 and **flips the r7 WDL verdict** on `WDL_SimplePitchShifter`.
|
|||||||
`process` allocation; measure per-voice CPU + onset latency against the polyphony cap. Treat
|
`process` allocation; measure per-voice CPU + onset latency against the polyphony cap. Treat
|
||||||
S16's Preserve-engine point as the phase's next real DSP spike, not a thin envelope add-on.
|
S16's Preserve-engine point as the phase's next real DSP spike, not a thin envelope add-on.
|
||||||
|
|
||||||
**Where the spec lives:** PLAN.md §S16 (reshaped to "pitch engine modes + pitch envelope",
|
**Where the spec lives:** `docs/ARCHIVE.md` §S16 (reshaped to "pitch engine modes + pitch
|
||||||
with forks S16-F1/F2 and the corrected WDL finding) and the S15 × S16 interaction note;
|
envelope", with forks S16-F1/F2 and the corrected WDL finding) and the S15 × S16
|
||||||
CONTEXT.md §Pitch engine modes — Varispeed vs Preserve + the corrected WDL surface finding.
|
interaction note; `src/core/instrument/CLAUDE.md` §Sampling modes — Varispeed vs Preserve
|
||||||
|
+ the WDL surface finding.
|
||||||
|
|
||||||
### Addendum — VST channel isolation (Daniel, 2026-07-26)
|
### Addendum — VST channel isolation (Daniel, 2026-07-26)
|
||||||
|
|
||||||
@@ -972,9 +976,9 @@ with or right after the in-flight waves (S9 ext_keys, S15/S16 processor/editor)
|
|||||||
channel's banks; stable-project + beta-VST = clean empty (not error); the S-NAME-1
|
channel's banks; stable-project + beta-VST = clean empty (not error); the S-NAME-1
|
||||||
rename/rebind test extends to the beta UID.
|
rename/rebind test extends to the beta UID.
|
||||||
|
|
||||||
**Where the spec lives:** PLAN.md §S18; CONTEXT.md §VST3 channel identity — the UID pair + the
|
**Where the spec lives:** `docs/ARCHIVE.md` §S18; `src/shell/instrument/CLAUDE.md` §VST3
|
||||||
pairing surface. The pairing surface's data half is already load-bearing V4 machinery; S18
|
channel identity — the UID pair + the pairing surface. The pairing surface's data half is
|
||||||
adds only the identity fork on top.
|
already load-bearing V4 machinery; S18 adds only the identity fork on top.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -986,8 +990,9 @@ good — but the two-view editor (today's "Browser" + "Zones" toggle) misallocat
|
|||||||
the default window is undersized for a 1080p world, and the drop-a-capture-onto-FX gesture is
|
the default window is undersized for a 1080p world, and the drop-a-capture-onto-FX gesture is
|
||||||
broken in practice. The directive: **make the one job — pick a capture, tune it, play it —
|
broken in practice. The directive: **make the one job — pick a capture, tune it, play it —
|
||||||
fast, easy, and fun. Style is a critical ingredient. No spreadsheet aesthetics.** These are the
|
fast, easy, and fun. Style is a critical ingredient. No spreadsheet aesthetics.** These are the
|
||||||
`r9` calls. Authoritative spec: **CONTEXT.md §Phase S — editor view-model redesign (S-VIEW)**;
|
`r9` calls. Current architecture: **`src/core/instrument/CLAUDE.md`** and
|
||||||
build roadmap: **PLAN.md §Phase S — editor view-model redesign**.
|
**`src/shell/instrument/CLAUDE.md`**; landed record: **`docs/ARCHIVE.md` §Phase S — editor
|
||||||
|
view-model redesign**.
|
||||||
|
|
||||||
**The reference devices (the north star for control density).** Daniel named Ableton **Simpler**
|
**The reference devices (the north star for control density).** Daniel named Ableton **Simpler**
|
||||||
and a Kilohearts/Phase-Plant **sampler group** as the composition targets. Both share one
|
and a Kilohearts/Phase-Plant **sampler group** as the composition targets. Both share one
|
||||||
@@ -1021,7 +1026,7 @@ grammar, and it is the grammar the redesign adopts:
|
|||||||
loading a new one is a distinct act), not a three-way radio. *Why the reframe matters:* it
|
loading a new one is a distinct act), not a three-way radio. *Why the reframe matters:* it
|
||||||
makes "I just want to play this capture" the zero-click default, and "I want a different one"
|
makes "I just want to play this capture" the zero-click default, and "I want a different one"
|
||||||
a single deliberate gesture, instead of making the user re-choose their whole stance every
|
a single deliberate gesture, instead of making the user re-choose their whole stance every
|
||||||
time. See CONTEXT.md §S-VIEW for the precise navigation model.
|
time. See `docs/ARCHIVE.md` §S-VIEW-1 for the precise navigation model as landed.
|
||||||
|
|
||||||
2. **The Sample view earns the hero treatment; Browse gets ruthlessly cut.** Browse today
|
2. **The Sample view earns the hero treatment; Browse gets ruthlessly cut.** Browse today
|
||||||
carries a waveform preview, root-note piano-roll, loop-point labels, a track-root message, and
|
carries a waveform preview, root-note piano-roll, loop-point labels, a track-root message, and
|
||||||
@@ -1037,10 +1042,10 @@ grammar, and it is the grammar the redesign adopts:
|
|||||||
3. **Two engineering prerequisites, framed but routed to implementation.** The **drop-to-FX bug**
|
3. **Two engineering prerequisites, framed but routed to implementation.** The **drop-to-FX bug**
|
||||||
(dropping a capture onto a track's FX chain does not instantiate + init ReaSampler 9000) and
|
(dropping a capture onto a track's FX chain does not instantiate + init ReaSampler 9000) and
|
||||||
the **undersized default window** are not design decisions — they are a bug and a one-line
|
the **undersized default window** are not design decisions — they are a bug and a one-line
|
||||||
default. Both are framed in CONTEXT.md §S-VIEW with the SDK reality swept (drop-to-FX: the S17
|
default. Both are recorded landed in `docs/ARCHIVE.md` §S-VIEW-BUG-1 (drop-to-FX: the S17
|
||||||
machinery is SDK-correct, so this is a *diagnosis* task, not a redesign; window size: the
|
machinery is SDK-correct, so this is a *diagnosis* task, not a redesign) and §S-VIEW-SIZE-1
|
||||||
`getSize`/`checkSizeConstraint` mechanism is verified), and both are flagged for
|
(window size: the `getSize`/`checkSizeConstraint` mechanism is verified), and both were
|
||||||
staff-engineer, not for a product fork.
|
flagged for staff-engineer, not for a product fork.
|
||||||
|
|
||||||
**New parameters this introduces (both instrument performance state, D-B — never bank facts):**
|
**New parameters this introduces (both instrument performance state, D-B — never bank facts):**
|
||||||
|
|
||||||
@@ -1064,7 +1069,7 @@ grammar, and it is the grammar the redesign adopts:
|
|||||||
new top-level `previewVelocity` field), **not** the extension's `persist` project ext-state —
|
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
|
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
|
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
|
seam. See `docs/ARCHIVE.md` §S-VIEW-4 for the round-trip and back-compat lift as landed. This is what makes the
|
||||||
preview button *fun*: tap it hard or soft without reaching for a controller — and it remembers.
|
preview button *fun*: tap it hard or soft without reaching for a controller — and it remembers.
|
||||||
|
|
||||||
**Two visual components the redesign commits to:**
|
**Two visual components the redesign commits to:**
|
||||||
@@ -1101,8 +1106,10 @@ persisted fields is not a compat event; saved instances rebind and restore. And
|
|||||||
(extended additively — `keyTrack` per-zone, `previewVelocity` per-instance via an envelope bump to
|
(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.
|
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
|
**Where the spec lives:** `src/core/instrument/CLAUDE.md` (envelope overlay, key-tracking,
|
||||||
§Phase S — editor view-model redesign. This Addendum is the *why*; those are the *what/how*.
|
preview-velocity ownership) documents the current architecture; `docs/ARCHIVE.md` §Phase S
|
||||||
|
— editor view-model redesign records what landed (S-VIEW-1 through S-VIEW-10). This
|
||||||
|
Addendum is the *why*; those are the *what/how*.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1217,8 +1224,9 @@ for preview velocity — a different struct on a different version axis). Concre
|
|||||||
the L1 kit, routing mouse through `velocity_curve`), gated on the foundation track and composing
|
the L1 kit, routing mouse through `velocity_curve`), gated on the foundation track and composing
|
||||||
with the S-VIEW-2 Sample face + S-VIEW-3 envelope-overlay work.
|
with the S-VIEW-2 Sample face + S-VIEW-3 envelope-overlay work.
|
||||||
|
|
||||||
**Where the spec lives:** CONTEXT.md §Phase S — editor view-model redesign (S-VIEW), velocity-curve
|
**Where the spec lives:** `src/core/instrument/CLAUDE.md` (the `velocity_curve` module, its
|
||||||
sub-section; PLAN.md §Phase S — editor view-model redesign (S-VIEW-9/S-VIEW-10 + fork R10-F1). This
|
engine application point, and its ownership rules) documents the current architecture;
|
||||||
|
`docs/ARCHIVE.md` §S-VIEW-9 / §S-VIEW-10 records what landed (fork R10-F1 resolved). This
|
||||||
Addendum is the *why*; those are the *what/how*.
|
Addendum is the *why*; those are the *what/how*.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -1278,11 +1286,13 @@ recomposition of *existing* controls; no new params, no component-state bump, VS
|
|||||||
unchanged. All drawing through the L1 kit by palette role; all layout/hit-test in new pure modules
|
unchanged. All drawing through the L1 kit by palette role; all layout/hit-test in new pure modules
|
||||||
(`knob_deck`, `curve_popup` — mirrors of `action_bar`/`overflow_menu`); the knobs and the hero's
|
(`knob_deck`, `curve_popup` — mirrors of `action_bar`/`overflow_menu`); the knobs and the hero's
|
||||||
envelope nodes remain two surfaces on one param model (S-VIEW-F2's structural sync, untouched).
|
envelope nodes remain two surfaces on one param model (S-VIEW-F2's structural sync, untouched).
|
||||||
The full inventory contract (every landed element → its r11 home) is in the CONTEXT.md spec.
|
The full inventory of what landed (every element → its r11 home) is recorded in
|
||||||
|
`docs/ARCHIVE.md` §FB1 and §FB2.
|
||||||
|
|
||||||
**Where the spec lives:** CONTEXT.md §Phase S — editor view-model redesign (S-VIEW) → "The
|
**Where the spec lives:** `src/core/instrument/CLAUDE.md` (the `knob_deck`/`curve_popup`/
|
||||||
Sample-face recomposition (r11)"; PLAN.md §Phase S — editor Wave B (S-VIEW-11/12/13 + forks
|
`master_gain` modules) documents the current architecture; `docs/ARCHIVE.md` §FB1 and §FB2
|
||||||
R11-F1/R11-F2). This Addendum is the *why*; those are the *what/how*.
|
record what landed (S-VIEW-11/12/13 + forks R11-F1/R11-F2 resolved). This Addendum is the
|
||||||
|
*why*; those are the *what/how*.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1329,7 +1339,8 @@ Post-DAW-test directives (2026-07-26; see the "product name + UX overhaul" Adden
|
|||||||
frameworks), DS-2 (Direction B "Neon Console" + Direction C's spectral keyboard strip), and
|
frameworks), DS-2 (Direction B "Neon Console" + Direction C's spectral keyboard strip), and
|
||||||
DS-3 (thorough panel layout) are all **SETTLED (2026-07-26)**. Framing + palette + the three
|
DS-3 (thorough panel layout) are all **SETTLED (2026-07-26)**. Framing + palette + the three
|
||||||
visual directions + forks: `docs/product/visual-design-language.md` (on `dev`); roadmap +
|
visual directions + forks: `docs/product/visual-design-language.md` (on `dev`); roadmap +
|
||||||
spec: **PLAN.md §Phase L + CONTEXT.md §Phase L** (on `dev`). **S10–S13 build with the
|
spec: **`docs/ARCHIVE.md` §Phase L** (landed record) and **`src/core/ui/CLAUDE.md`**
|
||||||
|
(current architecture) (on `dev`). **S10–S13 build with the
|
||||||
current drawing and adopt the L1 kit when it lands — not gated on Phase L.** Answers
|
current drawing and adopt the L1 kit when it lands — not gated on Phase L.** Answers
|
||||||
Daniel's "the VST is dogshit / temple os / does Cockos have a toolkit" (2026-07-26,
|
Daniel's "the VST is dogshit / temple os / does Cockos have a toolkit" (2026-07-26,
|
||||||
post-S1–S6 DAW test).
|
post-S1–S6 DAW test).
|
||||||
@@ -1350,15 +1361,16 @@ Post-DAW-test directives (2026-07-26; see the "product name + UX overhaul" Adden
|
|||||||
S15-F1 (choke, held) / S15-F2 (param granularity, lean per-zone). Feature set settled;
|
S15-F1 (choke, held) / S15-F2 (param granularity, lean per-zone). Feature set settled;
|
||||||
the engine default is Daniel's fork.
|
the engine default is Daniel's fork.
|
||||||
|
|
||||||
**Authoritative from here:** **PLAN.md §Phase S** is the roadmap (S1–S6 the original
|
**Authoritative from here:** **`docs/ARCHIVE.md` §Phase S** is the landed roadmap (S1–S6
|
||||||
dependency chain: spike → `Sample` fields → pure sampler core → Tier 0 → Tier 1 → embedded
|
the original dependency chain: spike → `Sample` fields → pure sampler core → Tier 0 → Tier
|
||||||
UI; then **S7** stereo, **S8** ingest, **S9** change-detection, **S10–S13** the ReaSampler
|
1 → embedded UI; then **S7** stereo, **S8** ingest, **S9** change-detection, **S10–S13** the
|
||||||
9000 UX overhaul, **S15/S16** the Trigger-vs-Gate + pitch-engine-modes engine features);
|
ReaSampler 9000 UX overhaul, **S15/S16** the Trigger-vs-Gate + pitch-engine-modes engine
|
||||||
**CONTEXT.md §Phase S** is the spec (seam-field semantics, scope contracts, the channel-mode
|
features); **`src/core/instrument/CLAUDE.md`**, **`src/shell/instrument/CLAUDE.md`**, and
|
||||||
/ ingest / bank-generation / sampling-mode / pitch-engine contracts, the UX-overhaul spec,
|
**`src/core/wire/CLAUDE.md`** are the current spec (seam-field semantics, scope contracts,
|
||||||
the product-name convention, the pure/shell split, the WDL finding, the must-verify
|
the channel-mode / ingest / bank-generation / sampling-mode / pitch-engine contracts, the
|
||||||
SDK/bridge surfaces). This doc is the framing/decision record they point back to. The "no
|
UX-overhaul spec, the product-name convention, the pure/shell split, the WDL finding, the
|
||||||
PLAN.md footprint" era is over.
|
must-verify SDK/bridge surfaces). This doc is the framing/decision record they point back
|
||||||
|
to. The "no landed-roadmap footprint" era is over.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# Multi-bank — product notes
|
# Multi-bank — product notes
|
||||||
|
|
||||||
Framing, rationale, and design-direction calls behind the **Multi-bank** phase.
|
Framing, rationale, and design-direction calls behind the **Multi-bank** phase.
|
||||||
The tickable spec lives in `PLAN.md` (Phase B) and the authoritative technical
|
The tickable spec's landed history lives in `docs/ARCHIVE.md` (Phase B) and the
|
||||||
detail in `CONTEXT.md` (§Multi-bank). This doc holds the *why* — the workflow
|
architecture detail in `src/core/model/CLAUDE.md` + `src/shell/bank_ops/CLAUDE.md`
|
||||||
|
(§Multi-bank). This doc holds the *why* — the workflow
|
||||||
narrative, the pool-privilege reasoning, the movement semantics, and the
|
narrative, the pool-privilege reasoning, the movement semantics, and the
|
||||||
design-direction recommendations — so those don't clutter the build docs.
|
design-direction recommendations — so those don't clutter the build docs.
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Provenance — product notes
|
# Provenance — product notes
|
||||||
|
|
||||||
Framing, rationale, and the dual-canvas reconciliation behind the reshaped
|
Framing, rationale, and the dual-canvas reconciliation behind the reshaped
|
||||||
**Milestone 10 (provenance)**. The tickable spec lives in `PLAN.md` (M10); the
|
**Milestone 10 (provenance)**. The tickable spec's landed history is in
|
||||||
authoritative technical detail is `CONTEXT.md` (§Data model, §capture) plus this
|
`docs/ARCHIVE.md` (M10); the architecture detail is in `src/core/model/CLAUDE.md`
|
||||||
note for the reconciliation calls. This doc holds the *why* and the open forks so
|
and `src/shell/capture/CLAUDE.md` plus this note for the reconciliation calls.
|
||||||
they don't clutter the build docs.
|
This doc holds the *why* and the open forks so they don't clutter the build docs.
|
||||||
|
|
||||||
Status: **IMPLEMENTED (2026-07-26).** Settled 2026-07-23; landed 2026-07-26.
|
Status: **IMPLEMENTED (2026-07-26).** Settled 2026-07-23; landed 2026-07-26.
|
||||||
Reshaped from the old "provenance + null-test verify" M10. Two decisions were fixed
|
Reshaped from the old "provenance + null-test verify" M10. Two decisions were fixed
|
||||||
|
|||||||
@@ -2,24 +2,26 @@
|
|||||||
|
|
||||||
Framing, rationale, and open forks behind the two missing removal capabilities:
|
Framing, rationale, and open forks behind the two missing removal capabilities:
|
||||||
**sample-remove** (a sample-level index verb) and **prune** (the file-lifecycle
|
**sample-remove** (a sample-level index verb) and **prune** (the file-lifecycle
|
||||||
path CONTEXT.md keeps forward-referencing but never scoped). The tickable spec
|
path the spec kept forward-referencing but never scoped). The tickable spec's
|
||||||
lives in `PLAN.md` (Phase B point B5 for remove; **Phase R** for prune) and the
|
landed history is in `docs/ARCHIVE.md` (Phase B point B5 for remove; **Phase R**
|
||||||
authoritative technical detail in `CONTEXT.md` (§Sample removal, §Prune — file
|
for prune) and the architecture detail lives in `src/core/model/CLAUDE.md` +
|
||||||
lifecycle). This doc holds the *why* — the workflow, the guardrails, the
|
`src/shell/bank_ops/CLAUDE.md` (§Sample removal) and `src/core/reclaim/CLAUDE.md`
|
||||||
index-vs-file boundary, and the forks that need a Daniel decision.
|
+ `src/shell/persist/CLAUDE.md` (§Prune — file lifecycle). This doc holds the
|
||||||
|
*why* — the workflow, the guardrails, the index-vs-file boundary, and the forks
|
||||||
|
that need a Daniel decision.
|
||||||
|
|
||||||
Status: framed by product-designer (2026-07-23); **all five forks settled by Daniel
|
Status: framed by product-designer (2026-07-23); **all five forks settled by Daniel
|
||||||
(2026-07-24)** — R-A this-bank-primary, R-B batched REAPER undo points
|
(2026-07-24)** — R-A this-bank-primary, R-B batched REAPER undo points
|
||||||
(Phase-B-wide), R-C trash-preferred-with-unlink-fallback, R-D owned-file manifest
|
(Phase-B-wide), R-C trash-preferred-with-unlink-fallback, R-D owned-file manifest
|
||||||
(seam lands early in Phase B / capture), R-E manual action + panel button. The
|
(seam lands early in Phase B / capture), R-E manual action + panel button. The
|
||||||
decisions are folded into the fork sections below and into the B5 / Phase R spec
|
decisions are folded into the fork sections below and into the B5 / Phase R
|
||||||
prose in CONTEXT.md and the tickable points in PLAN.md.
|
history in `docs/ARCHIVE.md` and the architecture docs above.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The one boundary that governs everything: index vs. file
|
## The one boundary that governs everything: index vs. file
|
||||||
|
|
||||||
ReaSampler already draws a hard line, stated repeatedly in CONTEXT.md: **a bank
|
ReaSampler already draws a hard line: **a bank
|
||||||
operation touches the *index*, never the *file*.** Move, copy, evacuate, and
|
operation touches the *index*, never the *file*.** Move, copy, evacuate, and
|
||||||
delete-bank are all index-only; files persist on disk "until prune." Every
|
delete-bank are all index-only; files persist on disk "until prune." Every
|
||||||
removal capability below sits on exactly one side of that line, and keeping the
|
removal capability below sits on exactly one side of that line, and keeping the
|
||||||
@@ -120,7 +122,7 @@ line. It is the verb Phase B forgot, not a new pillar.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Prune — the file-lifecycle path CONTEXT.md kept promising
|
## Prune — the file-lifecycle path the spec kept promising
|
||||||
|
|
||||||
### What the user is doing
|
### What the user is doing
|
||||||
|
|
||||||
@@ -131,7 +133,7 @@ purpose. Over a long project the bank folder accumulates dead `.wav` files that
|
|||||||
cost disk and clutter. **Prune is the reclaim pass**: "sweep the bank folder,
|
cost disk and clutter. **Prune is the reclaim pass**: "sweep the bank folder,
|
||||||
delete the files nothing references, tell me what you reclaimed."
|
delete the files nothing references, tell me what you reclaimed."
|
||||||
|
|
||||||
This is the path CONTEXT.md forward-references in at least four places ("files
|
This is the path the spec forward-references in at least four places ("files
|
||||||
persist on disk until prune," "the capture/prune path reclaims it") but never
|
persist on disk until prune," "the capture/prune path reclaims it") but never
|
||||||
scopes. It is a real, promised capability with **no phase, no module, no point**
|
scopes. It is a real, promised capability with **no phase, no module, no point**
|
||||||
— a dangling reference the plan has to make good on.
|
— a dangling reference the plan has to make good on.
|
||||||
@@ -141,8 +143,8 @@ scopes. It is a real, promised capability with **no phase, no module, no point**
|
|||||||
ReaSampler already shipped this exact shape once. Design View's `view_mode_model`
|
ReaSampler already shipped this exact shape once. Design View's `view_mode_model`
|
||||||
has **`ViewModeModel::reconcile(liveGuids)`** — a pure function fed the live set
|
has **`ViewModeModel::reconcile(liveGuids)`** — a pure function fed the live set
|
||||||
(the tracks that still exist), returning the residual membership entries to drop
|
(the tracks that still exist), returning the residual membership entries to drop
|
||||||
(CONTEXT.md §Design View: "prunes orphaned snapshots on every toggle/load;
|
(`src/core/view/CLAUDE.md`: "tolerates unknown/stale GUIDs (pruned on reconcile
|
||||||
tolerates unknown/stale GUIDs (prune on reconcile)"). Prune is the **file-pool
|
via `ViewModeModel::reconcile(liveGuids)`)"). Prune is the **file-pool
|
||||||
mirror of that pure pattern**:
|
mirror of that pure pattern**:
|
||||||
|
|
||||||
> `reconcile(liveGuids)` reconciles *membership entries* against *live tracks*.
|
> `reconcile(liveGuids)` reconciles *membership entries* against *live tracks*.
|
||||||
@@ -224,7 +226,7 @@ Prune is **not** a Phase B point. Three reasons it earns its own lettered phase
|
|||||||
|
|
||||||
1. **It is a different pillar.** Phase B is the *bank container* pillar
|
1. **It is a different pillar.** Phase B is the *bank container* pillar
|
||||||
(index-only, non-destructive, above the file). Prune is the *file lifecycle*
|
(index-only, non-destructive, above the file). Prune is the *file lifecycle*
|
||||||
pillar (the one path that deletes files). CONTEXT.md already names it as a
|
pillar (the one path that deletes files). The spec already named it as a
|
||||||
separate concern every time it says "the capture/**prune** path" — file
|
separate concern every time it says "the capture/**prune** path" — file
|
||||||
lifecycle is spoken of as its own thing, owned by neither the capture nor the
|
lifecycle is spoken of as its own thing, owned by neither the capture nor the
|
||||||
bank layer. Giving it its own phase matches how the spec already talks about it.
|
bank layer. Giving it its own phase matches how the spec already talks about it.
|
||||||
@@ -337,7 +339,8 @@ capture).** The book tracks the set of files it has created; prune reclaims
|
|||||||
ext-state**, and Phase R's R1/R2 *consume* that manifest. The exact persistence
|
ext-state**, and Phase R's R1/R2 *consume* that manifest. The exact persistence
|
||||||
shape — a sibling ext-state key vs. folded into the `banks` blob — is a small
|
shape — a sibling ext-state key vs. folded into the `banks` blob — is a small
|
||||||
residual to settle at build; the **manifest-now decision is firm**. (Settled
|
residual to settle at build; the **manifest-now decision is firm**. (Settled
|
||||||
2026-07-24; the up-front point is added to Phase B / the capture path in PLAN.md.)
|
2026-07-24; the up-front point is recorded in `docs/ARCHIVE.md` under Phase B /
|
||||||
|
the capture path.)
|
||||||
|
|
||||||
**Fork R-E — prune trigger: manual-only vs. offer-on-orphaning vs. periodic.
|
**Fork R-E — prune trigger: manual-only vs. offer-on-orphaning vs. periodic.
|
||||||
SETTLED: MANUAL ACTION + PANEL BUTTON.** Prune runs via a bindable manual action
|
SETTLED: MANUAL ACTION + PANEL BUTTON.** Prune runs via a bindable manual action
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ concrete and decidable:
|
|||||||
2. **A beta side-channel** — so development can continue and a beta build run
|
2. **A beta side-channel** — so development can continue and a beta build run
|
||||||
*alongside* the stable one without the beta clobbering the release.
|
*alongside* the stable one without the beta clobbering the release.
|
||||||
|
|
||||||
This doc holds the *why*, the forks, and a recommendation. When Daniel picks, the
|
This doc holds the *why*, the forks, and a recommendation. The tickable points'
|
||||||
tickable points land in `PLAN.md` and the deploy/build wiring hands off to dev-ops.
|
landed history is in `docs/ARCHIVE.md`; the deploy/build wiring hands off to dev-ops.
|
||||||
This is a framing note; it changes no source or CMake.
|
This is a framing note; it changes no source or CMake.
|
||||||
|
|
||||||
Status: framed by product-designer (2026-07-26); **all four forks settled by Daniel
|
Status: framed by product-designer (2026-07-26); **all four forks settled by Daniel
|
||||||
@@ -54,7 +54,7 @@ Two sharp edges follow directly and recur throughout this note:
|
|||||||
Both binaries reading/writing the same namespace on the same open project means a
|
Both binaries reading/writing the same namespace on the same open project means a
|
||||||
**beta can read — and rewrite — a stable project's saved bank/view state.** Given
|
**beta can read — and rewrite — a stable project's saved bank/view state.** Given
|
||||||
the forward-only migrations already in the design (legacy `bank_index` retired
|
the forward-only migrations already in the design (legacy `bank_index` retired
|
||||||
after promotion; `banks` authoritative thereafter — CONTEXT.md §Multi-bank), a
|
after promotion; `banks` authoritative thereafter — `docs/ARCHIVE.md` §Phase B), a
|
||||||
beta that writes a newer schema into a project a user then reopens in stable is a
|
beta that writes a newer schema into a project a user then reopens in stable is a
|
||||||
real corruption path, not a theoretical one.
|
real corruption path, not a theoretical one.
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,12 @@ written after Daniel DAW-tested the S1–S6 instrument:
|
|||||||
revised 2026-07-26 (Daniel)** from the single electric-cyan accent to a **three-accent
|
revised 2026-07-26 (Daniel)** from the single electric-cyan accent to a **three-accent
|
||||||
pastel system** (primary/secondary/tertiary = pastel lime green / pastel teal / pastel
|
pastel system** (primary/secondary/tertiary = pastel lime green / pastel teal / pastel
|
||||||
purple) — see §6. (A stylish-font upgrade was considered and **declined** by Daniel — no
|
purple) — see §6. (A stylish-font upgrade was considered and **declined** by Daniel — no
|
||||||
font bundling/redistribution; the kit keeps its current cached-font face.) The build points it feeds live in
|
font bundling/redistribution; the kit keeps its current cached-font face.) The build points it
|
||||||
**PLAN.md §Phase L** (**L1** the shared LICE drawing kit, **L2** the dock-panel layout
|
fed have landed; their history is in **`docs/ARCHIVE.md`** ("Phase L — Look-and-feel", **L1** the
|
||||||
redesign, **L3** the VST editor + embed-strip restyle) and **CONTEXT.md §Phase L** (the
|
shared LICE drawing kit, **L2** the dock-panel layout redesign, **L3** the VST editor +
|
||||||
design-system spec). Toolkit facts below are **verified against the vendored `vendor/WDL`
|
embed-strip restyle) and the architecture now lives in **`src/core/ui/CLAUDE.md`** /
|
||||||
tree**, not lore.
|
**`src/shell/panel/CLAUDE.md`** (the design-system implementation). Toolkit facts below are
|
||||||
|
**verified against the vendored `vendor/WDL` tree**, not lore.
|
||||||
|
|
||||||
> **Phase-boundary note (2026-07-26).** This look-and-feel effort was originally drafted
|
> **Phase-boundary note (2026-07-26).** This look-and-feel effort was originally drafted
|
||||||
> as Phase S points **S0-DS** (the shared kit) and **S14** (the panel refresh). It was
|
> as Phase S points **S0-DS** (the shared kit) and **S14** (the panel refresh). It was
|
||||||
@@ -497,7 +498,7 @@ within the pastel intent — is one file.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. The build shape (feeds PLAN.md / CONTEXT.md §Phase L)
|
## 5. The build shape (landed history in `docs/ARCHIVE.md` §Phase L)
|
||||||
|
|
||||||
Three points and their sequencing.
|
Three points and their sequencing.
|
||||||
|
|
||||||
@@ -547,7 +548,7 @@ draw it.
|
|||||||
affordances, per-selection **move / copy / remove** sample menu.
|
affordances, per-selection **move / copy / remove** sample menu.
|
||||||
- **Prune** button (R-E) — the byte-deleting action, `warn`-colored.
|
- **Prune** button (R-E) — the byte-deleting action, `warn`-colored.
|
||||||
|
|
||||||
*M11 adds (dev PLAN.md M11 — merging now):*
|
*M11 adds (landed — `docs/ARCHIVE.md` Milestone 11):*
|
||||||
- **Action trigger buttons** — clickable buttons that fire the capture + provenance
|
- **Action trigger buttons** — clickable buttons that fire the capture + provenance
|
||||||
action family directly (capture item / capture track scopes, re-capture from source,
|
action family directly (capture item / capture track scopes, re-capture from source,
|
||||||
resample-and-mute-source, batch capture, conform-on-insert, insert-at-cursor, drag-out,
|
resample-and-mute-source, batch capture, conform-on-insert, insert-at-cursor, drag-out,
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# src/app — REAPER extension entry point
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Contains only `main.cpp`. Since the Phase Q hoists (Q-W3 onward), this TU is ONLY
|
||||||
|
pointers + entry + dispatch — the actual capture/panel/persist/action orchestration
|
||||||
|
lives in `shell/`. `main.cpp` owns: receiving REAPER's dispatch struct
|
||||||
|
(`ReaperPluginEntry`), resolving the REAPER API function pointers
|
||||||
|
(`REAPERAPI_LoadAPI`), the globals other files reference via `extern` (`g_hInst`,
|
||||||
|
`g_rec`), the `ReaSamplerSession` instance, its own bindable-action family via the
|
||||||
|
Q-W6 data-driven registration table (`shell/actions/action_registry`), and invoking
|
||||||
|
the other action families' (`design_view` / `bank` / `ingest`) own
|
||||||
|
register/handle/unregister triples at load and unload.
|
||||||
|
|
||||||
|
Exactly **one** translation unit defines `REAPERAPI_IMPLEMENT` — that is `main.cpp`.
|
||||||
|
Every other `.cpp` includes `reaper_plugin_functions.h` without the define and gets
|
||||||
|
`extern` declarations for the global API function pointers.
|
||||||
|
|
||||||
|
See root `CLAUDE.md`'s "REAPER extension contract" section for the full four-step
|
||||||
|
action-registration contract (`command_id` / `gaccel` / `hookcommand` / unload
|
||||||
|
mirror-unregister) that both this file's own action-table rows and the other
|
||||||
|
families' register/handle/unregister triples follow.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `main.cpp` — the REAPER extension's entry point and the sole `REAPERAPI_IMPLEMENT` TU; see root `CLAUDE.md`'s "REAPER extension contract" section for the registration contract this file implements.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- This is intentionally a thin TU post-Phase-Q. Adding a new bindable action to
|
||||||
|
`main.cpp`'s own family means adding one row to its `ActionTableRow` table and a
|
||||||
|
flat handler function — do not hand-roll a parallel register/hookcommand/unregister
|
||||||
|
mechanism alongside the table.
|
||||||
|
- Never let a second `.cpp` define `REAPERAPI_IMPLEMENT` — that would double-allocate
|
||||||
|
the global REAPER API function pointers.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# src/core/audio — pure audio-data math
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Pure, REAPER-free audio-data math with no dependence on REAPER's own peak-cache
|
||||||
|
API. Currently one module: waveform min/max bin computation from raw PCM. Does
|
||||||
|
**not** include: LICE waveform drawing (`draw_kit`, `shell/panel`), the editor's
|
||||||
|
waveform/marker geometry (`waveform_view`, `core/instrument/ui`), or PCM
|
||||||
|
decoding itself.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `peaks` — waveform min/max bin computation from raw PCM; does not depend on REAPER's peak API.
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# src/core/capture — pure logic behind the capture pillar
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Pure, REAPER-free logic behind the capture pillar: path arithmetic, the RIFF/WAV
|
||||||
|
codec, render-settings/FX-scope/tail-mode mapping, `InsertMedia` mode-bit
|
||||||
|
computation, the realtime-record state machine, and batch-capture planning.
|
||||||
|
Does **not** include: the REAPER-bound capture backends themselves
|
||||||
|
(`shell/capture`), the docked panel's tail-toggle window/click-handling
|
||||||
|
(`shell/panel`), or the `InsertMedia` call/undo-block mechanics
|
||||||
|
(`shell/capture`'s `insert.cpp`).
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
The repo-wide precision invariants (null test, bit-identical repeats,
|
||||||
|
non-destructive, exact bounds, relative-paths-only, capture FX scope) are
|
||||||
|
authoritative in root `CLAUDE.md` — reference them, don't re-copy them.
|
||||||
|
Detail specific to these pure modules:
|
||||||
|
|
||||||
|
- **No silent time-stretch, made checkable.** `insert_plan` never sets the &4
|
||||||
|
("stretch/loop to fit time sel") bit; `kStretchToTimeSelBit` is exposed
|
||||||
|
precisely so a test can assert it is never present in any computed
|
||||||
|
`InsertMedia` mode.
|
||||||
|
- **Tail is a three-state mode (`docs/product/capture-tail.md`), not a
|
||||||
|
per-action variant:** None (exact bounds, byte-identical, the only mode for
|
||||||
|
null-test/verify captures), Auto (generous 8 s tail then trim trailing
|
||||||
|
silence to -72 dB surgical normalize), Manual (fixed length, clamped to the 8
|
||||||
|
s cap, no trim). `render_settings` owns the offline RENDER_* mapping;
|
||||||
|
`tail_control` owns the panel-facing toggle/cycle/clamp/label logic sharing
|
||||||
|
the same `TailMode` enum and the same 8 s / -72 dB constants (single source
|
||||||
|
of truth — do not hardcode a second copy in either module).
|
||||||
|
- **Capture FX scope is enforced via FX-bypass + gain-neutralize, not a render
|
||||||
|
bit.** `render_settings::fxBypassPlanFor` selects which tracks (self /
|
||||||
|
ancestors / master) get their FX bypassed for a given `CaptureScope`; there
|
||||||
|
is no master capture scope (to capture the master, render a track instead).
|
||||||
|
- **Relative paths only, by construction.** `capture_paths::BankPaths`
|
||||||
|
separates the absolute render directory REAPER needs from the
|
||||||
|
project-relative path the `BankIndex` stores; `bankRelativeForName` spells an
|
||||||
|
enumerated folder entry the identical way `deriveBankPaths` spelled it at
|
||||||
|
capture time, so the prune core's exact-string match cannot drift.
|
||||||
|
- **Project-identity transition is GUID-primary.** `capture_paths`'s
|
||||||
|
`classifyProjectTransition` checks the minted GUID before the live
|
||||||
|
`ReaProject*` object, specifically because REAPER can recycle a closed
|
||||||
|
project's pointer address onto an unrelated project.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `wav_codec` — chunk walker + layout parse + float32 build + size-field patch + content hashes; the single pure RIFF/WAV owner (`wav_trim` is retired; `wav_codec` is the sole owner).
|
||||||
|
- `capture_realtime` (`core/capture`, **renamed from `realtime_record` in Q-W3** — the Q-9 naming rider: pure module takes the stem, the shell takes the suffix, matching `drag_out`/`drag_out_win`) — the M8 realtime-record pure logic: capture scope + FX-tap point → `I_RECMODE`/`I_RECMODE_FLAGS` values, wet/dry → tap point, the recorded-file → `Sample` mapping, and the async record-phase state machine. Depends on `bank_model` for the plain `Sample`/`SourceMode` types. The transport/temp-track/send recipe lives in the shell (`shell/capture/capture_realtime_shell.cpp` + `capture_realtime_finalize.cpp`).
|
||||||
|
- `batch_capture` — pure batch-capture planner: maps source ranges to capture units and aggregates results.
|
||||||
|
- `capture_paths` — the REAPER-free path arithmetic behind offline capture: bank-subfolder + unique-filename derivation (`deriveBankPaths`, forward-slash form, no filesystem touch), the absolute-render-dir vs. project-relative-index-path split (`BankPaths`), the persist-side inverse (`resolveBankFile`, `projectDirOfRpp`), the Save-As bank-relocation plan (`deriveRelocationPlan`), and the GUID-primary project-identity classifier (`classifyProjectTransition` → `NoOp`/`Load`/`SaveAsRelocate`) the persist-poll timer drives.
|
||||||
|
- `insert_plan` — the REAPER-free logic behind the `insert` shell (M6): computes the `InsertMedia` `mode` bitmask from an `InsertOptions` struct (placement target, tempo-conform ratio, preserve-pitch flag), guaranteeing the &4 stretch-to-time-selection bit is never set and that no tempo bits are set when `conform == None`.
|
||||||
|
- `render_settings` — the REAPER-free logic behind the capture action family: `SourceMode` → `RENDER_SETTINGS` bit mapping, `P_RAZOREDITS` string parsing + range-union bounds, razor-else-time range inference, the FX-scope bypass plan (`fxBypassPlanFor`), the tail-mode → `RENDER_TAILFLAG`/`RENDER_NORMALIZE`/`RENDER_TRIMEND` mapping (`tailRenderSettingsFor`) and its realtime-window analog (`realtimeRecordWindowEnd`), and the capture-action taxonomy table (`captureActionTable`) `main.cpp` iterates to register the CAPTURE_ITEM/CAPTURE_TRACK family.
|
||||||
|
- `tail_control` — the REAPER-free logic behind the docked `bank_panel`'s tail-mode toggle: the cycle order (None → Auto → Manual → None), the Manual-length clamp/scroll-wheel fine-adjust (`clampManualMs`/`adjustManualMs`, 250 ms/notch, 2000 ms default), the toggle's label text (e.g. "Tail: Manual 2.0s"), and the `TailSetting` JSON round-trip persist stores per-project.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- `render_settings`'s `RENDER_SETTINGS`/`RENDER_NORMALIZE`/`RENDER_TAILFLAG`/
|
||||||
|
`RENDER_TRIMEND` bit values are transcribed verbatim from the SDK header
|
||||||
|
(`reaper_plugin_functions.h` lines ~3041/~3047/~3051/~3062) — re-verify
|
||||||
|
against the header before changing any bit value, per the root `CLAUDE.md`
|
||||||
|
API-verification rule.
|
||||||
|
- `kRenderPreFaderStems` (&8192) is deliberately **not** used — REAPER offline
|
||||||
|
render has no true pre-FX "dry" bit; FX scoping is done entirely by the
|
||||||
|
FX-bypass-around-render mechanism, never by a render bit.
|
||||||
|
- `tail_control`'s `kDefaultManualTailMs`/`kManualStepMs` and
|
||||||
|
`render_settings`'s `kMaxTailMs`/`kAutoTrimThresholdDb` are separate constants
|
||||||
|
in separate files by design (panel-facing default/step vs. runaway-guard cap)
|
||||||
|
— don't conflate them when touching either.
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
# src/core/instrument — pure VST3-instrument core (engine / map / ui)
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The ReaSampler 9000 instrument's pure, REAPER-free, VST3-free, unit-tested core, in three
|
||||||
|
subdirectories:
|
||||||
|
|
||||||
|
- **`engine/`** — the polyphonic voice engine, per-zone play params, pitch shifting,
|
||||||
|
velocity curve, and master-gain taper math.
|
||||||
|
- **`map/`** — the zone/keymap payload, the cross-artifact `ComponentState` codec, and the
|
||||||
|
small pure helpers the engine/shell share (bank-generation sync, bridge-read
|
||||||
|
marshalling, note-name parsing, Trigger frame↔fraction conversion).
|
||||||
|
- **`ui/`** — pure editor geometry/hit-test modules (layout, waveform, keyboard strip,
|
||||||
|
capture browser, param controls, envelope overlay/edit). These are geometry-and-math
|
||||||
|
only; the LICE draw + REAPER/VST3 plumbing is the `shell/instrument` editor shell,
|
||||||
|
**out of scope for this file** (owned by a parallel dispatch), along with the VST3
|
||||||
|
processor, `reaper_bridge`, `reasampler_embed`, and `vst_entry`.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
### The three locked decisions this spec assumes (settled 2026-07-26)
|
||||||
|
|
||||||
|
- **D1 — native VST3.** Not JSFX. Full sampler sophistication, clean integration, and
|
||||||
|
access to the REAPER VST-host bridge.
|
||||||
|
- **D5 — Windows-only, VST3-only, REAPER-only.** No cross-platform DSP/build/signing
|
||||||
|
matrix, no multi-format wrapper, no standalone-in-other-hosts concern.
|
||||||
|
- **D6 — two products, tightly integrated.** A separate artifact, but not a divorced
|
||||||
|
file-only companion: via the VST-host bridge it reads the live `"reasampler"` project
|
||||||
|
ext-state and is project-aware. (The bridge mechanism itself is documented in
|
||||||
|
`src/core/wire/CLAUDE.md`.)
|
||||||
|
|
||||||
|
### The two seams (audio via files, mapping via live state)
|
||||||
|
|
||||||
|
- **File seam (audio, permanent).** The sample **audio** is the on-disk 32-bit-float
|
||||||
|
WAVs — project-relative, travelling with the `.rpp`. The instrument resolves those
|
||||||
|
paths the same way `persist` does (a shared convention, not a re-implementation).
|
||||||
|
There is no live PCM stream across the bridge, by design.
|
||||||
|
- **Live-state seam (the mapping, via the bridge).** For everything that is not raw
|
||||||
|
audio — the bank index, the mapping, which project is active — the instrument reads
|
||||||
|
the live `"reasampler"` ext-state via the bridge.
|
||||||
|
|
||||||
|
### The seam fields — what becomes a bank intrinsic (D-B, settled 2026-07-26)
|
||||||
|
|
||||||
|
The split model is the settled answer, mirroring the capture/placement separation:
|
||||||
|
|
||||||
|
- **Bank intrinsics (facts about the captured file) live on `Sample`.** Root note (the
|
||||||
|
MIDI note the sample was recorded at) and loop points (sustain-loop start/end for held
|
||||||
|
notes) are facts about the file, added as an additive field extension (same shape as
|
||||||
|
`provenance`).
|
||||||
|
- **The performance map (a creative arrangement) lives in the instrument.** Key zones,
|
||||||
|
velocity layers, round-robin groups, amplitude envelopes, and per-sample tuning/gain
|
||||||
|
trim are a performance choice, not a fact about a file — they belong to the instrument,
|
||||||
|
not the bank. This "who owns which field" rule (D-B) governs every performance-map
|
||||||
|
field added since, including play mode/AHDSR/Trigger params (S15), pitch engine mode
|
||||||
|
and pitch envelope (S16), key-tracking, preview velocity, and the velocity curve
|
||||||
|
(S-VIEW) — all are per-instance/per-zone `ComponentState`, never written to `Sample` or
|
||||||
|
the bank.
|
||||||
|
|
||||||
|
### The pure core (D3 — the load-bearing split)
|
||||||
|
|
||||||
|
The sampler's voice engine, envelope math, key/velocity mapping, repitch/interpolation,
|
||||||
|
and keymap resolution are a pure, REAPER-free, DAW-free, unit-tested module — the mirror
|
||||||
|
of `bank_model`/`peaks`/`view_mode_model`/`bank_book`. The VST3 wrapper (the
|
||||||
|
`SingleComponentEffect` subclass, bus setup, `process` marshalling, the `IPlugView` LICE
|
||||||
|
editor, and the bridge calls) is the thin shell — the only part that touches VST3 or
|
||||||
|
REAPER at all. Any VST3 or REAPER type leaking into this core is a bug.
|
||||||
|
|
||||||
|
- **The bank is one source; the instrument is another view of it (never a fork).** The
|
||||||
|
instrument is a pure consumer of the bank — it does not copy samples, does not own a
|
||||||
|
private sample store, and does not mutate the bank.
|
||||||
|
- **`Sample` field additions are additive and lossless.** No existing `Sample` field
|
||||||
|
changes; no `BankIndex` behavior changes.
|
||||||
|
- **Relative-paths-only survives.** The instrument resolves audio via the project-relative
|
||||||
|
machinery; it introduces no absolute paths.
|
||||||
|
|
||||||
|
### Channel mode — current reality
|
||||||
|
|
||||||
|
**Current reality (root `CLAUDE.md`, GA post-launch pass): the output bus is
|
||||||
|
permanently stereo.** `ChannelMode` is decode-only; the dynamic mono↔stereo bus
|
||||||
|
renegotiation (`setBusArrangements` per-instance toggle) has been deleted. Channel mode
|
||||||
|
auto-defaults from the loaded capture's channel count via a pure `channelModeFor` helper,
|
||||||
|
gated by a persisted `channelModeExplicit` flag (`ComponentState` v9). Mono source +
|
||||||
|
stereo mode → dual-mono (same signal both channels, centered); stereo source + mono mode
|
||||||
|
→ downmix (existing decode-side policy).
|
||||||
|
|
||||||
|
> **Superseded design, do not reintroduce:** an earlier "Channel mode — mono |
|
||||||
|
> stereo (D-E)" design specified a per-instance toggle that **dynamically
|
||||||
|
> renegotiates the REAPER audio bus** via `setBusArrangements`/`getBusArrangement`
|
||||||
|
> (the instrument reporting mono or stereo per instance and REAPER's routing
|
||||||
|
> following). That dynamic-bus-negotiation design was superseded by the GA fix
|
||||||
|
> above; root `CLAUDE.md` is current and wins.
|
||||||
|
|
||||||
|
### Sampling modes — Gate vs Trigger, pitch engine, pitch envelope (S15/S16 — settled, landed)
|
||||||
|
|
||||||
|
Daniel's directive (2026-07-26, verbatim): *"Sampling mode: Trigger vs Gate. Gate has an
|
||||||
|
AHDSR envelope. Trigger has fade in, % length, and fade out. Both modes have modifiable
|
||||||
|
start point, Gate has modifiable loop points too. In addition to amp env, there will be a
|
||||||
|
pitch envelope/curve (AD?) which is off by default."*
|
||||||
|
|
||||||
|
- **Gate — classic held note.** Note-on enters the amp envelope; note-off enters
|
||||||
|
release; a sustain loop applies for held notes. Envelope is **AHDSR**: `0→1` over
|
||||||
|
attack, hold at 1 over `holdFrames`, `1→sustain` over decay, hold sustain until
|
||||||
|
note-off, `level→0` over release. `holdFrames == 0` is exactly the pre-Gate ADSR — a
|
||||||
|
back-compat degenerate.
|
||||||
|
- **Trigger — one-shot drum-pad.** Note-on fires playback of a defined `%` of sample
|
||||||
|
length with a fade-in and fade-out ramp; note-off is ignored (the voice plays through,
|
||||||
|
no sustain loop). Frame span `[startFrame, playEnd)` where `playEnd = startFrame +
|
||||||
|
round(lengthFraction·(frames − startFrame))`; amplitude ramps `0→1` over
|
||||||
|
`fadeInFrames` at the head and `1→0` over `fadeOutFrames` anchored to `playEnd`; fades
|
||||||
|
clamp so `fadeInFrames + fadeOutFrames ≤ play length`. Fade curve is equal-power
|
||||||
|
(constant-power sin/cos). **Note-off in Trigger is a no-op** — choke-on-note-off is
|
||||||
|
held/out of scope (fork S15-F1).
|
||||||
|
- **Both modes: modifiable start point.** Playback begins at `startFrame` (clamped `0 ≤
|
||||||
|
startFrame < frames`). Gate additionally has modifiable loop points; Trigger has none.
|
||||||
|
- **Pitch engine — Varispeed vs Preserve (per-zone toggle, S16).** Varispeed (current/
|
||||||
|
classic path): `ratio_ = pitchRatio(note,root)`, `readPos_ += ratio_` with linear
|
||||||
|
interp — resampling that couples pitch and duration; cheap, zero-latency, musically
|
||||||
|
right for drums/one-shots. Preserve (duration-preserving): the read advances at the
|
||||||
|
source rate while a pitch shifter transposes the output — musically right for
|
||||||
|
tempo-locked loops/phrases; **the engine default leans Preserve** (fork S16-F1).
|
||||||
|
Contract for Gate's sustain loop under Preserve: *loop the source, shift the output*
|
||||||
|
(loop points stay source-frame facts). `WDL_Resampler` is **not** a Preserve engine (it
|
||||||
|
is a resampler that couples duration) — never wire it as the duration-preserving path.
|
||||||
|
- **Pitch envelope — AD, off by default.** A short attack-decay pitch-offset curve
|
||||||
|
(`peakSemitones` over `attackFrames`, decaying to 0 over `decayFrames`) riding on top of
|
||||||
|
whichever pitch engine; a zero attack gives a pure percussive pitch drop. **Off by
|
||||||
|
default** — a regression that applies pitch modulation when the envelope is disabled is
|
||||||
|
a bug. Under Varispeed the offset is a per-frame multiply of `ratio_`; under Preserve it
|
||||||
|
is added to the shifter's shift amount.
|
||||||
|
- **Preserve RT discipline.** The shifter pre-warms at voice-allocation; no allocation in
|
||||||
|
`process()` in steady state. **Note (supersedes an earlier framing):** the
|
||||||
|
shifter's onset latency (~25 ms, half-window) was once described as "an
|
||||||
|
accepted property, not a defect." Root `CLAUDE.md`'s GA2 pass **eliminated** that onset
|
||||||
|
latency (ring buffer primed with the actual upcoming source at note-on instead of
|
||||||
|
zero-filled, so Preserve now speaks on frame 0, matching Varispeed) — a
|
||||||
|
cold-started/un-pre-warmed shifter producing a click or smear remains a bug.
|
||||||
|
- **S15/S16 stay channel-count-agnostic.** The mode/envelope logic is per-frame amplitude
|
||||||
|
and read-rate, independent of the stereo channel dimension — any S15/S16 code that
|
||||||
|
assumes a fixed (mono) channel count rather than operating per-frame pre-mix is a bug.
|
||||||
|
- **S15/S16 are Tier 0–1 engine features, not Tier 2/3** — do not let the held Tier-2
|
||||||
|
feature list (velocity layers / round-robin / filter work) drive their build shape.
|
||||||
|
|
||||||
|
### Non-goals / guardrails (instrument-specific; repo-wide invariants live in root CLAUDE.md)
|
||||||
|
|
||||||
|
- **No cross-platform / multi-format.** Windows-only, VST3-only, REAPER-only (D5). Do not
|
||||||
|
add an AU/AAX/VST2/CLAP wrapper, a mac/Linux build, or a standalone host target.
|
||||||
|
- **The pure core stays REAPER-free *and* VST3-free.** Any VST3 or REAPER type leaking
|
||||||
|
into the voice engine / envelope / keymap / repitch module is a bug (the D3 split).
|
||||||
|
- **Channel mode is a performance choice, not a bank fact.** Never written to `Sample` or
|
||||||
|
the bank.
|
||||||
|
- **Do not spec Tier 2/3** from this directory. Tier 2 is held, Tier 3 is
|
||||||
|
optional-forever; don't let their feature lists drive Tier 0–1's build shape.
|
||||||
|
|
||||||
|
### Envelope overlay + draggable nodes (S-VIEW, settled 2026-07-27, landed)
|
||||||
|
|
||||||
|
The amp envelope is drawn as a curve over the Sample view's hero waveform at the shared
|
||||||
|
time base — Gate → the AHDSR shape, Trigger → the fade-in/unity/%-length/fade-out shape
|
||||||
|
anchored to `playEnd`. **The overlay is directly editable — draggable nodes
|
||||||
|
(SETTLED, S-VIEW-F2).** Dragging a node and the existing sliders are two surfaces onto
|
||||||
|
one model: both read/write the same zone envelope fields, so a drag updates the params,
|
||||||
|
the sliders reflect them live, and a slider edit re-lays the nodes — one source of truth,
|
||||||
|
structural (re-read-every-paint), not a listener chain. Nodes are monotonic in time (a
|
||||||
|
node cannot be dragged past its neighbours) and range-clamped to the same per-param
|
||||||
|
min/max the sliders enforce, so node-drag can never produce a param the slider couldn't.
|
||||||
|
Two pure modules split the forward (draw) and inverse (edit) maps — see `envelope_overlay`
|
||||||
|
and `envelope_edit` in Modules below.
|
||||||
|
|
||||||
|
### New performance-map parameters — ownership and persistence (D-B)
|
||||||
|
|
||||||
|
- **Key-tracking** — per-zone, additive/version-bumped component state, default 100%
|
||||||
|
(absent field on an older blob lifts to 100%, bit-identical playback).
|
||||||
|
- **Preview velocity** — a per-instance utility setting for the Sample view's
|
||||||
|
preview-trigger button (not a musical parameter of the capture); **persists across
|
||||||
|
reloads** via the instrument's own `ComponentState` (envelope-bumped), never via the
|
||||||
|
extension's `persist` ext-state module (that would make it project-global rather than
|
||||||
|
per-instance and leak an instrument concern into the extension's key space).
|
||||||
|
- **Velocity curve** — per-zone; the one non-back-compat surface in S-VIEW: an
|
||||||
|
already-saved zone with no stored curve now plays every velocity at unity under the
|
||||||
|
flat-default (Option A), not bit-identical to the old linear `velocity/127` mapping —
|
||||||
|
a deliberate, Daniel-approved behavior change (see `velocity_curve` in Modules).
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
### `engine/`
|
||||||
|
|
||||||
|
- `sampler_core` — polyphonic voice engine with bounded stealing, user-parameterized voice count (1–32, default 16), `VoiceMode` Poly/Mono (last-note held-note stack, `MonoTrigger` Retrigger/Legato toggle), two-tier panic (CC 123 = all-notes-off release, CC 120 = immediate hard-stop including Trigger one-shots); per-zone `ZonePlayParams` (Gate/Trigger, AHDSR, pitch engine Varispeed/Preserve, AD pitch mod envelope), repitch/interpolation with loop-point-aware sustain. Preview injects a synthetic note-on at the loaded capture's root note into the main `VoiceEngine` — no dedicated `PreviewCard`; preview obeys polyphony/mono/voice-stealing/envelopes.
|
||||||
|
- `zone_params.h` (`core/instrument/engine`) is the sibling header split out of `sampler_core.h` (T4-14/T4-17): the per-zone play-parameter value structs (`ZonePlayParams`/`AdsrParams`/`TriggerParams`/`PitchEnvParams`) and the per-instance mode enums (`ChannelMode`/`VoiceMode`/`MonoTrigger`) the engine, the codec, and the editor all share.
|
||||||
|
- `pitch_shift` — hand-rolled **correlation-aligned SOLA** (splice-overlap-add) pitch shifter for the Preserve playback mode: one active read tap chases the write head at the shift ratio; each splice jump is refined by a cross-correlation search so the new read point is waveform-aligned, then old and new taps are crossfaded (raised-cosine, amplitude-complementary). Replaces the prior dual-tap OLA whose fixed half-window tap offset caused anti-phase cancellation on many source frequencies. **GA2:** ring buffer **primed with the actual upcoming source** at note-on (was zero-filled) → gap-free frame-0 onset, ~25 ms Preserve onset latency eliminated (Preserve now speaks on frame 0, matching Varispeed), and real-content-bounded tail (last-window tail-truncation gone). No third-party dependencies; RT-discipline: no allocation in `process()`.
|
||||||
|
- `velocity_curve` — pure velocity→amp transfer curve: `VelocityCurve` evaluated by a Fritsch–Carlson monotone cubic Hermite spline (no overshoot outside [0,1]). `eval(velocity)` called once per note-on. `flat()` default (y=1, every velocity→unity) replaces the prior fixed `velocity/127` path — a deliberate non-back-compat behavior change (Daniel-approved).
|
||||||
|
- `master_gain` — pure dB↔linear taper math (FB1): normalized [0,1] ↔ dB ↔ linear for the post-mixer master gain control (−∞…+24 dB, norm 0 = true silence, unity ≈ 0.714). Shared by the editor knob and the processor multiply so the needle, persisted value, and audio multiply cannot drift.
|
||||||
|
|
||||||
|
### `map/`
|
||||||
|
|
||||||
|
- `sample_map` — zone payload: zones keyed by note range. **Wall-clock times stored as rate-free SECONDS, resolved against the live project rate — NO hardcoded sample rates in `src/`** (Daniel's standing ruling, load-bearing). JSON round-trip.
|
||||||
|
- `component_state_io` (`core/instrument/map`) — the `ComponentState` envelope + zones-payload binary codec (envelope v1…v11, zones-payload v1…v7), split out of `sample_map` (Q-W2v, T4-13 ≡ T2-07) so BOTH artifacts can link the codec without the extension pulling in the whole voice engine (`sampler_core`/`pitch_shift`) to serialize one preset blob — the extension's `instrument_drop` and the instrument's processor read/write the identical bytes, so the cross-artifact contract cannot drift.
|
||||||
|
- `bank_sync` — generation change-detection + assignment-request consume: owns the yes/no decision logic so the rules are provable without a host. The processor shell owns cadence and side effects.
|
||||||
|
- `bridge_marshal` — pure marshalling helper for the REAPER VST-host bridge read: interprets the `GetProjExtState` int return against its filled buffer.
|
||||||
|
- `note_entry` — parses a raw string into a clamped MIDI note [0,127]; accepts plain decimal integers or note names (C4==60, DAW convention).
|
||||||
|
- `trigger_seam` — pure Trigger frames↔fraction converter: owns the shared formula for converting between engine source-frame fade counts and the overlay's fractional representation, threading `startFrame` correctly through pack and unpack directions.
|
||||||
|
|
||||||
|
### `ui/`
|
||||||
|
|
||||||
|
- `editor_geometry` (`core/instrument/ui`) — VST3 editor layout: aliases the shared `core::ui::Rect` (+ `contains()`) rather than defining its own; owns `EditorLayout`/`layoutEditor(w,h)`, the Tier-0/Tier-1 sample-list and keymap-editor row layout/hit-test, and — hoisted here off the former `reasampler_editor.cpp` god-TU (Q-W2v, T2-06) — the r11 Sample-face band layout (`SampleBands`/`ClusterRects`/`channelToggleRects`) and the Zone-face content/legend/deck layout, so the editor shell only draws + routes.
|
||||||
|
- `keyboard_strip` — piano-keyboard strip: MIDI-note→key rect mapping, black/white key layout, hit-test, zone highlight overlay geometry.
|
||||||
|
- `waveform_view` — waveform/marker geometry: maps frame span linearly across a rect; generic named draggable markers with drag-delta resolver, clamp, and zero-crossing snap.
|
||||||
|
- `capture_browser` — capture browser: card-grid layout + bank-filter tab strip geometry and hit-test; knows only counts and rects, draws nothing.
|
||||||
|
- `browser_scroll` — scroll + type-to-filter layered over `capture_browser`: vertical scroll offset, scrollbar thumb, thumb-drag mapping, and name-substring search.
|
||||||
|
- `param_slider` — parameter control-panel: vertical stack of TOGGLE (two-segment selector) and SLIDER (horizontal track) rows; maps normalized value to/from handle pixel.
|
||||||
|
- `embed_strip` — compact single-row control layout for embed mode in the track FX chain.
|
||||||
|
- `knob_deck` — pure knob-deck layout + hit-test (FB1): group-box / caption-row / compact-toggle / knob-cell geometry, deterministic whole-group wrap, `DeckLayout` / `DeckHit`. Mirror of `action_bar`/`param_slider`; no LICE or REAPER types.
|
||||||
|
- `curve_popup` — pure curve-popup geometry + dismissal test (FB1): centered sheet over the Sample face — width/height clamps, title row, Close button rect, curve-box rect, outside-sheet dismissal test. Mirror of `overflow_menu`; no LICE or REAPER types.
|
||||||
|
- `envelope_overlay` — pure amp-envelope→polyline geometry for the Sample-view envelope overlay (read from `envelope_overlay.h`): maps Gate's AHDSR shape or Trigger's fade-in/unity/%-length/fade-out shape to a polyline inside a rect at the shared time base (Gate: a bounded param-domain schematic, sample-length-free; Trigger: PCM-aligned wall-clock), every vertex clamped in-canvas (`x`/`y` inside the rect). Shares the `EnvNode`/`AmpEnvelope`/`timeToX`/`levelToY` vocabulary with `envelope_edit` so the drawn handle and its grab region agree pixel-for-pixel. No VST3/REAPER/LICE types at the boundary.
|
||||||
|
- `envelope_edit` — pure node hit-test + pixel-delta→clamped-param inverse map for the draggable envelope nodes (read from `envelope_edit.h`): `nodeAtPoint` resolves a grab to the nearest node within a pick radius (Chebyshev distance, draw-order tie-break); `resolveNodeDrag` maps a pixel delta since grab to a new `AmpEnvelope`, enforcing monotonic-in-time ordering between neighbouring nodes and the same caller-supplied per-param clamp bounds the sliders use — a drag can never produce a param a slider couldn't. Mirror of `card_drag`/`waveform_view`; the inverse of `envelope_overlay`'s params→polyline forward map, so node-drag and slider-edit read/write one shared model and can never diverge.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- **Gate's envelope-overlay x-axis is schematic, not PCM-aligned** (per `envelope_overlay.h`'s FA2 contract note) — it does NOT line up with the waveform under it; only Trigger's x-axis is wall-clock/PCM-aligned. Don't assume the Gate curve is time-accurate against the sample.
|
||||||
|
- **Trigger's fade fields require a non-trivial converter, not a field copy.** `TriggerParams` (engine) stores fades as source *frames*; `AmpEnvelope` (the overlay's view struct) stores them as *fractions* of the played span. A converter is owed on both the pack (draw) and unpack (commit) directions — `trigger_seam` owns this formula; do not copy the fields directly.
|
||||||
|
- **`param_slider`'s linear slider rows are retired on the Zone panel** — per root `CLAUDE.md`'s FB2 note, the `Knob` primitive (`editor_geometry`/knob deck grammar) is now the only live consumer of that half of `param_slider` on the Zone face. Don't assume `param_slider`'s SLIDER row type is still drawn there.
|
||||||
|
- **Two superseded designs are called out in Invariants above**: the earlier
|
||||||
|
Channel-mode (D-E) bus-renegotiation design and the earlier Preserve-onset-latency
|
||||||
|
framing in the S16 guardrails. Root `CLAUDE.md` is the current source of truth
|
||||||
|
for both — do not reintroduce either superseded design.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# src/core/json — the hand-rolled JSON lexical layer
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The ONE hand-rolled JSON lexical layer used across the pure core: string/number/
|
||||||
|
bool/null tokens, the scoped object `Writer`, and the bounds-checked `Reader`
|
||||||
|
cursor. Domain grammars — what fields a bank, view-mode, or manifest blob actually
|
||||||
|
has — stay in the consumers; this module owns lexing/emitting only.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `json` (`core/json`) — the ONE hand-rolled JSON lexical layer (Q-W1): string/number/bool/null tokens, the scoped object `Writer`, and the bounds-checked `Reader` cursor, byte-compatible with the five pre-extraction per-module writers it replaced (`bank_model` / `bank_book` / `view_mode_model` / `owned_manifest` / `tail_control`). Domain grammars stay in the consumers; this owns lexing/emitting only.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- Byte-compatible with the five pre-extraction per-module writers it replaced
|
||||||
|
(`bank_model` / `bank_book` / `view_mode_model` / `owned_manifest` /
|
||||||
|
`tail_control`) — a change here risks silently breaking round-trip compatibility
|
||||||
|
with ext-state blobs already persisted by projects written before the Q-W1
|
||||||
|
extraction.
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# src/core/model — the pure bank/sample index and its multi-bank container
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Pure (REAPER-free, unit-tested outside the DAW) sample-index models: the single-bank
|
||||||
|
index, the multi-bank registry that wraps it, its JSON codec, the gap-preserving
|
||||||
|
per-bank slot carrier, the owned-file manifest, and the capture-recipe fingerprint.
|
||||||
|
No REAPER types, no filesystem I/O — see root `CLAUDE.md` for the pure-core/shell
|
||||||
|
split this directory sits on.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
**Pool privileges (multi-bank).**
|
||||||
|
- The pool is privileged, not special-cased: structurally one `BankIndex` among many
|
||||||
|
in `bank_book`; semantically it always exists, is un-deletable, and un-renamable
|
||||||
|
(fixed id + fixed display name "Pool"). New projects and migrated single-bank
|
||||||
|
projects start with the pool and zero named banks. Enforced in the pure rules
|
||||||
|
layer, not just the UI.
|
||||||
|
- No action path may delete or rename the pool, leave a project with zero banks, or
|
||||||
|
evacuate the pool (the pool is evacuation's destination, not a source).
|
||||||
|
|
||||||
|
**Bank identity, movement, dedup.**
|
||||||
|
- Bank id is the stable key (GUID-style, minted on create); display name and ordinal
|
||||||
|
are mutable. Display names are unique — trimmed + case-insensitive (ASCII) —
|
||||||
|
enforced by `createBank`/`renameBank`; the pool's reserved name "Pool" is protected
|
||||||
|
by the same check.
|
||||||
|
- Movement moves the index entry, not the file: move/copy between banks is
|
||||||
|
index-only (remove from A's `BankIndex`, add to B's); the underlying file stays in
|
||||||
|
the shared project bank folder. Per-bank subfolders on disk are an explicit
|
||||||
|
non-goal.
|
||||||
|
- Dedup-by-hash is per-bank. Moving a sample whose hash already exists in the
|
||||||
|
destination bank collapses onto the existing entry there. Cross-bank dedup is not
|
||||||
|
enforced — the same hash may exist in the pool and a named bank simultaneously.
|
||||||
|
- Move is the default (removes from source, adds to destination); copy is the
|
||||||
|
deliberate secondary act (adds to destination, leaves source intact).
|
||||||
|
- Delete drops members (files are not deleted); evacuate returns all of a bank's
|
||||||
|
members to the pool (index-only, same destination-collapse rule). Evacuate cannot
|
||||||
|
be applied to the pool. A plain delete of a non-empty bank orphans those members
|
||||||
|
out of every index until prune reclaims their files — the UI confirms on
|
||||||
|
non-empty delete and offers evacuate as the alternative.
|
||||||
|
|
||||||
|
**Sample removal.**
|
||||||
|
- Remove is index-only: drops one `Sample` entry from one `BankIndex`; mutates only
|
||||||
|
index + ext-state, no file written/moved/deleted, no timeline item touched.
|
||||||
|
- Remove can orphan a file — the same designed orphaned-until-prune state a
|
||||||
|
non-empty delete-bank produces — when it drops the last index reference to a
|
||||||
|
file. Reclaimed later by prune, never by remove.
|
||||||
|
- The pool's contents are removable; the pool container is not. Remove-from-pool is
|
||||||
|
allowed.
|
||||||
|
- Remove scope is this-bank only (settled 2026-07-24): drops the entry from this
|
||||||
|
bank, leaving copies in other banks untouched. `scope: this-bank | all-banks` is a
|
||||||
|
latent seam; only this-bank is a surfaced verb.
|
||||||
|
- Removes are silent — no confirm dialog. Recoverability comes from batched REAPER
|
||||||
|
undo (`Undo_BeginBlock`/`Undo_EndBlock`): one Ctrl-Z restores the index entry.
|
||||||
|
This undo-batching is Phase-B-wide (create/rename/reorder/delete-bank, move, copy,
|
||||||
|
evacuate, and remove all batch this way). `hashReferencedElsewhere` is a tested
|
||||||
|
model API retained for Phase R prune; it has no shell caller in the remove path.
|
||||||
|
|
||||||
|
**Precision implications.**
|
||||||
|
- Relative-paths-only survives unchanged: every `BankIndex` in the book keeps the
|
||||||
|
relative-path invariant at its `add` boundary; movement is index-only so files
|
||||||
|
never relocate.
|
||||||
|
- Non-destructive: bank create/rename/delete/activate/evacuate and sample
|
||||||
|
move/copy/remove mutate only index + ext-state; no file is written, moved, or
|
||||||
|
deleted, and no timeline item is touched.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `bank_model` — `Sample` metadata struct + `BankIndex` (add/remove/query/tier/dedup-by-hash + JSON round-trip). Test it hard — it is the heart.
|
||||||
|
- `bank_book` — multi-bank registry: an ordered set of banks each wrapping a `BankIndex`. **Pool privileges (un-deletable/un-renamable/un-evacuable, never zero banks) enforced in-model.** Owns create/rename/reorder/delete of named banks, active-bank id, and index-only move/copy/remove of a sample between banks. The JSON round-trip lives in the sibling `bank_book_json` TU (Q-W5 split; serialize/deserialize via a private static `nameKey` seam) — one model, one codec, same public surface.
|
||||||
|
- `slot_map` (`core/model`) — the gap-preserving display-position carrier for ONE bank (sample id → slot, ≥0), extracted from `bank_book` (Q-W1): append/remove/reorder (insert-before-and-shift)/`reconcile` against live membership, `resetDense` migration seed, JSON round-trip. Wrapped (not merged) by `bank_book`.
|
||||||
|
- `owned_manifest` — the set of project-relative files the capture path itself created, persisted under the `"owned_files"` ext-state key, so the prune path can distinguish the bank system's own orphans from hand-dropped files.
|
||||||
|
- `provenance` — capture-recipe fingerprint: build/encode/compare a `rsprov1` fingerprint of scope, range, tail, rate/channels, track GUIDs, and FX-chain identity. **A thin reproducibility fingerprint — NOT a serialized chain to restore.**
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- `bank_book` wraps `BankIndex`; it does not modify it (additive — no `bank-id`
|
||||||
|
field on `Sample`). Do not add per-bank subfolders on disk or a global
|
||||||
|
cross-bank dedup — both are rejected-in-review non-goals.
|
||||||
|
- `bank_book_json` is a sibling TU, not a separate module — its round-trip is part
|
||||||
|
of `bank_book`'s public surface, not a distinct thing to describe separately.
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# src/core/reclaim — pure prune orphan computation
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Houses the safety-critical "which files are orphans" decision for the file-lifecycle
|
||||||
|
(prune) pillar — filesystem-free, unit-tested before any I/O exists. The filesystem
|
||||||
|
enumeration and the actual deletion live in `shell/persist` (`prune_fs`), not here.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- **The load-bearing rule: remove creates orphans; prune reclaims them.**
|
||||||
|
Sample-remove and delete-bank drop index entries and may leave a file referenced
|
||||||
|
by nothing. Prune is the single path that turns such an orphan back into free
|
||||||
|
disk space. No other operation deletes a file; prune deletes only files that no
|
||||||
|
index references.
|
||||||
|
- Prune reuses the shape Design View already shipped (`view_mode_model`'s
|
||||||
|
`reconcile(liveGuids)`): prune reconciles files on disk against referenced files
|
||||||
|
(the union of every bank's index) and returns the orphan set to delete — same
|
||||||
|
pure pattern, one level down (files instead of GUIDs).
|
||||||
|
- Referenced-set is the union across ALL banks, pool included: a file is an orphan
|
||||||
|
iff no bank in the book references it. This is the safety-critical computation —
|
||||||
|
the prune null test is *prune never deletes a file that any index references.*
|
||||||
|
- Orphan attribution is an owned-file manifest (fork R-D): the book tracks the set
|
||||||
|
of files it has created; prune reclaims `(owned ∩ on-disk) − referenced`. This
|
||||||
|
rejects folder-sweep (which would delete hand-dropped files).
|
||||||
|
- **Prune null test:** a prune of a folder whose every file is referenced by some
|
||||||
|
bank deletes nothing; a prune deletes exactly the `present − referenced` orphan
|
||||||
|
set and nothing else.
|
||||||
|
- Never a referenced file; never a non-bank file — the union-across-all-banks rule
|
||||||
|
protects referenced files; the ownership-attribution rule (fork R-D) protects
|
||||||
|
hand-dropped files.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `prune_reconcile` — pure prune core: `pruneOrphans(present, referenced, owned)` computes `(owned ∩ present) − referenced`; the safety-critical "which files are orphans" decision, filesystem-free and hard-tested before any I/O exists. Gains `mergeReferenced(bankRefs, liveInstanceHeldPaths)` (pS-usage) — unions live instance holds into the prune referenced-set so the pure orphan computation includes them.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- Dry-run/confirm UX, trash-preferred deletion mechanics (fork R-C), and the
|
||||||
|
manual-trigger guardrail (fork R-E) are deletion-*mechanics* concerns, not
|
||||||
|
orphan-*computation* ones — they live in `shell/persist`, not here.
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# src/core/ui — pure UI geometry, palette, and interaction-decision modules
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Pure, REAPER-free UI geometry, palette, and interaction-decision modules shared by
|
||||||
|
the extension's docked bank panel and the VST3 instrument's editor/embed surfaces:
|
||||||
|
layout math, hit-testing, hover/drag-gesture-precedence decisions, and the
|
||||||
|
role-based color palette. Does **not** include: the actual LICE drawing (`draw_kit`
|
||||||
|
lives in `shell/panel`; the editor's own paint code lives in `shell/instrument`),
|
||||||
|
REAPER/SWELL window or dialog mechanics, or the DAW-side Design View flag
|
||||||
|
application (`shell/view`).
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
Look-and-feel — visual design language (Phase L) (settled decisions, 2026-07-26;
|
||||||
|
L7 sub-pass, 2026-07-27):
|
||||||
|
|
||||||
|
- **DS-1 — toolkit discipline.** Draw with LICE + reused WDL/vwnd pieces directly;
|
||||||
|
external frameworks (iPlug2/JUCE/VSTGUI) are rejected. "**Caution, not a ban:**
|
||||||
|
keep hit-test **geometry** in pure CTest-covered modules — do not import vwnd's
|
||||||
|
retained-mode object model wholesale (its controls own their hit-test internally,
|
||||||
|
which would move geometry into untestable shell code and undercut the pure/shell
|
||||||
|
split)." This directory is that pure-geometry seam.
|
||||||
|
- **DS-2 — palette is role-based, not hardcoded hue**, in one constants block
|
||||||
|
(`theme`): `bg/base`, `bg/panel`, `bg/cell`, `line/hairline`, `text/primary`,
|
||||||
|
`text/dim`, `accent/primary`, `accent/secondary`, `accent/tertiary`,
|
||||||
|
`accent/hot`, `warn`. Neutral ladder sits in REAPER's mid-grey theme family
|
||||||
|
(`bg/base` ≈ `#2b2b2b`, `bg/panel` ≈ `#333333`, `bg/cell` ≈ `#3a3a3a`,
|
||||||
|
`line/hairline` ≈ `#4a4a4a`, `text/primary` ≈ `#dcdcdc`, `text/dim` ≈
|
||||||
|
`~#a0a0a0`+), elevation-ladder discipline (base < panel < cell by a few %,
|
||||||
|
micro-gradient + inner highlight/shadow carry elevation, not hard borders).
|
||||||
|
Three pastel accents carry categorical meaning: **primary (pastel lime) =
|
||||||
|
live/active/selected**, secondary (pastel teal) + tertiary (pastel purple) =
|
||||||
|
supporting categorical distinctions (kinds, not intensity). `warn` (red/amber)
|
||||||
|
is reserved **only** for byte-deleting or clip states (prune, delete).
|
||||||
|
- **WCAG-floor discipline (tight pairs to re-verify on any palette change):**
|
||||||
|
`text/dim` on `bg/panel`/`bg/cell` is the classic mid-grey-on-mid-grey floor
|
||||||
|
failure — must clear AA 4.5:1 body text. The three pastels as state
|
||||||
|
indicators/active fills on `bg/cell` have a shrunken contrast cushion
|
||||||
|
(~6:1–7:1, still clears 3:1 but re-check on any hue nudge). Body text on a
|
||||||
|
pastel fill is a tight AA 4.5:1 pair. Take the most pastel value that still
|
||||||
|
clears the floor, approached from the soft side, never re-saturated toward
|
||||||
|
neon "to be safe."
|
||||||
|
- **"Speed is the selling point" — a design constraint on this geometry, not
|
||||||
|
just the draw layer.** Sub-frame hover/press/drag feedback must repaint
|
||||||
|
immediately on the input message. **No decorative animation** — no
|
||||||
|
tweens/fades/pulses; the only permitted motion is a level/meter readout
|
||||||
|
following audio directly. Any glow/bloom state is a static drawn state, never
|
||||||
|
a pulse.
|
||||||
|
- **Precision/invariant implications (Phase L does not change these):** "the
|
||||||
|
pure/shell split holds" — all layout/hit-test stays in pure CTest-covered
|
||||||
|
geometry modules; the kit's *draw* half is shell, its *geometry* half is pure,
|
||||||
|
even where a WDL piece is reused. Look-and-feel work never touches capture,
|
||||||
|
placement, or bank data ownership.
|
||||||
|
- **L7 drag-gesture precedence is a pure decision helper.** The rule — leave
|
||||||
|
client rect → OS drag-out; else drop on a tab/other bank → move/copy; else
|
||||||
|
same-bank grid → reorder-to-slot (empty slot = place, occupied + no modifier =
|
||||||
|
insert-before-and-shift, occupied + Alt = replace) — is "encoded in a pure
|
||||||
|
decision helper (mirror `drag_out::decideGesture`)"; the shell only reads live
|
||||||
|
pointer/focus/client-rect/modifier state and calls it, then maps the resolved
|
||||||
|
gesture to a cursor via `SetCursor`. No cue or precedence logic belongs in the
|
||||||
|
shell.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `rect` (`core/ui`, header-only) — the ONE concrete pixel rectangle (Q-W1): XYWH storage + `right()`/`bottom()`/`ltrb()`/`contains()`, replacing 12+ byte-identical role structs (`ButtonRect`/`FooterRect`/`CellRect`/`KitBox`/…) and the VST side's separate LTRB `Rect`; every prior role name survives as a `using` alias at its old site (e.g. `editor_geometry::Rect`).
|
||||||
|
- `bank_grid` — REAPER-free grid layout, selection, keyboard-nav, and thumbnail-cache-key logic for the docked bank panel.
|
||||||
|
- `tab_strip` — REAPER-free scrollable tab-strip layout + hit-test for the named-banks strip.
|
||||||
|
- `prune_button` — pure layout/hit-test for the `bank_panel` footer Prune button.
|
||||||
|
- `drag_out` — pure OS drag-out module: gesture-boundary decision and path-list assembly. The `InstrumentDrop` gesture signals that the shell should execute an instrument-drop rather than a file-copy drag.
|
||||||
|
- `theme` — pure palette module: role→color mapping, REAPER-grey neutral ladder + three-accent pastel system, WCAG contrast-floor helpers.
|
||||||
|
- `component_geometry` — pure button/slider/list-row geometry + hover hit-test helpers.
|
||||||
|
- `action_bar` — pure task-grouped action-bar layout/hit-test: clusters (Capture / Placement / Maintenance / Tagging / Switching).
|
||||||
|
- `footer_bar` — pure footer layout/hit-test: `[Arrange|Design]` mode-toggle geometry, Tail button, and Prune placement.
|
||||||
|
- `overflow_menu` — pure overflow-menu-button geometry/reserve/hit-test for the top-toolbar More (⋯) button.
|
||||||
|
- `mode_enable` — pure opposite-mode enablement predicate: given the active mode, computes per-button live/disabled state for the four Item/Track × Arrange/Design tag buttons.
|
||||||
|
- `tooltip` — pure tooltip placement + prefix-strip: strips the `ReaSampler:` display prefix from the registered action phrase; width clamped to the client rect.
|
||||||
|
- `card_drag` — pure drag-gesture precedence + slot hit-test: leave-client → OS drag-out; other-bank → move/copy; same-bank → reorder / Alt-over-occupied → replace.
|
||||||
|
- `card_meta` — pure card-metadata formatters: bars.beats.subdivisions and seconds.milliseconds; blank when the sample is unstamped.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- The WCAG contrast-floor pairs above are real math in `theme`'s tests, not a
|
||||||
|
visual eyeball check — any new hue or role needs its own contrast-floor
|
||||||
|
assertion.
|
||||||
|
- `card_drag`'s precedence order must stay a pure decision helper mirroring
|
||||||
|
`drag_out::decideGesture` — don't let a shell reimplement gesture precedence
|
||||||
|
ad hoc; the cursor-cue mapping in the shell must stay a thin lookup over the
|
||||||
|
pure result.
|
||||||
|
- `rect`'s prior role names survive only as `using` aliases at their old call
|
||||||
|
sites — changing `rect.h` itself ripples across every directory that aliases
|
||||||
|
it (e.g. `editor_geometry::Rect`); check all alias sites, not just this one.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# src/core/util — small shared pure utilities
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Tiny, dependency-free pure helpers linked by both artifacts: whole-file byte
|
||||||
|
loading and unit-interval clamping.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `file_bytes` (`core/util`) — the ONE whole-file byte loader (Q-W1), linked by both artifacts; blocking I/O, off-audio-thread only.
|
||||||
|
- `clamp01` (`core/util`, header-only) — the ONE unit-interval clamp (Q-W1), replacing four per-module static copies; NaN passes through unchanged rather than collapsing to a bound.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- `clamp01` lets NaN pass through unchanged rather than collapsing it to a bound —
|
||||||
|
this is deliberate (it replaced four per-module static copies that already
|
||||||
|
behaved this way); don't "fix" it to clamp NaN to 0 or 1.
|
||||||
|
- `file_bytes` does blocking I/O — off-audio-thread only, never call it from
|
||||||
|
`process()`.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# src/core/version — version/channel identity
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
REAPER-free version/channel identity consumed by both artifacts (the REAPER
|
||||||
|
extension and the VST3 instrument) to derive binary names, ext-state namespaces,
|
||||||
|
command-id prefixes, action-name prefixes, dock idents, and version display
|
||||||
|
strings.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `app_version` — REAPER-free version/channel identity: CMake-sourced semver constant, ext-state stamp value, and the full set of channel-derived identity accessors. All channel strings derive from one `REASAMPLER_CHANNEL_IS_BETA` bit; no scattered `#ifdef`s in the shells.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- All channel strings derive from one `REASAMPLER_CHANNEL_IS_BETA` bit — route new
|
||||||
|
channel-specific behavior through this module's accessors rather than adding a
|
||||||
|
scattered `#ifdef` in a shell. See root `CLAUDE.md` "Beta channel build" for the
|
||||||
|
full fan-out (binary name, ext-state namespace, command-id prefix, action-name
|
||||||
|
prefix, dock ident, version display).
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# src/core/view — pure Design View mode model
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Pure, REAPER-free Design View model: mode/track membership, folder-derived
|
||||||
|
visibility, snapshot-based park/restore planning, new-content (GUID) detection,
|
||||||
|
and the managed/manual lane-identity convention that underlies per-item mode
|
||||||
|
separation (fixed lanes). Does **not** include: the actual DAW-side flag
|
||||||
|
application (hide, CPU-park, per-FX offline, restore via `B_SHOWINTCP` /
|
||||||
|
`B_SHOWINMIXER` / `B_MAINSEND` / `I_FXEN`) or the never-touch-master/mute/solo
|
||||||
|
enforcement — those live in `shell/view`.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
Design View — additive phase spec (settled decisions; Two-canvas sub-phase,
|
||||||
|
settled 2026-07-23):
|
||||||
|
|
||||||
|
- **Membership.** Default = Arrange; every untagged leaf belongs to it. Leaves
|
||||||
|
opt in to Design (or any mode). No track appears in two modes at once except
|
||||||
|
via an explicit show-both toggle or parent/folder derivation.
|
||||||
|
- **Parents are derived, never tagged.** A parent/folder track is visible in
|
||||||
|
mode M if either any descendant leaf is visible in M, or the parent belongs to
|
||||||
|
M by its own membership; an untagged parent is an Arrange member by default. A
|
||||||
|
parent is never parked in any mode it is visible in. Master track is always
|
||||||
|
visible and never touched.
|
||||||
|
- **N-mode model, two-mode UI.** The data model carries arbitrarily many modes;
|
||||||
|
the UI ships Arrange + Design. A mode is (stable id, display name, ordinal).
|
||||||
|
- **Persistence.** Membership index + last-active mode + per-track flag
|
||||||
|
snapshots ride in the existing `"reasampler"` project ext-state namespace and
|
||||||
|
travel with the `.rpp`.
|
||||||
|
- **Non-destructive restore (enforced in the pure layer).** For every flag the
|
||||||
|
tool drives, snapshot the prior value before parking; on toggle-back, restore
|
||||||
|
from the snapshot, never to a hardcoded default. Round-trip (snapshot → park →
|
||||||
|
restore) returns every driven flag to its captured value — this is the
|
||||||
|
phase's trust anchor, the analog of the capture null test.
|
||||||
|
- **GUID-keyed, reorder-safe.** Membership keys on track GUID (`GetTrackGUID`),
|
||||||
|
never track index; tolerates unknown/stale GUIDs (pruned on reconcile via
|
||||||
|
`ViewModeModel::reconcile(liveGuids)`).
|
||||||
|
- **Relative/portable state only** in the persisted view section (GUID strings,
|
||||||
|
mode ids — no absolute paths, no index positions).
|
||||||
|
- **Show-both semantics.** A per-track "pin visible across modes" flag that
|
||||||
|
re-enables processing whenever shown. A show-both leaf appears in every
|
||||||
|
mode's visible set and is never parked — its driven flags stay at
|
||||||
|
snapshot/restored values. Stored on the membership record; persists;
|
||||||
|
togglable per selection.
|
||||||
|
- **Non-goals / guardrails:** No literal second canvas — a second window or
|
||||||
|
duplicated project stays rejected; subproject/second-project-file approaches
|
||||||
|
and overloading item `D_POSITION` with mode semantics (timebase-offset
|
||||||
|
regions) are rejected paths (the latter collides with the capture null test).
|
||||||
|
Every leaf is managed: an untagged leaf is an Arrange member and, when the
|
||||||
|
active mode is not Arrange, is fully parked and snapshot-restored exactly
|
||||||
|
like a tagged leaf; show-both is the only way to opt a leaf out of parking.
|
||||||
|
Restore from snapshot, never to a default: no hardcoded "on" restores.
|
||||||
|
|
||||||
|
**Two-canvas sub-phase (Phase D2/E) — settled and landed parts only:**
|
||||||
|
|
||||||
|
- **Mechanism: fixed item lanes.** Map mode → lane; toggle drives per-lane
|
||||||
|
play/show so only the active mode's lane is present. Items keep their real
|
||||||
|
position and real track — nothing is moved in time or deleted.
|
||||||
|
- **Membership: adoption rule for new items; active mode for new tracks.** New
|
||||||
|
tracks are tagged to the active mode at creation. New items follow an
|
||||||
|
adoption rule: if the item's track has pre-existing managed-eligible content
|
||||||
|
spanning exactly one mode, the item adopts that mode; the active-mode
|
||||||
|
fallback applies only when the track is empty or already spans multiple
|
||||||
|
modes. Items in manual lanes are excluded from the prior-mode computation and
|
||||||
|
are not auto-tagged at all. Membership is exclusive per item except via
|
||||||
|
show-both.
|
||||||
|
- **Managed vs. manual lanes — indexed and distinct.** The tool maintains a
|
||||||
|
lane-ownership index — per (track GUID, lane): managed (which mode owns it)
|
||||||
|
vs. manual (user-minted, outside the mode system). Mode operations touch only
|
||||||
|
managed lanes; manual lanes are never shown, hidden, silenced, or re-laned by
|
||||||
|
a toggle, and their `C_LANEPLAYS` stays exactly as the user set it. The
|
||||||
|
ownership index rides in `"reasampler"` `view_state` alongside the membership
|
||||||
|
index, GUID-keyed and portable. **New invariant — mode operations touch only
|
||||||
|
managed lanes:** "the 'which lanes may this toggle touch' decision is a pure
|
||||||
|
query over the ownership index; only reading REAPER's live lane state is
|
||||||
|
shell."
|
||||||
|
- **REAPER floor: v7** for this sub-phase (fixed lanes shipped in v7); no
|
||||||
|
version-gate branch — below v7 the sub-phase is simply unavailable.
|
||||||
|
- Precision invariants (null test, bit-identical repeats, non-destructive,
|
||||||
|
exact bounds, relative-paths-only) are entirely unaffected by this
|
||||||
|
sub-phase — no capture path changes; lane assignment and `C_LANEPLAYS` are
|
||||||
|
reversible flags, never a destructive operation.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `view_mode_model` — Design View mode system: mode registry, GUID-keyed membership, folder-tree-aware visibility derivation, snapshot-based park/restore planner, JSON round-trip.
|
||||||
|
- `view_tree` — pure `I_FOLDERDEPTH`→FolderTree helper for the Design View shell.
|
||||||
|
- `mode_switch` — REAPER-free segment layout + hit-test for the bank_panel's Design View mode switch.
|
||||||
|
- `guid_diff` — the pure, REAPER-free core of the D2 Wave-2 new-content detection: `newGuids(previous, current)` computes the GUIDs present in `current` but absent from `previous` (empty GUIDs ignored); `GuidBaseline` tracks the live GUID set across polls for one project, implementing the first-poll-after-open guard (the first `observe()` after construction/`reset()` records a baseline and reports nothing new, so pre-existing content is never mass-tagged) and re-arms via `reset()` on a detected project switch so detection never diffs across two unrelated projects.
|
||||||
|
- `lane_keys` — the pure, REAPER-free convention mapping a fixed lane's durable REAPER name (`P_LANENAME:n`) to the opaque lane-key `view_mode_model` keys by, plus the managed/manual heuristic both the toggle-apply path and the new-content/auto-tag exemption path share: `kManagedLanePrefix` ("reasampler:") stamps every lane the tool mints; `isManagedLaneName`/`managedLaneKey`/`laneNameForMode`/`modeIdFromLaneName` round-trip a lane name ↔ its owning mode id; `isOnManualLane` is the single predicate governing which lanes a toggle may drive and which items are exempt from auto-tag.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- REAPER exposes no durable per-lane GUID — the only lane identity is the
|
||||||
|
ordinal `I_FIXEDLANE`, which REAPER renumbers on reorder/delete. Lane
|
||||||
|
identity must ride on the durable `P_LANENAME` (`lane_keys`), never the raw
|
||||||
|
ordinal, or a reorder will silently corrupt managed/manual ownership.
|
||||||
|
- `kManagedLanePrefix` ("reasampler:") is stable-forever like an action-id
|
||||||
|
string — changing it strands the ownership of every already-minted lane in
|
||||||
|
every already-saved project.
|
||||||
|
- `guid_diff::GuidBaseline` must have `reset()` called on every detected
|
||||||
|
project switch, or the next `observe()` will diff across two unrelated
|
||||||
|
projects and mass-tag (or miss) content.
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# src/core/wire — pure ext-state and wire-format codecs, cross-artifact contracts
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The lexical/wire layer underneath the domain grammars: length-prefixed ext-state field
|
||||||
|
codecs, the little-endian byte codec, the `GetProjExtState` grow-loop retry policy, the
|
||||||
|
FOREVER-FROZEN VST3 class-UID macros, and the two mirror-image cross-artifact wires
|
||||||
|
(`assignment_request` extension→instrument, `sample_usage` instrument→extension) plus
|
||||||
|
the FX-drop payload builder (`instrument_drop`). Domain grammars themselves (what the
|
||||||
|
fields *mean*) stay in their own modules (`bank_model`, `sample_map`, `provenance`,
|
||||||
|
`bank_sync`, …) — this directory owns lexing/emitting/marshalling only, not domain
|
||||||
|
semantics.
|
||||||
|
|
||||||
|
This directory owns two cross-artifact contracts specifically:
|
||||||
|
- `reasampler_uid.h` is the FOREVER-FROZEN VST3 class-UID header shared by the runtime
|
||||||
|
`FUID` (`reasampler_vst.h`) and the `.vstpreset` hex string (`instrument_drop`) — so
|
||||||
|
binary identity and preset identity cannot diverge.
|
||||||
|
- `assignment_request` and `sample_usage` are mirror-image wires: the former carries the
|
||||||
|
drop payload extension→instrument, the latter carries usage records instrument→extension.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- **The VST-host bridge (the integration mechanism, stated once here).** A VST3
|
||||||
|
hosted inside REAPER can call back into REAPER's own API by
|
||||||
|
resolving function pointers by name over the host callback (`hostcb(&effect,
|
||||||
|
0xdeadbeef, 0xdeadf00d, 0, "FunctionName", 0.0)` — the same string-keyed API table the
|
||||||
|
extension uses; verified in `video_processor.h` and the `reaper_plugin_functions.h`
|
||||||
|
`GetProjExtState`/`SetProjExtState`/`EnumProjExtState` entries). The plugin can also
|
||||||
|
fetch its host context — the track/take/project it was instantiated in (opcode
|
||||||
|
`0xdeadf00e`). Consequence: the instrument reads the *same* live `"reasampler"`
|
||||||
|
ext-state that `persist` writes, follows the active project, and needs no
|
||||||
|
"point me at the bank folder" wiring — it asks REAPER which project it is in. Confirm
|
||||||
|
the bridge opcodes and by-name resolution against the vendored `vendor/reaper-sdk/sdk/`
|
||||||
|
headers (`reaper_plugin.h`, `video_processor.h`, `reaper_plugin_functions.h`) before
|
||||||
|
relying on new opcodes.
|
||||||
|
|
||||||
|
- **Instance-usage wire — `rsusage_<instanceGuid>` (pS-usage; pure portions only —
|
||||||
|
the extension-side scan shell `usage_scan` and the prune-abort behavior are owned
|
||||||
|
by the `shell/persist` layer, a parallel dispatch).**
|
||||||
|
Each VST3 instance holds a per-instance GUID persisted in `ComponentState` v11
|
||||||
|
(`instanceGuid` field; pre-v11 blobs mint the guid on first publish). At the tail of
|
||||||
|
every `reloadInstrument` call (off audio thread) the processor publishes its held
|
||||||
|
`SampleRefs` paths to the ext-state key `rsusage_<instanceGuid>` in the `"reasampler"`
|
||||||
|
namespace via `reaper_bridge::writeUsageExtState` — an entry point that is
|
||||||
|
**prefix-guarded** (accepts only `rsusage_`-prefixed keys, refuses all others), so the
|
||||||
|
read-only-bank invariant is structurally enforced. Direction: the instrument writes
|
||||||
|
usage keys; the extension reads them — the one sanctioned instrument→ext-state write,
|
||||||
|
a deliberate exception analogous to `assignment_request` on the other wire.
|
||||||
|
Usage records are **never cleared by the instrument at teardown** (REAPER destroys the
|
||||||
|
plugin instance when an FX chain is set offline, including Design View's CPU-park, so a
|
||||||
|
terminate-time clear would strip a still-live instance's record); liveness is decided
|
||||||
|
extension-side at prune-scan time.
|
||||||
|
- **The pure fold (`sample_usage::foldUsageRecords` / `usageHeldPaths`).** A record
|
||||||
|
counts iff its publishing track still hosts at least one instance (offline FX
|
||||||
|
included); a record with no track context counts while any instance exists; and when
|
||||||
|
records exist but zero instances were identified, **every** record's paths are
|
||||||
|
protected (identity-failure net — a matcher failure must never degrade toward
|
||||||
|
delete). The guarantee: a capture held by any live instance can never be deleted; if
|
||||||
|
the prune cannot determine with certainty which captures are held, it aborts
|
||||||
|
entirely (deletes nothing). Over-protection is the accepted residual; under-protection
|
||||||
|
is a data-loss bug.
|
||||||
|
- **Collision safety (`planUsagePublish`).** A persisted GUID is copyable (FX copy /
|
||||||
|
track duplication). `ownerNonce` — a per-lifetime nonce minted fresh in memory at
|
||||||
|
instance creation, never persisted — proves "exactly this incarnation wrote the key
|
||||||
|
last." `unioned` — a sticky multi-writer poison: once a same-track sibling is
|
||||||
|
detected, the key enters union-forever mode (holds only accumulate, never drop).
|
||||||
|
Resolution always leans over-protect: same-nonce + not-unioned → clean replace;
|
||||||
|
same-track foreign nonce or unioned → union; cross-track foreign nonce → remint under
|
||||||
|
a fresh key. None of the three directions can under-protect.
|
||||||
|
- **Deferred follow-up (TODO.md, not this dispatch's scope):** `ownerNonce` is not
|
||||||
|
persisted, so after save→reopen an instance cannot recognize its own prior-session
|
||||||
|
usage record — it unions and marks the record `unioned` forever, so prune stops
|
||||||
|
reclaiming captures the instance once held but no longer uses (safe, but the bank
|
||||||
|
folder grows unbounded). Persisting the nonce is deferred because a persisted nonce
|
||||||
|
would be inherited by a Ctrl+D in-place FX duplicate, and a divergent clone must
|
||||||
|
still be detected and protected fail-safe without reintroducing the sibling-drop bug.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `wire` (`core/wire`) — the ONE length-prefixed ext-state wire codec (Q-W1): `putField`/`parseUnsignedDecimal` + the bounds-checked `Cursor` (`field`/`fieldInt`/`fieldInt64`/`fieldSizeT`/`fieldDouble`), replacing four near-identical copies (`provenance` / `assignment_request` / `sample_usage` / `bank_sync`). `core/wire/bytes.h` is the sibling little-endian byte codec (`putLE`, `ByteReader`, `doubleToBits`/`bitsToDouble`) that `component_state_io` is the biggest consumer of. `core/wire/ext_state_read.h` owns the `GetProjExtState` grow-loop retry policy (Absent/Complete/Overflow) shared by `persist`, `usage_scan`, and `reaper_bridge`. `core/wire/reasampler_uid.h` (the FOREVER-FROZEN VST3 class-UID macros) also lives in this directory.
|
||||||
|
- `reasampler_uid.h` — SDK-free header owning the FOREVER-FROZEN VST3 class-UID integer macros (stable + beta pairs, `REASAMPLER_PROC_UID_*` / `REASAMPLER_PROC_UID_BETA_*`) and the `REASAMPLER_ACTIVE_UID_*` channel-selector macros. Split out of `reasampler_vst.h` so the pure extension side (`instrument_drop`) can derive the `.vstpreset` class-ID hex string without pulling in the VST3 SDK. Both `reasampler_vst.h` (runtime `FUID`) and `instrument_drop` (preset hex string) source from this single header — the binary identity and the preset-file identity cannot diverge.
|
||||||
|
- `assignment_request` — pure ingest-assign wire: typed request record carrying the drop payload from the `ingest` shell through to the VST3 bridge.
|
||||||
|
- `instrument_drop` — pure FX-drop payload builder: constructs a Steinberg-format `.vstpreset` image (channel-active class ID + the instrument's own component state, capture pre-selected) the shell applies via `TrackFX_SetPreset`; owns the `infoNamesFxHotspot` prefix classifier for `GetThingFromPoint` tokens. All-or-nothing contract — caller rolls back via `TrackFX_Delete` on any failure.
|
||||||
|
- `sample_usage` — instance-usage wire: `UsageRecord`, `planUsagePublish` (fresh/heal/clean-replace/union/remint publish plan), `foldUsageRecords`/`usageHeldPaths` (liveness fold — protect-all when records exist but no instance is live; abort→protect-all on unreadable record), `identityMatches` (ReaSampler 9000 FX identity). REAPER-free, unit-tested. The mirror of `assignment_request` on the instrument→extension direction: the wire format and the two safety-critical decisions (what to write on publish, which records count at prune time) are pure so they are provable without a DAW.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- `bytes.h`, `ext_state_read.h`, and `reasampler_uid.h` are header-only (no sibling
|
||||||
|
`.cpp` / no dedicated `_tests` target of their own) — they are consumed directly by
|
||||||
|
the modules named in their bullets above; don't go looking for a standalone build
|
||||||
|
target for them.
|
||||||
|
- `sample_usage` is deliberately silent on liveness *enumeration* (which FX instances
|
||||||
|
are currently live) — that scan lives in `shell/persist`'s `usage_scan`, not here. This
|
||||||
|
directory owns only the wire format and the two pure fold/collision decisions.
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# src/shell/actions — bindable REAPER actions, drag/drop shells, ingest
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The bindable action families routed through REAPER's `command_id`/`gaccel`/
|
||||||
|
`hookcommand` contract (Design View toggle actions, bank actions, the prune
|
||||||
|
action, and the shared registration plumbing/table), plus the OS drag-out and
|
||||||
|
FX-drop shells, plus the extension-side ingest-through-the-bank shell. This is
|
||||||
|
where user-facing REAPER actions and OS-level drag/drop live; the underlying
|
||||||
|
mutation logic (bank verbs, prune's orphan computation, view-mode reconciliation)
|
||||||
|
is owned by other directories and only skinned here.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- **Ingest is an extension act; the instrument is a read-only bank consumer.** Any
|
||||||
|
instrument code path that captures, imports, inserts a timeline item, or writes
|
||||||
|
back into the bank is a bug — the instrument reads and plays only.
|
||||||
|
- **Ingest NEVER inserts a timeline item.** Arrange capture→bank→assign reuses the
|
||||||
|
existing capture add-path and assigns the resulting `Sample` id to the target
|
||||||
|
instance; it never places anything on the timeline — capture/placement
|
||||||
|
separation is load-bearing here same as everywhere else. Only the arrange-capture
|
||||||
|
surface writes the `assignment_request` wire; Media-Explorer import and file-drop
|
||||||
|
onto the bank panel do not, and neither affects a live instance's selection.
|
||||||
|
- **Every bank index verb wraps its mutation in a batched REAPER undo point**
|
||||||
|
(`Undo_BeginBlock2`/`EndBlock2`, `UNDO_STATE_MISCCFG`) so one bank operation is
|
||||||
|
one Ctrl-Z.
|
||||||
|
- **The prune action is the ONLY file-deletion action in the system**; it opens no
|
||||||
|
undo point (file deletion is not REAPER-undoable). It halts on
|
||||||
|
`abortedUnreadableUsage` and prints the offending `rsusage_*` key names.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `shell/actions` (`action_registry` / `design_view_actions` / `bank_actions` / `prune_action`) — the bindable action families, all routed via the `command_id`/`gaccel`/`hookcommand` contract. `action_registry` owns the shared registration plumbing (interned channel-qualified id strings; register and mirror-unregister present the identical pointer) **and the Q-W6 registration TABLE**: `main.cpp`'s own family (capture scopes, panel toggle, insert, batch, realtime, recapture, version) is one `ActionTableRow` array — suffix, phrase, flat function-pointer handler — that registration, hookcommand dispatch, and the unload mirror-unregister all iterate, so adding an action touches the table only (OCP). Bank mutations flow through the promptless `shell/bank_ops` verbs (`bankOp*` + `persistBankOp`, taking `ReaSamplerSession&`), which the panel menus and `bank_actions` consume as thin UX skins. **Every bank index verb wraps its mutation in a batched REAPER undo point (`Undo_BeginBlock2`/`EndBlock2`, `UNDO_STATE_MISCCFG`) so one bank operation is one Ctrl-Z.** The prune action (`prune_action`, `BANK_PRUNE_FOLDER`) is **the ONLY file-deletion action in the system**; it opens no undo point (file deletion is not REAPER-undoable). **pS-usage:** `BANK_PRUNE_FOLDER` halts on `abortedUnreadableUsage` and prints the offending `rsusage_*` key names with clear instructions.
|
||||||
|
- `drag_out_win` — OS drag-out shell: Windows OLE `DoDragDrop`/`CF_HDROP`, copy-only (`DROPEFFECT_MOVE` not offered); macOS/Linux via `SWELL_InitiateDragDropOfFileList`.
|
||||||
|
- `instrument_drop_win` — FX-button drop shell: resolves a screen point to a track + FX-surface hotspot, then adds a ReaSampler 9000 instance and applies the dragged capture's state via a transient `.vstpreset` + `TrackFX_SetPreset` (the former `TrackFX_SetNamedConfigParm` "vst_chunk" write was silently unappliable for VST3). Exposes `loadInstrumentOntoTrack` (inner half, no own undo block) and `performInstrumentDrop` (wraps in its own undo block). **Never captures, never writes the bank, never inserts a timeline item.**
|
||||||
|
- `ingest` — ingest-through-the-bank shell on the EXTENSION side: three surfaces — (1) arrange capture→bank→assign (bindable action), (2) Media-Explorer import→bank→instrument on the selected track, (3) file drop onto the bank panel→bank only. Only surface (1) writes the `assignment_request` ext-state wire. **ingest NEVER inserts a timeline item.**
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- **Structural wart, not yet fixed:** `ingest.cpp` / `ingest.h`, plus `ext_keys.h`
|
||||||
|
and `resource.h`, physically live at `src/` root rather than under
|
||||||
|
`shell/actions/` — Phase Q's reorg did not re-home these files into
|
||||||
|
`core/`/`shell/`/`app/`. `ingest` is documented here as its nearest sibling by
|
||||||
|
role, but the files themselves are not in this directory. This is a code
|
||||||
|
organization issue, not a documentation one — see Open questions in the
|
||||||
|
originating dispatch report.
|
||||||
|
- Media-Explorer import is single-file, pull-on-action (`OpenMediaExplorer` +
|
||||||
|
`MediaExplorerGetLastPlayedFileInfo`) — there is no enumerate-selected-files or
|
||||||
|
register-a-drop-handler API on the Media Explorer surface.
|
||||||
|
- REAPER exposes no drag-drop registration API; drop handling is only on
|
||||||
|
ReaSampler's own HWNDs (`WM_DROPFILES`/`IDropTarget` on the docked `bank_panel`).
|
||||||
|
A drop onto the VST3 editor window relaying to the extension is an unproven
|
||||||
|
spike, not a shipped path.
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# src/shell/bank_ops — promptless bank-mutation verbs
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The single home for bank-mutation logic: create/rename/delete/evacuate/activate/
|
||||||
|
transfer/remove a sample or bank, plus the undo-batched persist that follows a
|
||||||
|
mutation. No prompts, no message boxes, no panel-state reads — this is the verb
|
||||||
|
seam that `shell/panel/panel_bank_ops` (menu/prompt UX) and `shell/actions/
|
||||||
|
bank_actions` (bindable-action UX) both consume as thin skins, so the mutation
|
||||||
|
logic has exactly one home. The in-model enforcement of bank rules (pool
|
||||||
|
privileges, uniqueness, etc.) lives in `core/model` (`bank_book`) — this directory
|
||||||
|
calls into that model, it does not reimplement its rules.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- **Pool privileges are inviolable.** The pool always exists, is un-deletable, is
|
||||||
|
un-renamable (fixed id + fixed display name "Pool"), and a project can never be
|
||||||
|
left with zero banks. No verb in this directory may delete or rename the pool,
|
||||||
|
or evacuate it (the pool is evacuation's destination, not a source).
|
||||||
|
- **Delete drops members; evacuate returns them.** Deleting a named bank drops its
|
||||||
|
member index entries only — files are never deleted by a bank op (file lifecycle
|
||||||
|
stays owned by the capture/prune path). Evacuate moves all of a bank's members
|
||||||
|
back to the pool (index-only, same destination-collapse-by-hash as move), leaving
|
||||||
|
the bank empty. A plain delete of a non-empty bank orphans those members out of
|
||||||
|
every index until prune reclaims them — an accepted, designed window, not a bug.
|
||||||
|
- **Movement is index-only.** Moving/copying a sample between banks removes/adds
|
||||||
|
the index entry only; the underlying file never moves on disk. No bank operation
|
||||||
|
writes, moves, or deletes a file.
|
||||||
|
- **One bank operation is one Ctrl-Z.** Every bank index verb wraps its mutation in
|
||||||
|
a batched REAPER undo point (`Undo_BeginBlock2`/`EndBlock2`, `UNDO_STATE_MISCCFG`)
|
||||||
|
via `persistBankOp` — this is enforced in this directory, not left to callers.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `bank_ops` (`shell/bank_ops`) — the promptless bank-mutation verb seam (Q-W6 lift out of `panel_bank_ops`): `bankOpCreate`/`Rename`/`Delete`/`Evacuate`/`Activate`/`Transfer`/`Remove` + `persistBankOp` (the undo-batched ext-state persist), each taking a `ReaSamplerSession&` and returning whether the model accepted the mutation — no prompts, no message boxes, no panel-state reads. `shell/panel/panel_bank_ops` (menu/prompt UX) and `shell/actions/bank_actions` (bindable-action UX) both consume these as thin skins, so the mutation logic has exactly one home.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- The pool-privilege rules and uniqueness rules are enforced in the pure
|
||||||
|
`core/model` `bank_book` layer, not re-checked here defensively — if a mutation
|
||||||
|
looks like it should be rejected but isn't, the bug is more likely in `bank_book`
|
||||||
|
than in this seam. Reference `core/model`, do not duplicate its rules here.
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# src/shell/capture — REAPER-facing capture backends and action bodies
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Everything that turns a capture request into a rendered file + populated `Sample`,
|
||||||
|
plus the action bodies that drive capture from REAPER's UI/action list: the two
|
||||||
|
concrete capture backends (offline render, realtime record), scope/source
|
||||||
|
resolution, the realtime in-flight state machine, single- and batch-capture
|
||||||
|
orchestration, insert-to-timeline, provenance stamping, and the shared
|
||||||
|
`MediaItem*`/`MediaTrack*` GUID-read helpers. Pure decision logic (what counts as
|
||||||
|
an orphan, how a range maps to capture units, etc.) lives in the corresponding
|
||||||
|
`core/` modules this shell calls into — this directory is the REAPER API surface
|
||||||
|
only.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
This directory implements, but does not restate, the repo-wide capture precision
|
||||||
|
invariants (null test, bit-identical repeats, non-destructive, exact bounds,
|
||||||
|
relative-paths-only) and the load-bearing capture/placement separation — see root
|
||||||
|
`CLAUDE.md` §Precision invariants and §The load-bearing principle. Shell-specific
|
||||||
|
detail not covered there:
|
||||||
|
|
||||||
|
- **FX-bypass guard ordering.** `scope_resolve` reads the M10 provenance-assembly
|
||||||
|
inputs (track/item selection, FX-chain identity) BEFORE the FX-bypass guard
|
||||||
|
neutralizes the in-scope chain — provenance must see the chain as it really is,
|
||||||
|
not as capture temporarily leaves it.
|
||||||
|
- **Realtime capture drives off REAPER's transport across timer ticks** —
|
||||||
|
`capture_realtime_shell` cannot block REAPER's UI for the duration of a realtime
|
||||||
|
record, so `begin`/`tick`/`abort` are async by construction and the temp-track +
|
||||||
|
send recipe lives in the shell, not the pure core.
|
||||||
|
- **`RunInsertSelected` is the one deliberate exception to capture-never-places**
|
||||||
|
(see `capture_orchestrator` below) — every other capture entry point writes only
|
||||||
|
a file + index entry.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `capture` — two CONCRETE backends with deliberately different lifecycles (no shared interface — the former `ICaptureBackend` was deleted in Q-W3, T4-26: one deriver, zero polymorphic call sites): `OfflineRenderBackend` (deterministic default, synchronous) and `RealtimeRecordBackend` (async begin/tick/abort). Input: `CaptureRequest`. Output: finished file + populated `Sample` handed to `bank_model`.
|
||||||
|
- `scope_resolve` (`shell/capture`) — scope/source resolution shared by every capture entry point (Q-W3 hoist out of `main.cpp`): razor-else-time range inference, selected-track/selected-item-owning-track collection with canonical GUIDs, and the M10 provenance-assembly inputs (read BEFORE the FX-bypass guard neutralizes the in-scope chain).
|
||||||
|
- `capture_orchestrator` (`shell/capture`) — single-capture orchestration + the realtime/insert action bodies (Q-W3 hoist, T4-02): `renderOffline` (one offline render under the scope's FX-bypass guard), `captureAndIndexOne` (render + provenance stamp + bank add + owned-manifest record, unpersisted), `RunCapture`/`RunCaptureItemAssign`, `RunCaptureRealtimeTrack`/`RunCancelRealtime` (the realtime action bodies — the in-flight state lives in `realtime_lifecycle`), and `RunInsertSelected` (the ONE deliberate exception to capture-never-places).
|
||||||
|
- `capture_batch` (`shell/capture`) — the batch-capture family + re-capture-from-source (Q-W3 hoist, T4-02): `RunBatchCaptureItems` (one sample per selected item), `RunBatchCaptureRazor` (one sample per razor area), `RunRecaptureFromSource` (regenerate a provenanced sample from its recorded source's current state, bank-only). Every unit routes through `capture_orchestrator` so every precision invariant holds; persist is batched to one ext-state write per action.
|
||||||
|
- `realtime_lifecycle` (`shell/capture`) — the in-flight realtime-capture state machine + globals (Q-W3 hoist): the action starts it, `OnTimer` drives it per tick via `DriveRealtimeCapture` (a single-pointer-test idle fast path — load-bearing hot-path guardrail), `CommitRealtimeResult` lands a finished capture in the bank, `AbortRealtimeCaptureForUnload` tears down cleanly on extension unload.
|
||||||
|
- `capture_realtime_shell` (`shell/capture`) — the async realtime-record backend surface (Q-W6 split of the former fat `capture.h`): `RealtimeRecordBackend::begin`/`tick`/`abort`, transport-driven across timer ticks (a realtime record cannot block REAPER's UI for its own duration). Deliberately shares NO interface with the offline backend — the lifecycles genuinely differ (the former `ICaptureBackend` interface was deleted in Q-W3, T4-26).
|
||||||
|
- `capture_realtime_finalize` (`shell/capture`) — the file-side half of the realtime-record shell (Q-W3, T4-08): discovers the file REAPER actually recorded, moves it into the bank, runs the Auto-tail PCM decay-scan trim, and populates the finished `Sample`.
|
||||||
|
- `insert` — placement via `InsertMedia`. **Conform-to-project-tempo is an explicit opt-in flag, never silent stretching.**
|
||||||
|
- `provenance_shell` — FX-chain identity queries via `TrackFX_*`/`TakeFX_*` APIs; feeds the pure `provenance` fingerprint builder. Stamps `Sample.provenance` on capture; ambiguous/mixed cases record nothing conservatively.
|
||||||
|
- `track_guid` — shared `MediaTrack*` → canonical GUID-string formatter; single source of truth for membership keys.
|
||||||
|
- `item_read` — the ONE place a `MediaItem*` is read for its canonical GUID string (`itemGuid`) and for the durable `P_LANENAME` of the fixed lane it sits on (`itemLaneName`); extracted from previously-duplicated `itemGuid`/`itemLaneName` pairs in `view.cpp` and `bank_panel.cpp` — the item-read analog of `track_guid`'s single `MediaTrack*`→GUID-key formatter. Callers must already know the track is fixed-lane (`I_FREEMODE==2`) before calling `itemLaneName`; the pure `isOnManualLane` predicate handles the non-fixed-lane case separately.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- This directory's governing precision invariants are the repo-wide capture
|
||||||
|
invariants in root `CLAUDE.md`, not a standalone spec block here.
|
||||||
|
- `capture` and `capture_realtime_shell` deliberately share NO common interface with
|
||||||
|
each other (the former `ICaptureBackend` was removed) — do not reintroduce one
|
||||||
|
without a real second polymorphic call site.
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# src/shell/instrument — ReaSampler 9000 VST3 shells
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The REAPER/VST3-facing shells for the ReaSampler 9000 instrument: the read-only bank
|
||||||
|
bridge, the processor, the editor, the embed strip, and the VST3 entry point — plus
|
||||||
|
two small identity/helper headers this directory owns outright
|
||||||
|
(`reasampler_vst.h`, `editor_internal.h`).
|
||||||
|
|
||||||
|
The pure engine/geometry core this shell wraps (`sampler_core`, `pitch_shift`,
|
||||||
|
`sample_map`, `component_state_io`, `zone_params.h`, `editor_geometry`,
|
||||||
|
`keyboard_strip`, `waveform_view`, `capture_browser`, `browser_scroll`, `note_entry`,
|
||||||
|
`param_slider`, `trigger_seam`, `velocity_curve`, `embed_strip`, `knob_deck`,
|
||||||
|
`curve_popup`, `master_gain`, `reasampler_uid.h`) lives in `core/instrument/*` and
|
||||||
|
`core/wire` and is documented there — this directory consumes it but does not own it.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
**The build shape (D-A, settled 2026-07-26 — bare Steinberg VST3 SDK + LICE editor).**
|
||||||
|
Bare Steinberg VST3 SDK, no JUCE, with the editor drawn in the same LICE/SWELL stack
|
||||||
|
`bank_panel` already uses. `SingleComponentEffect` (the SDK's combined
|
||||||
|
processor+controller base) plus the SDK's factory macros is the audio-processing
|
||||||
|
scaffolding. Drawing the editor in a VST3 `IPlugView` that hosts a LICE surface reuses
|
||||||
|
the `bank_panel` docking muscle, keeps the look house-consistent, and avoids JUCE's
|
||||||
|
AGPL-or-pay license posture. The `IPlugView`↔LICE bridge (window lifecycle, sizing,
|
||||||
|
event routing from the host into the draw/hit-test loop) is the same class of work as
|
||||||
|
docking `bank_panel`, not a new competence.
|
||||||
|
|
||||||
|
**Embedded TCP/MCP UI (D-D) — `reasampler_embed`.** A REAPER-hosted VST3 can draw its
|
||||||
|
own UI inline in the track/mixer control panel via `reaper_plugin_fx_embed.h` (the
|
||||||
|
plugin implements `IReaperUIEmbedInterface` — the same Cockos surface REAPER's own
|
||||||
|
embedded FX use). Because this uses the same LICE-class drawing as the main editor
|
||||||
|
path, it composes naturally with the bare-SDK-plus-LICE build. **Must-verify:** the
|
||||||
|
`IReaperUIEmbedInterface` contract and embed message/lifecycle against
|
||||||
|
`vendor/reaper-sdk/sdk/reaper_plugin_fx_embed.h`.
|
||||||
|
|
||||||
|
**Channel mode (D-E) — current reality.** An earlier design (D-E, settled
|
||||||
|
2026-07-26) specified a per-instance mono/stereo toggle negotiating the REAPER
|
||||||
|
audio bus via `setBusArrangements`/`getBusArrangement`, with mono-source+stereo-mode
|
||||||
|
→ dual-mono and stereo-source+mono-mode → downmix as the cross-mode policy. **This
|
||||||
|
was superseded by the GA post-launch DAW-fix pass**: the output bus is now
|
||||||
|
permanently stereo, `ChannelMode` is decode-only (the dynamic mono↔stereo bus
|
||||||
|
renegotiation from the earlier design was deleted), and channel mode auto-defaults
|
||||||
|
from the loaded capture via `ComponentState` v9's `channelModeExplicit` flag + the
|
||||||
|
pure `channelModeFor` helper. Root `CLAUDE.md` is authoritative for this behavior —
|
||||||
|
do not reintroduce per-instance bus renegotiation.
|
||||||
|
|
||||||
|
**VST3 channel identity — the UID pair + the pairing surface (S18).** A beta-built VST
|
||||||
|
pairs with the beta extension only, a stable VST with stable only, both installable
|
||||||
|
side-by-side in one REAPER — one channel per binary; all channel identity derives from
|
||||||
|
the ONE `REASAMPLER_CHANNEL_IS_BETA` bit via the pure `app_version` module (no
|
||||||
|
scattered `#ifdef`s in the VST shell, except the one described below).
|
||||||
|
- **The UID-pair invariant is a permanent commitment.** The VST3 class UID is the
|
||||||
|
plugin's identity — a saved REAPER project records it and rebinds a saved instance
|
||||||
|
by it. BOTH channel UIDs (`reasampler_uid.h`'s stable + beta pairs) are frozen
|
||||||
|
forever once shipped; the channel bit selects which one is compiled into this
|
||||||
|
binary (one `DEF_CLASS2`, one class per binary — never both classes in one binary).
|
||||||
|
The UID selection is the ONLY channel `#ifdef` in the VST shell, because an
|
||||||
|
`INLINE_UID` needs literal brace-init tokens and cannot route through
|
||||||
|
`app_version`'s runtime string accessors.
|
||||||
|
- **Binary + display identity are channel-derived**, sourced from `app_version`'s
|
||||||
|
VST-name accessors — never a literal in `reasampler_vst.h`/`vst_entry.cpp`.
|
||||||
|
- **The complete pairing surface is structural, not per-key.** Plugin identity
|
||||||
|
(UID + filename + display) is channel-forked, and all wire keys live under the
|
||||||
|
channel-derived ext-state namespace — the two together make pairing complete: no
|
||||||
|
per-key or per-seam isolation work is ever needed for a new wire key.
|
||||||
|
- **Verify** all identity/factory wiring against the vendored Steinberg SDK
|
||||||
|
(`DEF_CLASS2` / `INLINE_UID` / `FUID` from `pluginfactory.h` + `funknown.h`).
|
||||||
|
|
||||||
|
**Non-goals / guardrails.**
|
||||||
|
- The instrument never captures and never inserts into the arrange. Playback is a
|
||||||
|
read-only act over the bank. Any instrument path that captures, places a timeline
|
||||||
|
item, or writes back into the bank is a bug.
|
||||||
|
- The instrument never ingests. Capture, import, and drop-ingest are *extension*
|
||||||
|
acts; the instrument only reads and plays. A drop onto the editor window (if ever
|
||||||
|
shipped) is relayed to the extension as an ingest request — the instrument never
|
||||||
|
writes the bank itself.
|
||||||
|
- No cross-platform / multi-format. Windows-only, VST3-only, REAPER-only (D5). Do not
|
||||||
|
add an AU/AAX/VST2/CLAP wrapper, a mac/Linux build, or a standalone host target.
|
||||||
|
- The pure core stays REAPER-free *and* VST3-free — the voice engine / envelope /
|
||||||
|
keymap / repitch module takes no VST3 or REAPER type at its boundary; the shell
|
||||||
|
marshals. Any VST3 or REAPER type leaking into `core/instrument` is a bug.
|
||||||
|
- Verify Steinberg SDK, bridge, embed, and LICE-view surfaces against the vendored
|
||||||
|
headers before use.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `reaper_bridge` — READ-ONLY bank consumer: receives bank snapshots from the extension and exposes them as a read-only view. **Never writes to the extension's bank** — this is a load-bearing invariant; no mutation path exists in this module. **pS-usage:** gains `writeUsageExtState` (prefix-guarded — accepts only `rsusage_`-prefixed keys, refuses all others) so the processor can publish usage without weakening the read-only-bank invariant.
|
||||||
|
- `reasampler_processor` (`shell/instrument/`: `reasampler_processor.cpp` lifecycle + `process()`, `processor_state.cpp` component-state I/O + UI-thread parameter accessors, `processor_reload.cpp` the off-audio-thread `reloadInstrument`/publish family — Q-W2v, T4-12 split; `process()` and its per-block work stay ONE TU on purpose, no cross-TU call on the per-sample path) — VST3 `SingleComponentEffect` shell: declares event-input bus + **permanently stereo** output (GA fix: dynamic mono↔stereo bus renegotiation deleted; `ChannelMode` is now decode-only), marshals MIDI note-on/off into the VoiceEngine, renders audio; owns off-audio-thread `reloadInstrument` + atomic pointer swap so `process()` does no allocation, no file I/O, no bridge calls. **Self-contained playback (pS):** `ComponentState` v10 adds a `SampleRefs` table — per referenced sample, a project-relative path + decode intrinsics (root, loop, channels, displayName); `reloadInstrument` decodes directly from `SampleRefs`, bank-free (plays with the extension absent). The bank/bridge is a browser source: loading a capture copies its reference in; the reopen-heal timer + poll-to-play apparatus are removed. `retireIdleDrain()` retires fully-idle drain snapshots on the UI-timer cadence. Voice-param edits (`setVoiceCount`/`setVoiceMode`/`setMonoTrigger`) rebuild the engine from the already-decoded keymap via the drain-slot swap — no bank re-read, no WAV re-decode, no audible cut to ringing tails. **FB1:** applies the post-mixer `masterGainLinear` (from `ComponentState` v8) as a per-sample ramp over the summed output — no zipper noise. **GA v9:** `channelModeExplicit_` flag persisted; `channelModeFor()` auto-defaults the mode from the loaded capture's channel count when the flag is not set. **pS:** `ComponentState` bumped v9→v10 (`SampleRefs` table); pre-v10 blobs lift to empty refs and re-save self-contained. **pS-usage:** publishes instance usage (held `SampleRefs` paths) to `rsusage_<instanceGuid>` at the tail of `reloadInstrument` (off audio thread) via `reaper_bridge::writeUsageExtState`; `ComponentState` bumped v10→**v11** (`instanceGuid` field); pre-v11 blobs mint guid on first publish.
|
||||||
|
- `reasampler_editor` (`shell/instrument/`: eight face-axis TUs — `editor_session` session/bridge state, `editor_controls` parameter plumbing, `editor_paint_sample`/`editor_paint_browse_zone` paint, `editor_input_sample`/`editor_input_browse_zone` input, `editor_platform` IPlugView/Win32 window plumbing, plus the pure `editor_geometry` layout hoist as the eighth axis; shared internals in `editor_internal.h`, no TU of its own — Q-W2v, T4-11 split of the former god-TU) — VST3 `IPlugView` LICE editor shell: hosts a LICE-drawn child window; default face is the capture browser, then single-capture setup, with opt-in zones panel. Drop-onto-editor ingest is NOT shipped (deferred).
|
||||||
|
- `reasampler_embed` — implements `IReaperUIEmbedInterface` so the instrument draws inline in the TCP/MCP without a plugin-owned HWND; delegates layout/hit-test to `embed_strip`.
|
||||||
|
- `vst_entry` — VST3 entry point: `GetPluginFactory` export, class registration, channel-forked class UIDs.
|
||||||
|
- `editor_internal.h` — INTERNAL shared helpers for the `reasampler_editor` TU family (Q-W2v split), included only by the editor's own shell TUs (`editor_session` / `editor_controls` / `editor_paint_*` / `editor_input_*` / `editor_platform`), never a public seam: the `Rect`↔kit adapters, small draw primitives (knob face / spectral strip / root marker / title band), label helpers, deck group ids, and the velocity-curve box derivation — the former god-TU's anonymous-namespace helpers that more than one split TU needs. *(Newly authored per this dispatch's brief — no existing root-CLAUDE.md bullet; verified by reading `src/shell/instrument/editor_internal.h`'s own header comment and body.)*
|
||||||
|
- `reasampler_vst.h` — shared identity constants for the ReaSampler VST3 instrument (Phase S): the plugin's class UID (the channel-selected `Steinberg::FUID`, built from the FOREVER-FROZEN macros in `core/wire/reasampler_uid.h`), vendor name/URL/email, so the processor, factory, and editor agree. A class UID is FOREVER-STABLE once shipped — minted once, never regenerated. *(Newly authored per this dispatch's brief — no existing root-CLAUDE.md bullet; verified by reading `src/shell/instrument/reasampler_vst.h` directly.)*
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- `editor_internal.h` is include-only — it has no TU of its own and must never become
|
||||||
|
a public seam; only the eight `reasampler_editor` face-axis TUs include it.
|
||||||
|
- The two VST3 class UIDs (`core/wire/reasampler_uid.h`, consumed via
|
||||||
|
`reasampler_vst.h`) are FOREVER-FROZEN — never regenerate an already-shipped UID.
|
||||||
|
- The UID selection `#ifdef` in `reasampler_vst.h` is the one deliberate exception to
|
||||||
|
"channel identity derives from `app_version` accessors, no scattered `#ifdef`s" —
|
||||||
|
`INLINE_UID` needs literal brace-init tokens, so it can't route through a runtime
|
||||||
|
string accessor.
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# src/shell/panel — the docked bank-panel shell + the shared LICE draw kit
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The REAPER-facing shell for the docked bank panel: the eight `bank_panel` split TUs
|
||||||
|
(`panel_window` / `panel_layout` / `panel_render` / `panel_input` / `panel_drag` /
|
||||||
|
`panel_thumbnails` / `panel_audition` / `panel_bank_ops`, sharing state via
|
||||||
|
`panel_state.h`), plus `draw_kit`, the shared LICE draw shell also consumed by the
|
||||||
|
VST3 editor (`shell/instrument/`).
|
||||||
|
|
||||||
|
Pure layout/hit-test/palette modules the panel draws through (`theme`,
|
||||||
|
`component_geometry`, `bank_grid`, `tab_strip`, `mode_switch`, `action_bar`,
|
||||||
|
`footer_bar`, `overflow_menu`, `prune_button`, `mode_enable`, `tooltip`, `card_drag`,
|
||||||
|
`card_meta`) live in `core/ui` / `core/model` and are documented there — this
|
||||||
|
directory consumes them but does not own them. The promptless bank-mutation verbs
|
||||||
|
(`bankOpCreate`/`Rename`/`Delete`/… + `persistBankOp`) that `panel_bank_ops` skins
|
||||||
|
live in `shell/bank_ops`, a sibling directory, not here.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- **Draw through the kit, by palette ROLE, not hardcoded hue.** The shared LICE-based
|
||||||
|
drawing kit is the one source of drawing for the whole system — a button, row,
|
||||||
|
slider, or waveform looks identical in the bank panel, the embed strip, and the VST
|
||||||
|
editor because it is the same kit function, drawn against palette roles (`bg/base`,
|
||||||
|
`bg/panel`, `bg/cell`, `accent/primary`, `accent/secondary`, `accent/tertiary`,
|
||||||
|
`accent/hot`, `text/primary`, `text/dim`, `line/hairline`, `warn`) rather than a
|
||||||
|
literal color. The kit palette stays abstract (role→color, one constants block), so
|
||||||
|
a whole visual direction is a single-file change (Phase L, DS-1/DS-2).
|
||||||
|
- **Dock-panel layout is a thorough redesign, not a light re-skin (DS-3).** The panel
|
||||||
|
lays out the full button/affordance inventory intuitively and uncluttered, then
|
||||||
|
applies the kit — but this does **not** restructure the panel bones: the
|
||||||
|
vertical-split / grid / tab structure is sound and stays as-is; a redesign designs
|
||||||
|
the layout of the button inventory *around* it, not through it.
|
||||||
|
- **What Phase L does not change (Precision / invariant implications):**
|
||||||
|
- The pure/shell split holds — all layout/hit-test stays in pure CTest-covered
|
||||||
|
geometry modules; the kit's *draw* half is shell, its *geometry* half is pure,
|
||||||
|
even where a WDL piece is reused. No hit-test math moves into untestable code.
|
||||||
|
- RT discipline is untouched — the kit is draw-thread only; nothing here touches
|
||||||
|
`process` or any off-thread reload handoff (a VST3-instrument concern).
|
||||||
|
- Read-only-over-bank is untouched — this is look-and-feel; no data-ownership
|
||||||
|
change.
|
||||||
|
- The capture/placement load-bearing principle is untouched — Phase L draws; it
|
||||||
|
does not capture, place, or mutate the bank.
|
||||||
|
- VST3 class UID / component-state contract is unchanged.
|
||||||
|
- Windows-only (D5) — font/GDI/HFONT choices assume Windows; no cross-platform
|
||||||
|
font-fallback concern.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `bank_panel` (`shell/panel/`: `panel_window` / `panel_layout` / `panel_render` / `panel_input` / `panel_drag` / `panel_thumbnails` / `panel_audition` / `panel_bank_ops`, sharing state via `panel_state.h` — Q-W2 split of the former god-module into eight TUs) — docked LICE-drawn grid with three-zone layout: top toolbar (Capture → Maintenance → Placement via `action_bar`, short labels, More (⋯) overflow menu via `overflow_menu`), bottom toolbar (four opposite-mode tag buttons + Show Both), and footer (`[Arrange|Design]` toggle, Tail button, Prune via `footer_bar`). Grid renders in sparse slot order with gap cells, drop dispatch, metadata overlay, and selection via `accent/tertiary` purple border. Draws through the L1 kit by palette role; OS drag-out via `drag_out` + `drag_out_win`. `panel_window` owns the SWELL dialog lifecycle + dialog proc + drop-target opt-in; `panel_layout` the toolbar/footer/menu rects + vertical-split geometry (the one geometry source both paint and hit-test read); `panel_render` the WM_PAINT draw; `panel_input` click/wheel/keyboard routing + the new-content auto-tag timer; `panel_drag` the hover + card-drag state machine + drop dispatch; `panel_thumbnails` the PCM→envelope thumbnail cache + the bank-change fingerprint pass; `panel_audition` the preview-playback engine; `panel_bank_ops` the menu/prompt UX skin over the promptless `shell/bank_ops` verbs. `draw_kit` (shared with the VST3 editor) stays a separate TU.
|
||||||
|
- `panel_window` — SWELL dialog lifecycle + dialog proc + drop-target opt-in.
|
||||||
|
- `panel_layout` — toolbar/footer/menu rects + vertical-split geometry (the one geometry source both paint and hit-test read).
|
||||||
|
- `panel_render` — the WM_PAINT draw.
|
||||||
|
- `panel_input` — click/wheel/keyboard routing + the new-content auto-tag timer.
|
||||||
|
- `panel_drag` — the hover + card-drag state machine + drop dispatch.
|
||||||
|
- `panel_thumbnails` — the PCM→envelope thumbnail cache + the bank-change fingerprint pass.
|
||||||
|
- `panel_audition` — the preview-playback engine.
|
||||||
|
- `panel_bank_ops` — the menu/prompt UX skin over the promptless `shell/bank_ops` verbs.
|
||||||
|
- `draw_kit` — shared LICE draw shell: `fillSurface`, `drawButton`/`drawSlider`/`drawListRow`/`drawWaveform`, cached-font `text()`, full interaction-state model, double-buffer preserved. Consumes `theme` + `component_geometry`.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- **No external UI framework.** iPlug2 / JUCE / VSTGUI are rejected (DS-1). LICE +
|
||||||
|
reused WDL pieces are the toolkit; reject any path that pulls in a new framework.
|
||||||
|
- **No hit-test geometry in untestable shell code.** Even when reusing a WDL piece
|
||||||
|
(e.g. a `vwnd` control for a long scroll list), layout/hit-test math stays in pure
|
||||||
|
CTest-covered modules — do not import a WDL control's retained-mode object model
|
||||||
|
wholesale, since its controls own their hit-test internally and that would move
|
||||||
|
geometry into untestable shell code.
|
||||||
|
- Verify every LICE/WDL/SWELL API name/signature against `vendor/WDL` before use.
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# src/shell/persist — project ext-state persistence, prune filesystem I/O, usage scan
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The persist seam: project ext-state read/write (`session` / `ext_state_io`), the
|
||||||
|
prune path's filesystem half (`prune_fs`), and the extension-side instance-usage
|
||||||
|
scan (`usage_scan`) that feeds prune's referenced-set. Internal helpers shared only
|
||||||
|
within the persist TU family live in `persist_internal.h`. The pure orphan
|
||||||
|
computation is owned elsewhere (`core/reclaim`); the pure usage wire is owned
|
||||||
|
elsewhere (`core/wire`) — this directory is the REAPER/filesystem-facing half only.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- **Prune is the single, exclusive file-deletion authority.** No bank op, no
|
||||||
|
capture op, no Design View op deletes a file; if any path other than prune
|
||||||
|
deletes a bank file, reject it in review.
|
||||||
|
- **Dry-run first, always; no silent deletion.** Prune reports before it deletes
|
||||||
|
(orphan count, reclaimed size, and — for a small set — the files); actual
|
||||||
|
deletion is a confirmed second step. No periodic/background sweep.
|
||||||
|
- **Referenced-set is the union across ALL banks, pool included**, further unioned
|
||||||
|
(pS-usage) with every live instance's held paths via `usage_scan` →
|
||||||
|
`prune_reconcile::mergeReferenced`. A file is an orphan iff no bank AND no live
|
||||||
|
instance references it.
|
||||||
|
- **Safest platform deletion available.** Trash-preferred, unlink fallback — Windows
|
||||||
|
routes through `SHFileOperationW` (`FOF_ALLOWUNDO`, verified against SDK
|
||||||
|
10.0.26100); macOS/Linux fall back to unlink (no portable SWELL trash surface).
|
||||||
|
`prune_fs` is the only module that calls this.
|
||||||
|
- **Manual, explicit trigger only** — a bindable action + a `bank_panel` button,
|
||||||
|
never a silent background sweep.
|
||||||
|
- **Instance-usage fail-safe (pS-usage):** a capture held by any live ReaSampler
|
||||||
|
9000 instance can never be deleted by prune. If any `rsusage_*` record is
|
||||||
|
unreadable or ambiguous, prune **aborts entirely and deletes nothing** —
|
||||||
|
over-protection is the accepted residual, under-protection is a data-loss bug.
|
||||||
|
`usage_scan` decodes every `rsusage_*` key, enumerates every ReaSampler 9000 FX
|
||||||
|
instance (all tracks incl. master, normal + record/input chains, containers
|
||||||
|
recursively, take FX), and folds via the pure `sample_usage::foldUsageRecords` /
|
||||||
|
`usageHeldPaths` (a record with no live instance context protects all its paths —
|
||||||
|
identity-failure net, never degrades toward delete). This is read-only at
|
||||||
|
prune-scan time: `usage_scan` writes no ext-state.
|
||||||
|
- **`PruneReport` carries `abortedUnreadableUsage` + `offendingUsageKeys`**; dry-run,
|
||||||
|
orphan-set, and reclaim each independently abort (delete nothing) when usage
|
||||||
|
state is unreadable. `BANK_PRUNE_FOLDER` (in `shell/actions`) halts on this flag
|
||||||
|
and prints the offending keys.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `shell/persist` (`session` / `ext_state_io` / `prune_fs`) — the persist seam, split by responsibility (Q-W5; the former `persist.cpp` god-TU and its `persist.h` compatibility umbrella are both retired — callers include `shell/persist/session.h` / `ext_state_io.h` directly). `session` owns the `ReaSamplerSession` lifecycle: the poll identity-transition detection (load / Save-As / forked sibling / recycled pointer) and the `projectconfig`-driven deferred undo/redo reload. `ext_state_io` owns project ext state (`SetProjExtState`/`GetProjExtState`, namespace `"reasampler"`) ↔ `BankBook` JSON, `ViewModeModel` JSON, `TailSetting` JSON, `OwnedManifest` JSON, the writing-version stamp, GUID minting, and bank-folder relocation. `prune_fs` hosts the prune dry-run / full-set orphan queries (supplying `referencedPaths()` + `owned().paths()` to the `prune_reconcile` pure core) and is **the single file-deletion authority over user files in the bank folder** (`deleteOrphanFile` via `SHFileOperationW`); nothing else in the system deletes bank-folder bytes. **pS-usage:** the prune scan unions instance usage via `usage_scan`; `PruneReport` carries `abortedUnreadableUsage` + `offendingUsageKeys`; dry-run / orphan-set / reclaim each independently abort (delete nothing) when usage state is unreadable.
|
||||||
|
- `usage_scan` — extension-side prune-scan shell (pS-usage): at prune-scan time, enumerates every `rsusage_*` ext-state key, decodes each `sample_usage` wire record, enumerates every ReaSampler 9000 FX instance across all tracks + master / normal + record chains / containers (recursive) / take FX, and folds with `sample_usage::foldUsageRecords` / `usageHeldPaths` to produce the set of held paths — or `abortPrune` when any record is unreadable (fail-safe: an unreadable record may protect anything, so the prune halts). Feeds `prune_reconcile::mergeReferenced`. Read-only: writes no ext-state.
|
||||||
|
- `persist_internal.h` — internal-only shared helpers for the persist TU family (`session` / `ext_state_io` / `prune_fs`); included only by those three TUs, never a public seam (mirror of the panel's `panel_state.h` / the editor's `editor_internal.h` precedent). Holds the former anonymous-namespace helpers more than one split TU needs (active-project + `.rpp` path lookup, project-dir derivation, growing `GetProjExtState` read, project-GUID minting, bank-folder relocation) — all definitions live in `ext_state_io.cpp`. REAPER-free header: the project handle crosses this seam as the same opaque `void*` the public `session` header already uses.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- The pure orphan computation (`prune_reconcile`, `(owned ∩ present) − referenced`)
|
||||||
|
is documented under `core/reclaim`, not here — do not duplicate its spec in this
|
||||||
|
file.
|
||||||
|
- The pure usage wire (`sample_usage`: `UsageRecord`, `planUsagePublish`,
|
||||||
|
`foldUsageRecords`/`usageHeldPaths`, `identityMatches`) is documented under
|
||||||
|
`core/wire`, not here.
|
||||||
|
- `persist_internal.h` is an internal seam, not a public header — do not include it
|
||||||
|
outside `session.cpp` / `ext_state_io.cpp` / `prune_fs.cpp`.
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# src/shell/view — Design View mode application shell
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The REAPER-facing half of Design View: applying a mode's visibility/processing
|
||||||
|
state to live tracks (park/restore), snapshotting flag values before parking, and
|
||||||
|
restoring from snapshot on toggle-back. The mode registry, membership derivation,
|
||||||
|
and the pure park/restore planner are owned by `core/view` (`view_mode_model`) —
|
||||||
|
this directory is the shell that reads/writes REAPER track flags, it does not
|
||||||
|
decide membership or mode rules.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- **Never touches master or `B_MUTE`/`I_SOLO`.** The tool owns only visibility,
|
||||||
|
`B_MAINSEND`, `I_FXEN`, and per-FX offline, on every managed leaf, tagged or
|
||||||
|
untagged. User mute/solo survives every toggle untouched; the master track's
|
||||||
|
visibility flags are never driven (the SDK forbids `B_SHOWINTCP`/`B_SHOWINMIXER`
|
||||||
|
on master).
|
||||||
|
- **Parking a track** (inactive-mode leaf) drives `B_SHOWINTCP=0`, `B_SHOWINMIXER=0`
|
||||||
|
(hide both panels), `B_MAINSEND=0` (out of mix), `I_FXEN=0` (FX bypassed), and
|
||||||
|
`TrackFX_SetOffline(track, fx, true)` for each FX (reclaim CPU) — full CPU-park,
|
||||||
|
not mix-removal-only.
|
||||||
|
- **Non-destructive restore.** For every flag the tool drives, snapshot the prior
|
||||||
|
value BEFORE parking; on toggle-back restore FROM the snapshot, never to a
|
||||||
|
hardcoded "on." Round-trip (snapshot → park → restore) returns every driven flag
|
||||||
|
to its captured value — the phase's trust anchor, the analog of the capture null
|
||||||
|
test.
|
||||||
|
- **GUID-keyed, reorder-safe.** Membership/snapshot keys on track GUID
|
||||||
|
(`GetTrackGUID`), never track index; tolerates unknown/stale GUIDs (pruned on
|
||||||
|
reconcile).
|
||||||
|
- **Relative/portable state only** in the persisted view section (GUID strings,
|
||||||
|
mode ids — no absolute paths, no index positions).
|
||||||
|
- **Documented caveat:** offlined FX re-instantiate when a track returns to the
|
||||||
|
active mode — stateful plugins (convolution, loaded samplers, tail-holding
|
||||||
|
effects) re-initialize on return (possible load hitch, un-persisted internal
|
||||||
|
state lost). Accepted cost of the CPU reclaim; surfaced at the toggle affordance
|
||||||
|
(tooltip).
|
||||||
|
- **Show-both semantics:** a per-track "pin visible across modes" flag re-enables
|
||||||
|
processing whenever shown. A show-both leaf appears in every mode's visible set
|
||||||
|
and is never parked — its driven flags stay at snapshot/restored values, FX
|
||||||
|
online, in the mix. ("Show but keep parked" is not offered.) Stored on the
|
||||||
|
membership record; persists; togglable per selection.
|
||||||
|
- **No literal second canvas.** A literal second arrange surface, a second window,
|
||||||
|
or a duplicated project stays rejected — reject any such path in review.
|
||||||
|
|
||||||
|
**Two-canvas sub-phase (Phase D2/E) — settled and landed parts, DAW-application half:**
|
||||||
|
|
||||||
|
- **Fixed-lane item-level separation mechanics.** Map mode → lane; toggle drives
|
||||||
|
per-lane play/show so only the active mode's lane is present. Items keep their
|
||||||
|
real position and real track — nothing is moved in time or deleted. SDK surface
|
||||||
|
(verified present in `vendor/reaper-sdk`): track-side `I_FREEMODE = 2`,
|
||||||
|
`I_NUMFIXEDLANES`, `C_LANEPLAYS:N`; item-side `I_FIXEDLANE`, `C_LANEPLAYS`,
|
||||||
|
`B_FIXEDLANE_HIDDEN`. `I_FREEMODE` changes require `UpdateTimeline()` to take
|
||||||
|
visible effect.
|
||||||
|
- **Inactive-mode content is hidden AND silenced.** The off-mode lane is set
|
||||||
|
`C_LANEPLAYS = 0` — neither shown nor played — consistent with exclusive
|
||||||
|
membership and with D1's "flipping modes is a real change, not cosmetic."
|
||||||
|
Show-both is the deliberate opt-out for a lane that must stay audible across
|
||||||
|
modes.
|
||||||
|
- **Capture placement is mode-aware.** An explicit placement while in Design mode
|
||||||
|
— including capture-and-place — lands the item in the Design lane; the same
|
||||||
|
rule governs manual insertion. The capture load-bearing principle is untouched:
|
||||||
|
capture still writes a file + index entry and never auto-inserts; this governs
|
||||||
|
only *where* an explicit placement lands.
|
||||||
|
- **REAPER floor: v7** for this sub-phase (fixed lanes shipped in v7); no
|
||||||
|
version-gate branch — below v7 the sub-phase is simply unavailable.
|
||||||
|
|
||||||
|
Item→lane membership rules (the adoption rule, exclusive-per-item membership, the
|
||||||
|
managed/manual lane distinction, and the lane-ownership index) are model concepts
|
||||||
|
owned by `core/view` — see that directory's Invariants; this directory only
|
||||||
|
applies the resulting lane state to live tracks.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `view` — Design View shell: snapshots flag values before parking, drives hide + CPU-park on inactive-mode leaves (`B_SHOWINTCP`/`B_SHOWINMIXER`/`B_MAINSEND`/`I_FXEN` + per-FX offline), restores from snapshot. **Never touches master or `B_MUTE`/`I_SOLO`.**
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- The pure mode model (`ViewModeModel`, membership, `reconcile(liveGuids)`, the
|
||||||
|
snapshot-based park/restore planner) lives in `core/view` — reference it, do not
|
||||||
|
duplicate its spec here.
|
||||||
|
- The Two-canvas sub-phase (Phase D2/E)'s settled DAW-application rules
|
||||||
|
(fixed-lane mechanics, mode-aware capture placement, hidden-AND-silenced) are
|
||||||
|
reflected in Invariants above; the membership/lane-ownership model concepts
|
||||||
|
it also covers live in `core/view`'s Invariants.
|
||||||
Reference in New Issue
Block a user