docs: archive Phase V (V1-V4) to COMPLETED; reconcile CLAUDE.md

Phase V landed: app_version module, ext-state version stamp, show-version
action, beta-in-isolation channel build. CLAUDE.md gains the module entry,
version key, test target, and beta-build note.
This commit is contained in:
2026-07-26 16:56:04 -04:00
parent 88e10c18c7
commit 6a6d305cf2
4 changed files with 113 additions and 56 deletions
+13 -2
View File
@@ -35,8 +35,18 @@ Key targets (see CMakeLists.txt for the full list):
| `bank_book_tests` | executable | Pure unit tests for `bank_book` — no REAPER, no DAW. |
| `wav_trim_tests` | executable | Pure unit tests for `wav_trim` — no REAPER, no DAW. |
| `owned_manifest_tests` | executable | Pure unit tests for `owned_manifest` — no REAPER, no DAW. |
| `app_version_tests` | executable | Pure unit tests for `app_version` — no REAPER, no DAW. |
| `reaper_reasampler` | loadable module | The actual extension binary (`.dll` / `.dylib` / `.so`). |
### Beta channel build (Phase V, V4)
To build the fully isolated beta binary (`reaper_reasampler_beta`), pass the channel flag at configure time:
cmake -B build-beta -S . -DREASAMPLER_CHANNEL=beta
cmake --build build-beta
The flag threads through `configure_file``version_generated.h` and fans out via `app_version` into the binary name, ext-state namespace (`"reasampler_beta"`), command-id prefix (`CEREBELLUM_REASAMPLER_BETA_`), action-name prefix (`"ReaSampler beta: "`), dock ident, and version display (`"0.9.01-beta"`). The default build (no flag) is byte-identical to the pre-V4 stable identity.
### macOS / Linux: SWELL dialog resources
`src/resource.rc` must be pre-processed by SWELL's resgen once per platform:
@@ -59,13 +69,14 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde
- `mode_switch` — REAPER-free segment layout + hit-test math for the bank_panel's Design View mode switch; divides a header rectangle into N equal segments and hit-tests a point to a segment. Mirror of `bank_grid`.
- `bank_book` — multi-bank registry (Phase B): an ordered set of banks (pool seeded as bank-zero + named banks), each wrapping a `BankIndex`. Owns create/rename/reorder/delete of named banks, pool privileges (un-deletable/un-renamable/un-evacuable, never zero banks) enforced in-model, active-bank id, index-only move/copy/remove of a sample between or from banks, `hashReferencedElsewhere` cross-bank reference query, JSON round-trip + legacy-`bank_index`→pool migration. Wraps `BankIndex` (bank_model untouched; no `bankId` on `Sample`).
- `owned_manifest` — owned-file manifest seam (Phase B B-cap): the set of project-relative files the capture path itself created, persisted under the `"owned_files"` ext-state key, so Phase R prune can distinguish the bank system's own orphans from hand-dropped files. Deliberately decoupled from `bank_book` — tracks files created, not index membership. Phase R (R1/R2) consumes it; no prune logic here.
- `app_version` — REAPER-free version/channel identity (Phase V, V1+V4): CMake-sourced semver constant (`appVersion()`), ext-state stamp value (`stampVersion()` — numeric triple only, no channel suffix), `parseVersion`/`versionLess`/`classifyWritingVersion`, and the full set of channel-derived identity accessors (`extStateNamespace()`, `commandIdPrefix()`, `actionDisplayPrefix()`, `binaryName()`, `dockTitle()`, `dockIdent()`, `channelCommandId()`, `channelActionName()`). All channel strings derive from the one `REASAMPLER_CHANNEL_IS_BETA` bit threaded in via `configure_file``version_generated.h`; no scattered `#ifdef`s in the shells.
- `wav_trim` — 32-bit-float WAV parse + header-aware truncate plan (RIFF/data size rewrite) for the realtime tail's PCM decay-scan trim (T2). Rejects WAVE_FORMAT_EXTENSIBLE with non-float SubFormat GUID. Depends on `peaks` for the `AudioSample` float alias.
**REAPER-facing shells:**
- `capture``ICaptureBackend` interface; `OfflineRenderBackend` (deterministic default) and `RealtimeRecordBackend`. Input: `CaptureRequest`. Output: finished file + populated `Sample` handed to `bank_model`.
- `insert` — placement via `InsertMedia`; conform-to-project-tempo is an explicit opt-in flag, never silent stretching.
- `bank_panel` — docked LICE-drawn grid: thumbnails, audition, multi-select, keyboard navigation.
- `persist` — project ext state (`SetProjExtState` / `GetProjExtState`, namespace `"reasampler"`) ↔ `BankBook` JSON (`"banks"` key) + `ViewModeModel` JSON (`"view_state"` key) + `TailSetting` JSON (`"tail_setting"` key) + `OwnedManifest` JSON (`"owned_files"` key); project-relative path resolution. A `projectconfig` hook (`BeginLoadProjectState(isUndo)`) triggers a deferred session reload on undo/redo so Ctrl-Z/redo visibly restores book/view/tail/manifest in-session.
- `persist` — project ext state (`SetProjExtState` / `GetProjExtState`, namespace `"reasampler"`) ↔ `BankBook` JSON (`"banks"` key) + `ViewModeModel` JSON (`"view_state"` key) + `TailSetting` JSON (`"tail_setting"` key) + `OwnedManifest` JSON (`"owned_files"` key) + writing-version stamp (`"version"` key, written via `stampVersion()` on every `saveToActiveProject()`); project-relative path resolution. A `projectconfig` hook (`BeginLoadProjectState(isUndo)`) triggers a deferred session reload on undo/redo so Ctrl-Z/redo visibly restores book/view/tail/manifest in-session.
- `view` — Design View shell: reads the folder tree via `view_tree`, snapshots flag values before parking, drives hide + CPU-park on inactive-mode leaves (`B_SHOWINTCP`/`B_SHOWINMIXER`/`B_MAINSEND`/`I_FXEN` + per-FX offline) and derived visibility on parents; restores from snapshot. Never touches master or `B_MUTE`/`I_SOLO`.
- `track_guid` — shared `MediaTrack*` → canonical GUID-string formatter; single source of truth for membership keys used by both the view shell and the actions layer.
- `actions` — registers the capture/placement/slot action family, the Design View action family (toggle active mode, activate Arrange/Design, tag/untag selected tracks, show-both), and the multi-bank action family (create/rename/reorder/delete bank, evacuate, activate, move/copy/remove selected samples); routes each to the modules above via the `command_id`/`gaccel`/`hookcommand` contract. 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.
@@ -75,7 +86,7 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde
- 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.
- REAPER dlopen()s any `reaper_*.dll|dylib|so` found in `UserPlugins/` and calls the `ReaperPluginEntry` export (produced by `REAPER_PLUGIN_ENTRYPOINT`). `rec->GetFunc` resolves API pointers; `rec->Register` plugs extension callbacks in.
- Action registration pattern (preserve this for all new actions):
1. `rec->Register("command_id", (void*)"STABLE_FOREVER_STRING")` — mints a persistent command id. **Never change this string after shipping**; user keybindings key off it.
1. `rec->Register("command_id", (void*)"STABLE_FOREVER_STRING")` — mints a persistent command id. **Never change this string after shipping**; user keybindings key off it. Since Phase V (V4), ids and display names are composed via `channelCommandId(suffix)` and `channelActionName(phrase)` from `app_version` — the FOREVER-STABLE contract applies per channel (stable and beta each have their own permanent id family).
2. `rec->Register("gaccel", &accel)` — puts the action in the Actions list.
3. `rec->Register("hookcommand", ...)` — receives every action fired; claim only your own id, return `false` otherwise.
4. On unload (`rec == nullptr`), mirror-unregister everything with the same strings prefixed by `'-'`.
+91
View File
@@ -657,6 +657,97 @@ items exempt; one undo block per action. ctest 14/14 green.
---
# Phase V — Versioning & release
> **New pillar, own lettered namespace.** Version scheme + beta side-channel.
> Namespaced **`V` (Versioning)** alongside `M`/`D`/`B`/`R` — a distinct concern
> (build identity + channel isolation) that touches CMake, `main.cpp`'s
> forever-stable command-id contract, and the `"reasampler"` ext-state. Product
> framing + full option analysis: `docs/product/versioning-and-release.md`.
> Deploy/CD wiring (two named artifacts per platform) hands off to dev-ops.
## V1/V3 — app_version module: version constant, ext-state stamp, show-version action
**Goal:** Pure `app_version` module — single-source semver from CMake
`REASAMPLER_VERSION "0.9.01"` via `configure_file` → `version_generated.h`;
ext-state writing-version stamp under key `"version"` riding
`saveToActiveProject()`; absent stamp = silent pre-versioning; on-demand
`"ReaSampler: show version"` action (no startup print). New CTest target
`app_version_tests`.
**Verify:** CTest green. Stamp written under `"version"` key on every
`saveToActiveProject()` call. Absent key classifies as `PreVersioning` (silent).
Show-version action fires on demand only.
- [x] `app_version` pure module (`src/app_version.{h,cpp}`): exports the CMake
version string constant (`appVersion()`), the ext-state stamp value
(`stampVersion()` — numeric triple only, no channel suffix), `parseVersion`,
`versionLess`, `classifyWritingVersion` (empty → `PreVersioning`; unparseable →
`Unknown`; well-formed → `Stamped`). No REAPER types; standard library only.
- [x] `configure_file` wires `REASAMPLER_VERSION` (the one CMake variable) +
`REASAMPLER_CHANNEL_IS_BETA` into `version_generated.h` in the build tree;
`app_version` reads from there — one edit re-threads the version string through
every consumer.
- [x] Writing-version stamp: `persist` calls `SetProjExtState` under
`kProjExtVersionKey` (`"version"`) with `stampVersion()` inside
`saveToActiveProject()` on every save. Absent key on load → `PreVersioning`
(silent; graceful for pre-versioning projects).
- [x] On-demand show-version action (`channelCommandId("SHOW_VERSION")` /
`channelActionName("show version")`): prints the CMake-sourced `appVersion()`
string to the console when fired. **No unconditional startup print** (no version
line added to the extension load message).
- [x] `app_version_tests` CTest target: version parse/compare/classify round-trip;
`PreVersioning` on empty; `Unknown` on malformed; `Stamped` on well-formed;
`versionLess` numeric ordering (10 > 9, not lexicographic).
## V4 — beta-in-isolation: fully isolated coexisting binary via compile-time channel flag
**Goal:** Compile-time channel flag `-DREASAMPLER_CHANNEL=beta` → fully isolated
`reaper_reasampler_beta` binary: ext-state namespace `reasampler_beta`, FOREVER-STABLE
command-id prefix `CEREBELLUM_REASAMPLER_BETA_`, `"ReaSampler beta: "` action names,
channel-qualified dock title/ident, `0.9.01-beta` display render, bank-panel footer
version/channel readout. Stable build byte-identical to prior identity.
**Verify (in DAW):** Both binaries load simultaneously in one REAPER via the startup
dlopen. Stable produces no change to any existing action id, ext-state key, or panel
string. Beta reads/writes only `"reasampler_beta"` namespace; its actions carry
`CEREBELLUM_REASAMPLER_BETA_` prefix; its panel shows `0.9.01-beta`. No shared-state
collision path between channels.
- [x] `app_version` extended as the single source of truth for channel identity (V4):
`channel()`, `isBeta()`, `extStateNamespace()`, `commandIdPrefix()`,
`actionDisplayPrefix()`, `binaryName()`, `dockTitle()`, `dockIdent()` — all derived
from the one `REASAMPLER_CHANNEL_IS_BETA` bit. Stable values byte-identical to
pre-V4 build.
- [x] `channelCommandId(suffix)` / `channelActionName(phrase)` composition helpers:
every action-registering shell funnels through these so no shell re-implements the
channel-qualified concatenation. FOREVER-STABLE per channel.
- [x] `configure_file` threads `REASAMPLER_CHANNEL_IS_BETA` (0 for the default build,
1 for `-DREASAMPLER_CHANNEL=beta`) alongside the version string. Beta binary name,
namespace, prefix, and display suffix all derive from this one bit.
- [x] All shells (`main.cpp`, `actions.cpp`, `bank_panel.cpp`, `persist.cpp`) updated
to compose ids/names via `channelCommandId`/`channelActionName` and read
`extStateNamespace()` — no scattered `#ifdef` forks in the shells.
- [x] Bank-panel footer version/channel readout: displays `appVersion()` (stable:
`"0.9.01"`, beta: `"0.9.01-beta"`).
- [x] The lane-name `reasampler:` prefix is deliberately NOT channel-qualified (shared
naming convention; ownership isolated by namespace).
- [x] Stable build: byte-identical to pre-V4 identity on every string that was
previously shipped.
**Notes/decisions:**
- The stamp value (`stampVersion()`) is the numeric triple only on BOTH channels —
no `-beta` suffix in the stamp. The channel is carried by the isolated namespace
(`extStateNamespace()`), not baked into the stamp, so the stamp parses as `Stamped`
on read-back and stable's stamp is byte-identical regardless of channel build.
- Two permanent commitments accepted: a second forever-stable command-id prefix
(`CEREBELLUM_REASAMPLER_BETA_`) and a second ext-state namespace
(`"reasampler_beta"`). Beta keybindings are a distinct forever-family from stable's.
- Isolation semantics (accepted, not a bug): a channel reads/writes only its own
namespace — a stable project looks empty/default when opened in beta, and vice versa.
No cross-namespace read, migration, or fallback.
- Deploy implication (dev-ops hand-off): two named artifacts per platform
(`reaper_reasampler` + `reaper_reasampler_beta`), built by toggling
`-DREASAMPLER_CHANNEL`.
---
# Phase B — Multi-bank (parallel to the M0M11 capture roadmap and Phase D)
> **Separate phase namespace.** The M-numbers belong to the capture pillar
-40
View File
@@ -238,46 +238,6 @@ CONTEXT.md §Prune (Settled forks).
---
# Phase V — Versioning & release (release-milestone pillar, own lettered namespace)
> **New pillar, own lettered namespace.** Version scheme + beta side-channel — the
> release-deployment path M11 forward-implies but that had no phase or points.
> Namespaced **`V` (Versioning)** alongside `M`/`D`/`B`/`R` because it is a distinct
> concern (build identity + channel isolation) that touches CMake, `main.cpp`'s
> forever-stable command-id contract, and the `"reasampler"` ext-state — not a
> capture step. Product framing + the full option analysis: `docs/product/versioning-
> and-release.md`. **Forks V1V4 SETTLED (Daniel, 2026-07-26)** — semver via
> `project(VERSION)` + ext-state version stamp prioritized first-wave; plain `-beta`
> suffix; console line + panel readout (about-box deferred); and **beta ships as a
> separate, fully isolated coexisting binary (beta-in-isolation)** — a reversal of the
> note's original one-at-a-time recommendation. Deploy/CD wiring (now two named
> artifacts per platform) hands off to dev-ops. Build-scoped points to be drawn up.
## Settled decisions (Daniel, 2026-07-26 — see `docs/product/versioning-and-release.md`)
- **V1 — version scheme: APPROVED as recommended.** Semver, single source of truth in
CMake `project(reaper_reasampler VERSION x.y.z)`, threaded into the binary. **The
`"reasampler"` ext-state writing-version stamp is prioritized to the first wave, not
deferred** — every project saved without the stamp is harder to migrate later, so
the migration seam lands early.
- **V2 — beta suffix: plain `-beta`.** `project(VERSION)` owns the release triple; beta
carries a `-beta` suffix. `git describe` decoration considered and rejected for
legibility.
- **V3 — user-visible home: recommendation accepted.** Startup console line
(`"ReaSampler x.y.z loaded"`) + a bank-panel version/channel readout; about-box
deferred. Panel placement is the residual polish call.
- **V4 — beta channel shape: BETA-IN-ISOLATION (full coexistence).** *Reverses the
original recommendation.* Beta ships as a **separate binary** (`reaper_reasampler_
beta`) with an **isolated ext-state namespace** (distinct from stable's
`"reasampler"` — a beta cannot corrupt a stable project's saved state) and an
**isolated forever-stable command-id prefix** (beta/stable keybindings don't
collide), so both install and run side-by-side. Built through a compile-time channel
flag (`-DREASAMPLER_CHANNEL=beta`) as the mechanism. **Two permanent commitments
locked in:** a second forever-stable command-id prefix and a second ext-state
namespace. **Dev-ops:** the build now produces two named artifacts (stable + beta)
per platform.
---
# Phase S — MIDI-playback instrument (native VST3 sampler; a second build artifact)
> **New pillar, own lettered namespace, and — uniquely — a second build artifact.**
+9 -14
View File
@@ -13,20 +13,15 @@ tickable points land in `PLAN.md` and the deploy/build wiring hands off to dev-o
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
(2026-07-26).** V1 approved as recommended (ext-state version stamp prioritized to
the first wave); V2 plain `-beta` suffix (`git describe` decoration rejected); V3
recommendation accepted (console line + panel readout, about-box deferred); **V4
reversed the recommendation** — Daniel chose **beta-in-isolation / full coexistence**
(separate binary, isolated ext-state namespace, isolated command-id prefix) rather
than the branch-discipline/one-at-a-time path the note originally recommended. The
"Recommendation" and "Open decisions" sections below have been superseded to reflect
the settled state; each records what was chosen and why. Deploy/build wiring hands off
to dev-ops.
> **Note on V4 phrasing (provisional).** The-boss's read of "beta-in-isolation" as
> the separate-binary + isolated-namespace + isolated-command-id-prefix coexistence
> path is provisional pending a final confirm from Daniel. Written that way here; a
> minor correction is a cheap edit.
(2026-07-26); V1V4 implemented and landed (2026-07-26).** V1 approved as recommended
(ext-state version stamp prioritized to the first wave); V2 plain `-beta` suffix
(`git describe` decoration rejected); V3 recommendation accepted (console line + panel
readout, about-box deferred); **V4 reversed the recommendation** — Daniel chose
**beta-in-isolation / full coexistence** (separate binary, isolated ext-state
namespace, isolated command-id prefix) rather than the branch-discipline/one-at-a-time
path the note originally recommended. The "Recommendation" and "Open decisions"
sections below have been superseded to reflect the settled state; each records what
was chosen and why. Deploy/build wiring hands off to dev-ops.
---