From 6a6d305cf2cafc572f6f4913228676be3fd1ccd6 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Sun, 26 Jul 2026 16:56:04 -0400 Subject: [PATCH] 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. --- CLAUDE.md | 15 ++++- COMPLETED.md | 91 ++++++++++++++++++++++++++ PLAN.md | 40 ----------- docs/product/versioning-and-release.md | 23 +++---- 4 files changed, 113 insertions(+), 56 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 34d8744..7f1005b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 `'-'`. diff --git a/COMPLETED.md b/COMPLETED.md index 0c40657..a6d3ae2 100644 --- a/COMPLETED.md +++ b/COMPLETED.md @@ -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 M0–M11 capture roadmap and Phase D) > **Separate phase namespace.** The M-numbers belong to the capture pillar diff --git a/PLAN.md b/PLAN.md index 78ace0b..1dbffb9 100644 --- a/PLAN.md +++ b/PLAN.md @@ -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 V1–V4 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.** diff --git a/docs/product/versioning-and-release.md b/docs/product/versioning-and-release.md index 847662e..da5d543 100644 --- a/docs/product/versioning-and-release.md +++ b/docs/product/versioning-and-release.md @@ -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); V1–V4 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. ---