From f3be4d8ccef6726808cbe1ee9915a6429b924ac3 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Wed, 29 Jul 2026 13:40:09 -0400 Subject: [PATCH 1/2] Q-W6: registration table (OCP) in main.cpp; bank verbs -> shell/bank_ops(Session&); persist.h + wav_trim + namespaces.h shims deleted; 61/61 capture.h realtime seam split to capture_realtime_shell.h; GetProjExtState grow-loop rehomed to core/wire/ext_state_read; stale persist.cpp/bank_panel.cpp comment refs fixed; CLAUDE.md persist/bank_book/actions bullets updated. Command-id suffixes, display phrases, and undo labels byte-identical. --- CLAUDE.md | 6 +- CMakeLists.txt | 20 +- src/app/main.cpp | 521 +++++-------------- src/core/capture/capture_paths.cpp | 2 +- src/core/capture/tail_control.h | 4 +- src/core/capture/wav_trim.h | 15 - src/core/instrument/engine/pitch_shift.h | 4 +- src/core/instrument/engine/sampler_core.h | 2 +- src/core/instrument/map/bridge_marshal.h | 63 +-- src/core/instrument/map/sample_map.cpp | 2 +- src/core/instrument/map/sample_map.h | 8 +- src/core/instrument/ui/envelope_overlay.h | 2 +- src/core/instrument/ui/waveform_view.h | 2 +- src/core/model/owned_manifest.h | 2 +- src/core/namespaces.h | 48 -- src/core/ui/bank_grid.h | 4 +- src/core/ui/card_drag.h | 2 +- src/core/ui/drag_out.h | 2 +- src/core/ui/footer_bar.h | 2 +- src/core/ui/prune_button.h | 4 +- src/core/ui/tab_strip.h | 2 +- src/core/view/mode_switch.h | 2 +- src/core/wire/ext_state_read.h | 70 +++ src/ext_keys.h | 6 +- src/ingest.cpp | 21 +- src/ingest.h | 1 - src/persist.h | 27 - src/resource.h | 2 +- src/shell/actions/action_registry.cpp | 54 +- src/shell/actions/action_registry.h | 68 ++- src/shell/actions/bank_actions.cpp | 38 +- src/shell/actions/design_view_actions.cpp | 2 +- src/shell/actions/drag_out_win.cpp | 1 - src/shell/actions/drag_out_win.h | 1 - src/shell/actions/instrument_drop_win.cpp | 7 +- src/shell/actions/instrument_drop_win.h | 1 - src/shell/actions/prune_action.cpp | 6 +- src/shell/bank_ops/bank_ops.cpp | 183 +++++++ src/shell/bank_ops/bank_ops.h | 83 +++ src/shell/capture/capture.h | 116 +---- src/shell/capture/capture_batch.cpp | 2 +- src/shell/capture/capture_orchestrator.cpp | 2 +- src/shell/capture/capture_realtime_shell.cpp | 2 +- src/shell/capture/capture_realtime_shell.h | 121 +++++ src/shell/capture/insert.cpp | 11 +- src/shell/capture/insert.h | 3 +- src/shell/capture/item_read.cpp | 1 - src/shell/capture/item_read.h | 1 - src/shell/capture/provenance_shell.cpp | 5 +- src/shell/capture/provenance_shell.h | 4 +- src/shell/capture/realtime_lifecycle.cpp | 2 +- src/shell/capture/realtime_lifecycle.h | 2 +- src/shell/capture/track_guid.cpp | 1 - src/shell/capture/track_guid.h | 1 - src/shell/instrument/editor_controls.cpp | 1 + src/shell/instrument/editor_platform.cpp | 2 +- src/shell/instrument/editor_session.cpp | 2 +- src/shell/instrument/processor_reload.cpp | 8 +- src/shell/instrument/processor_state.cpp | 1 + src/shell/instrument/reaper_bridge.cpp | 19 +- src/shell/instrument/reaper_bridge.h | 4 +- src/shell/instrument/reasampler_embed.cpp | 11 +- src/shell/instrument/reasampler_embed.h | 5 +- src/shell/instrument/reasampler_vst.h | 1 - src/shell/instrument/vst_entry.cpp | 5 +- src/shell/panel/draw_kit.cpp | 12 +- src/shell/panel/draw_kit.h | 14 +- src/shell/panel/panel_bank_ops.cpp | 234 ++------- src/shell/panel/panel_bank_ops.h | 90 +--- src/shell/panel/panel_drag.cpp | 6 +- src/shell/panel/panel_input.cpp | 5 +- src/shell/panel/panel_layout.cpp | 2 +- src/shell/panel/panel_render.cpp | 2 +- src/shell/panel/panel_state.h | 18 +- src/shell/persist/ext_state_io.cpp | 10 +- src/shell/persist/persist_internal.h | 4 +- src/shell/persist/usage_scan.cpp | 18 +- src/shell/persist/usage_scan.h | 1 - src/shell/view/view.cpp | 8 +- src/shell/view/view.h | 1 - tests/test_bridge_marshal.cpp | 7 +- 81 files changed, 970 insertions(+), 1085 deletions(-) delete mode 100644 src/core/capture/wav_trim.h delete mode 100644 src/core/namespaces.h create mode 100644 src/core/wire/ext_state_read.h delete mode 100644 src/persist.h create mode 100644 src/shell/bank_ops/bank_ops.cpp create mode 100644 src/shell/bank_ops/bank_ops.h create mode 100644 src/shell/capture/capture_realtime_shell.h diff --git a/CLAUDE.md b/CLAUDE.md index 4db0389..02c078e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,7 +61,7 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde - `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. - `mode_switch` — REAPER-free segment layout + hit-test for the bank_panel's Design View mode switch. -- `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, index-only move/copy/remove of a sample between banks, and JSON round-trip. +- `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. - `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. - `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. - `wav_codec` — chunk walker + layout parse + float32 build + size-field patch + content hashes; the single pure RIFF/WAV owner. (`wav_trim` is now a transitional forwarding alias onto `wav_codec`, kept only so the Q-W2v TUs it feeds compile untouched; retire it once that wave lands.) @@ -88,7 +88,7 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde - `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`. - `insert` — placement via `InsertMedia`. **Conform-to-project-tempo is an explicit opt-in flag, never silent stretching.** - `bank_panel` — 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`. -- `persist` — project ext state (`SetProjExtState`/`GetProjExtState`, namespace `"reasampler"`) ↔ `BankBook` JSON, `ViewModeModel` JSON, `TailSetting` JSON, `OwnedManifest` JSON, and writing-version stamp. A `projectconfig` hook triggers a deferred session reload on undo/redo. Hosts the prune dry-run and full-set orphan queries; supplies `referencedPaths()` + `owned().paths()` to the `prune_reconcile` pure core. **pS-usage:** `scanPruneOrphans` unions instance usage via `usage_scan`; `PruneReport` gains `abortedUnreadableUsage` + `offendingUsageKeys`; dry-run / orphan-set / reclaim each independently abort (delete nothing) when usage state is unreadable. +- `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. - `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`.** - `track_guid` — shared `MediaTrack*` → canonical GUID-string formatter; single source of truth for membership keys. @@ -97,7 +97,7 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde - `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.** - `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.** - `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`. -- `actions` — registers the capture/placement/slot, Design View, multi-bank, and prune action families; routes each 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.** The prune action (`BANK_PRUNE_FOLDER`) is **the ONLY file-deletion authority 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. +- `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. **VST3 instrument (`src/vst/`) — pure core:** - `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. diff --git a/CMakeLists.txt b/CMakeLists.txt index 304e682..6a21685 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,7 +281,7 @@ target_link_libraries(bank_book PRIVATE json) # itself created, so Phase R prune can tell the bank system's own orphans from # hand-dropped files. Deliberately DECOUPLED from bank_book — it tracks files # CREATED, not index membership (sample-remove is not manifest-remove). Small -# pure type + JSON round-trip; mirror of wav_trim / tab_strip. B-cap writes + +# pure type + JSON round-trip; mirror of wav_codec / tab_strip. B-cap writes + # persists it; Phase R (R1/R2) consumes it — no prune logic here. # --------------------------------------------------------------------------- add_library(owned_manifest STATIC src/core/model/owned_manifest.cpp) @@ -335,18 +335,13 @@ target_link_libraries(capture_realtime PUBLIC bank_model) # build (formerly hand-rolled in ingest), and the WAV-aware content hashes # (formerly in capture_paths). The dedup-by-hash and null-test invariants rest # on this one implementation. File I/O stays in the shells. Depends on peaks -# for the AudioSample float alias. -# `wav_trim` remains as a TRANSITIONAL alias (forwarding header + INTERFACE -# target) so the Q-W2v-owned TUs (sample_map, VST editor/processor) build -# untouched in their parallel wave; retire both once Q-W2v lands. +# for the AudioSample float alias. (The transitional `wav_trim` alias was +# retired in Q-W6 — every includer points here directly.) # --------------------------------------------------------------------------- add_library(wav_codec STATIC src/core/capture/wav_codec.cpp) target_include_directories(wav_codec PUBLIC src) target_link_libraries(wav_codec PUBLIC peaks) -add_library(wav_trim INTERFACE) -target_link_libraries(wav_trim INTERFACE wav_codec) - # --------------------------------------------------------------------------- # 2i) Pure app_version library — NO REAPER, NO SWELL. The Phase V (V1) version-identity # core: re-exports the ONE CMake-sourced version string (via the configure_file'd @@ -563,7 +558,7 @@ target_link_libraries(card_drag PUBLIC drag_out bank_grid) # tested hard outside any host. Lives under core/instrument/engine/ but # links NEITHER SDK — the plain-data boundary is enforced structurally: the test # target below links only sampler_core (+ its peaks dep for the AudioSample alias, -# the one house precedent wav_trim also relies on). The VST3 shell (shell/instrument/ +# the one house precedent wav_codec also relies on). The VST3 shell (shell/instrument/ # reasampler_processor.cpp) marshals MIDI/audio to/from it and is DAW-verified. # --------------------------------------------------------------------------- # pitch_shift (S16) — the pure duration-preserving PitchShifter (Preserve-engine DSP core). @@ -849,13 +844,13 @@ target_link_libraries(embed_strip PUBLIC editor_geometry) # instrument: the live bank blob -> selected sample (via the SHARED bank_book JSON parse, # NOT a second parser), interleaved->mono downmix (the channel policy), the Tier-0/zoned # keymap builds, and the refs/performance resolution. Links the three pure modules it -# composes — bank_book (shared JSON), wav_trim (shared WAV parse), and sampler_core (the +# composes — bank_book (shared JSON), wav_codec (shared WAV parse), and sampler_core (the # Keymap/SampleData it yields) — and NEITHER SDK. The VST3 shell (reasampler_processor) # does the bridge read + file I/O off the audio thread, then calls these; the process # callback stays allocation-free. The ComponentState codec is component_state_io below. add_library(sample_map STATIC src/core/instrument/map/sample_map.cpp) target_include_directories(sample_map PUBLIC src) -target_link_libraries(sample_map PUBLIC bank_book wav_trim sampler_core) +target_link_libraries(sample_map PUBLIC bank_book wav_codec sampler_core) # component_state_io (Q-W2v split of sample_map, T4-13 ≡ T2-07) — the ComponentState # ENVELOPE + zones-payload binary codec (envelope v1..v11, zones payload v1..v7, every @@ -1107,6 +1102,7 @@ add_library(reaper_reasampler MODULE src/shell/persist/session.cpp src/shell/persist/ext_state_io.cpp src/shell/persist/prune_fs.cpp + src/shell/bank_ops/bank_ops.cpp src/shell/panel/panel_audition.cpp src/shell/panel/panel_bank_ops.cpp src/shell/panel/panel_drag.cpp @@ -1283,7 +1279,7 @@ if(WIN32 AND EXISTS "${VST3_SDK}/public.sdk/source/main/pluginfactory.cpp") ) # editor_geometry + bridge_marshal: the pure spike helpers. sample_map (S4): the pure # bank->keymap mapping + state (de)ser the processor drives off the audio thread; - # linking it pulls its pure deps (bank_book, wav_trim, sampler_core, bank_model, + # linking it pulls its pure deps (bank_book, wav_codec, sampler_core, bank_model, # peaks) transitively. capture_paths: the shared M4 path resolution (resolveBankFile / # projectDirOfRpp) the bridge + processor use. Its PUBLIC include dir (src) # gives the shell TUs their headers (ext_keys.h, bank_book.h, sampler_core.h, ...). diff --git a/src/app/main.cpp b/src/app/main.cpp index 85f08f4..1a5bc47 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -14,24 +14,25 @@ // storage for those global pointers. Every other .cpp includes // reaper_plugin_functions.h WITHOUT the define and gets `extern` declarations. // -// Since Q-W3 this TU is ONLY pointers + entry + dispatch: the capture -// orchestration it used to carry lives in shell/capture/ (capture_orchestrator / -// capture_batch / scope_resolve / realtime_lifecycle). The registration blocks -// below are slated for Q-W6's registration table. +// Since Q-W3 this TU is ONLY pointers + entry + dispatch; since Q-W6 its own +// action family registers through the DATA-DRIVEN TABLE below (kMainActionRows + +// action_registry's registerActionTable/actionTableHandleCommand/ +// unregisterActionTable) — adding a bindable action here means adding ONE row and +// its handler function, nothing else (OCP). The design_view / bank / ingest +// families keep their own register/handle/unregister triples, called from entry. #define REAPERAPI_IMPLEMENT #include "reaper_plugin.h" #include "reaper_plugin_functions.h" #include -#include #include #include #include "core/capture/render_settings.h" // captureActionTable -#include "core/version/app_version.h" // channelCommandId / channelActionName / appVersion +#include "core/version/app_version.h" // channelCommandId / appVersion #include "ingest.h" -#include "persist.h" +#include "shell/actions/action_registry.h" // the Q-W6 registration table #include "shell/actions/bank_actions.h" // multi-bank action family (B3; Q-W4 home) #include "shell/actions/design_view_actions.h" // Design View action family (D4; Q-W4 home) #include "shell/capture/capture_batch.h" // batch + recapture action bodies @@ -39,75 +40,26 @@ #include "shell/capture/realtime_lifecycle.h" // in-flight realtime state + tick driver #include "shell/panel/panel_input.h" // bankPanelRefresh / bankPanelNotifyProjectLoaded #include "shell/panel/panel_window.h" // panel lifecycle (init/toggle/open-query/shutdown) +#include "shell/persist/session.h" // ReaSamplerSession #include "shell/view/view.h" // reconcileManagedLanes / applyMode namespace capture = reasampler::capture; -using reasampler::version::channelActionName; -using reasampler::version::channelCommandId; - -// Persistent action-id family (Phase V, V4 — channel-qualified). Every bindable action -// mints its command id from commandIdPrefix() + a per-action SUFFIX, and its Actions-list -// name from actionDisplayPrefix() + a phrase, both derived from the ONE channel bit in the -// pure app_version module (channelCommandId / channelActionName). Stable rebuilds the exact -// shipped id ("CEREBELLUM_REASAMPLER_CAPTURE_TRACK"); beta yields the isolated forever- -// family id ("CEREBELLUM_REASAMPLER_BETA_CAPTURE_TRACK"). FOREVER-STABLE per channel: a -// shipped suffix is as permanent as the prefix; user keybindings key off the composed id. -// -// The composed id strings are held here for the module's lifetime (idStore) so both the -// register call and the mirroring '-command_id' unregister pass the SAME stable pointer. -// A std::deque (NOT vector) is used deliberately: it never invalidates references to -// existing elements on push_back, so a c_str() handed out early stays valid after later -// interning — the unload path re-presents these same pointers. -static std::deque g_idStore; - -// Interns a composed command-id string for the module lifetime and returns its C string. -// Appended-to only during startup registration and read on unload; never cleared until -// process exit, and deque guarantees the returned pointer stays valid. -static const char* internCmdId(const std::string& suffix) { - g_idStore.push_back(channelCommandId(suffix)); - return g_idStore.back().c_str(); -} // Globals other files reference via `extern`. REAPER_PLUGIN_HINSTANCE g_hInst = nullptr; // this module's instance handle reaper_plugin_info_t* g_rec = nullptr; // REAPER's dispatch struct -// ---- Capture action family (two FX scopes) --------------------------------- -// Two bindable SCOPE actions from captureActionTable() (render_settings, pure): -// capture item / track. Each infers its range (razor-else-time) and enforces the -// FX-scope invariant via FX-bypass-around-render (FxBypassGuard, now in -// capture_orchestrator): -// Item -> take/item FX only (bypass the item's track + ancestors + master). -// Track -> item FX + track's own FX (bypass ancestors + master). -// There is NO master scope — to capture the master you render a track. (The master -// track's FX/gain/pan are STILL neutralized for both scopes as the out-of-scope -// chain — master is a bypass target, not a capture scope.) The retired M7 -// CAPTURE_TRACKS_WET / CAPTURE_ITEMS_WET / CAPTURE_RAZOR_WET ids AND the removed -// CAPTURE_MASTER / CAPTURE_MASTER_REALTIME ids are mirror-unregistered on unload so -// old keybindings clear cleanly. -// -// The minted command ids parallel the table rows 1:1 (same index). gaccel storage -// must outlive registration (REAPER holds each pointer), so both vectors are file- -// scope and sized to the table. FOREVER-STABLE id strings live in the table. -static std::vector g_captureCmdIds; -static std::vector g_captureAccels; -// Channel-qualified capture-action labels, one per table row. REAPER holds each gaccel's -// `desc` pointer, so the composed strings live here for the module lifetime (parallel to -// g_captureAccels; never resized after the registration loop sets it). -static std::vector g_captureDescs; - -// Retired capture-action command-id SUFFIXES. Kept ONLY to mirror-unregister them on -// unload so a user's stale keybindings are cleaned up. Never re-register these. Composed -// through the channel prefix at unload (channelCommandId) so a beta unload clears beta- -// qualified retired ids and a stable unload clears stable's — each channel cleans up only -// its own family. +// Retired command-id SUFFIXES. Kept ONLY to mirror-unregister them on unload so a +// user's stale keybindings are cleaned up. Never re-register these. Composed through +// the channel prefix at unload (channelIdFor) so a beta unload clears beta-qualified +// retired ids and a stable unload clears stable's — each channel cleans up only its +// own family. // * The M7 four-mode ids (tracks/items/razor WET). // * CAPTURE_MASTER and CAPTURE_MASTER_REALTIME — the master offline scope and the // master realtime action are REMOVED (capture is now item + track only; realtime // taps the selected track). Their shipped ids are retired so old keybindings clear. // * CAPTURE_ITEM_TAIL and CAPTURE_TRACK_TAIL — the former per-action tail variants -// are REMOVED; tail is now a panel-setting toggle, not a paired action. Retired so -// old keybindings clear. +// are REMOVED; tail is now a panel-setting toggle, not a paired action. static const char* const kRetiredCaptureCmdSuffixes[] = { "CAPTURE_TRACKS_WET", "CAPTURE_ITEMS_WET", @@ -118,60 +70,6 @@ static const char* const kRetiredCaptureCmdSuffixes[] = { "CAPTURE_TRACK_TAIL", }; -// Command id for "ReaSampler: toggle bank panel" (M5). FOREVER-STABLE string. -// The docked grid window is display-only this wave (Wave A) — the action just -// shows/hides it; it never captures, inserts, or mutates the bank. -static int g_cmdToggleBankPanel = 0; - -// Command ids for the M6 insert actions. FOREVER-STABLE strings. Two variants that -// differ ONLY in the InsertOptions they build: the default inserts at native length -// (no stretch, no conform); the "conform" variant is the EXPLICIT opt-in to REAPER's -// try-to-match-project-tempo path (CONTEXT.md §insert: conform is opt-in, never -// silent). Both read the bank panel's current selection and place at the edit cursor. -static int g_cmdInsertSelected = 0; -static int g_cmdInsertSelectedConform = 0; - -// Command ids for the M11 batch-capture actions. NEW FOREVER-STABLE strings. One action -// fires N captures: CAPTURE_BATCH_ITEMS -> one bank sample per selected item (item scope); -// CAPTURE_BATCH_RAZOR -> one bank sample per razor area (track scope, each area's range). -// Each unit honors every precision invariant; the original selection is restored on every -// exit path. Bank-only, never places on the timeline (load-bearing principle). -static int g_cmdCaptureBatchItems = 0; -static int g_cmdCaptureBatchRazor = 0; - -// Command id for the "capture selected track (realtime)" action. NEW FOREVER-STABLE -// string. Records the selected track's OWN output in realtime (transport-driven) into -// a hidden temp track via RealtimeRecordBackend, then moves the recorded file into the -// bank. The realtime SIBLING of the offline CAPTURE_TRACK scope action: same range -// logic (razor-else-time), same track selection, same bank/persist path, different -// backend. Dialog-free. (Replaces the removed CAPTURE_MASTER_REALTIME action.) -static int g_cmdCaptureTrackRealtime = 0; - -// Command id for the M10 "re-capture from source" action. NEW FOREVER-STABLE string -// (suffix RECAPTURE_FROM_SOURCE). Regenerates the bank panel's selected PROVENANCED -// sample from its recorded source's current state and updates the Sample in place — -// BANK-ONLY, never places on the timeline (load-bearing principle). -static int g_cmdRecaptureFromSource = 0; - -// Command id for the S8 "capture selected item / time-selection into bank + assign" -// action. NEW FOREVER-STABLE string (suffix CAPTURE_ITEM_ASSIGN). Reuses the offline -// Item-scope capture path (RunCapture) verbatim, then writes an S8 assignment request. -// Lives in the capture family (not the ingest family) because it leans on the capture -// render machinery (capture_orchestrator). -static int g_cmdCaptureItemAssign = 0; - -// Command id for the M8 "cancel realtime capture" action. FOREVER-STABLE string. -// Aborts the in-flight realtime capture (stop + restore, non-destructive). No-op -// (with a note) when nothing is in flight. -static int g_cmdCancelRealtime = 0; - -// Command id for the Phase V "show version" action. FOREVER-STABLE string. On demand -// ONLY — prints the CMake-sourced version string to the console when fired. This is the -// SOLE new console output the versioning wave adds; there is no unconditional startup -// version print (routine console chatter was deliberately removed — it pops the console -// window). The user copies this line into a bug report. -static int g_cmdShowVersion = 0; - // The persistence session (M4): owns the in-memory BankModel and bridges it to // project ext state. A timer tick drives g_session.poll() to detect project // load / Save-As; capture adds Samples to g_session.bank() — which (B2) resolves to @@ -180,6 +78,102 @@ static int g_cmdShowVersion = 0; // with the .rpp. Replaces the M3 session-only g_bank. static reasampler::ReaSamplerSession g_session; +// Command id of the TOGGLE_BANK_PANEL row, resolved from the table once at load so +// OnToggleAction's checked-state poll is a single int compare (no per-poll lookup). +static int g_cmdToggleBankPanel = 0; + +// --- Action handlers (the table's function pointers) -------------------------- +// +// Each is a thin stateless routing shim: (session, per-row arg) -> the action body +// hoisted in Q-W3/Q-W4 (shell/capture/, shell/panel/). The bodies own all behavior; +// these exist only so the table rows can be plain data with flat function pointers. + +// Capture scope family: `arg` is the captureActionTable() row index — the table rows +// below are built by iterating that pure taxonomy, so the routing stays 1:1 by +// construction (never a hand-kept parallel list). +static void RunCaptureScopeRow(int arg) { + capture::RunCapture(g_session, + capture::captureActionTable()[static_cast(arg)]); +} +static void RunToggleBankPanel(int) { reasampler::bankPanelToggle(); } +static void RunCaptureItemAssign(int) { capture::RunCaptureItemAssign(g_session); } +// Insert: `arg` != 0 is the EXPLICIT conform-to-project-tempo opt-in (CONTEXT.md +// §insert: conform is opt-in, never silent); 0 inserts at native length. +static void RunInsertSelected(int arg) { + capture::RunInsertSelected(g_session, arg != 0); +} +static void RunBatchCaptureItems(int) { capture::RunBatchCaptureItems(g_session); } +static void RunBatchCaptureRazor(int) { capture::RunBatchCaptureRazor(g_session); } +static void RunCaptureRealtime(int) { capture::RunCaptureRealtimeTrack(g_session); } +static void RunCancelRealtime(int) { capture::RunCancelRealtime(g_session); } +static void RunRecaptureFromSource(int) { capture::RunRecaptureFromSource(g_session); } +static void RunShowVersion(int) { + // On-demand version readout — the ONLY version output on any path (Phase V: no + // unconditional startup print; routine console chatter pops the console window). + ShowConsoleMsg(("ReaSampler " + reasampler::version::appVersion() + "\n").c_str()); +} + +// --- The registration table (Q-W6) -------------------------------------------- +// +// ONE row per bindable action this TU owns: FOREVER-STABLE id suffix (channel prefix +// composed at register — stable rebuilds the exact shipped id, e.g. +// "CEREBELLUM_REASAMPLER_CAPTURE_TRACK"; beta its isolated forever-family), the +// Actions-list phrase (after the "ReaSampler[ beta]: " lead), the handler, and its +// per-row arg. Registration, hookcommand dispatch, and the unload mirror-unregister +// all iterate this data — adding an action = adding a row + a handler above. +// +// The capture scope rows (CAPTURE_ITEM / CAPTURE_TRACK) come first, sourced from the +// pure captureActionTable() taxonomy (render_settings) — suffix/phrase live in that +// one testable list, and `arg` carries the row index back to RunCapture. The +// remaining rows are this TU's singles, in the pre-table registration order. +static std::vector buildMainActionTable() { + using reasampler::ActionTableRow; + std::vector rows; + + const auto& cap = capture::captureActionTable(); + for (std::size_t i = 0; i < cap.size(); ++i) + rows.push_back(ActionTableRow{cap[i].commandSuffix, cap[i].descriptionPhrase, + &RunCaptureScopeRow, static_cast(i)}); + + // M5: show/hide the docked bank panel (display-only; never captures/inserts). + rows.push_back({"TOGGLE_BANK_PANEL", "toggle bank panel", &RunToggleBankPanel}); + // S8: Item-scope capture + assignment-request write (capture family because it + // leans on the capture render machinery; the other ingest surfaces live in the + // ingest family and the panel drop callback). + rows.push_back({"CAPTURE_ITEM_ASSIGN", + "capture selected item into bank + assign to active instance", + &RunCaptureItemAssign}); + // M6: place the panel's selected sample at the edit cursor. Two variants that + // differ ONLY in InsertOptions — native length vs the explicit conform opt-in. + rows.push_back({"INSERT_SELECTED", "insert selected sample at edit cursor", + &RunInsertSelected, 0}); + rows.push_back({"INSERT_SELECTED_CONFORM", + "insert selected sample at edit cursor (conform to tempo)", + &RunInsertSelected, 1}); + // M11: one action fires N captures (per selected item / per razor area); the + // original selection is restored on every exit path. Bank-only, never places. + rows.push_back({"CAPTURE_BATCH_ITEMS", + "batch capture selected items (one per item)", + &RunBatchCaptureItems}); + rows.push_back({"CAPTURE_BATCH_RAZOR", "batch capture razor areas (one per area)", + &RunBatchCaptureRazor}); + // M8: realtime sibling of the offline CAPTURE_TRACK scope — records the selected + // track's own output into a hidden temp track, dialog-free — plus its + // cancel-in-flight companion (stop + restore, non-destructive). + rows.push_back({"CAPTURE_TRACK_REALTIME", "capture selected track (realtime)", + &RunCaptureRealtime}); + rows.push_back({"CANCEL_REALTIME_CAPTURE", "cancel realtime capture", + &RunCancelRealtime}); + // M10: regenerate the selected PROVENANCED sample from its recorded source's + // current state, in place. Bank-only, never places on the timeline. + rows.push_back({"RECAPTURE_FROM_SOURCE", "re-capture from source", + &RunRecaptureFromSource}); + // Phase V: on-demand version readout for bug reports. + rows.push_back({"SHOW_VERSION", "show version", &RunShowVersion}); + + return rows; +} + // The timer callback REAPER runs periodically (registered via "timer"). It only // forwards to the session poll — cheap per tick (reads the active project id and // its .rpp path, acts only on a change). @@ -275,33 +269,12 @@ static project_config_extension_t g_projectConfig{ }; // REAPER calls this for EVERY action fired anywhere; claim only our own id, -// return false otherwise so REAPER keeps looking. +// return false otherwise so REAPER keeps looking. This TU's own family dispatches +// through the registration table; the Q-W4 families claim their own ids after it. static bool OnHookCommand(int command, int /*flag*/) { if (command == 0) return false; - // Three-scope capture family: command ids parallel captureActionTable() 1:1 by index. - // Claim the fired id if it is one of ours and route to its table row. - for (std::size_t i = 0; i < g_captureCmdIds.size(); ++i) - if (command == g_captureCmdIds[i]) - { - capture::RunCapture(g_session, capture::captureActionTable()[i]); - return true; - } - if (command == g_cmdToggleBankPanel) { reasampler::bankPanelToggle(); return true; } - if (command == g_cmdCaptureItemAssign) { capture::RunCaptureItemAssign(g_session); return true; } - if (command == g_cmdInsertSelected) { capture::RunInsertSelected(g_session, false); return true; } - if (command == g_cmdInsertSelectedConform) { capture::RunInsertSelected(g_session, true); return true; } - if (command == g_cmdCaptureBatchItems) { capture::RunBatchCaptureItems(g_session); return true; } - if (command == g_cmdCaptureBatchRazor) { capture::RunBatchCaptureRazor(g_session); return true; } - if (command == g_cmdCaptureTrackRealtime) { capture::RunCaptureRealtimeTrack(g_session); return true; } - if (command == g_cmdCancelRealtime) { capture::RunCancelRealtime(g_session); return true; } - if (command == g_cmdRecaptureFromSource) { capture::RunRecaptureFromSource(g_session); return true; } - if (command == g_cmdShowVersion) - { - // On-demand version readout — the ONLY version output on any path. - ShowConsoleMsg(("ReaSampler " + reasampler::version::appVersion() + "\n").c_str()); - return true; - } + if (reasampler::actionTableHandleCommand(command)) return true; // Design View action family (D4). Claims only its own ids; returns false for the // rest so this hook keeps looking (per the contract). if (reasampler::designViewHandleCommand(command)) return true; @@ -316,51 +289,11 @@ static bool OnHookCommand(int command, int /*flag*/) // Return 1 (on) / 0 (off) for ids we own, -1 for everything else (per the contract). static int OnToggleAction(int command) { - if (command == g_cmdToggleBankPanel) + if (command != 0 && command == g_cmdToggleBankPanel) return reasampler::bankPanelIsOpen() ? 1 : 0; return -1; // not ours / non-toggling } -// gaccel storage must outlive registration — REAPER holds the pointer. -// (The capture family's accels live in g_captureAccels, sized to the table.) -static gaccel_register_t g_accelToggleBankPanel{}; -static gaccel_register_t g_accelCaptureItemAssign{}; -static gaccel_register_t g_accelInsertSelected{}; -static gaccel_register_t g_accelInsertSelectedConform{}; -static gaccel_register_t g_accelCaptureBatchItems{}; -static gaccel_register_t g_accelCaptureBatchRazor{}; -static gaccel_register_t g_accelCaptureTrackRealtime{}; -static gaccel_register_t g_accelCancelRealtime{}; -static gaccel_register_t g_accelRecaptureFromSource{}; -static gaccel_register_t g_accelShowVersion{}; - -// gaccel desc storage. The Actions-list label is channel-qualified at runtime -// (channelActionName) so it cannot be a string literal; REAPER holds the gaccel's `desc` -// pointer, so each label lives here for the module lifetime. Composed once at registration. -static std::string g_descToggleBankPanel; -static std::string g_descCaptureItemAssign; -static std::string g_descInsertSelected; -static std::string g_descInsertSelectedConform; -static std::string g_descCaptureBatchItems; -static std::string g_descCaptureBatchRazor; -static std::string g_descCaptureTrackRealtime; -static std::string g_descCancelRealtime; -static std::string g_descRecaptureFromSource; -static std::string g_descShowVersion; - -// Composed command-id strings (channel-qualified), interned so register and the mirroring -// '-command_id' unregister pass the SAME pointer. Set during registration; read on unload. -static const char* g_idToggleBankPanel = nullptr; -static const char* g_idCaptureItemAssign = nullptr; -static const char* g_idInsertSelected = nullptr; -static const char* g_idInsertSelectedConform = nullptr; -static const char* g_idCaptureBatchItems = nullptr; -static const char* g_idCaptureBatchRazor = nullptr; -static const char* g_idCaptureTrackRealtime = nullptr; -static const char* g_idCancelRealtime = nullptr; -static const char* g_idRecaptureFromSource = nullptr; -static const char* g_idShowVersion = nullptr; - extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT( REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t* rec) { @@ -387,49 +320,15 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT( reasampler::bankUnregisterActions(g_rec); // Tear down the S8 ingest action family — same mirror-unregister. reasampler::ingestUnregisterActions(g_rec); - // Each '-command_id' re-presents the SAME interned, channel-qualified pointer - // used at register (g_id*), so the mirror-unregister matches exactly. - g_rec->Register("-gaccel", (void*)&g_accelShowVersion); - g_rec->Register("-command_id", (void*)g_idShowVersion); - g_rec->Register("-gaccel", (void*)&g_accelRecaptureFromSource); - g_rec->Register("-command_id", (void*)g_idRecaptureFromSource); - g_rec->Register("-gaccel", (void*)&g_accelCancelRealtime); - g_rec->Register("-command_id", (void*)g_idCancelRealtime); - g_rec->Register("-gaccel", (void*)&g_accelCaptureTrackRealtime); - g_rec->Register("-command_id", (void*)g_idCaptureTrackRealtime); - g_rec->Register("-gaccel", (void*)&g_accelCaptureBatchRazor); - g_rec->Register("-command_id", (void*)g_idCaptureBatchRazor); - g_rec->Register("-gaccel", (void*)&g_accelCaptureBatchItems); - g_rec->Register("-command_id", (void*)g_idCaptureBatchItems); - g_rec->Register("-gaccel", (void*)&g_accelInsertSelectedConform); - g_rec->Register("-command_id", (void*)g_idInsertSelectedConform); - g_rec->Register("-gaccel", (void*)&g_accelInsertSelected); - g_rec->Register("-command_id", (void*)g_idInsertSelected); - g_rec->Register("-gaccel", (void*)&g_accelCaptureItemAssign); - g_rec->Register("-command_id", (void*)g_idCaptureItemAssign); - g_rec->Register("-gaccel", (void*)&g_accelToggleBankPanel); - g_rec->Register("-command_id", (void*)g_idToggleBankPanel); - // Mirror-unregister the capture family: gaccel + command_id per row, with - // '-'-prefixed strings (per the contract). The command id is re-composed from - // the same suffix + channel prefix used at register — identical string. - { - const auto& table = capture::captureActionTable(); - for (std::size_t i = 0; i < table.size(); ++i) - { - if (i < g_captureAccels.size()) - g_rec->Register("-gaccel", (void*)&g_captureAccels[i]); - const std::string id = channelCommandId(table[i].commandSuffix); - g_rec->Register("-command_id", (void*)id.c_str()); - } - } - // Retire the removed M7 command ids (command_id only — we never held a gaccel + // Tear down this TU's own family from the registration table (reverse + // table order; each '-command_id' re-presents the SAME interned, + // channel-qualified pointer used at register). + reasampler::unregisterActionTable(g_rec); + // Retire the REMOVED command ids (command_id only — we never held a gaccel // for them this session). Clears stale user keybindings on unload. Composed - // per channel so a beta clears beta-qualified retired ids, stable clears its own. + // per channel so a beta clears beta-qualified retired ids, stable its own. for (const char* suffix : kRetiredCaptureCmdSuffixes) - { - const std::string id = channelCommandId(suffix); - g_rec->Register("-command_id", (void*)id.c_str()); - } + g_rec->Register("-command_id", (void*)reasampler::channelIdFor(suffix)); } // Destroy the docked window and release cached thumbnails before we drop // the API pointers (DockWindowRemove/DestroyWindow need them live). @@ -450,173 +349,23 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT( g_hInst = hInstance; g_rec = rec; - // Register the three-scope capture action family (command_id -> gaccel per table row). - // The single hookcommand below routes every fired id back to its row by index. - // g_captureAccels must be sized BEFORE the loop and never reallocated after — - // REAPER holds a pointer to each element until we mirror-unregister it. - { - const auto& table = capture::captureActionTable(); - g_captureCmdIds.assign(table.size(), 0); - g_captureAccels.assign(table.size(), gaccel_register_t{}); - g_captureDescs.assign(table.size(), std::string{}); - for (std::size_t i = 0; i < table.size(); ++i) - { - // Compose the channel-qualified id (prefix + suffix) and label - // ("ReaSampler[ beta]: " + phrase). The id is interned so unregister re-presents - // the same pointer; the label lives in g_captureDescs for the gaccel's lifetime. - const int cmd = - rec->Register("command_id", (void*)internCmdId(table[i].commandSuffix)); - g_captureCmdIds[i] = cmd; - if (cmd) - { - g_captureDescs[i] = channelActionName(table[i].descriptionPhrase); - g_captureAccels[i].accel.cmd = cmd; - g_captureAccels[i].desc = g_captureDescs[i].c_str(); - rec->Register("gaccel", (void*)&g_captureAccels[i]); - } - } - } - // Point the bank panel at the live session BEFORE registering its action, so // a toggle firing immediately has a session to read (M5). Does not open the // window — only stores the session pointer. reasampler::bankPanelInit(&g_session); - // Register the M5 "toggle bank panel" action (command_id -> gaccel -> - // hookcommand + toggleaction for the checked state). Id + label are channel-qualified. - g_idToggleBankPanel = internCmdId("TOGGLE_BANK_PANEL"); - g_cmdToggleBankPanel = rec->Register("command_id", (void*)g_idToggleBankPanel); + // Register this TU's whole action family from the table: command_id -> gaccel + // per row, all channel-qualified, all FOREVER-STABLE per channel. + { + const std::vector rows = buildMainActionTable(); + reasampler::registerActionTable(rec, rows.data(), rows.size()); + } + + // The panel toggle renders a checked state — resolve its minted id once and + // register the toggleaction hook that reports it. + g_cmdToggleBankPanel = reasampler::actionTableCommandId("TOGGLE_BANK_PANEL"); if (g_cmdToggleBankPanel) - { - g_descToggleBankPanel = channelActionName("toggle bank panel"); - g_accelToggleBankPanel.accel.cmd = g_cmdToggleBankPanel; - g_accelToggleBankPanel.desc = g_descToggleBankPanel.c_str(); - rec->Register("gaccel", (void*)&g_accelToggleBankPanel); rec->Register("toggleaction", (void*)&OnToggleAction); - } - - // Register the S8 "capture selected item / time-selection into bank + assign" action - // (command_id -> gaccel -> hookcommand). Reuses the Item-scope offline capture path and - // writes an assignment request so the active instance plays the new sample. Channel- - // qualified FOREVER-STABLE id (suffix CAPTURE_ITEM_ASSIGN). MIDI-bindable like every - // capture action. Registered in the capture family (main.cpp) because it leans on the - // capture render machinery; the other two ingest surfaces live in the ingest family - // (Media-Explorer import) and the panel drop callback. - g_idCaptureItemAssign = internCmdId("CAPTURE_ITEM_ASSIGN"); - g_cmdCaptureItemAssign = rec->Register("command_id", (void*)g_idCaptureItemAssign); - if (g_cmdCaptureItemAssign) - { - g_descCaptureItemAssign = channelActionName( - "capture selected item into bank + assign to active instance"); - g_accelCaptureItemAssign.accel.cmd = g_cmdCaptureItemAssign; - g_accelCaptureItemAssign.desc = g_descCaptureItemAssign.c_str(); - rec->Register("gaccel", (void*)&g_accelCaptureItemAssign); - } - - // Register the M6 insert actions (command_id -> gaccel -> hookcommand). Two - // variants: native-length (default, no stretch) and the EXPLICIT conform-to- - // tempo opt-in. Both read the bank panel selection and place at the edit cursor. - g_idInsertSelected = internCmdId("INSERT_SELECTED"); - g_cmdInsertSelected = rec->Register("command_id", (void*)g_idInsertSelected); - if (g_cmdInsertSelected) - { - g_descInsertSelected = channelActionName("insert selected sample at edit cursor"); - g_accelInsertSelected.accel.cmd = g_cmdInsertSelected; - g_accelInsertSelected.desc = g_descInsertSelected.c_str(); - rec->Register("gaccel", (void*)&g_accelInsertSelected); - } - - g_idInsertSelectedConform = internCmdId("INSERT_SELECTED_CONFORM"); - g_cmdInsertSelectedConform = rec->Register("command_id", (void*)g_idInsertSelectedConform); - if (g_cmdInsertSelectedConform) - { - g_descInsertSelectedConform = channelActionName( - "insert selected sample at edit cursor (conform to tempo)"); - g_accelInsertSelectedConform.accel.cmd = g_cmdInsertSelectedConform; - g_accelInsertSelectedConform.desc = g_descInsertSelectedConform.c_str(); - rec->Register("gaccel", (void*)&g_accelInsertSelectedConform); - } - - // Register the M11 batch-capture actions (command_id -> gaccel -> hookcommand). Each - // fires N captures (one bank sample per selected item / per razor area), honoring every - // precision invariant per unit and restoring the original selection on every path. - // Channel-qualified FOREVER-STABLE ids. - g_idCaptureBatchItems = internCmdId("CAPTURE_BATCH_ITEMS"); - g_cmdCaptureBatchItems = rec->Register("command_id", (void*)g_idCaptureBatchItems); - if (g_cmdCaptureBatchItems) - { - g_descCaptureBatchItems = - channelActionName("batch capture selected items (one per item)"); - g_accelCaptureBatchItems.accel.cmd = g_cmdCaptureBatchItems; - g_accelCaptureBatchItems.desc = g_descCaptureBatchItems.c_str(); - rec->Register("gaccel", (void*)&g_accelCaptureBatchItems); - } - - g_idCaptureBatchRazor = internCmdId("CAPTURE_BATCH_RAZOR"); - g_cmdCaptureBatchRazor = rec->Register("command_id", (void*)g_idCaptureBatchRazor); - if (g_cmdCaptureBatchRazor) - { - g_descCaptureBatchRazor = - channelActionName("batch capture razor areas (one per area)"); - g_accelCaptureBatchRazor.accel.cmd = g_cmdCaptureBatchRazor; - g_accelCaptureBatchRazor.desc = g_descCaptureBatchRazor.c_str(); - rec->Register("gaccel", (void*)&g_accelCaptureBatchRazor); - } - - // Register the "capture selected track (realtime)" action (command_id -> gaccel -> - // hookcommand). Realtime sibling of the offline CAPTURE_TRACK scope: records the - // selected track's own output in realtime into a hidden temp track, moves it into - // the bank. Dialog-free. Channel-qualified FOREVER-STABLE id. - g_idCaptureTrackRealtime = internCmdId("CAPTURE_TRACK_REALTIME"); - g_cmdCaptureTrackRealtime = rec->Register("command_id", (void*)g_idCaptureTrackRealtime); - if (g_cmdCaptureTrackRealtime) - { - g_descCaptureTrackRealtime = - channelActionName("capture selected track (realtime)"); - g_accelCaptureTrackRealtime.accel.cmd = g_cmdCaptureTrackRealtime; - g_accelCaptureTrackRealtime.desc = g_descCaptureTrackRealtime.c_str(); - rec->Register("gaccel", (void*)&g_accelCaptureTrackRealtime); - } - - // Cancel-in-flight sibling: aborts a running realtime capture (stop + restore). - // Channel-qualified FOREVER-STABLE id. - g_idCancelRealtime = internCmdId("CANCEL_REALTIME_CAPTURE"); - g_cmdCancelRealtime = rec->Register("command_id", (void*)g_idCancelRealtime); - if (g_cmdCancelRealtime) - { - g_descCancelRealtime = channelActionName("cancel realtime capture"); - g_accelCancelRealtime.accel.cmd = g_cmdCancelRealtime; - g_accelCancelRealtime.desc = g_descCancelRealtime.c_str(); - rec->Register("gaccel", (void*)&g_accelCancelRealtime); - } - - // Register the M10 "re-capture from source" action (command_id -> gaccel -> - // hookcommand). Regenerates the selected provenanced sample from its recorded - // source's current state; bank-only, never places on the timeline. Channel- - // qualified FOREVER-STABLE id (suffix RECAPTURE_FROM_SOURCE). - g_idRecaptureFromSource = internCmdId("RECAPTURE_FROM_SOURCE"); - g_cmdRecaptureFromSource = rec->Register("command_id", (void*)g_idRecaptureFromSource); - if (g_cmdRecaptureFromSource) - { - g_descRecaptureFromSource = channelActionName("re-capture from source"); - g_accelRecaptureFromSource.accel.cmd = g_cmdRecaptureFromSource; - g_accelRecaptureFromSource.desc = g_descRecaptureFromSource.c_str(); - rec->Register("gaccel", (void*)&g_accelRecaptureFromSource); - } - - // Register the Phase V "show version" action (command_id -> gaccel -> hookcommand). - // On-demand only — prints the CMake-sourced version to the console when fired; no - // startup print. Channel-qualified FOREVER-STABLE id; label carries the channel prefix - // so a beta's "show version" is distinguishable from stable's in the Actions list. - g_idShowVersion = internCmdId("SHOW_VERSION"); - g_cmdShowVersion = rec->Register("command_id", (void*)g_idShowVersion); - if (g_cmdShowVersion) - { - g_descShowVersion = channelActionName("show version"); - g_accelShowVersion.accel.cmd = g_cmdShowVersion; - g_accelShowVersion.desc = g_descShowVersion.c_str(); - rec->Register("gaccel", (void*)&g_accelShowVersion); - } // Register the Design View action family (D4): toggle/activate mode, tag/untag/ // show-both selected tracks. Each mints its own command_id + gaccel; the single @@ -632,11 +381,11 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT( // Register the S8 ingest action family: the Media-Explorer import-into-bank+assign // action. Shares g_session with the other families; routed by the same hookcommand via - // ingestHandleCommand. (The arrange capture+assign action is registered in the capture - // family above; the drop path is a bank_panel callback, not a bindable action.) + // ingestHandleCommand. (The arrange capture+assign action is a table row above; the + // drop path is a bank_panel callback, not a bindable action.) reasampler::ingestRegisterActions(rec, &g_session); - // One hookcommand routes every ReaSampler action (spike + toggle + Design View). + // One hookcommand routes every ReaSampler action (table + the three families). // Registered once, after all command ids are minted. rec->Register("hookcommand", (void*)&OnHookCommand); diff --git a/src/core/capture/capture_paths.cpp b/src/core/capture/capture_paths.cpp index 705b28f..575af3e 100644 --- a/src/core/capture/capture_paths.cpp +++ b/src/core/capture/capture_paths.cpp @@ -110,7 +110,7 @@ std::string resolveBankFile(const std::string& projectDir, std::string projectDirOfRpp(const std::string& rppPath) { // An unsaved project reports an empty .rpp path; keep it empty so downstream - // resolution refuses (no default-location fallback). Mirrors persist.cpp's prior + // resolution refuses (no default-location fallback). Mirrors the former persist shell's // projectDirOf exactly: parent_path of the .rpp, then normalizeSlashes. if (rppPath.empty()) return {}; std::string dir = std::filesystem::path(rppPath).parent_path().string(); diff --git a/src/core/capture/tail_control.h b/src/core/capture/tail_control.h index 9430dba..fa4ae4b 100644 --- a/src/core/capture/tail_control.h +++ b/src/core/capture/tail_control.h @@ -1,6 +1,6 @@ #pragma once // tail_control — the REAPER-free logic behind the docked bank_panel's tail-mode -// toggle. The panel shell (bank_panel.cpp) owns the SWELL window, LICE drawing, and +// toggle. The panel shell (shell/panel/) owns the SWELL window, LICE drawing, and // click hit-testing; what is NOT DAW-bound — the cycle order, the manual-length // clamp, and the toggle's label text — lives here so it is unit-tested outside the // DAW (CLAUDE.md §load-bearing split). Mirror of bank_grid / mode_switch. @@ -27,7 +27,7 @@ inline constexpr double kDefaultManualTailMs = 2000.0; inline constexpr double kManualStepMs = 250.0; // The panel's current tail setting: the mode plus the length used ONLY when the -// mode is Manual. Held as in-memory panel/session state (bank_panel.cpp), default +// mode is Manual. Held as in-memory panel/session state (shell/panel), default // None so a capture with no explicit choice stays exact-bounds / byte-identical to // today. `manualMs` is a stored default a future fine-adjust UI can tune; it is // clamped to the 8 s cap (kMaxTailMs) before it ever reaches a CaptureRequest. diff --git a/src/core/capture/wav_trim.h b/src/core/capture/wav_trim.h deleted file mode 100644 index e2d182f..0000000 --- a/src/core/capture/wav_trim.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -// wav_trim — TRANSITIONAL forwarding header (Q-W3, audit §4e WAV/RIFF consolidation). -// -// The one pure owner of the WAV/RIFF byte format is now core/capture/wav_codec.{h,cpp} -// (chunk walker + layout parse + float32 build + size-field patch + content hash). -// Everything this header used to declare (WavLayout / parseWavLayout / -// extractFloatFrames / WavTruncatePlan / planWavTruncate) lives there, same -// namespace (reasampler::capture), same signatures — this include is a pure alias. -// -// Kept ONLY so the TUs a parallel wave owns (sample_map.h and the VST editor/ -// processor god-TUs, Q-W2v) compile untouched — editing them here would collide -// with that wave's in-flight split. Retire this header (and point its includers at -// wav_codec.h) once Q-W2v lands. - -#include "core/capture/wav_codec.h" diff --git a/src/core/instrument/engine/pitch_shift.h b/src/core/instrument/engine/pitch_shift.h index 2a73b8d..f9fa407 100644 --- a/src/core/instrument/engine/pitch_shift.h +++ b/src/core/instrument/engine/pitch_shift.h @@ -25,7 +25,7 @@ // #include ` unconditionally, which CANNOT enter the pure sampler_core module // (CLAUDE.md load-bearing split: NO vendor/host/SDK types; sampler_core_tests links neither // SDK and compiles outside the DAW). So the Preserve DSP lands as route (b): a house-native -// pure module alongside peaks / wav_trim, CTest-testable, RT-disciplined. Same +// pure module alongside peaks / wav_codec, CTest-testable, RT-disciplined. Same // PitchEngine::Preserve contract behind the seam — if WDL is ever preferred it swaps in at // the SHELL, never in the pure core. // @@ -53,7 +53,7 @@ // // PURE MODULE: NO VST3, NO REAPER, NO SWELL, NO vendor/ includes. Standard library only. // Shares the `AudioSample` float alias from peaks (the one house precedent — sampler_core / -// wav_trim do the same). +// wav_codec does the same). // // RT DISCIPLINE (S16 hard constraint). `configure()` sizes the ring ONCE (off the audio // thread, at voice allocation). `prime()` / `warm()` only copy into the pre-sized ring diff --git a/src/core/instrument/engine/sampler_core.h b/src/core/instrument/engine/sampler_core.h index 5ef1e88..e241e1a 100644 --- a/src/core/instrument/engine/sampler_core.h +++ b/src/core/instrument/engine/sampler_core.h @@ -13,7 +13,7 @@ // structurally: sampler_core_tests links neither SDK (see CMakeLists §2i). // // It shares the `AudioSample` float alias from peaks — the one house precedent for a -// pure module leaning on peaks for the audio-domain type (wav_trim does the same). The +// pure module leaning on peaks for the audio-domain type (wav_codec does the same). The // S2 seam fields (root note, loop points) enter as plain int / frame-index inputs; the // core does no file I/O — it is handed decoded sample frames and produces audio frames. diff --git a/src/core/instrument/map/bridge_marshal.h b/src/core/instrument/map/bridge_marshal.h index e5db3f8..53a003c 100644 --- a/src/core/instrument/map/bridge_marshal.h +++ b/src/core/instrument/map/bridge_marshal.h @@ -4,7 +4,7 @@ // The bridge shell (reaper_bridge.cpp) resolves REAPER API functions by name over the // host callback and invokes them; the one fiddly-and-easy-to-get-wrong part around // GetProjExtState — interpreting its int return against the buffer it filled — is pure -// and unit-tested here. Mirror of capture_paths / wav_trim splitting the arithmetic out +// and unit-tested here. Mirror of capture_paths / wav_codec splitting the arithmetic out // of a REAPER-facing shell. // // The S1 spike ALSO carried a string-scan JSON reader (extractJsonStringField) as a @@ -36,63 +36,8 @@ namespace reasampler::instrument::map { std::optional decodeGetProjExtState(int apiReturn, const std::string& buffer); -// --------------------------------------------------------------------------- -// The GetProjExtState GROW-LOOP retry policy (Q-W5 rider, T2-04). -// --------------------------------------------------------------------------- -// GetProjExtState writes into a caller-supplied buffer with no documented -// query-the-size call, so a large value (bank blob, usage record) must be read by -// growing a buffer until the value fits strictly inside it. Three shells carried -// hand-rolled copies of that loop (persist's ext-state reads, usage_scan's -// prune-safety-adjacent record read, reaper_bridge's VST-side bank read); the ONE -// policy now lives here so the retry/termination rules cannot drift. The fiddly -// part is the termination taxonomy, which each caller folds differently: -// -// * Absent — the API returned <= 0 on some attempt: the key holds no value. -// (persist -> "" empty bank; usage_scan / bridge -> nullopt) -// * Complete — the written C string fits STRICTLY inside the buffer (size+1 < -// cap), so it cannot have been clipped: `value` is the whole value. -// * Overflow — the value never fit under the 16 MB ceiling: it is unreadable -// WHOLE, which is NOT the same as absent. (persist warns on the -// console; usage_scan folds it to the prune fail-safe abort) -// -// `read` is one GetProjExtState-shaped attempt: int read(char* buf, int cap), -// returning the API's int. A template, statically dispatched per call site — no -// virtual calls, no std::function (the §3 performance guardrail); the caller binds -// the project/namespace/key (or a resolved function pointer, VST side) in a lambda. -struct GrowingExtStateRead { - enum class Status { Absent, Complete, Overflow }; - Status status = Status::Absent; - int apiReturn = 0; // the FINAL attempt's return (<= 0 iff Absent); feeds - // decodeGetProjExtState on the bridge path unchanged - std::string value; // the whole value; meaningful only when Complete -}; - -template -GrowingExtStateRead readProjExtStateGrowing(ReadFn&& read) { - // Start generous; grow ×4 if REAPER reports the value may have been clipped - // (the return is the value length; equal-to-capacity-minus-NUL is ambiguous, - // so only a strict fit terminates). Ceiling 16 MB — give up rather than loop - // forever on a pathological value. - GrowingExtStateRead result; - for (int cap = 1 << 16; cap <= (1 << 24); cap <<= 2) { - std::vector buf(static_cast(cap), '\0'); - const int rv = read(buf.data(), cap); - result.apiReturn = rv; - if (rv <= 0) { - result.status = GrowingExtStateRead::Status::Absent; - return result; - } - buf[static_cast(cap) - 1] = '\0'; // defensive: guard against a read() that fills the buffer without honoring NUL-termination within cap - std::string s(buf.data()); - if (static_cast(s.size()) + 1 < cap) { - result.status = GrowingExtStateRead::Status::Complete; - result.value = std::move(s); - return result; - } - // else: possibly truncated -> grow and retry. - } - result.status = GrowingExtStateRead::Status::Overflow; - return result; -} +// The GetProjExtState GROW-LOOP retry policy (T2-04) lived here through Q-W5; it +// was rehomed to core/wire/ext_state_read.h in Q-W6 (its consumers are 2:1 +// extension-side, so it belongs on the neutral wire seam, not the instrument map). } // namespace reasampler::instrument::map diff --git a/src/core/instrument/map/sample_map.cpp b/src/core/instrument/map/sample_map.cpp index cfb35be..4f92b13 100644 --- a/src/core/instrument/map/sample_map.cpp +++ b/src/core/instrument/map/sample_map.cpp @@ -1,6 +1,6 @@ // sample_map — pure implementation (the RESOLUTION half; the ComponentState codec // lives in component_state_io.cpp since Q-W2v). See sample_map.h. NO VST3 / REAPER / -// SWELL / vendor includes; standard library + the pure bank_book / wav_trim / sampler_core. +// SWELL / vendor includes; standard library + the pure bank_book / wav_codec / sampler_core. #include "core/instrument/map/sample_map.h" diff --git a/src/core/instrument/map/sample_map.h b/src/core/instrument/map/sample_map.h index cf06733..3f6d5a8 100644 --- a/src/core/instrument/map/sample_map.h +++ b/src/core/instrument/map/sample_map.h @@ -3,7 +3,7 @@ // "reasampler" bank ext-state + a decoded WAV into the plain data the sampler core // plays, and (de)serialize the instance's selected-sample choice for VST3 component // state. NO VST3, NO REAPER, NO SWELL, NO vendor/ includes at the boundary — the -// mirror of capture_paths / wav_trim / bridge_marshal splitting the fiddly, testable +// mirror of capture_paths / wav_codec / bridge_marshal splitting the fiddly, testable // arithmetic out of a host-facing shell. // // WHY IT EXISTS (S4 seams). The instrument reads the bank over the live-state seam @@ -14,7 +14,7 @@ // downmix its decoded PCM to the core's mono contract, and build the Tier-0 chromatic // Keymap — is pure and unit-tested here. // -// It links bank_book (the shared BankBook::deserialize) and wav_trim (the shared +// It links bank_book (the shared BankBook::deserialize) and wav_codec (the shared // 32-bit-float WAV parse — no third WAV reader) and sampler_core (the Keymap / // SampleData it produces). All three are pure; this stays pure. @@ -25,7 +25,7 @@ #include "core/model/bank_book.h" // BankBook::deserialize (shared bank JSON parse) #include "core/instrument/engine/sampler_core.h" // Keymap, SampleData, SampleLoop -#include "core/capture/wav_trim.h" // parseWavLayout, extractFloatFrames (shared WAV parse) +#include "core/capture/wav_codec.h" // parseWavLayout, extractFloatFrames (shared WAV parse) namespace reasampler::instrument::map { @@ -167,7 +167,7 @@ struct BankChoice { }; std::vector listBanks(const std::string& banksJson); -// Downmix interleaved float frames (the shape wav_trim::extractFloatFrames yields: +// Downmix interleaved float frames (the shape wav_codec's extractFloatFrames yields: // [f0c0,f0c1,...,f1c0,...]) to the core's MONO contract by AVERAGING channels per // frame. `channelCount` is the interleave stride (>= 1). CHANNEL POLICY (Tier 0, // documented + surfaced): the S3 core is mono-per-sample by design; bank WAVs preserve diff --git a/src/core/instrument/ui/envelope_overlay.h b/src/core/instrument/ui/envelope_overlay.h index 0d06945..e25e647 100644 --- a/src/core/instrument/ui/envelope_overlay.h +++ b/src/core/instrument/ui/envelope_overlay.h @@ -46,7 +46,7 @@ // right edge and wins the tie, so the fade can be dragged open from zero). // // DELIBERATELY ENGINE-FREE (house pattern — param_slider does the same). It does NOT depend on -// sample_map / sampler_core (which would drag bank_book / wav_trim in). The shell reads the +// sample_map / sampler_core (which would drag bank_book / wav_codec in). The shell reads the // zone's AdsrSeconds / TriggerParams and packs them into the small AmpEnvelope view struct here. // AHDSR times are wall-clock SECONDS (rate-free, matching the stored domain — Daniel's no- // hardcoded-rate ruling); Trigger fades are FRACTIONS of the play span. The one rate-bound input diff --git a/src/core/instrument/ui/waveform_view.h b/src/core/instrument/ui/waveform_view.h index 171b39a..8b552cf 100644 --- a/src/core/instrument/ui/waveform_view.h +++ b/src/core/instrument/ui/waveform_view.h @@ -17,7 +17,7 @@ // // It reuses the same Rect + contains() as editor_geometry (one shared geometry idiom), so // this header depends on editor_geometry.h rather than redefining a rectangle type. Audio -// is the peaks AudioSample float alias (the one house precedent — sampler_core / wav_trim do +// is the peaks AudioSample float alias (the one house precedent — sampler_core / wav_codec do // the same), so the zero-crossing helper takes the same mono PCM the shell already decoded. #pragma once diff --git a/src/core/model/owned_manifest.h b/src/core/model/owned_manifest.h index 9135b75..b1bf611 100644 --- a/src/core/model/owned_manifest.h +++ b/src/core/model/owned_manifest.h @@ -3,7 +3,7 @@ // // PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO // vendor/ includes. Standard library only. Unit-tested outside the DAW — the same -// "small pure type + JSON round-trip" pattern as wav_trim / tab_strip. +// "small pure type + JSON round-trip" pattern as wav_codec / tab_strip. // // -- What it is -------------------------------------------------------------- // diff --git a/src/core/namespaces.h b/src/core/namespaces.h deleted file mode 100644 index fd5431d..0000000 --- a/src/core/namespaces.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once -// core/namespaces.h — Q-W1 INTERIM flat-namespace shim for the not-yet-split -// god/shell TUs (bank_panel / actions / persist / ingest / main / view / capture -// shells / the VST editor+processor). The Q-W1 sub-namespaces move every clean pure -// module's symbols out of the flat `reasampler` namespace; the god modules keep their -// pre-split internals, which reference those symbols unqualified (or qualified as -// `reasampler::X`). Nominating every sub-namespace inside `reasampler` restores both -// forms ([namespace.qual]p2 routes qualified lookup through using-directives), so the -// god internals stay untouched until their own split waves. -// -// SCOPE CONTRACT: included ONLY by god/shell TUs pending their split wave -// (Q-W2/Q-W2v/Q-W3/Q-W4/Q-W5). Clean core modules must NOT include this — they -// reference cross-subsystem symbols by their real namespace homes. Each split wave -// drops this include from the TUs it rewrites; when the last split lands, delete -// this header. - -namespace reasampler { - -namespace model {} -namespace view {} -namespace capture {} -namespace audio {} -namespace ui {} -namespace reclaim {} -namespace version {} -namespace json {} -namespace util {} -namespace wire {} -namespace instrument { -namespace engine {} -namespace map {} -namespace ui {} -} // namespace instrument - -using namespace model; -using namespace view; -using namespace capture; -using namespace audio; -using namespace ui; -using namespace reclaim; -using namespace version; -using namespace util; -using namespace wire; -using namespace instrument::engine; -using namespace instrument::map; -using namespace instrument::ui; - -} // namespace reasampler diff --git a/src/core/ui/bank_grid.h b/src/core/ui/bank_grid.h index 6c4dd00..c6845a6 100644 --- a/src/core/ui/bank_grid.h +++ b/src/core/ui/bank_grid.h @@ -1,7 +1,7 @@ #pragma once #include "core/ui/rect.h" // bank_grid — the REAPER-free layout math and cache-key logic behind the docked -// bank_panel (M5, Wave A). The panel shell (bank_panel.cpp) owns the SWELL window, +// bank_panel (M5, Wave A). The panel shell (shell/panel/) owns the SWELL window, // LICE drawing, and PCM reads; ALL of that is REAPER-bound and DAW-verified. What // is NOT DAW-bound — how N sample cells tile a panel of a given pixel size, and // the key that identifies a cached thumbnail — lives here so it is unit-tested @@ -80,7 +80,7 @@ std::string thumbnailKeyString(const ThumbnailKey& key); // --- Interaction (M5 Wave B): hit-test, selection, keyboard nav -------------- // // All REAPER-free so the panel's interaction LOGIC is unit-tested outside the DAW, -// exactly as the layout math is. The panel shell (bank_panel.cpp) reads live mouse +// exactly as the layout math is. The panel shell (shell/panel/) reads live mouse // coordinates / key codes / modifier state via SWELL and calls into these; it owns // no selection arithmetic of its own. diff --git a/src/core/ui/card_drag.h b/src/core/ui/card_drag.h index e64ce05..1e2b379 100644 --- a/src/core/ui/card_drag.h +++ b/src/core/ui/card_drag.h @@ -2,7 +2,7 @@ // card_drag — the REAPER-free decision logic behind the L7 in-grid reorder drag. Three // pure concerns live here so they are unit-tested outside the DAW (CLAUDE.md §load-bearing // split); the SWELL wiring, SetCursor call, cursor resources, and drop-target draw stay in -// the shell (bank_panel.cpp). Mirror of drag_out::decideGesture. +// the shell (shell/panel/panel_drag.cpp). Mirror of drag_out::decideGesture. // // 1. GESTURE PRECEDENCE (F3 settled). A live drag resolves to exactly one gesture, in a // strict precedence the shell evaluates on every mouse-move / at drop: diff --git a/src/core/ui/drag_out.h b/src/core/ui/drag_out.h index a24dd2c..aee7a2e 100644 --- a/src/core/ui/drag_out.h +++ b/src/core/ui/drag_out.h @@ -3,7 +3,7 @@ // drag_out — the REAPER-free / OS-free decision logic behind the bank_panel's native OS // drag-out (Milestone 11, the final polish point). Two pure concerns live here so they are // unit-tested outside the DAW (CLAUDE.md §load-bearing split); the OLE / SWELL initiation -// and the bank_panel gesture hook stay in the shell (drag_out_win.* + bank_panel.cpp). +// and the bank_panel gesture hook stay in the shell (drag_out_win.* + shell/panel/panel_drag.cpp). // // 1. GESTURE BOUNDARY (invariant #4 — do not regress the internal drag). The panel // already runs an INTERNAL drag: press a selected cell, cross a threshold, drop onto diff --git a/src/core/ui/footer_bar.h b/src/core/ui/footer_bar.h index 4d1e58f..fb608ca 100644 --- a/src/core/ui/footer_bar.h +++ b/src/core/ui/footer_bar.h @@ -3,7 +3,7 @@ // footer_bar — the REAPER-free, LICE-free layout + hit-test math for the bank_panel's L4 // footer LEFT group: the narrowed [Arrange|Design] mode toggle, its compact per-mode count // label, and the Tail button, laid out left-to-right at the footer's left. The panel shell -// (bank_panel.cpp) owns the SWELL window, LICE drawing, and the click dispatch (cycle tail / +// (shell/panel/) owns the SWELL window, LICE drawing, and the click dispatch (cycle tail / // activate a mode); what is NOT DAW-bound — WHERE the toggle box, the count label, and the // Tail button sit, and which one a click lands on — lives here so it is unit-tested outside // the DAW (CLAUDE.md §load-bearing split). Mirror of action_bar / mode_switch / prune_button. diff --git a/src/core/ui/prune_button.h b/src/core/ui/prune_button.h index fbd5b3a..cf7f853 100644 --- a/src/core/ui/prune_button.h +++ b/src/core/ui/prune_button.h @@ -3,7 +3,7 @@ // prune_button — the REAPER-free layout math behind the bank_panel's Prune button // (Phase R, Wave 3 — R3, fork R-E). A single labelled button drawn in the panel's // tail-footer strip that fires the "Prune bank folder" command. The panel shell -// (bank_panel.cpp) owns the SWELL window, LICE drawing, and the Main_OnCommand +// (shell/panel/) owns the SWELL window, LICE drawing, and the Main_OnCommand // dispatch of the registered command id — all REAPER-bound, DAW-verified. What is // NOT DAW-bound — WHERE the button sits in the footer and whether a click lands on // it — lives here so it is unit-tested outside the DAW (CLAUDE.md §load-bearing @@ -42,7 +42,7 @@ using ButtonRect = Rect; // Q-W1: the shared concrete ui::Rect (core/ui/rect.h) // * buttonWidth — the button's fixed width. // * rightInset — gap from the footer's right edge to the button's right edge (the // button sits left of this inset, clearing the right-aligned version -// readout). COUPLED TO drawFooter (bank_panel.cpp): the version readout +// readout). COUPLED TO drawFooter (panel_render.cpp): the version readout // uses an 8 px right margin. The button's right edge lands at // footer.right - 84, i.e. 76 px left of the readout's right margin — // enough clearance for the ~10-char label. ALSO COUPLED to diff --git a/src/core/ui/tab_strip.h b/src/core/ui/tab_strip.h index 4f05ee9..ea11b38 100644 --- a/src/core/ui/tab_strip.h +++ b/src/core/ui/tab_strip.h @@ -8,7 +8,7 @@ // tabs). What is NOT DAW-bound — how N fixed-width tabs tile a strip of a given // pixel width, where the overflow chevrons sit, which tab/chevron a click lands in, // and how far the strip may scroll — lives here so it is unit-tested outside the -// DAW (CLAUDE.md §load-bearing split). The panel shell (bank_panel.cpp) owns the +// DAW (CLAUDE.md §load-bearing split). The panel shell (shell/panel/) owns the // SWELL window, LICE drawing, and the live BankBook read; it calls into this seam // for every rect and every hit. Mirror of mode_switch / bank_grid. // diff --git a/src/core/view/mode_switch.h b/src/core/view/mode_switch.h index 44b8250..0b85942 100644 --- a/src/core/view/mode_switch.h +++ b/src/core/view/mode_switch.h @@ -3,7 +3,7 @@ // mode_switch — the REAPER-free layout math behind the bank_panel's Design-View // mode switch (Phase D, Wave 4 — D5). A segmented control `[ Arrange | Design ]` // (N-mode general, one segment per registered mode) drawn in a fixed-height header -// strip at the top of the docked panel. The panel shell (bank_panel.cpp) owns the +// strip at the top of the docked panel. The panel shell (shell/panel/) owns the // SWELL window, LICE drawing, and the live ViewModeModel read + mode activation — // all REAPER-bound, DAW-verified. What is NOT DAW-bound — how N segments tile a // header rectangle, and which segment a click lands in — lives here so it is diff --git a/src/core/wire/ext_state_read.h b/src/core/wire/ext_state_read.h new file mode 100644 index 0000000..0f0ec75 --- /dev/null +++ b/src/core/wire/ext_state_read.h @@ -0,0 +1,70 @@ +#pragma once +// ext_state_read — the GetProjExtState GROW-LOOP retry policy (T2-04; rehomed to +// core/wire in Q-W6 — its consumers are the extension's persist/usage-scan shells +// AND the instrument's bridge, so it lives on the neutral wire seam rather than in +// the instrument-side bridge_marshal decode helper it started in). +// +// GetProjExtState writes into a caller-supplied buffer with no documented +// query-the-size call, so a large value (bank blob, usage record) must be read by +// growing a buffer until the value fits strictly inside it. Three shells carried +// hand-rolled copies of that loop (persist's ext-state reads, usage_scan's +// prune-safety-adjacent record read, reaper_bridge's VST-side bank read); the ONE +// policy lives here so the retry/termination rules cannot drift. The fiddly part +// is the termination taxonomy, which each caller folds differently: +// +// * Absent — the API returned <= 0 on some attempt: the key holds no value. +// (persist -> "" empty bank; usage_scan / bridge -> nullopt) +// * Complete — the written C string fits STRICTLY inside the buffer (size+1 < +// cap), so it cannot have been clipped: `value` is the whole value. +// * Overflow — the value never fit under the 16 MB ceiling: it is unreadable +// WHOLE, which is NOT the same as absent. (persist warns on the +// console; usage_scan folds it to the prune fail-safe abort) +// +// `read` is one GetProjExtState-shaped attempt: int read(char* buf, int cap), +// returning the API's int. A template, statically dispatched per call site — no +// virtual calls, no std::function (the §3 performance guardrail); the caller binds +// the project/namespace/key (or a resolved function pointer, VST side) in a lambda. + +#include +#include +#include + +namespace reasampler::wire { + +struct GrowingExtStateRead { + enum class Status { Absent, Complete, Overflow }; + Status status = Status::Absent; + int apiReturn = 0; // the FINAL attempt's return (<= 0 iff Absent); feeds + // decodeGetProjExtState on the bridge path unchanged + std::string value; // the whole value; meaningful only when Complete +}; + +template +GrowingExtStateRead readProjExtStateGrowing(ReadFn&& read) { + // Start generous; grow ×4 if REAPER reports the value may have been clipped + // (the return is the value length; equal-to-capacity-minus-NUL is ambiguous, + // so only a strict fit terminates). Ceiling 16 MB — give up rather than loop + // forever on a pathological value. + GrowingExtStateRead result; + for (int cap = 1 << 16; cap <= (1 << 24); cap <<= 2) { + std::vector buf(static_cast(cap), '\0'); + const int rv = read(buf.data(), cap); + result.apiReturn = rv; + if (rv <= 0) { + result.status = GrowingExtStateRead::Status::Absent; + return result; + } + buf[static_cast(cap) - 1] = '\0'; // defensive: guard against a read() that fills the buffer without honoring NUL-termination within cap + std::string s(buf.data()); + if (static_cast(s.size()) + 1 < cap) { + result.status = GrowingExtStateRead::Status::Complete; + result.value = std::move(s); + return result; + } + // else: possibly truncated -> grow and retry. + } + result.status = GrowingExtStateRead::Status::Overflow; + return result; +} + +} // namespace reasampler::wire diff --git a/src/ext_keys.h b/src/ext_keys.h index 71d6953..6064c4a 100644 --- a/src/ext_keys.h +++ b/src/ext_keys.h @@ -1,6 +1,6 @@ #pragma once // ext_keys — the SINGLE SOURCE OF TRUTH for the "reasampler" project ext-state -// namespace + key names, shared by the extension (writer, via persist.h) and the +// namespace + key names, shared by the extension (writer, via shell/persist) and the // VST3 instrument (reader, via the bridge). Both sides include this header so the // wire contract cannot drift between the two artifacts (the S4 reviewer flagged the // spike's duplicated constants as a drift risk). @@ -12,7 +12,7 @@ // either SDK. // // FOREVER-STABLE once shipped: these strings key every already-saved project's -// stored state. Changing any of them orphans that state. See persist.h for the +// stored state. Changing any of them orphans that state. See shell/persist/ext_state_io.h for the // per-key retirement / migration semantics — this header only owns the spellings. #include "core/version/app_version.h" @@ -29,7 +29,7 @@ namespace reasampler { inline const char* kProjExtNamespace() { return version::extStateNamespace().c_str(); } // The multi-bank key: the whole serialized BankBook (pool + named banks). This is -// the key the VST3 instrument reads to see the live bank (read-only, S4). persist.h +// the key the VST3 instrument reads to see the live bank (read-only, S4). ext_state_io // documents its authority + the legacy-key migration around it. inline constexpr const char* kProjExtBanksKey = "banks"; diff --git a/src/ingest.cpp b/src/ingest.cpp index 8bf93da..43e288c 100644 --- a/src/ingest.cpp +++ b/src/ingest.cpp @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // ingest.cpp — the S8 "ingest through the bank" shell (extension side). See ingest.h. // // Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h WITHOUT @@ -25,7 +24,7 @@ #include "core/util/file_bytes.h" // shared whole-file loader (Q-W1, T2-03) #include "core/wire/instrument_drop.h" // pure buildInstrumentDropPreset (.vstpreset image for a sampleId) #include "shell/actions/instrument_drop_win.h" // shell loadInstrumentOntoTrack (FX add+apply, no own undo block) -#include "persist.h" // ReaSamplerSession +#include "shell/persist/session.h" // ReaSamplerSession #include "core/capture/wav_codec.h" // parseWavLayout (32f fast-path validator), buildFloat32Wav, hashWavContent @@ -47,6 +46,22 @@ namespace reasampler { +// Real-namespace-home using-declarations (Q-W6: the interim core/namespaces.h shim +// is retired; each symbol names its Q-W1 home explicitly). +using capture::BankPaths; +using capture::buildFloat32Wav; +using capture::deriveBankPaths; +using capture::hashWavContent; +using capture::parseWavLayout; +using capture::projectDirOfRpp; +using capture::WavLayout; +using util::readFileBytes; +using version::channelActionName; +using version::channelCommandId; +using wire::AssignmentRequest; +using wire::buildInstrumentDropPreset; +using wire::encodeAssignmentRequest; + namespace { // The live session the ingest paths mutate. Set once by ingestRegisterActions and read by @@ -153,7 +168,7 @@ struct ImportResult { // Imports one OS-native source file into the ACTIVE bank: convert-if-needed to 32-bit- // float WAV, write to the project-relative bank folder, index-add, hash-dedup applied. // -// BANK CONTRACT: the instrument (wav_trim) expects every bank file to be a canonical +// BANK CONTRACT: the instrument (wav_codec parse) expects every bank file to be a canonical // 32-bit-float WAV (WAVE_FORMAT_IEEE_FLOAT, 32 bits). A verbatim copy of a non-WAV (or // an integer-PCM or double-float WAV) would be unplayable. This function therefore: // 1. Checks whether the source IS already a valid 32f WAV (parseWavLayout fast path). diff --git a/src/ingest.h b/src/ingest.h index 6c74d57..a6b2203 100644 --- a/src/ingest.h +++ b/src/ingest.h @@ -1,5 +1,4 @@ #pragma once -#include "core/namespaces.h" // ingest — the S8 "ingest through the bank" shell (EXTENSION side). // // Compiled into the reaper_reasampler MODULE. REAPER-facing (PCM_Source metadata reads, diff --git a/src/persist.h b/src/persist.h deleted file mode 100644 index 016984d..0000000 --- a/src/persist.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#include "core/namespaces.h" -// persist.h — COMPATIBILITY UMBRELLA (Q-W5). The former persist god-TU split into -// three TUs under shell/persist/ by responsibility: -// -// * shell/persist/session.h + session.cpp — the ReaSamplerSession class (lifecycle, -// poll identity-transition detection, the projectconfig undo/redo reload drain). -// * shell/persist/ext_state_io.h + ext_state_io.cpp — the ext-state ↔ JSON -// serialization bridge, key contract, GUID minting, bank-folder relocation. -// * shell/persist/prune_fs.cpp — the prune scan + THE SINGLE FILE-DELETION -// AUTHORITY over USER files in the bank folder (deleteOrphanFile, file-local); -// a shell's self-cleanup of its own transient scratch files (.vstpreset temp, -// realtime finalize temp) is excluded from this authority. -// -// This header re-exports the split APIs so every existing caller (actions.cpp, -// main.cpp, ingest.cpp, the panel TUs, capture shells) keeps compiling untouched — -// Q-W4 is rewriting actions.cpp in parallel, so touching callers this wave is a -// guaranteed conflict. Retiring this umbrella (callers include the split headers -// directly) is Q-W6 cleanup. -// -// core/namespaces.h stays HERE, not in the split headers/TUs: the unsplit callers -// still reference flat-namespace symbols (TailSetting, PruneReport, BankModel, ...) -// through this include, while the split persist TUs themselves reference real -// namespace homes and are shim-free. - -#include "shell/persist/ext_state_io.h" -#include "shell/persist/session.h" diff --git a/src/resource.h b/src/resource.h index 0cad811..1064b9a 100644 --- a/src/resource.h +++ b/src/resource.h @@ -6,5 +6,5 @@ // numeric ids stable and unique across the extension. // The docked bank panel (M5). A bare owner-drawn child dialog: it carries no -// controls — bank_panel.cpp paints the whole client area with LICE. +// controls — the panel shell (shell/panel/panel_render.cpp) paints the whole client area with LICE. #define IDD_BANK_PANEL 1000 diff --git a/src/shell/actions/action_registry.cpp b/src/shell/actions/action_registry.cpp index 1b7c1ec..2112111 100644 --- a/src/shell/actions/action_registry.cpp +++ b/src/shell/actions/action_registry.cpp @@ -1,9 +1,10 @@ -// action_registry.cpp — shared registration plumbing (Q-W4 split of actions.cpp). -// See action_registry.h. Needs no REAPER API pointers: rec->Register is a member -// call on the dispatch struct REAPER hands the entry point. +// action_registry.cpp — shared registration plumbing (Q-W4) + the registration +// table (Q-W6). See action_registry.h. Needs no REAPER API pointers: rec->Register +// is a member call on the dispatch struct REAPER hands the entry point. #include "shell/actions/action_registry.h" +#include #include #include @@ -23,6 +24,18 @@ using version::channelCommandId; // pointer for a given action. std::deque g_strStore; +// One registered table row: the row data plus the registry-owned registration +// artifacts (interned id, minted cmd, gaccel storage REAPER holds a pointer to). +// A std::deque so element addresses never move after push_back — REAPER keeps each +// &accel until the mirror-unregister. +struct TableEntry { + ActionTableRow row; + const char* id = nullptr; // interned channel-qualified command id + int cmd = 0; // minted command id (0 = mint failed / inert row) + gaccel_register_t accel{}; // Actions-list entry; address handed to REAPER +}; +std::deque g_table; + } // namespace const char* channelIdFor(const char* suffix) { @@ -46,4 +59,39 @@ int registerAction(reaper_plugin_info_t* rec, const char* suffix, return cmd; } +void registerActionTable(reaper_plugin_info_t* rec, const ActionTableRow* rows, + std::size_t count) { + for (std::size_t i = 0; i < count; ++i) { + g_table.push_back(TableEntry{rows[i]}); + TableEntry& e = g_table.back(); + e.id = channelIdFor(e.row.suffix); + e.cmd = registerAction(rec, e.row.suffix, e.accel, e.row.phrase); + } +} + +bool actionTableHandleCommand(int command) { + if (command == 0) return false; + for (const TableEntry& e : g_table) + if (e.cmd != 0 && command == e.cmd) { + e.row.run(e.row.arg); + return true; + } + return false; +} + +int actionTableCommandId(const char* suffix) { + for (const TableEntry& e : g_table) + if (std::strcmp(e.row.suffix, suffix) == 0) return e.cmd; + return 0; +} + +void unregisterActionTable(reaper_plugin_info_t* rec) { + // Reverse table order, mirroring the register loop. Each '-command_id' + // re-presents the SAME interned pointer channelIdFor handed out at register. + for (auto it = g_table.rbegin(); it != g_table.rend(); ++it) { + rec->Register("-gaccel", (void*)&it->accel); + rec->Register("-command_id", (void*)it->id); + } +} + } // namespace reasampler diff --git a/src/shell/actions/action_registry.h b/src/shell/actions/action_registry.h index fd7f4ce..9f45fbe 100644 --- a/src/shell/actions/action_registry.h +++ b/src/shell/actions/action_registry.h @@ -1,13 +1,30 @@ #pragma once -// action_registry — shared registration plumbing for the bindable action families -// (Q-W4 split of actions.cpp). Owns the durable interned-string store both the -// Design View and multi-bank families register through, so a composed command id -// keeps ONE stable pointer from register to the mirror-unregister, and the -// register-a-command_id-then-gaccel sequence has one implementation. +// action_registry — shared registration plumbing + the Q-W6 registration TABLE. +// +// Two layers, one TU: +// +// * The Q-W4 plumbing (channelIdFor / registerAction): the durable interned-string +// store the action families register through, so a composed command id keeps ONE +// stable pointer from register to the mirror-unregister, and the +// register-a-command_id-then-gaccel sequence has one implementation. The +// design_view / bank / ingest families still register row-by-row through this. +// +// * The Q-W6 registration TABLE (ActionTableRow + registerActionTable / +// actionTableHandleCommand / actionTableCommandId / unregisterActionTable): the +// data-driven home of main.cpp's own action family (capture scopes, panel toggle, +// insert, batch, realtime, recapture, version). One row = one action (FOREVER- +// STABLE id suffix, display phrase, flat function-pointer handler); registration +// iterates the rows, hookcommand dispatch walks the same rows, and unload +// mirror-unregisters from them — adding an action touches the table only (OCP). +// Handlers are plain function pointers (a static dispatch walk, no std::function, +// no virtual — the §3 performance guardrail); gaccel + interned-id storage is +// owned here for the module lifetime, so REAPER's held pointers stay valid and +// the '-command_id' unregister re-presents the IDENTICAL pointer registered. // // Includes reaper_plugin.h (gaccel_register_t / reaper_plugin_info_t full defs); -// only the action-family TUs include this header. Q-W6's registration table -// subsumes this helper when the hand-written blocks become data. +// only the action-family TUs and main.cpp include this header. + +#include #include "reaper_plugin.h" // reaper_plugin_info_t, gaccel_register_t @@ -26,4 +43,41 @@ const char* channelIdFor(const char* suffix); int registerAction(reaper_plugin_info_t* rec, const char* suffix, gaccel_register_t& accel, const char* phrase); +// --- The registration table (Q-W6) ------------------------------------------- + +// One bindable action. `suffix` and `phrase` are the channel-AGNOSTIC pieces (the +// registry composes the full id/label via channelCommandId / channelActionName); +// both must have static storage duration (string literals, or a pure static table +// like captureActionTable()). `run` fires when the minted command does; `arg` is an +// opaque per-row value passed through to it (e.g. a captureActionTable row index, or +// a bool-like flag), so sibling actions can share one handler without captures. +struct ActionTableRow { + const char* suffix; // FOREVER-STABLE command-id suffix — never change shipped + const char* phrase; // Actions-list display phrase (after the channel prefix) + void (*run)(int arg); // handler — a flat function pointer, no state + int arg = 0; // opaque per-row handler argument +}; + +// Registers every row (command_id -> gaccel, via the same interning plumbing as +// registerAction) in table order. Rows are COPIED into registry-owned storage whose +// element addresses never move (REAPER holds each gaccel pointer until unload). +// Call once at load; a failed command_id mint (cmd 0) leaves that row inert but +// still mirror-unregistered on unload (harmless, matches the pre-table behavior). +void registerActionTable(reaper_plugin_info_t* rec, const ActionTableRow* rows, + std::size_t count); + +// Dispatches one fired command: fires the matching row's handler and returns true; +// false when the command belongs to no table row (caller's hookcommand keeps +// looking, per the claim-only contract). A flat walk over the registered rows. +bool actionTableHandleCommand(int command); + +// The minted command id for `suffix` (0 when unregistered / mint failed). For the +// callers that need a raw command id outside dispatch — e.g. the toggleaction +// checked-state hook resolving TOGGLE_BANK_PANEL once at load. +int actionTableCommandId(const char* suffix); + +// Mirror-unregisters every table row (reverse table order): '-gaccel' with the same +// held storage, '-command_id' with the SAME interned pointer used at register. +void unregisterActionTable(reaper_plugin_info_t* rec); + } // namespace reasampler diff --git a/src/shell/actions/bank_actions.cpp b/src/shell/actions/bank_actions.cpp index 0defb26..32e854d 100644 --- a/src/shell/actions/bank_actions.cpp +++ b/src/shell/actions/bank_actions.cpp @@ -1,11 +1,12 @@ // bank_actions.cpp — the multi-bank bindable action family (Phase B3; Q-W4 split of // actions.cpp). See bank_actions.h. // -// Q-W4 dedupe: each mutating handler is a THIN UX SKIN — text prompts (promptBankName), -// name resolution, and console feedback — over the promptless bankOp* inner verbs -// homed in panel_bank_ops (model op + persistBankOp, one bank op = one Ctrl-Z). The -// book's rules (pool privileges, collapse-by-hash, active-fallback-to-pool) all live -// in bank_book; these handlers only drive the verbs and react to the boolean. +// Q-W4 dedupe / Q-W6 seam: each mutating handler is a THIN UX SKIN — text prompts +// (promptBankName), name resolution, and console feedback — over the promptless +// bankOp* inner verbs homed in shell/bank_ops (model op + persistBankOp, one bank op +// = one Ctrl-Z), driven against this family's registered session. The book's rules +// (pool privileges, collapse-by-hash, active-fallback-to-pool) all live in +// bank_book; these handlers only drive the verbs and react to the boolean. // // REFERENCE-INVALIDATION GUARDRAIL (B2 review): book().activeIndex() / bank()->index // return a reference INTO the book's internal vector, which a create/delete can @@ -27,9 +28,10 @@ #include "shell/actions/prune_action.h" // doBankPruneFolder — the guarded prune body #include "core/model/bank_book.h" // BankBook, nextBankId, kPoolBankId (B1) -#include "persist.h" // ReaSamplerSession (owns book()) -#include "shell/panel/panel_bank_ops.h" // bankOp* inner verbs + promptBankName + selection seam +#include "shell/bank_ops/bank_ops.h" // bankOp* promptless verbs (Q-W6 non-UI seam) +#include "shell/panel/panel_bank_ops.h" // promptBankName + the panel selection seam #include "shell/panel/panel_layout.h" // full-height toggles (B3) +#include "shell/persist/session.h" // ReaSamplerSession (owns book()) #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_ShowConsoleMsg @@ -61,9 +63,9 @@ constexpr const char* kIdBankBanksFull = "BANK_BANKS_FULLHEIGHT"; // and R3 extends the confirm-and-delete step behind this SAME id — never a throwaway id. constexpr const char* kIdBankPruneFolder = "BANK_PRUNE_FOLDER"; -// The live session the actions read (name resolution, member counts, prune). The -// mutations themselves run through the bankOp* verbs, which resolve the same session -// via the panel seam. Set once by bankRegisterActions; not owned here. +// The live session the actions read (name resolution, member counts, prune) and +// pass to the bankOp* verbs by reference (bankHandleCommand guards it non-null +// before any handler runs). Set once by bankRegisterActions; not owned here. ReaSamplerSession* g_session = nullptr; int g_cmdBankCreate = 0; @@ -116,7 +118,7 @@ std::string bankIdByDisplayName(const std::string& name) { void doBankCreate() { std::string name; if (!promptBankName("ReaSampler: create bank", "Bank name:", "", name)) return; - if (bankOpCreate(name).empty()) { + if (bankOpCreate(*g_session, name).empty()) { ShowConsoleMsg( ("ReaSampler: could not create bank \"" + name + "\" (a bank with that name already exists).\n") @@ -139,7 +141,7 @@ void doBankRename() { } std::string newName; if (!promptBankName("ReaSampler: rename bank", "New name:", which, newName)) return; - if (!bankOpRename(id, newName)) { + if (!bankOpRename(*g_session, id, newName)) { // The verb rejects the pool (un-renamable) or a name already used by another // bank (unique display names, trimmed + case-insensitive). ShowConsoleMsg("ReaSampler: cannot rename that bank (the pool is un-renamable, " @@ -184,7 +186,7 @@ void doBankDelete() { } // S9: bump only when the deleted bank held samples — dropping them changes what a live // instance referencing one could play. Deleting an EMPTY bank is purely organizational. - if (!bankOpDelete(id, /*bumpGeneration=*/members > 0)) { + if (!bankOpDelete(*g_session, id, /*bumpGeneration=*/members > 0)) { ShowConsoleMsg("ReaSampler: cannot delete that bank (the pool is un-deletable).\n"); } } @@ -202,7 +204,7 @@ void doBankEvacuate() { ShowConsoleMsg(("ReaSampler: no bank named \"" + which + "\".\n").c_str()); return; } - if (!bankOpEvacuate(id)) { + if (!bankOpEvacuate(*g_session, id)) { ShowConsoleMsg("ReaSampler: cannot evacuate that bank (the pool is the " "destination, not a source).\n"); } @@ -218,13 +220,13 @@ void doBankActivateNext() { for (const Bank& b : g_session->book().banks()) ids.push_back(b.id); const std::string target = nextBankId(ids, g_session->book().activeBankId()); if (target.empty()) return; // degenerate (no banks) — cannot happen (pool seeded) - bankOpActivate(target); + bankOpActivate(*g_session, target); } // Activate the pool directly (the common "back to the default target" jump). Bindable // direct-by-id form; a general activate-bank-by-name/menu is a panel affordance. void doBankActivatePool() { - bankOpActivate(kPoolBankId); + bankOpActivate(*g_session, kPoolBankId); } // Move or copy the panel's selected samples into a named destination bank (prompted @@ -257,7 +259,7 @@ void doBankTransferSelected(bool copy) { ShowConsoleMsg("ReaSampler: source and destination are the same bank.\n"); return; } - bankOpTransfer(selected, srcId, destId, copy); + bankOpTransfer(*g_session, selected, srcId, destId, copy); } // Remove the panel's selected samples from the SOURCE bank (the focused region's @@ -275,7 +277,7 @@ void doBankRemoveSelected() { ShowConsoleMsg("ReaSampler: the selection's bank no longer exists.\n"); return; } - bankOpRemove(selected, srcId); + bankOpRemove(*g_session, selected, srcId); } } // namespace diff --git a/src/shell/actions/design_view_actions.cpp b/src/shell/actions/design_view_actions.cpp index 11a66b8..91b76e9 100644 --- a/src/shell/actions/design_view_actions.cpp +++ b/src/shell/actions/design_view_actions.cpp @@ -29,7 +29,7 @@ #include "core/view/lane_keys.h" // view::isOnManualLane — the single managed/manual predicate #include "core/view/view_mode_model.h" -#include "persist.h" // ReaSamplerSession (owns view() model) +#include "shell/persist/session.h" // ReaSamplerSession (owns view() model) #include "shell/capture/item_read.h" // shared MediaItem* -> GUID + fixed-lane-name reads (D2 W3-B) #include "shell/capture/track_guid.h" // shared MediaTrack* -> canonical GUID key #include "shell/panel/panel_window.h" // bankPanelInvalidate — footer toggle repaint diff --git a/src/shell/actions/drag_out_win.cpp b/src/shell/actions/drag_out_win.cpp index 3aa6e83..8316a86 100644 --- a/src/shell/actions/drag_out_win.cpp +++ b/src/shell/actions/drag_out_win.cpp @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // drag_out_win — OS/COM initiation of native OS drag-out (M11). See drag_out_win.h. // // Windows path (primary): a hand-rolled minimal IDataObject exposing exactly one format, diff --git a/src/shell/actions/drag_out_win.h b/src/shell/actions/drag_out_win.h index 1a5781b..04ccb6b 100644 --- a/src/shell/actions/drag_out_win.h +++ b/src/shell/actions/drag_out_win.h @@ -1,5 +1,4 @@ #pragma once -#include "core/namespaces.h" // drag_out_win — the OS/COM initiation half of native OS drag-out (Milestone 11). The pure // gesture-boundary decision and path-list assembly live in drag_out.*; THIS is the platform // shell that hands a resolved, existing-file path list to the operating system's drag-drop diff --git a/src/shell/actions/instrument_drop_win.cpp b/src/shell/actions/instrument_drop_win.cpp index 763d3ba..fa2503d 100644 --- a/src/shell/actions/instrument_drop_win.cpp +++ b/src/shell/actions/instrument_drop_win.cpp @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // instrument_drop_win — the REAPER shell for S17 drop-and-load. See instrument_drop_win.h. // // Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h WITHOUT @@ -30,6 +29,10 @@ namespace reasampler { +// Real-namespace-home using-declarations (Q-W6: the namespaces.h shim is retired). +using version::vstPluginName; +using wire::infoNamesFxHotspot; + namespace { // Write `bytes` to a fresh uniquely-named .vstpreset in the OS temp dir and return its path; @@ -43,7 +46,7 @@ namespace { // // Non-throwing: every std::filesystem call uses the error_code overload. The whole body is // wrapped in try/catch to guarantee no exception crosses the REAPER C callback boundary -// (the same discipline persist.cpp uses — see its non-throwing scanPruneOrphans comment). +// (the same discipline the prune shell uses — see prune_fs.cpp's non-throwing scan comment). // // Returns the path object (not a narrow string) so the caller can: // (a) pass path.u8string() to TrackFX_SetPreset — UTF-8 on MSVC, not ACP-converted, diff --git a/src/shell/actions/instrument_drop_win.h b/src/shell/actions/instrument_drop_win.h index f9aa016..20a65cb 100644 --- a/src/shell/actions/instrument_drop_win.h +++ b/src/shell/actions/instrument_drop_win.h @@ -1,5 +1,4 @@ #pragma once -#include "core/namespaces.h" // instrument_drop_win — the REAPER-facing shell half of S17 drop-and-load. The pure gesture // decision lives in drag_out (DragGesture::InstrumentDrop) and the pure payload construction // in instrument_drop; THIS is the platform shell that (a) resolves a screen point to a track diff --git a/src/shell/actions/prune_action.cpp b/src/shell/actions/prune_action.cpp index 2c73851..a0b037d 100644 --- a/src/shell/actions/prune_action.cpp +++ b/src/shell/actions/prune_action.cpp @@ -9,7 +9,7 @@ #include #include -#include "persist.h" // ReaSamplerSession — pruneDryRun / pruneOrphanSet / pruneReclaim +#include "shell/persist/session.h" // ReaSamplerSession — pruneDryRun / pruneOrphanSet / pruneReclaim #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_ShowConsoleMsg @@ -31,7 +31,7 @@ namespace reasampler { // is opened (file deletion is not REAPER-undoable and pruneReclaim mutates no project // state) — a Ctrl-Z after a prune correctly cannot claim to restore deleted files. void doBankPruneFolder(ReaSamplerSession& session) { - const PruneReport report = session.pruneDryRun(); + const reclaim::PruneReport report = session.pruneDryRun(); // pS-usage FAIL-SAFE: a present instance-usage record could not be read — the // protected set is unknowable, so the prune HALTS outright (deletes nothing) rather @@ -85,7 +85,7 @@ void doBankPruneFolder(ReaSamplerSession& session) { } // Confirmed -> delete exactly the confirmed set (recomputed fresh, stale entries skipped). - const PruneDeletionResult del = session.pruneReclaim(orphanSet); + const reclaim::PruneDeletionResult del = session.pruneReclaim(orphanSet); std::string done = "ReaSampler prune: reclaimed " + std::to_string(del.reclaimedCount) + " file(s), " + diff --git a/src/shell/bank_ops/bank_ops.cpp b/src/shell/bank_ops/bank_ops.cpp new file mode 100644 index 0000000..55dfe4e --- /dev/null +++ b/src/shell/bank_ops/bank_ops.cpp @@ -0,0 +1,183 @@ +// bank_ops.cpp — the promptless bank-verb seam (Q-W6 lift; see bank_ops.h for the +// contract). The ONE implementation home of the bank verbs (create / rename / +// delete / evacuate / activate / move / copy / remove): each mutates the given +// session's book() then persists via persistBankOp() (one bank op = one Ctrl-Z; a +// true index no-op opens NO undo point). It DOES mutate the bank BOOK — but only +// the index/model + ext-state, never the arrange, never a sample file on disk +// (bank ops are index-only; files stay put — CONTEXT.md §Multi-bank). +// REFERENCE-INVALIDATION GUARDRAIL: after a STRUCTURAL mutation any +// Bank*/BankModel& is invalid — verbs take ids and resolve fresh per model call. +// +// Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h +// WITHOUT REAPERAPI_IMPLEMENT — main.cpp owns the API pointers; here they are +// extern (CLAUDE.md §contract). DAW-verified, not unit tested. + +#include "shell/bank_ops/bank_ops.h" + +#include +#include + +#include "core/model/bank_book.h" // BankBook / TransferResult / RemoveScope +#include "shell/persist/session.h" // ReaSamplerSession — the session the verbs mutate + +#define REAPERAPI_MINIMAL +#define REAPERAPI_WANT_genGuid +#define REAPERAPI_WANT_guidToString +#define REAPERAPI_WANT_Undo_BeginBlock2 +#define REAPERAPI_WANT_Undo_EndBlock2 +#include "reaper_plugin_functions.h" + +namespace reasampler { + +namespace { + +// Mints a fresh, genuine REAPER GUID string as a stable bank id (the B2/model +// design: ids are caller-supplied and stable; the model stays pure and mints none). +// Distinct from a track GUID by origin only — both are canonical guidToString output. +std::string mintBankId() { + GUID g{}; + genGuid(&g); + char buf[64] = {0}; // guidToString needs >=64 chars (SDK contract) + guidToString(&g, buf); + return std::string(buf); +} + +} // namespace + +// Persists a completed bank-index verb as a SINGLE batched REAPER undo point (R-B) — +// one bank op = one Ctrl-Z. +// +// WHY THIS WRAPS AND saveToActiveProject() DOES NOT: a bank verb mutates ONLY our +// project ext-state (SetProjExtState under "reasampler"), which REAPER's undo system +// captures iff UNDO_STATE_MISCCFG is set in the Undo_EndBlock2 flags — the SDK +// documents MISCCFG as covering "extensions!" project ext-state (reaper_plugin.h +// ~1544, ~1199). We pass exactly UNDO_STATE_MISCCFG (not -1 / UNDO_STATE_ALL as the +// item-move family does): a bank verb touches no tracks, FX, items, or envelopes, so +// snapshotting them would be both heavier and semantically wrong. The persist runs +// INSIDE the block so the post-mutation ext-state is the block's "after" image. +// +// UNSAVED-PROJECT GUARDRAIL: on an unsaved / no-active project saveToActiveProject() +// no-ops (nothing is written to ext state). We must still CLOSE the block we opened, +// but with an EMPTY label and a zero flag so REAPER DISCARDS the point instead of +// recording a no-effect undo entry — mirroring view.cpp's empty-plan close. The +// in-session model change stands and persists on the user's next save; it just earns +// no undo point until there is a project to persist into (undo of an unsaved bank op +// has nothing to roll back to anyway). The Begin/End must still be balanced, hence +// the close-either-way. (Quiet persist by design — mirrors the CAPTURE path, NOT the +// Design-View path; deliberately NO Save-As prompt.) +void persistBankOp(ReaSamplerSession& session, const char* label, + bool bumpGeneration) { + Undo_BeginBlock2(nullptr); + // S9: bump the bank-generation counter INSIDE the block, before the persist, so the + // fresh generation rides the same ext-state write (saveToActiveProject() stamps + // bankGeneration()). Bumped only for content-changing verbs (the caller decides); a + // pure-organizational verb passes false and leaves the counter be, so a + // rename/activate does not needlessly refresh live instances. + if (bumpGeneration) session.bumpBankGeneration(); + const bool persisted = session.saveToActiveProject(); + if (persisted) + Undo_EndBlock2(nullptr, label, UNDO_STATE_MISCCFG); + else + Undo_EndBlock2(nullptr, "", 0); // no ext-state write -> discard the empty point +} + +// --- Promptless inner bank verbs (one home) ------------------------------------ +// Model op + persistBankOp only; NO UX. Callers own prompts/confirms/nudges and the +// session-liveness question. Each verb persists ONLY after the model accepted — a +// rejected op opens no undo point. + +std::string bankOpCreate(ReaSamplerSession& session, const std::string& name) { + const std::string id = mintBankId(); + if (!session.book().createBank(id, name)) return {}; // duplicate display name (model rule) + persistBankOp(session, "ReaSampler: create bank"); + return id; +} + +bool bankOpRename(ReaSamplerSession& session, const std::string& bankId, + const std::string& newName) { + if (!session.book().renameBank(bankId, newName)) return false; // pool / name in use + persistBankOp(session, "ReaSampler: rename bank"); + return true; +} + +bool bankOpDelete(ReaSamplerSession& session, const std::string& bankId, + bool bumpGeneration) { + if (!session.book().deleteBank(bankId)) return false; // pool un-deletable (model rule) + persistBankOp(session, "ReaSampler: delete bank", bumpGeneration); + return true; +} + +bool bankOpEvacuate(ReaSamplerSession& session, const std::string& bankId) { + if (!session.book().evacuate(bankId)) return false; // pool is a destination, not a source + // S9: evacuate moves members between banks (bank membership changes) -> bump. + persistBankOp(session, "ReaSampler: evacuate bank", /*bumpGeneration=*/true); + return true; +} + +bool bankOpActivate(ReaSamplerSession& session, const std::string& bankId) { + if (!session.book().setActiveBank(bankId)) return false; // rejects an unknown id + persistBankOp(session, "ReaSampler: activate bank"); + return true; +} + +// NO-OP GUARDRAIL — VERB-AWARE (a collapse means different things per verb): +// * MOVE collapse: the source entry WAS removed (bank_book moveSample removes +// unconditionally before the dest add collapses on hash), so the index DID +// mutate — it counts toward opening an undo point. +// * COPY collapse: the source is left intact AND the dest already held the hash, +// so NOTHING changed — a true index no-op. It must NOT open an undo point. +// Hence: copy counts only real gains; move counts gains OR collapses. Ids pass +// straight to the model op — no BankModel& cached across the loop's mutations. +bool bankOpTransfer(ReaSamplerSession& session, + const std::vector& sampleIds, + const std::string& srcBankId, const std::string& destBankId, + bool copy) { + BankBook& b = session.book(); + if (sampleIds.empty() || srcBankId == destBankId) return false; + if (!b.bank(srcBankId) || !b.bank(destBankId)) return false; + int ok = 0, collapsed = 0; + for (const std::string& sid : sampleIds) { + const TransferResult r = + copy ? b.copySample(sid, srcBankId, destBankId) + : b.moveSample(sid, srcBankId, destBankId); + switch (r) { + case TransferResult::Moved: + case TransferResult::Copied: ++ok; break; + case TransferResult::Collapsed: ++collapsed; break; + case TransferResult::RejectedUnknownBank: + case TransferResult::RejectedSampleAbsent: + case TransferResult::RejectedSameBank: break; + } + } + const bool mutated = copy ? (ok > 0) : (ok > 0 || collapsed > 0); + if (!mutated) return false; // nothing changed — no persist, no undo point + // S9: a move/copy changes bank membership (a sample arrives in / leaves a bank an + // instance may reference) -> bump so assigned instances refresh hands-free. + persistBankOp(session, + copy ? "ReaSampler: copy sample(s)" : "ReaSampler: move sample(s)", + /*bumpGeneration=*/true); + return true; +} + +// Index-only, this-bank scope (fork R-A: the sole surfaced verb; RemoveScope::AllBanks +// stays latent in the model). Non-destructive to the file: a last-reference remove +// leaves the file on disk, orphaned until Phase R prune — remove NEVER deletes bytes +// (the manifest is untouched). Silent: recoverability is the batched undo (R-B). +bool bankOpRemove(ReaSamplerSession& session, + const std::vector& sampleIds, + const std::string& srcBankId) { + BankBook& b = session.book(); + if (sampleIds.empty() || !b.bank(srcBankId)) return false; + int removed = 0; + for (const std::string& sid : sampleIds) + if (b.removeSample(sid, srcBankId, RemoveScope::ThisBank) == + RemoveResult::Removed) + ++removed; + if (removed == 0) return false; // every id already absent — no undo point + // S9: a remove drops a sample from a bank (an instance referencing it must refresh — + // it will resolve to silence, per the stale-id policy) -> bump. + persistBankOp(session, "ReaSampler: remove sample(s)", /*bumpGeneration=*/true); + return true; +} + +} // namespace reasampler diff --git a/src/shell/bank_ops/bank_ops.h b/src/shell/bank_ops/bank_ops.h new file mode 100644 index 0000000..659efa0 --- /dev/null +++ b/src/shell/bank_ops/bank_ops.h @@ -0,0 +1,83 @@ +#pragma once +// bank_ops — the promptless bank-verb seam (Q-W6 lift of the Q-W4 single-owner +// verbs out of shell/panel/panel_bank_ops into a NON-UI home). Each verb is a model +// op on the given session's BankBook + persistBankOp (undo-batched ext-state +// persist) — NO prompts, NO message boxes, NO panel-state nudges, NO panel-global +// reads. The two UX surfaces consume these as thin skins: +// +// * shell/panel/panel_bank_ops — the panel's menu handlers (prompts / confirms / +// repaints), passing the panel's live session. +// * shell/actions/bank_actions — the bindable family (text prompts / console +// feedback), passing its registered session. +// +// The session arrives BY REFERENCE: there is exactly one session pointer question +// per call site (the caller's), so a missing session can never be half-reported as +// a model rejection from in here (the Q-W4 review's fail-safe-collapse concern). +// Every verb returns whether the model accepted the mutation — a rejected op +// persists nothing and opens no undo point. +// +// REAPER-facing (persist + undo blocks + GUID minting) but SDK-free in this header. + +#include +#include + +namespace reasampler { + +class ReaSamplerSession; + +// Mints a stable GUID bank id, creates `name` in the book. Returns the new bank id, +// or "" when the model rejects the name (duplicate, trimmed + case-insensitive). +// Create is purely organizational — no generation bump. +std::string bankOpCreate(ReaSamplerSession& session, const std::string& name); + +// Renames `bankId`. False when the model rejects (pool un-renamable / name in use). +bool bankOpRename(ReaSamplerSession& session, const std::string& bankId, + const std::string& newName); + +// Deletes `bankId`. False when the model rejects (pool un-deletable). The caller +// passes `bumpGeneration` from the member count it read BEFORE any evacuate/delete +// (an evacuate-then-delete flow must still bump on the ORIGINAL membership). +bool bankOpDelete(ReaSamplerSession& session, const std::string& bankId, + bool bumpGeneration); + +// Evacuates `bankId`'s members to the pool. False when the model rejects (the pool +// itself). Bumps the generation (membership changed). +bool bankOpEvacuate(ReaSamplerSession& session, const std::string& bankId); + +// Activates `bankId` as the capture target. False on an unknown id. No bump. +bool bankOpActivate(ReaSamplerSession& session, const std::string& bankId); + +// Moves (copy=false) or copies (copy=true) `sampleIds` from `srcBankId` to +// `destBankId` (index-only; files never relocate). Returns whether the index +// actually mutated — the verb-aware no-op guardrail: a COPY collapse changes +// nothing (no undo point); a MOVE collapse did remove the source entry (counts). +// Persists ONE undo point ("move/copy sample(s)") only when mutated. +bool bankOpTransfer(ReaSamplerSession& session, + const std::vector& sampleIds, + const std::string& srcBankId, const std::string& destBankId, + bool copy); + +// Removes `sampleIds` from `srcBankId` (index-only, this-bank scope; never deletes +// bytes). Returns whether anything was removed; persists one undo point when so. +bool bankOpRemove(ReaSamplerSession& session, + const std::vector& sampleIds, + const std::string& srcBankId); + +// Persists a completed bank-index verb as a single REAPER undo point (R-B). +// Wraps the session persist (SetProjExtState) in a Begin/End block with +// UNDO_STATE_MISCCFG so the bank op is one Ctrl-Z. On an unsaved / no-active project +// the persist no-ops and the block is closed with an empty label + zero flag (REAPER +// discards it). Callers must invoke this ONLY after a successful/effective mutation — +// rejected ops (duplicate name, un-deletable pool, etc.) must return before reaching +// here so no empty undo point is ever opened for a no-op. +// +// S9 bank-generation bump: pass `bumpGeneration = true` for a verb that changes what a +// live instance would PLAY — move / copy / remove / evacuate / delete-with-members. Leave +// it false (the default) for a PURELY ORGANIZATIONAL verb — create / rename / activate / +// reorder. The bump (when requested) happens INSIDE the block, BEFORE the persist, so +// the stamped counter rides the same ext-state write and undo captures the pre/post +// generation with the rest of the blob. +void persistBankOp(ReaSamplerSession& session, const char* label, + bool bumpGeneration = false); + +} // namespace reasampler diff --git a/src/shell/capture/capture.h b/src/shell/capture/capture.h index 6fbb964..e02aa56 100644 --- a/src/shell/capture/capture.h +++ b/src/shell/capture/capture.h @@ -1,16 +1,16 @@ #pragma once // capture — the REAPER-facing capture shell (CLAUDE.md §load-bearing split). // -// This header declares the capture *seam* the later milestones fill: -// * CaptureRequest — everything a capture needs, source-mode-agnostic. +// This header declares the SHARED capture seam (Q-W6 split of the former fat +// header — the realtime backend's async begin/tick/abort surface now lives in +// capture_realtime_shell.h): +// * CaptureRequest / CaptureResult — everything a capture needs and yields, +// source-mode-agnostic; the types BOTH backends speak. // * OfflineRenderBackend — the deterministic default; a plain CONCRETE class // (the former ICaptureBackend interface was deleted in // Q-W3, T4-26 — it had one deriver and zero polymorphic // call sites; every construction site instantiates the // concrete type). -// * RealtimeRecordBackend — the ASYNC realtime seam (begin/tick/abort), driven -// across timer ticks; a genuinely different lifecycle -// (see the SEAM CHOICE note at its declaration). // * makeUniqueTag / stampCaptureSample — the shared file-tag mint and the shared // finished-capture metadata stamp both backends call // (Q-W3 riders T1-11 / T2-09). @@ -20,7 +20,6 @@ // REAPER-free lets callers (the capture orchestration TUs) depend on the seam // without dragging the SDK into every include site. -#include #include #include @@ -156,109 +155,4 @@ void stampCaptureSample(Sample& s, const CaptureRequest& req, ReaProject* rateProj, ReaProject* timeSigProj, const std::string& absolutePath); -// --- Realtime-record backend: the ASYNC seam --------------------------------- -// -// A realtime record is inherently asynchronous: CSurf_OnRecord starts the transport -// on REAPER's audio thread and returns immediately — it does NOT block until the -// range completes, which takes (end - start) wall-clock seconds. Blocking the main -// thread for that duration freezes REAPER's UI, so the realtime backend is DRIVEN -// ACROSS TIMER TICKS instead: begin() starts and returns at once; tick() (called -// from the same OnTimer that runs session.poll()) advances the in-flight record and -// reports when it is done. -// -// SEAM CHOICE (surfaced): the two backends deliberately share NO interface. The -// lifecycles are genuinely different (offline is headless + immediate — one -// synchronous capture() call returns a finished Sample; realtime is -// transport-driven + async — begin/tick/abort across timer ticks), so a shared -// interface would make offline fake a lifecycle it does not have (its tick() -// would always be Done on the first call — dead code / an LSP smell). Offline -// stays synchronous; the realtime backend owns this small bespoke async seam, -// driven by exactly one caller (the timer-driven realtime_lifecycle). This is the -// split-sync/async fork, chosen over a unified async interface for that reason. -// (The old synchronous ICaptureBackend interface over OfflineRenderBackend was -// deleted in Q-W3 — T4-26: one deriver, zero polymorphic call sites.) - -// One tick's verdict from the in-flight record. -enum class RealtimeTickStatus { - InProgress, // still recording — call tick() again next timer tick - Done, // finished (range end reached, or the user stopped) — `result` is set - Failed, // an error tore the capture down — `result.message` explains -}; - -struct RealtimeTickResult { - RealtimeTickStatus status = RealtimeTickStatus::InProgress; - CaptureResult result; // meaningful only when status == Done or Failed -}; - -// The opaque in-flight capture state. Owns the snapshot of everything to restore -// (temp track + its receive sends from the source tracks, other tracks' I_RECARM, -// transport, edit cursor, time selection) and the record's own project handle. -// Defined in capture_realtime_shell.cpp; the header stays REAPER-free (nothing is -// dereferenced here) by holding it behind a forward-declared type + unique_ptr. -// -// restore()/teardown is idempotent and lives ON THIS OBJECT (not a function-scope -// RAII guard) because the record spans ticks — no single stack frame outlives it. -// Every terminal path (normal completion, user stop, error, project switch, unload) -// funnels through the same single restore, safe to call once from whichever fires. -class RealtimeCaptureState; - -// Out-of-line deleter so callers (realtime_lifecycle) can own a unique_ptr to the -// opaque RealtimeCaptureState WITHOUT its full (REAPER-typed) definition — the -// delete is compiled in capture_realtime_shell.cpp where the type is complete, -// keeping this header REAPER-free (load-bearing split). -struct RealtimeCaptureStateDeleter { - void operator()(RealtimeCaptureState* p) const noexcept; -}; -using RealtimeCaptureHandle = - std::unique_ptr; - -// Realtime-record backend — captures by RECORDING in realtime (transport-driven) -// into a hidden temp track, then moves the recorded file into the bank as a Sample. -// For sources offline render cannot do (hardware, performed FX) and as the true -// pre-FX-dry path (I_RECMODE_FLAGS &3==1 — the only pre-FX tap in the SDK; offline -// render has none). Dialog-free: never invokes the offline-render progress window. -// -// Non-bit-identical by nature (it is realtime); offline stays the deterministic -// default. Non-destructive across EVERY terminal path — the review gate — which is -// harder here than offline because the record spans ticks: the snapshot + restore -// live on RealtimeCaptureState, not a function-scope RAII destructor. -// -// SCOPE (this increment): TRACK scope only — records the selected track's OWN -// output (item + that track's own FX + its own fader/pan, PRE-parent), matching -// offline's track scope. This needs NO FxBypassGuard: a send tapping a track's -// output is naturally PRE-parent (the parent has not summed it yet), so the tap is -// chain-independent by construction. Item realtime is deferred (UnsupportedMode). -class RealtimeRecordBackend { -public: - // Starts a realtime record: validates the request (track scope, non-empty range, - // at least one source track, active + saved project, transport idle), snapshots - // all state to restore, creates the hidden temp track, routes a send FROM each - // source track INTO the temp track, arms, and CSurf_OnRecord — then returns - // IMMEDIATELY (no wait, no UI block). `sourceTracks` are the selected tracks to - // tap (resolved by the action layer — the CaptureRequest itself stays REAPER-free, - // carrying only the provenance GUIDs). On success the returned unique_ptr owns the - // in-flight state; drive it with tick(). On a validation/setup failure returns - // nullptr and fills `outFailure` with the CaptureStatus + message (nothing was - // left mutated — begin() restores on its own failure paths). - RealtimeCaptureHandle begin(const CaptureRequest& request, - const std::vector& sourceTracks, - CaptureResult& outFailure); - - // Advances the in-flight record one tick. Reads the transport (bound to the - // record's OWN project handle so a project switch cannot confuse it), and on a - // terminal verdict stops the transport, finalizes the recorded file into the - // bank Sample (Done) or reports the failure (Failed), then restores ALL - // snapshotted state. Returns InProgress while the record is still running. - // After Done/Failed the state is spent — the caller drops the unique_ptr. - RealtimeTickResult tick(RealtimeCaptureState& state); - - // Force-terminate an in-flight record NOW without waiting for the range end: - // stops the transport, finalizes whatever was captured (best effort) or abandons - // it, and restores ALL snapshotted state. For the shutdown / project-switch - // paths (extension unload, a new project became active) where the record must - // not leak a temp track / armed track / altered transport into the user's - // project. Idempotent — safe even if a prior tick already tore the state down. - RealtimeTickResult abort(RealtimeCaptureState& state); -}; - } // namespace reasampler::capture diff --git a/src/shell/capture/capture_batch.cpp b/src/shell/capture/capture_batch.cpp index 268a20a..fc15bb5 100644 --- a/src/shell/capture/capture_batch.cpp +++ b/src/shell/capture/capture_batch.cpp @@ -19,7 +19,7 @@ #include "core/capture/batch_capture.h" // planCaptureUnits / BatchOutcome #include "core/model/bank_book.h" // BankBook / Bank #include "core/model/provenance.h" // recipe parse/build, fingerprint -#include "persist.h" // ReaSamplerSession +#include "shell/persist/session.h" // ReaSamplerSession #include "shell/capture/capture_orchestrator.h" // captureAndIndexOne / renderOffline #include "shell/capture/provenance_shell.h" // fxChainIdentity* / trackByGuid #include "shell/capture/scope_resolve.h" // ResolvedSource diff --git a/src/shell/capture/capture_orchestrator.cpp b/src/shell/capture/capture_orchestrator.cpp index 6841ccf..486cb6e 100644 --- a/src/shell/capture/capture_orchestrator.cpp +++ b/src/shell/capture/capture_orchestrator.cpp @@ -17,7 +17,7 @@ #include "core/capture/tail_control.h" // TailSetting #include "core/model/provenance.h" // model::Provenance #include "ingest.h" // ingestAssignActiveInstance -#include "persist.h" // ReaSamplerSession +#include "shell/persist/session.h" // ReaSamplerSession #include "shell/capture/insert.h" // runInsert / InsertRequest #include "shell/capture/realtime_lifecycle.h" // the in-flight realtime state diff --git a/src/shell/capture/capture_realtime_shell.cpp b/src/shell/capture/capture_realtime_shell.cpp index 981e76d..9539c61 100644 --- a/src/shell/capture/capture_realtime_shell.cpp +++ b/src/shell/capture/capture_realtime_shell.cpp @@ -74,7 +74,7 @@ // Item realtime is deferred (UnsupportedMode): item scope would need per-item take // isolation on top of the tap, which is a separate increment. -#include "shell/capture/capture.h" +#include "shell/capture/capture_realtime_shell.h" #include #include diff --git a/src/shell/capture/capture_realtime_shell.h b/src/shell/capture/capture_realtime_shell.h new file mode 100644 index 0000000..4557b19 --- /dev/null +++ b/src/shell/capture/capture_realtime_shell.h @@ -0,0 +1,121 @@ +#pragma once +// capture_realtime_shell — the ASYNC realtime-record seam (Q-W6 split of the former +// fat capture.h: this header owns the realtime backend's begin/tick/abort surface; +// capture.h keeps the shared CaptureRequest/CaptureResult types, the offline +// backend, and the shared backend helpers). Implemented by +// capture_realtime_shell.cpp; driven by exactly one caller (realtime_lifecycle). +// +// A realtime record is inherently asynchronous: CSurf_OnRecord starts the transport +// on REAPER's audio thread and returns immediately — it does NOT block until the +// range completes, which takes (end - start) wall-clock seconds. Blocking the main +// thread for that duration freezes REAPER's UI, so the realtime backend is DRIVEN +// ACROSS TIMER TICKS instead: begin() starts and returns at once; tick() (called +// from the same OnTimer that runs session.poll()) advances the in-flight record and +// reports when it is done. +// +// SEAM CHOICE (surfaced): the two backends deliberately share NO interface. The +// lifecycles are genuinely different (offline is headless + immediate — one +// synchronous capture() call returns a finished Sample; realtime is +// transport-driven + async — begin/tick/abort across timer ticks), so a shared +// interface would make offline fake a lifecycle it does not have (its tick() +// would always be Done on the first call — dead code / an LSP smell). Offline +// stays synchronous; the realtime backend owns this small bespoke async seam. +// This is the split-sync/async fork, chosen over a unified async interface for +// that reason. (The old synchronous ICaptureBackend interface over +// OfflineRenderBackend was deleted in Q-W3 — T4-26: one deriver, zero polymorphic +// call sites.) +// +// REAPER-free like capture.h: MediaTrack is forward-declared there and never +// dereferenced here; the REAPER-facing TU is capture_realtime_shell.cpp. + +#include +#include + +#include "shell/capture/capture.h" // CaptureRequest / CaptureResult / MediaTrack fwd + +namespace reasampler::capture { + +// One tick's verdict from the in-flight record. +enum class RealtimeTickStatus { + InProgress, // still recording — call tick() again next timer tick + Done, // finished (range end reached, or the user stopped) — `result` is set + Failed, // an error tore the capture down — `result.message` explains +}; + +struct RealtimeTickResult { + RealtimeTickStatus status = RealtimeTickStatus::InProgress; + CaptureResult result; // meaningful only when status == Done or Failed +}; + +// The opaque in-flight capture state. Owns the snapshot of everything to restore +// (temp track + its receive sends from the source tracks, other tracks' I_RECARM, +// transport, edit cursor, time selection) and the record's own project handle. +// Defined in capture_realtime_shell.cpp; the header stays REAPER-free (nothing is +// dereferenced here) by holding it behind a forward-declared type + unique_ptr. +// +// restore()/teardown is idempotent and lives ON THIS OBJECT (not a function-scope +// RAII guard) because the record spans ticks — no single stack frame outlives it. +// Every terminal path (normal completion, user stop, error, project switch, unload) +// funnels through the same single restore, safe to call once from whichever fires. +class RealtimeCaptureState; + +// Out-of-line deleter so callers (realtime_lifecycle) can own a unique_ptr to the +// opaque RealtimeCaptureState WITHOUT its full (REAPER-typed) definition — the +// delete is compiled in capture_realtime_shell.cpp where the type is complete, +// keeping this header REAPER-free (load-bearing split). +struct RealtimeCaptureStateDeleter { + void operator()(RealtimeCaptureState* p) const noexcept; +}; +using RealtimeCaptureHandle = + std::unique_ptr; + +// Realtime-record backend — captures by RECORDING in realtime (transport-driven) +// into a hidden temp track, then moves the recorded file into the bank as a Sample. +// For sources offline render cannot do (hardware, performed FX) and as the true +// pre-FX-dry path (I_RECMODE_FLAGS &3==1 — the only pre-FX tap in the SDK; offline +// render has none). Dialog-free: never invokes the offline-render progress window. +// +// Non-bit-identical by nature (it is realtime); offline stays the deterministic +// default. Non-destructive across EVERY terminal path — the review gate — which is +// harder here than offline because the record spans ticks: the snapshot + restore +// live on RealtimeCaptureState, not a function-scope RAII destructor. +// +// SCOPE (this increment): TRACK scope only — records the selected track's OWN +// output (item + that track's own FX + its own fader/pan, PRE-parent), matching +// offline's track scope. This needs NO FxBypassGuard: a send tapping a track's +// output is naturally PRE-parent (the parent has not summed it yet), so the tap is +// chain-independent by construction. Item realtime is deferred (UnsupportedMode). +class RealtimeRecordBackend { +public: + // Starts a realtime record: validates the request (track scope, non-empty range, + // at least one source track, active + saved project, transport idle), snapshots + // all state to restore, creates the hidden temp track, routes a send FROM each + // source track INTO the temp track, arms, and CSurf_OnRecord — then returns + // IMMEDIATELY (no wait, no UI block). `sourceTracks` are the selected tracks to + // tap (resolved by the action layer — the CaptureRequest itself stays REAPER-free, + // carrying only the provenance GUIDs). On success the returned unique_ptr owns the + // in-flight state; drive it with tick(). On a validation/setup failure returns + // nullptr and fills `outFailure` with the CaptureStatus + message (nothing was + // left mutated — begin() restores on its own failure paths). + RealtimeCaptureHandle begin(const CaptureRequest& request, + const std::vector& sourceTracks, + CaptureResult& outFailure); + + // Advances the in-flight record one tick. Reads the transport (bound to the + // record's OWN project handle so a project switch cannot confuse it), and on a + // terminal verdict stops the transport, finalizes the recorded file into the + // bank Sample (Done) or reports the failure (Failed), then restores ALL + // snapshotted state. Returns InProgress while the record is still running. + // After Done/Failed the state is spent — the caller drops the unique_ptr. + RealtimeTickResult tick(RealtimeCaptureState& state); + + // Force-terminate an in-flight record NOW without waiting for the range end: + // stops the transport, finalizes whatever was captured (best effort) or abandons + // it, and restores ALL snapshotted state. For the shutdown / project-switch + // paths (extension unload, a new project became active) where the record must + // not leak a temp track / armed track / altered transport into the user's + // project. Idempotent — safe even if a prior tick already tore the state down. + RealtimeTickResult abort(RealtimeCaptureState& state); +}; + +} // namespace reasampler::capture diff --git a/src/shell/capture/insert.cpp b/src/shell/capture/insert.cpp index 15e5dde..fc6e5f2 100644 --- a/src/shell/capture/insert.cpp +++ b/src/shell/capture/insert.cpp @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // insert.cpp — REAPER-facing placement shell (M6). See insert.h. // // Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h @@ -40,7 +39,7 @@ #include "core/model/bank_model.h" #include "shell/panel/panel_bank_ops.h" // bankPanelSelectedSampleIds / SourceBankId #include "core/capture/capture_paths.h" -#include "persist.h" +#include "shell/persist/session.h" #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_CountSelectedTracks @@ -58,13 +57,19 @@ namespace reasampler { +// Real-namespace-home using-declarations (Q-W6: the namespaces.h shim is retired). +using capture::computeInsertMode; +using capture::normalizeSlashes; +using capture::resolveBankFile; +using capture::TempoConform; + namespace { namespace fs = std::filesystem; // The current project's directory (mirrors bank_panel/capture/persist). The bank // index stores relative paths; resolving a bank file needs the current .rpp dir. -// FOLLOW-UP (already noted in bank_panel.cpp): a shared "current project dir" +// FOLLOW-UP (already noted in panel_bank_ops.cpp): a shared "current project dir" // REAPER helper is a clean small refactor now that a fourth consumer exists — out // of scope for M6. std::string currentProjectDir() { diff --git a/src/shell/capture/insert.h b/src/shell/capture/insert.h index 18aa2c7..cb53934 100644 --- a/src/shell/capture/insert.h +++ b/src/shell/capture/insert.h @@ -1,5 +1,4 @@ #pragma once -#include "core/namespaces.h" // insert — placement of bank samples into the arrange (M6). REAPER-facing shell: // it reads the bank_panel's current selection, resolves each selected sample's // file, and drops it into the arrange at the edit cursor via InsertMedia, wrapped @@ -28,7 +27,7 @@ class ReaSamplerSession; // tempo-conform choice) so the two action variants (native-length vs // conform-to-tempo) differ only by this struct — no divergent code paths. struct InsertRequest { - InsertOptions options; // defaults: current track, no conform, native length + capture::InsertOptions options; // defaults: current track, no conform, native length }; // The outcome of an insert action, for the caller to log to the console. diff --git a/src/shell/capture/item_read.cpp b/src/shell/capture/item_read.cpp index fb84fa6..afc640f 100644 --- a/src/shell/capture/item_read.cpp +++ b/src/shell/capture/item_read.cpp @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // item_read.cpp — the single MediaItem* read seam (GUID + fixed-lane name). See // item_read.h. Compiled into the reaper_reasampler MODULE; includes // reaper_plugin_functions.h WITHOUT REAPERAPI_IMPLEMENT (main.cpp is the one TU that diff --git a/src/shell/capture/item_read.h b/src/shell/capture/item_read.h index 4c8da44..7bb74e9 100644 --- a/src/shell/capture/item_read.h +++ b/src/shell/capture/item_read.h @@ -1,5 +1,4 @@ #pragma once -#include "core/namespaces.h" // item_read — the ONE place a MediaItem* is read for its canonical GUID string and for // the durable P_LANENAME of the fixed lane it sits on. Before this seam, view.cpp and // bank_panel.cpp each carried a near-identical private itemGuid / itemLaneName pair diff --git a/src/shell/capture/provenance_shell.cpp b/src/shell/capture/provenance_shell.cpp index f0a1319..632f68e 100644 --- a/src/shell/capture/provenance_shell.cpp +++ b/src/shell/capture/provenance_shell.cpp @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // provenance_shell.cpp — the REAPER reads behind Milestone 10. See provenance_shell.h. // // Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h @@ -52,6 +51,10 @@ namespace reasampler { +// Real-namespace-home using-declarations (Q-W6: the namespaces.h shim is retired). +using capture::normalizeSlashes; +using capture::resolveBankFile; + std::string fxChainIdentityForTrack(MediaTrack* tr) { if (!tr) return fxChainIdentity({}); std::vector rows; diff --git a/src/shell/capture/provenance_shell.h b/src/shell/capture/provenance_shell.h index 1a1dab5..ab2cafa 100644 --- a/src/shell/capture/provenance_shell.h +++ b/src/shell/capture/provenance_shell.h @@ -1,5 +1,4 @@ #pragma once -#include "core/namespaces.h" // provenance_shell — the REAPER-facing reads Milestone 10 needs, in one place. // // The PURE provenance module (provenance.h) owns the fingerprint encoding, the @@ -29,6 +28,9 @@ namespace reasampler { class BankBook; +// Real-namespace-home using-declaration (Q-W6: the namespaces.h shim is retired). +using model::BankFileRef; + // The in-scope FX-chain identity of a source track (Track scope), folded to the // pure provenance string. Reads the track's own FX chain via TrackFX_GetCount / // TrackFX_GetFXName / TrackFX_GetFXGUID / TrackFX_GetEnabled in chain order. diff --git a/src/shell/capture/realtime_lifecycle.cpp b/src/shell/capture/realtime_lifecycle.cpp index 275694c..44f7f2f 100644 --- a/src/shell/capture/realtime_lifecycle.cpp +++ b/src/shell/capture/realtime_lifecycle.cpp @@ -8,7 +8,7 @@ #include "shell/capture/realtime_lifecycle.h" -#include "persist.h" // ReaSamplerSession +#include "shell/persist/session.h" // ReaSamplerSession #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_EnumProjects diff --git a/src/shell/capture/realtime_lifecycle.h b/src/shell/capture/realtime_lifecycle.h index ac71be5..d76e58f 100644 --- a/src/shell/capture/realtime_lifecycle.h +++ b/src/shell/capture/realtime_lifecycle.h @@ -16,7 +16,7 @@ // REAPER-facing: the .cpp includes reaper_plugin_functions.h WITHOUT // REAPERAPI_IMPLEMENT (main.cpp owns the API pointers — CLAUDE.md §contract). -#include "shell/capture/capture.h" // RealtimeRecordBackend / RealtimeCaptureHandle +#include "shell/capture/capture_realtime_shell.h" // RealtimeRecordBackend / RealtimeCaptureHandle namespace reasampler { class ReaSamplerSession; diff --git a/src/shell/capture/track_guid.cpp b/src/shell/capture/track_guid.cpp index 4a47d0b..2588952 100644 --- a/src/shell/capture/track_guid.cpp +++ b/src/shell/capture/track_guid.cpp @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // track_guid.cpp — the single MediaTrack* -> canonical GUID key formatter. See // track_guid.h. Compiled into the reaper_reasampler MODULE; includes // reaper_plugin_functions.h WITHOUT REAPERAPI_IMPLEMENT (main.cpp is the one TU diff --git a/src/shell/capture/track_guid.h b/src/shell/capture/track_guid.h index f1fa3d7..64f2026 100644 --- a/src/shell/capture/track_guid.h +++ b/src/shell/capture/track_guid.h @@ -1,5 +1,4 @@ #pragma once -#include "core/namespaces.h" // track_guid — the ONE place a MediaTrack* is formatted into the canonical GUID // string used as a membership-index key. Both the Design View shell (view.cpp) and // the actions layer (design_view_actions.cpp) key membership on this exact string, so the key diff --git a/src/shell/instrument/editor_controls.cpp b/src/shell/instrument/editor_controls.cpp index 56892de..7852597 100644 --- a/src/shell/instrument/editor_controls.cpp +++ b/src/shell/instrument/editor_controls.cpp @@ -22,6 +22,7 @@ namespace reasampler::vst { using namespace reasampler::instrument::map; // ZonePlaySeconds vocabulary + trigger_seam converters +using instrument::ui::EnvMode; // envelope_overlay's mode enum (Q-W6: shim retired) using instrument::engine::formatMasterGainLabel; using instrument::engine::masterGainLinearFromNorm; using instrument::engine::masterGainNormFromLinear; diff --git a/src/shell/instrument/editor_platform.cpp b/src/shell/instrument/editor_platform.cpp index 956f405..3c569df 100644 --- a/src/shell/instrument/editor_platform.cpp +++ b/src/shell/instrument/editor_platform.cpp @@ -209,7 +209,7 @@ LRESULT CALLBACK ReaSamplerEditor::wndProc(HWND hwnd, UINT msg, WPARAM wParam, // Capture stolen mid-drag (modal dialog, alt-tab, etc.) — restore map_ to its // pre-grab snapshot so the in-flight live-drag mutation is rolled back, then reset // the drag state machine so stale capture-less WM_MOUSEMOVEs don't keep editing. - // Mirror of bank_panel.cpp's WM_CAPTURECHANGED handler. + // Mirror of the panel shell's WM_CAPTURECHANGED handler (panel_window.cpp). if (self) { // A held preview note must be released here too (peer of WM_LBUTTONUP) — capture // loss otherwise leaves the momentary-key voice hung with no note-off. diff --git a/src/shell/instrument/editor_session.cpp b/src/shell/instrument/editor_session.cpp index a7a18e2..5042ac1 100644 --- a/src/shell/instrument/editor_session.cpp +++ b/src/shell/instrument/editor_session.cpp @@ -15,7 +15,7 @@ #include "core/audio/peaks.h" // computeEnvelope (the cached peak thumbnail) #include "core/capture/capture_paths.h" // resolveBankFile (shared M4 path resolution) -#include "core/capture/wav_trim.h" // parseWavLayout, extractFloatFrames +#include "core/capture/wav_codec.h" // parseWavLayout, extractFloatFrames #include "core/ui/bank_grid.h" // ThumbnailKey / thumbnailKeyString (T2-10: the pure key) #include "core/util/file_bytes.h" // shared whole-file loader (Q-W1, T2-03) #include "ext_keys.h" diff --git a/src/shell/instrument/processor_reload.cpp b/src/shell/instrument/processor_reload.cpp index d1fef2f..0aa5cff 100644 --- a/src/shell/instrument/processor_reload.cpp +++ b/src/shell/instrument/processor_reload.cpp @@ -19,7 +19,7 @@ #include #include "core/capture/capture_paths.h" // resolveBankFile (shared M4 path resolution) -#include "core/capture/wav_trim.h" // parseWavLayout, extractFloatFrames (shared WAV parse) +#include "core/capture/wav_codec.h" // parseWavLayout, extractFloatFrames (shared WAV parse) #include "core/instrument/map/bank_sync.h" // S9/S8 pure decisions: parseBankGeneration, consumeDecision #include "core/instrument/map/sample_map.h" // refs resolve, buildZonedKeymap (pS self-contained) #include "core/util/file_bytes.h" // shared whole-file loader (Q-W1, T2-03) @@ -31,6 +31,12 @@ namespace reasampler::vst { using namespace instrument::map; // resolution + bank-sync vocabulary this TU drives using namespace reasampler::wire; // assignment_request + sample_usage wire records +// Q-W6 (shim retired): the shared WAV parse + file loader by their real homes. +using capture::extractFloatFrames; +using capture::parseWavLayout; +using capture::resolveBankFile; +using capture::WavLayout; +using util::readFileBytes; namespace { diff --git a/src/shell/instrument/processor_state.cpp b/src/shell/instrument/processor_state.cpp index 202e82e..5f8bbe1 100644 --- a/src/shell/instrument/processor_state.cpp +++ b/src/shell/instrument/processor_state.cpp @@ -24,6 +24,7 @@ using namespace Steinberg::Vst; namespace reasampler::vst { using namespace instrument::map; // the codec + resolution vocabulary this TU marshals +using instrument::engine::masterGainMaxLinear; // FB1 taper ceiling (Q-W6: shim retired) tresult PLUGIN_API ReaSamplerProcessor::setState(IBStream* state) { if (!state) return kResultFalse; diff --git a/src/shell/instrument/reaper_bridge.cpp b/src/shell/instrument/reaper_bridge.cpp index fc2c3b9..92062df 100644 --- a/src/shell/instrument/reaper_bridge.cpp +++ b/src/shell/instrument/reaper_bridge.cpp @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // reaper_bridge.cpp — see reaper_bridge.h. The DAW-facing edge; keep it thin. #include "shell/instrument/reaper_bridge.h" @@ -6,6 +5,7 @@ #include #include "core/instrument/map/bridge_marshal.h" +#include "core/wire/ext_state_read.h" // readProjExtStateGrowing (T2-04 grow-loop policy) #include "core/capture/capture_paths.h" // projectDirOfRpp (shared M4 project-dir derivation) #include "ext_keys.h" // kProjExtNamespace (shared wire contract) @@ -39,6 +39,10 @@ DEF_CLASS_IID(Steinberg::IReaperHostApplication) namespace reasampler::vst { +// Real-namespace-home using-declarations (Q-W6: the namespaces.h shim is retired). +using capture::projectDirOfRpp; +using instrument::map::decodeGetProjExtState; + bool ReaperBridge::connect(Steinberg::FUnknown* context) { getProjExtState_ = nullptr; enumProjExtState_ = nullptr; @@ -63,7 +67,8 @@ bool ReaperBridge::connect(Steinberg::FUnknown* context) { enumProjExtState_ = reinterpret_cast( reaper->getReaperApi("EnumProjExtState")); // EnumProjects(-1, ...) yields the active project AND its .rpp path — the same call - // persist.cpp uses, so the instrument derives the project directory identically. + // the persist shell (ext_state_io.cpp) uses, so the instrument derives the project + // directory identically. enumProjects_ = reinterpret_cast( reaper->getReaperApi("EnumProjects")); // pS-usage: the (prefix-guarded) usage publish write + the track-identity pair the @@ -93,17 +98,17 @@ std::optional ReaperBridge::readReasamplerExtState(const std::strin // GetProjExtState writes into a caller buffer; the bank blob can be large (many // samples), so grow the buffer until the value fits rather than risk a silent - // truncation. The retry policy is the SHARED pure - // instrument::map::readProjExtStateGrowing (Q-W5 rider T2-04 — one loop for the + // truncation. The retry policy is the SHARED pure wire::readProjExtStateGrowing + // (T2-04 — one loop for the // extension's persist/usage reads and this bridge read; the rules cannot drift): // absent (rv <= 0) and the >16 MB ceiling both fold to nullopt here, and a // complete value still runs through decodeGetProjExtState (the stale/empty-buffer // guard) exactly as before. - const auto read = instrument::map::readProjExtStateGrowing( + const auto read = wire::readProjExtStateGrowing( [&](char* buf, int cap) { return getProjExtState_(proj, kProjExtNamespace(), key.c_str(), buf, cap); }); - if (read.status != instrument::map::GrowingExtStateRead::Status::Complete) + if (read.status != wire::GrowingExtStateRead::Status::Complete) return std::nullopt; // absent / empty key, or pathologically large (>16 MB) return decodeGetProjExtState(read.apiReturn, read.value); } @@ -147,7 +152,7 @@ std::string ReaperBridge::currentTrackGuid() { std::string ReaperBridge::activeProjectDir() { if (!enumProjects_) return {}; // idx=-1 is the current project tab; the out-buffer receives the full .rpp path, - // EMPTY for a never-saved project. Same call + convention as persist.cpp; the pure + // EMPTY for a never-saved project. Same call + convention as the persist shell; the pure // projectDirOfRpp turns the .rpp path into the project directory (parent, forward- // slashed) and keeps an unsaved project's empty path empty (no default-location // fallback — the tool's invariant). diff --git a/src/shell/instrument/reaper_bridge.h b/src/shell/instrument/reaper_bridge.h index ef7183b..29bf057 100644 --- a/src/shell/instrument/reaper_bridge.h +++ b/src/shell/instrument/reaper_bridge.h @@ -16,7 +16,6 @@ // reaper_vst3_interfaces.h + reaper_plugin_functions.h at the spike. #pragma once -#include "core/namespaces.h" #include #include @@ -87,7 +86,8 @@ private: int valOut_sz); // EnumProjects(-1, projfnOut, sz) -> active project + its .rpp path (SDK line // ~1264). The instrument uses idx=-1 (current tab) so it follows the active project, - // and reads the .rpp path from the out-buffer exactly as persist.cpp does. + // and reads the .rpp path from the out-buffer exactly as the persist shell + // (ext_state_io.cpp) does. using EnumProjectsFn = void* (*)(int idx, char* projfnOut, int projfnOut_sz); // SetProjExtState(proj, extname, key, value) -> int (SDK line ~6290). Used ONLY by // writeUsageExtState (prefix-guarded) — see the read-only-bank note there. diff --git a/src/shell/instrument/reasampler_embed.cpp b/src/shell/instrument/reasampler_embed.cpp index f5b8ddc..53e9d8d 100644 --- a/src/shell/instrument/reasampler_embed.cpp +++ b/src/shell/instrument/reasampler_embed.cpp @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // reasampler_embed.cpp — see reasampler_embed.h. The IReaperUIEmbedInterface shell. // Windows-only (D5); guarded so a non-Windows build degrades to a stub that reports // "not supported" and draws nothing. @@ -43,6 +42,14 @@ DEF_CLASS_IID(Steinberg::IReaperUIEmbedInterface) namespace reasampler::vst { +// Real-namespace-home using-directives (Q-W6: the namespaces.h shim is retired): +// the embed strip speaks the map vocabulary (listSamples / parseBankGeneration) and +// the pure UI layout (embed_strip / editor_geometry Rect) wholesale. +using namespace reasampler::instrument::map; +using namespace reasampler::instrument::ui; +using reasampler::ui::spectralColor; +using version::vstPluginName; + namespace { #ifdef _WIN32 // Kit adapter (Phase L, L3): the embed shell's Rect (editor_geometry) -> the kit's KitBox @@ -201,7 +208,7 @@ bool ReaSamplerEmbed::paint(TPtrInt bitmap, TPtrInt drawInfo) { // reads as "present, no zones" — the default single-capture face lives in the editor. LICE_FillRect(bmp, layout.keymap.x, layout.keymap.y, layout.keymap.width, layout.keymap.height, toLice(roleColor(Role::BgCell)), 0.5f, 0); - const std::string label = reasampler::vstPluginName() + // channel-derived (S18) + const std::string label = version::vstPluginName() + // channel-derived (S18) (samples_.empty() ? " (bank empty)" : " (no zones)"); const Rect labelR = Rect::ltrb(layout.keymap.x + 4, layout.keymap.y, layout.keymap.right(), layout.keymap.bottom()); diff --git a/src/shell/instrument/reasampler_embed.h b/src/shell/instrument/reasampler_embed.h index 23e9b56..bcc31c1 100644 --- a/src/shell/instrument/reasampler_embed.h +++ b/src/shell/instrument/reasampler_embed.h @@ -31,7 +31,6 @@ // REAPER's messages to/from it and draws with the same LICE idiom as reasampler_editor. #pragma once -#include "core/namespaces.h" #include #include @@ -52,6 +51,10 @@ namespace reasampler::vst { class ReaSamplerProcessor; +// Real-namespace-home using-declarations (Q-W6: the namespaces.h shim is retired). +using instrument::map::PerformanceMap; +using instrument::map::SampleChoice; + // Implements IReaperUIEmbedInterface. Lifetime is OWNED by the processor (the processor // holds the sole unique_ptr and hands out AddRef'd references from queryInterface); the // back-pointer to the processor is therefore always valid while this lives. diff --git a/src/shell/instrument/reasampler_vst.h b/src/shell/instrument/reasampler_vst.h index 5e99b76..3993279 100644 --- a/src/shell/instrument/reasampler_vst.h +++ b/src/shell/instrument/reasampler_vst.h @@ -18,7 +18,6 @@ // binary UID identity — the string identity lives in the pure module). #pragma once -#include "core/namespaces.h" #include "pluginterfaces/base/funknown.h" diff --git a/src/shell/instrument/vst_entry.cpp b/src/shell/instrument/vst_entry.cpp index f41070c..0ca6ed3 100644 --- a/src/shell/instrument/vst_entry.cpp +++ b/src/shell/instrument/vst_entry.cpp @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // vst_entry.cpp — the VST3 module class factory (Phase S1). Enumerates the one class // this module offers (the ReaSampler instrument) via the SDK's factory macros. The // Windows module exports — GetPluginFactory (here, via BEGIN_FACTORY) and @@ -74,10 +73,10 @@ DEF_CLASS2(INLINE_UID(REASAMPLER_ACTIVE_UID_1, REASAMPLER_ACTIVE_UID_2, REASAMPLER_ACTIVE_UID_3, REASAMPLER_ACTIVE_UID_4), Steinberg::PClassInfo::kManyInstances, // cardinality kVstAudioEffectClass, // component category (fixed) - reasampler::vstPluginName().c_str(), // plug-in display name (channel-derived) + reasampler::version::vstPluginName().c_str(), // plug-in display name (channel-derived) 0, // single-component => 0 Steinberg::Vst::PlugType::kInstrumentSynthSampler, // subcategory - reasampler::appVersion().c_str(), // plug-in version (channel: -beta render) + reasampler::version::appVersion().c_str(), // plug-in version (channel: -beta render) kVstVersionString, // VST3 SDK version (fixed) reasampler::vst::ReaSamplerProcessor::createInstance) diff --git a/src/shell/panel/draw_kit.cpp b/src/shell/panel/draw_kit.cpp index bc7587e..c734d01 100644 --- a/src/shell/panel/draw_kit.cpp +++ b/src/shell/panel/draw_kit.cpp @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // draw_kit — the LICE/SWELL shell half of the drawing kit. See draw_kit.h. // // Compiled into the reaper_reasampler MODULE. SHELL layer: it is the only kit file that @@ -12,7 +11,7 @@ #include "core/ui/bank_grid.h" // compressAmplitudeForDisplay — the shared dB display curve (pure) // SWELL / LICE. On Windows use native Win32 (windows.h first); on mac/linux SWELL is -// provided by the host. Mirrors bank_panel.cpp's include discipline. +// provided by the host. Mirrors the panel TUs' (shell/panel/) include discipline. #ifdef _WIN32 #include #else @@ -24,6 +23,15 @@ namespace reasampler { +// Real-namespace-home using-declarations (Q-W6: the namespaces.h shim is retired). +using audio::ChannelEnvelope; +using audio::columnMinMax; +using audio::MinMax; +using ui::compressAmplitudeForDisplay; +using ui::roleColor; +using ui::roleColorState; +using ui::spectralColor; + // --- KitColor <-> LICE boundary ---------------------------------------------- // The one place a pure KitColor becomes a LICE_pixel. Verified packing: LICE_RGBA(r,g,b,a) diff --git a/src/shell/panel/draw_kit.h b/src/shell/panel/draw_kit.h index c8696d0..311d308 100644 --- a/src/shell/panel/draw_kit.h +++ b/src/shell/panel/draw_kit.h @@ -1,5 +1,4 @@ #pragma once -#include "core/namespaces.h" // draw_kit — the LICE-facing SHELL half of the shared drawing kit (Phase L, L1). This is // the ONE source of drawing for the whole system: every surface (bank_panel now; the VST // editor + embed strip at L3) fills, buttons, rows, sliders, waveforms, and — above all — @@ -41,6 +40,19 @@ class LICE_IBitmap; namespace reasampler { +// Real-namespace-home using-declarations (Q-W6: the interim core/namespaces.h shim +// is retired; the kit's pure vocabulary names its Q-W1 homes explicitly). These are +// deliberate re-exports: every draw_kit consumer speaks these types at the call +// boundary, so they surface here exactly as panel_state.h surfaces the panel's. +using audio::Envelope; +using ui::InteractionState; +using ui::KitBox; +using ui::KitButtonBox; +using ui::KitColor; +using ui::ListRowBox; +using ui::Role; +using ui::SliderGeometry; + // The kit's four cached fonts (§3.1 type scale). Consumers pass a Font to text() to pick // the size/weight; the kit maps it to the matching LICE_CachedFont. enum class Font { diff --git a/src/shell/panel/panel_bank_ops.cpp b/src/shell/panel/panel_bank_ops.cpp index 497c326..9b4fce0 100644 --- a/src/shell/panel/panel_bank_ops.cpp +++ b/src/shell/panel/panel_bank_ops.cpp @@ -1,17 +1,10 @@ -// panel_bank_ops.cpp — the bank-CRUD + menus seam of the docked bank panel (Q-W2 -// split of bank_panel.cpp; Phase B4/B5). Since Q-W4 this TU is the ONE implementation -// home of the bank verbs (create / rename / delete / evacuate / activate / move / -// copy / remove): the promptless bankOp* inner verbs (model op + persistBankOp only) -// serve BOTH thin UX skins — the panel's menu handlers here and the bindable -// bank_actions family — plus the book/bank accessors, the popup menus that drive -// them, and the selection-id / OS-drag path resolvers. -// -// Each verb mutates the session's book() then persists via persistBankOp() (one bank -// op = one Ctrl-Z; a true index no-op opens NO undo point). It DOES mutate the bank -// BOOK — that is the whole point of B4 — but only the index/model + ext-state, never -// the arrange, never a sample file on disk (bank ops are index-only; files stay put — -// CONTEXT.md §Multi-bank). REFERENCE-INVALIDATION GUARDRAIL: after a STRUCTURAL -// mutation any Bank*/BankModel& is invalid — resolve fresh, pass ids. +// panel_bank_ops.cpp — the bank-CRUD-UX + menus seam of the docked bank panel +// (Q-W2 split of the former bank_panel god-module; Phase B4/B5). Since Q-W6 the +// promptless bank verbs live in shell/bank_ops (model op + persistBankOp, taking +// ReaSamplerSession&); this TU is the panel's THIN UX SKIN over them — the menu +// handlers (prompts / confirms / message boxes / panel-state nudges / repaint), +// the book/bank accessors, the popup menus that drive them, and the selection-id / +// OS-drag path resolvers. The bindable bank_actions family is the sibling skin. // // Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h // WITHOUT REAPERAPI_IMPLEMENT — main.cpp owns the API pointers; here they are @@ -25,24 +18,21 @@ #include "shell/panel/panel_state.h" #include "shell/panel/panel_bank_ops.h" -#include "persist.h" // ReaSamplerSession — the live session the ops mutate +#include "shell/bank_ops/bank_ops.h" // bankOp* promptless verbs (the Q-W6 non-UI seam) +#include "shell/persist/session.h" // ReaSamplerSession — the live session the ops mutate #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_EnumProjects #define REAPERAPI_WANT_GetUserInputs #define REAPERAPI_WANT_ShowMessageBox #define REAPERAPI_WANT_Main_OnCommand -#define REAPERAPI_WANT_genGuid -#define REAPERAPI_WANT_guidToString -#define REAPERAPI_WANT_Undo_BeginBlock2 -#define REAPERAPI_WANT_Undo_EndBlock2 #include "reaper_plugin_functions.h" namespace reasampler::panel { namespace fs = std::filesystem; -// --- Current-project directory (mirrors persist.cpp's derivation) ------------- +// --- Current-project directory (mirrors the persist shell's derivation, ext_state_io.cpp) std::string currentProjectDir() { std::vector buf(4096, '\0'); EnumProjects(-1, buf.data(), static_cast(buf.size())); @@ -84,19 +74,21 @@ std::vector namedBanks() { // --- Bank management ops (id-keyed; THIN UX SKINS over the bankOp* verbs) ------ // -// Q-W4: each handler here owns only the panel's UX (prompts / confirms / message -// boxes / panel-state nudges / repaint); the model op + persist is the shared -// bankOp* inner verb (defined in the public section below). After a STRUCTURAL -// mutation (create/delete/evacuate) any Bank*/BankModel& is invalid — we resolve -// fresh, pass ids, and let the next refreshFingerprint repaint. On an unsaved -// project the empty-close discard in persistBankOp ensures no stale state -// survives (matches the capture/B3 quiet-persist idiom). +// Q-W4/Q-W6: each handler here owns only the panel's UX (prompts / confirms / +// message boxes / panel-state nudges / repaint); the model op + persist is the +// shared bankOp* inner verb (shell/bank_ops), which takes the live session by +// reference — the book() check answers the one session-liveness question per +// handler. After a STRUCTURAL mutation (create/delete/evacuate) any +// Bank*/BankModel& is invalid — we resolve fresh, pass ids, and let the next +// refreshFingerprint repaint. On an unsaved project the empty-close discard in +// persistBankOp ensures no stale state survives (matches the capture/B3 +// quiet-persist idiom). void doCreateBank() { if (!book()) return; std::string name; if (!promptBankName("ReaSampler: create bank", "Bank name:", "", name)) return; - const std::string id = bankOpCreate(name); + const std::string id = bankOpCreate(*g_panel.session, name); if (id.empty()) { ShowMessageBox("A bank with that name already exists.", "ReaSampler: create bank", 0); @@ -116,7 +108,7 @@ void doRenameBank(const std::string& bankId) { const std::string current = bk->displayName; // copy before any mutation std::string newName; if (!promptBankName("ReaSampler: rename bank", "New name:", current, newName)) return; - if (!bankOpRename(bankId, newName)) { + if (!bankOpRename(*g_panel.session, bankId, newName)) { ShowMessageBox("Another bank already uses that name.", "ReaSampler: rename bank", 0); return; @@ -156,7 +148,7 @@ void doDeleteBank(const std::string& bankId) { // delete path moved/dropped members) — both change what a live instance could play. An // empty-bank delete is purely organizational, no bump. The ORIGINAL member count decides // (the No-path evacuated them moments ago, but the membership still changed). - if (!bankOpDelete(bankId, /*bumpGeneration=*/members > 0)) return; + if (!bankOpDelete(*g_panel.session, bankId, /*bumpGeneration=*/members > 0)) return; // shownBankId is reconciled by the next fingerprint pass. If no named banks remain, // nudge focus to the pool so the selection has a valid home. if (namedBanks().empty()) g_panel.focusedRegion = Region::Pool; @@ -167,12 +159,13 @@ void doEvacuateBank(const std::string& bankId) { if (!book()) return; const Bank* bk = book()->bank(bankId); if (!bk || bk->isPool()) return; - if (!bankOpEvacuate(bankId)) return; + if (!bankOpEvacuate(*g_panel.session, bankId)) return; invalidatePanel(); } void doActivateBank(const std::string& bankId) { - if (!bankOpActivate(bankId)) return; // rejects an unknown id + if (!book()) return; // no live session — nothing to activate against + if (!bankOpActivate(*g_panel.session, bankId)) return; // rejects an unknown id invalidatePanel(); } @@ -185,7 +178,8 @@ void doActivateBank(const std::string& bankId) { void transferSamples(const std::vector& sampleIds, const std::string& srcBankId, const std::string& destBankId, bool copy) { - if (!bankOpTransfer(sampleIds, srcBankId, destBankId, copy)) + if (!book()) return; // no live session — nothing to transfer within + if (!bankOpTransfer(*g_panel.session, sampleIds, srcBankId, destBankId, copy)) return; // nothing changed — no persist, no undo point // The selection indexed into the source; after a move those indices are stale, so // clear it (the fingerprint pass will also clear, but do it now for immediacy). @@ -198,7 +192,8 @@ void transferSamples(const std::vector& sampleIds, // one-Ctrl-Z contract. Clears the stale selection and repaints on an actual removal. void removeSamples(const std::vector& sampleIds, const std::string& srcBankId) { - if (!bankOpRemove(sampleIds, srcBankId)) + if (!book()) return; // no live session — nothing to remove from + if (!bankOpRemove(*g_panel.session, sampleIds, srcBankId)) return; // nothing changed — no persist, no undo point // The selection indexed into the source; after a remove those indices are stale, so // clear it (the fingerprint pass will also clear, but do it now for immediacy). @@ -410,35 +405,7 @@ void showSelectionMenu(int screenX, int screenY) { namespace reasampler { -namespace { - -// Persists the book after a bank mutation. Mirrors the CAPTURE path, NOT the -// Design-View path: quiet persist — saveToActiveProject no-ops on an unsaved project -// (the change stays valid for the session and persists on the user's next save). -// Deliberately NO Save-As prompt; do not "align" with persistViewState's prompt -// idiom. Returns whether a persist actually happened, so persistBankOp can discard -// its undo block when nothing was written. Guards a null session pointer (false, -// no-op) — see persistBankOp's guard below for why this is defensive rather than -// dead code. -bool persistBook() { - if (!panel::g_panel.session) return false; // no live session: nothing to persist - return panel::g_panel.session->saveToActiveProject(); -} - -// Mints a fresh, genuine REAPER GUID string as a stable bank id (the B2/model -// design: ids are caller-supplied and stable; the model stays pure and mints none). -// Distinct from a track GUID by origin only — both are canonical guidToString output. -std::string mintBankId() { - GUID g{}; - genGuid(&g); - char buf[64] = {0}; // guidToString needs >=64 chars (SDK contract) - guidToString(&g, buf); - return std::string(buf); -} - -} // namespace - -// One home (Q-W4) for the former actions.cpp/panel_bank_ops.cpp byte-identical twins. +// One home (Q-W4) for the former actions/panel byte-identical twins. // COMMA GUARD: GetUserInputs splits returned values on a separator defaulting to ',', // so the return separator is overridden to \x1f (un-typeable) via the documented // `separator=X` trailing pseudo-caption (SDK ~3806) — any printable name round-trips. @@ -457,147 +424,6 @@ bool promptBankName(const char* title, const char* caption, const std::string& i return true; } -// Persists a completed bank-index verb as a SINGLE batched REAPER undo point (R-B) — -// one bank op = one Ctrl-Z. -// -// WHY THIS WRAPS AND persistBook() DOES NOT: a bank verb mutates ONLY our project -// ext-state (SetProjExtState under "reasampler"), which REAPER's undo system captures -// iff UNDO_STATE_MISCCFG is set in the Undo_EndBlock2 flags — the SDK documents -// MISCCFG as covering "extensions!" project ext-state (reaper_plugin.h ~1544, ~1199). -// We pass exactly UNDO_STATE_MISCCFG (not -1 / UNDO_STATE_ALL as the item-move family -// does): a bank verb touches no tracks, FX, items, or envelopes, so snapshotting them -// would be both heavier and semantically wrong. persistBook() (= SetProjExtState) runs -// INSIDE the block so the post-mutation ext-state is the block's "after" image. -// -// UNSAVED-PROJECT GUARDRAIL: on an unsaved / no-active project persistBook() no-ops -// (nothing is written to ext state). We must still CLOSE the block we opened, but with -// an EMPTY label and a zero flag so REAPER DISCARDS the point instead of recording a -// no-effect undo entry — mirroring view.cpp's empty-plan close. The in-session model -// change stands and persists on the user's next save; it just earns no undo point until -// there is a project to persist into (undo of an unsaved bank op has nothing to roll -// back to anyway). The Begin/End must still be balanced, hence the close-either-way. -// -// NULL-SESSION GUARD: this is a public API (panel_bank_ops.h) with callers outside -// this TU (e.g. panel_drag.cpp), not all of which are guaranteed to have re-checked -// the session pointer immediately beforehand. Bail out BEFORE Undo_BeginBlock2 — no -// block is opened, so there is nothing to balance and no risk of an unbalanced -// Begin/End pair. -void persistBankOp(const char* label, bool bumpGeneration) { - if (!panel::g_panel.session) return; // no live session: no-op, no undo point opened - Undo_BeginBlock2(nullptr); - // S9: bump the bank-generation counter INSIDE the block, before persistBook(), so the - // fresh generation rides the same ext-state write the persist makes (persistBook() -> - // saveToActiveProject() stamps bankGeneration()). Bumped only for content-changing verbs - // (the caller decides); a pure-organizational verb passes false and leaves the counter be, - // so a rename/activate does not needlessly refresh live instances. - if (bumpGeneration) panel::g_panel.session->bumpBankGeneration(); - const bool persisted = persistBook(); - if (persisted) - Undo_EndBlock2(nullptr, label, UNDO_STATE_MISCCFG); - else - Undo_EndBlock2(nullptr, "", 0); // no ext-state write -> discard the empty point -} - -// --- Promptless inner bank verbs (Q-W4 single home) ---------------------------- -// Model op + persistBankOp only; NO UX. Callers own prompts/confirms/nudges. Each -// verb resolves the book fresh (panel::book(), null when no live session) and -// persists ONLY after the model accepted — a rejected op opens no undo point. - -std::string bankOpCreate(const std::string& name) { - BankBook* b = panel::book(); - if (!b) return {}; - const std::string id = mintBankId(); - if (!b->createBank(id, name)) return {}; // duplicate display name (model rule) - persistBankOp("ReaSampler: create bank"); - return id; -} - -bool bankOpRename(const std::string& bankId, const std::string& newName) { - BankBook* b = panel::book(); - if (!b || !b->renameBank(bankId, newName)) return false; // pool / name in use - persistBankOp("ReaSampler: rename bank"); - return true; -} - -bool bankOpDelete(const std::string& bankId, bool bumpGeneration) { - BankBook* b = panel::book(); - if (!b || !b->deleteBank(bankId)) return false; // pool un-deletable (model rule) - persistBankOp("ReaSampler: delete bank", bumpGeneration); - return true; -} - -bool bankOpEvacuate(const std::string& bankId) { - BankBook* b = panel::book(); - if (!b || !b->evacuate(bankId)) return false; // pool is a destination, not a source - // S9: evacuate moves members between banks (bank membership changes) -> bump. - persistBankOp("ReaSampler: evacuate bank", /*bumpGeneration=*/true); - return true; -} - -bool bankOpActivate(const std::string& bankId) { - BankBook* b = panel::book(); - if (!b || !b->setActiveBank(bankId)) return false; // rejects an unknown id - persistBankOp("ReaSampler: activate bank"); - return true; -} - -// NO-OP GUARDRAIL — VERB-AWARE (a collapse means different things per verb): -// * MOVE collapse: the source entry WAS removed (bank_book moveSample removes -// unconditionally before the dest add collapses on hash), so the index DID -// mutate — it counts toward opening an undo point. -// * COPY collapse: the source is left intact AND the dest already held the hash, -// so NOTHING changed — a true index no-op. It must NOT open an undo point. -// Hence: copy counts only real gains; move counts gains OR collapses. Ids pass -// straight to the model op — no BankModel& cached across the loop's mutations. -bool bankOpTransfer(const std::vector& sampleIds, - const std::string& srcBankId, const std::string& destBankId, - bool copy) { - BankBook* b = panel::book(); - if (!b || sampleIds.empty() || srcBankId == destBankId) return false; - if (!b->bank(srcBankId) || !b->bank(destBankId)) return false; - int ok = 0, collapsed = 0; - for (const std::string& sid : sampleIds) { - const TransferResult r = - copy ? b->copySample(sid, srcBankId, destBankId) - : b->moveSample(sid, srcBankId, destBankId); - switch (r) { - case TransferResult::Moved: - case TransferResult::Copied: ++ok; break; - case TransferResult::Collapsed: ++collapsed; break; - case TransferResult::RejectedUnknownBank: - case TransferResult::RejectedSampleAbsent: - case TransferResult::RejectedSameBank: break; - } - } - const bool mutated = copy ? (ok > 0) : (ok > 0 || collapsed > 0); - if (!mutated) return false; // nothing changed — no persist, no undo point - // S9: a move/copy changes bank membership (a sample arrives in / leaves a bank an - // instance may reference) -> bump so assigned instances refresh hands-free. - persistBankOp(copy ? "ReaSampler: copy sample(s)" : "ReaSampler: move sample(s)", - /*bumpGeneration=*/true); - return true; -} - -// Index-only, this-bank scope (fork R-A: the sole surfaced verb; RemoveScope::AllBanks -// stays latent in the model). Non-destructive to the file: a last-reference remove -// leaves the file on disk, orphaned until Phase R prune — remove NEVER deletes bytes -// (the manifest is untouched). Silent: recoverability is the batched undo (R-B). -bool bankOpRemove(const std::vector& sampleIds, - const std::string& srcBankId) { - BankBook* b = panel::book(); - if (!b || sampleIds.empty() || !b->bank(srcBankId)) return false; - int removed = 0; - for (const std::string& sid : sampleIds) - if (b->removeSample(sid, srcBankId, RemoveScope::ThisBank) == - RemoveResult::Removed) - ++removed; - if (removed == 0) return false; // every id already absent — no undo point - // S9: a remove drops a sample from a bank (an instance referencing it must refresh — - // it will resolve to silence, per the stale-id policy) -> bump. - persistBankOp("ReaSampler: remove sample(s)", /*bumpGeneration=*/true); - return true; -} - // --- Selection read seam -------------------------------------------------------- std::vector bankPanelSelectedSampleIds() { diff --git a/src/shell/panel/panel_bank_ops.h b/src/shell/panel/panel_bank_ops.h index 2f1c5ac..73877d0 100644 --- a/src/shell/panel/panel_bank_ops.h +++ b/src/shell/panel/panel_bank_ops.h @@ -1,97 +1,31 @@ #pragma once -// panel_bank_ops — the bank-CRUD + selection-read seam of the bank panel (Q-W2 split -// of bank_panel.h; Phase B4/B5). The .cpp is the SINGLE implementation home of the -// bank verbs (create / rename / delete / evacuate / activate / move / copy / remove): -// each promptless inner verb below drives the B1 BankBook model on the session and -// persists via persistBankOp (one bank op = one Ctrl-Z). Q-W4 dedupe: the panel's -// menu handlers and the bank_actions bindable family are both thin UX skins -// (prompts / confirms / console vs. message boxes / panel-state nudges) over these -// one-home verbs. This header carries that verb surface, the shared prompt/persist -// helpers, and the panel's public selection-read surface. +// panel_bank_ops — the bank-CRUD-UX + selection-read seam of the bank panel (Q-W2 +// split of the former bank_panel god-module; Phase B4/B5). Since Q-W6 the +// promptless bank verbs themselves live in the NON-UI shell/bank_ops seam +// (bankOp* + persistBankOp, taking ReaSamplerSession&); this TU is the panel's +// thin UX skin over them — prompts / confirms / message boxes / panel-state +// nudges / repaints — plus the popup menus that drive them. The bank_actions +// bindable family is the sibling skin over the same verbs. This header carries +// the shared prompt helper and the panel's public selection-read surface. // -// The selection reads are REAPER-free; the verbs and helpers are REAPER-facing -// (persist + stock dialogs) but SDK-free in this header. +// The selection reads are REAPER-free; the prompt helper is REAPER-facing (stock +// dialogs) but SDK-free in this header. #include #include namespace reasampler { -// --- Promptless inner bank verbs (Q-W4 single home) -------------------------- -// Each verb: model op on the session's BankBook + persistBankOp (undo-batched -// ext-state persist) — NO prompts, NO message boxes, NO panel-state nudges. The -// caller owns all UX. Every verb returns whether the model accepted the mutation -// (a rejected op persists nothing and opens no undo point). Verbs resolve the -// session via the panel's live session pointer (set at load by bankPanelInit, -// before any action can fire) and fail safe (false / "") when it is absent. - -// Mints a stable GUID bank id, creates `name` in the book. Returns the new bank id, -// or "" when the model rejects the name (duplicate, trimmed + case-insensitive). -// Create is purely organizational — no generation bump. -std::string bankOpCreate(const std::string& name); - -// Renames `bankId`. False when the model rejects (pool un-renamable / name in use). -bool bankOpRename(const std::string& bankId, const std::string& newName); - -// Deletes `bankId`. False when the model rejects (pool un-deletable). The caller -// passes `bumpGeneration` from the member count it read BEFORE any evacuate/delete -// (an evacuate-then-delete flow must still bump on the ORIGINAL membership). -bool bankOpDelete(const std::string& bankId, bool bumpGeneration); - -// Evacuates `bankId`'s members to the pool. False when the model rejects (the pool -// itself). Bumps the generation (membership changed). -bool bankOpEvacuate(const std::string& bankId); - -// Activates `bankId` as the capture target. False on an unknown id. No bump. -bool bankOpActivate(const std::string& bankId); - -// Moves (copy=false) or copies (copy=true) `sampleIds` from `srcBankId` to -// `destBankId` (index-only; files never relocate). Returns whether the index -// actually mutated — the verb-aware no-op guardrail: a COPY collapse changes -// nothing (no undo point); a MOVE collapse did remove the source entry (counts). -// Persists ONE undo point ("move/copy sample(s)") only when mutated. -bool bankOpTransfer(const std::vector& sampleIds, - const std::string& srcBankId, const std::string& destBankId, - bool copy); - -// Removes `sampleIds` from `srcBankId` (index-only, this-bank scope; never deletes -// bytes). Returns whether anything was removed; persists one undo point when so. -bool bankOpRemove(const std::vector& sampleIds, - const std::string& srcBankId); - -// --- Shared UX/persist helpers ------------------------------------------------ - // Prompts the user for a single line of text via REAPER's stock input dialog // (GetUserInputs). `initial` pre-fills the field. Returns false (leaving `out` // untouched) on cancel or an empty entry. COMMA GUARD: the return separator is // overridden to \x1f (un-typeable) via the documented `separator=X` pseudo-caption, // so any printable name — commas included — round-trips whole (SDK ~3806/3808). -// One home (Q-W4) for the former actions.cpp/panel_bank_ops.cpp twins. +// One home (Q-W4) for the former actions/panel byte-identical twins; shared by the +// panel menus and the bank_actions bindable family. bool promptBankName(const char* title, const char* caption, const std::string& initial, std::string& out); -// Persists a completed bank-index verb as a single REAPER undo point (R-B). -// Wraps the session persist (SetProjExtState) in a Begin/End block with -// UNDO_STATE_MISCCFG so the bank op is one Ctrl-Z. On an unsaved / no-active project -// the persist no-ops and the block is closed with an empty label + zero flag (REAPER -// discards it). Callers must invoke this ONLY after a successful/effective mutation — -// rejected ops (duplicate name, un-deletable pool, etc.) must return before reaching -// here so no empty undo point is ever opened for a no-op. -// -// NULL-SESSION GUARD: this is a public API with callers outside panel_bank_ops.cpp -// (e.g. panel_drag.cpp). If the panel's session pointer is absent (no live session), -// this is a no-op — no undo block is opened. Today every real caller only reaches -// here via a prior session-backed check, so the guard is not yet reachable in -// practice; it exists to make the function safe to call standalone. -// -// S9 bank-generation bump: pass `bumpGeneration = true` for a verb that changes what a -// live instance would PLAY — move / copy / remove / evacuate / delete-with-members. Leave -// it false (the default) for a PURELY ORGANIZATIONAL verb — create / rename / activate / -// reorder. The bump (when requested) happens INSIDE the block, BEFORE the persist, so -// the stamped counter rides the same ext-state write and undo captures the pre/post -// generation with the rest of the blob. -void persistBankOp(const char* label, bool bumpGeneration = false); - // The stable ids of the currently-selected samples, in bank (insertion) order. // Empty when nothing is selected or the panel has never opened. This is the clean // seam the `insert` action reads to know WHAT to place — it returns ids (not grid diff --git a/src/shell/panel/panel_drag.cpp b/src/shell/panel/panel_drag.cpp index f2999b2..8edda00 100644 --- a/src/shell/panel/panel_drag.cpp +++ b/src/shell/panel/panel_drag.cpp @@ -20,7 +20,7 @@ #include "shell/panel/panel_state.h" -#include "shell/panel/panel_bank_ops.h" // persistBankOp — shared undo-block wrapper (R-B) +#include "shell/bank_ops/bank_ops.h" // persistBankOp — shared undo-block wrapper (R-B) #include "shell/actions/drag_out_win.h" // OLE / SWELL drag-out initiation seam (M11) #include "shell/actions/instrument_drop_win.h" // resolveFxDropTarget / performInstrumentDrop (S17) @@ -374,7 +374,7 @@ namespace { void doReorderDrop(const std::string& id, const std::string& bankId, int targetSlot) { if (!book() || id.empty() || bankId.empty() || targetSlot < 0) return; if (!book()->reorderSample(id, bankId, targetSlot)) return; // rejected/no-op: no undo point - persistBankOp("ReaSampler: reorder sample"); + persistBankOp(*g_panel.session, "ReaSampler: reorder sample"); g_panel.selection = Selection{}; invalidatePanel(); } @@ -387,7 +387,7 @@ void doReplaceDrop(const std::string& newId, const std::string& oldId, const std::string& bankId) { if (!book() || newId.empty() || oldId.empty() || bankId.empty()) return; if (!book()->replaceSample(newId, oldId, bankId)) return; // pool-guard reject: NO-OP - persistBankOp("ReaSampler: replace sample"); + persistBankOp(*g_panel.session, "ReaSampler: replace sample"); g_panel.selection = Selection{}; invalidatePanel(); } diff --git a/src/shell/panel/panel_input.cpp b/src/shell/panel/panel_input.cpp index 84243fb..7edade5 100644 --- a/src/shell/panel/panel_input.cpp +++ b/src/shell/panel/panel_input.cpp @@ -18,7 +18,7 @@ #include "shell/panel/panel_input.h" #include "shell/actions/bank_actions.h" // bankPruneCommandId — the footer Prune dispatch (R3) -#include "persist.h" // ReaSamplerSession — view/tail reads + mutation +#include "shell/persist/session.h" // ReaSamplerSession — view/tail reads + mutation #include "core/view/view_mode_model.h" // autoTagNewContent / NewItem / AutoTag (D2 Wave 2) #include "shell/capture/item_read.h" // itemGuid / itemLaneName — shared item-read seam (D2 W3-B) #include "shell/capture/track_guid.h" // guidString — canonical track GUID key (D2 Wave 2) @@ -157,7 +157,8 @@ void enumerateLiveGuids(ReaProject* proj, std::set& allGuids, // // INTENTIONAL: membership mutation happens OUTSIDE any Undo block. Auto-tag is a // background metadata update (like setting a label), not a destructive project edit. -// persist.cpp writes it on the next project save alongside the bank and view state, the +// the persist shell (ext_state_io.cpp) writes it on the next project save alongside the +// bank and view state, the // same way an action-driven tag is persisted. Wrapping this in an Undo block would flood // the REAPER undo history with a new entry for every timer tick that sees new content. // Returns true iff this tick tagged at least one new GUID into a mode — the signal the diff --git a/src/shell/panel/panel_layout.cpp b/src/shell/panel/panel_layout.cpp index b1a2e30..0a1eceb 100644 --- a/src/shell/panel/panel_layout.cpp +++ b/src/shell/panel/panel_layout.cpp @@ -20,7 +20,7 @@ #include "shell/panel/panel_state.h" #include "shell/panel/panel_layout.h" -#include "persist.h" // ReaSamplerSession — mode/view reads +#include "shell/persist/session.h" // ReaSamplerSession — mode/view reads #include "core/view/view_mode_model.h" // ViewModeModel — modes()/activeModeId() // Action-trigger buttons (M11): resolve each button's command id at runtime from the diff --git a/src/shell/panel/panel_render.cpp b/src/shell/panel/panel_render.cpp index 04e44d6..8e19795 100644 --- a/src/shell/panel/panel_render.cpp +++ b/src/shell/panel/panel_render.cpp @@ -18,7 +18,7 @@ #include "shell/panel/panel_state.h" #include "shell/panel/draw_kit.h" // kit text()/fillSurface/drawButton/drawWaveform (L1) -#include "persist.h" // ReaSamplerSession — mode/view/tail reads +#include "shell/persist/session.h" // ReaSamplerSession — mode/view/tail reads #include "core/view/view_mode_model.h" // ViewModeModel / Mode — the footer toggle's model namespace reasampler::panel { diff --git a/src/shell/panel/panel_state.h b/src/shell/panel/panel_state.h index 16fdefc..1cc153c 100644 --- a/src/shell/panel/panel_state.h +++ b/src/shell/panel/panel_state.h @@ -14,13 +14,9 @@ // plain free function — direct call-through, no interface, no virtual dispatch // (T4-28: the audition path and the per-mouse-move path must stay direct calls). // * Explicit using-declarations pulling the pure modules' symbols into -// reasampler::panel from their REAL namespace homes (Q-W1 sub-namespaces) — this -// header itself does not directly include the interim core/namespaces.h shim -// (Q-W2 retires that direct dependency for this module; Q-W4 retired the -// actions.h carrier with the actions split). Several panel TUs still pull the -// shim in TRANSITIVELY via persist.h/ingest.h/draw_kit.h/view.h; only -// panel_thumbnails.cpp and panel_audition.cpp are shim-free end to end. -// Nothing HERE depends on it either way. +// reasampler::panel from their REAL namespace homes (Q-W1 sub-namespaces). The +// interim core/namespaces.h shim is GONE (deleted in Q-W6 with the last split); +// every symbol below names its true home. // // REFERENCE-INVALIDATION GUARDRAIL (CONTEXT.md §Multi-bank): a bank-structural // mutation (create/delete/evacuate/activate/move) can reallocate the book's vector, @@ -84,10 +80,10 @@ namespace reasampler::panel { // --- Real-namespace-home using-declarations ----------------------------------- // // The panel's pre-split internals reference the pure modules' symbols unqualified; -// these explicit per-symbol usings (NOT the core/namespaces.h shim) keep those -// references valid while documenting each symbol's Q-W1 home. Flat-`reasampler` -// symbols (BankBook / ViewModeModel / the draw_kit shell / persistBankOp / ...) -// resolve via the enclosing namespace and need no using. +// these explicit per-symbol usings keep those references valid while documenting +// each symbol's Q-W1 home. Flat-`reasampler` symbols (BankBook / ViewModeModel / +// the draw_kit shell / the shell/bank_ops verbs / ...) resolve via the enclosing +// namespace and need no using. // core/ui using ui::ActionBarRect; diff --git a/src/shell/persist/ext_state_io.cpp b/src/shell/persist/ext_state_io.cpp index f5c7e03..3f7d92d 100644 --- a/src/shell/persist/ext_state_io.cpp +++ b/src/shell/persist/ext_state_io.cpp @@ -36,7 +36,7 @@ #include "core/capture/capture_paths.h" // projectDirOfRpp (pure path arithmetic) #include "core/instrument/map/bank_sync.h" // parseBankGeneration / formatBankGeneration (SHARED with the instrument reader) -#include "core/instrument/map/bridge_marshal.h" // readProjExtStateGrowing (T2-04: the ONE grow-loop policy) +#include "core/wire/ext_state_read.h" // readProjExtStateGrowing (T2-04: the ONE grow-loop policy) #include "core/version/app_version.h" #define REAPERAPI_MINIMAL @@ -77,15 +77,15 @@ std::string projectDirOf(const std::string& rppPath) { // GetProjExtState needs a caller-supplied buffer; the index JSON can be large // (many samples). The grow-until-strict-fit retry policy is the SHARED pure -// instrument::map::readProjExtStateGrowing (Q-W5 rider T2-04 — the same policy the -// usage_scan and VST-bridge reads run); this wrapper binds the REAPER call and +// wire::readProjExtStateGrowing (T2-04 — the same policy the usage_scan and +// VST-bridge reads run); this wrapper binds the REAPER call and // folds the terminal cases persist's callers expect: "" for an absent key (a valid // empty bank, not an error) and a console warning + "" for a value exceeding the // 16 MB ceiling, so an over-large value reads as "too large to load", not silent // data loss (mirrors the malformed-JSON warning in loadFromProject). std::string getProjExtStateString(void* proj, const char* ns, const char* key) { - using instrument::map::GrowingExtStateRead; - const GrowingExtStateRead read = instrument::map::readProjExtStateGrowing( + using wire::GrowingExtStateRead; + const GrowingExtStateRead read = wire::readProjExtStateGrowing( [&](char* buf, int cap) { return GetProjExtState(static_cast(proj), ns, key, buf, cap); }); diff --git a/src/shell/persist/persist_internal.h b/src/shell/persist/persist_internal.h index 7d41a0f..f574387 100644 --- a/src/shell/persist/persist_internal.h +++ b/src/shell/persist/persist_internal.h @@ -29,8 +29,8 @@ std::string projectDirOf(const std::string& rppPath); // Growing GetProjExtState read for `key` in namespace `ns` against `proj`. Returns // "" when the key is absent (a valid empty bank, not an error) and warns on the // console for a value exceeding the 16 MB read ceiling (unreadable whole, ignored). -// The retry policy itself is the shared pure instrument::map::readProjExtStateGrowing -// (Q-W5 rider, T2-04); this wrapper binds the REAPER call + persist's fold. +// The retry policy itself is the shared pure wire::readProjExtStateGrowing +// (T2-04; rehomed to core/wire in Q-W6); this wrapper binds the REAPER call + persist's fold. std::string getProjExtStateString(void* proj, const char* ns, const char* key); // The GUID we mint per project, formatted "{XXXXXXXX-....}" by guidToString. diff --git a/src/shell/persist/usage_scan.cpp b/src/shell/persist/usage_scan.cpp index 82adb98..2c8b261 100644 --- a/src/shell/persist/usage_scan.cpp +++ b/src/shell/persist/usage_scan.cpp @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // usage_scan.cpp — see usage_scan.h. The REAPER reads behind the pS-usage prune // protection; every decision is in the pure sample_usage module, this TU only reads. // @@ -27,7 +26,7 @@ #include #include "core/version/app_version.h" // vstPluginName / vstOutputName (channel name needles) -#include "core/instrument/map/bridge_marshal.h" // readProjExtStateGrowing (T2-04: the ONE grow-loop policy) +#include "core/wire/ext_state_read.h" // readProjExtStateGrowing (T2-04: the ONE grow-loop policy) #include "ext_keys.h" // kProjExtNamespace / kProjExtUsageKeyPrefix #include "core/wire/instrument_drop.h" // vstClassIdHex — the frozen channel class-UID hex #include "core/wire/sample_usage.h" // identityMatches, foldUsageRecords (the pure decisions) @@ -53,6 +52,14 @@ namespace reasampler { +// Real-namespace-home using-directive (Q-W6: the namespaces.h shim is retired): +// this TU speaks the sample_usage wire vocabulary wholesale (UsageRecord / +// decodeUsageRecord / foldUsageRecords / identityMatches / toUpperAscii) plus the +// channel-identity accessors + the preset class-id hex. +using namespace reasampler::wire; +using version::vstOutputName; +using version::vstPluginName; + namespace { // The three UPPERCASED channel needles identityMatches (pure, sample_usage) checks @@ -169,16 +176,15 @@ bool itemHasInstance(MediaItem* item, const FxIdentityNeedles& id) { // Growing GetProjExtState read: the usage record scales with the hold count, so a // fixed buffer risks a truncated decode. The retry policy is the SHARED pure -// instrument::map::readProjExtStateGrowing (Q-W5 rider T2-04 — one loop for persist, -// this prune-safety-adjacent read, and the VST bridge; the rules cannot drift). +// wire::readProjExtStateGrowing (T2-04 — one loop for persist, this +// prune-safety-adjacent read, and the VST bridge; the rules cannot drift). // Returns nullopt when the key cannot be read WHOLE — absent-after-enumeration // (rv <= 0) or pathologically large (> 16 MB give-up). The caller only queries keys // the enumeration just listed, so a nullopt here is a PRESENT-BUT-UNREADABLE record: // it folds to abortPrune (fail-safe — silently reduced protection is the delete // direction). std::optional readExtStateValue(ReaProject* proj, const char* key) { - using instrument::map::GrowingExtStateRead; - const GrowingExtStateRead read = instrument::map::readProjExtStateGrowing( + const GrowingExtStateRead read = readProjExtStateGrowing( [&](char* buf, int cap) { return GetProjExtState(proj, kProjExtNamespace(), key, buf, cap); }); diff --git a/src/shell/persist/usage_scan.h b/src/shell/persist/usage_scan.h index c1bfe0b..e9fd5d8 100644 --- a/src/shell/persist/usage_scan.h +++ b/src/shell/persist/usage_scan.h @@ -1,5 +1,4 @@ #pragma once -#include "core/namespaces.h" // usage_scan — the EXTENSION-side shell of the pS-usage seam (see sample_usage.h for // the pure core, the fail-safe folds, and the full design note). At prune-scan time it // answers ONE question: which project-relative bank paths are held by a LIVE ReaSampler diff --git a/src/shell/view/view.cpp b/src/shell/view/view.cpp index 3c6f5fd..a427f05 100644 --- a/src/shell/view/view.cpp +++ b/src/shell/view/view.cpp @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // view.cpp — REAPER-facing Design View shell (Phase D2). See view.h. // // Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h @@ -48,6 +47,13 @@ namespace reasampler { +// Real-namespace-home using-declarations (Q-W6: the namespaces.h shim is retired). +using view::buildFolderTree; +using view::isOnManualLane; +using view::managedLaneKey; +using view::modeIdFromLaneName; +using view::TrackFolderEntry; + namespace { // Track fixed-lane mode value (I_FREEMODE=2). See SDK: 0=normal, 1=free item diff --git a/src/shell/view/view.h b/src/shell/view/view.h index 4ad8c58..9260cd7 100644 --- a/src/shell/view/view.h +++ b/src/shell/view/view.h @@ -1,5 +1,4 @@ #pragma once -#include "core/namespaces.h" // view — the REAPER-facing shell of the Design View feature (Phase D2). It is the // mirror of the capture shell: the ViewModeModel (pure, D1) holds the mode/ // membership/snapshot state and emits the toggle plan; this shell reads the live diff --git a/tests/test_bridge_marshal.cpp b/tests/test_bridge_marshal.cpp index 04730cd..e4d7b65 100644 --- a/tests/test_bridge_marshal.cpp +++ b/tests/test_bridge_marshal.cpp @@ -6,13 +6,15 @@ // Covers: decodeGetProjExtState hit/absent/zero-return/empty-buffer (the stale-buffer // guard). The S1 spike's extractJsonStringField string-scan reader was retired in S4 // (the instrument now parses the bank through the shared bank_book JSON path), so its -// cases are gone with it. Q-W5 (rider T2-04) adds readProjExtStateGrowing — the ONE -// grow-loop retry policy shared by persist / usage_scan / reaper_bridge — covered +// cases are gone with it. Q-W5 (rider T2-04) added readProjExtStateGrowing — the ONE +// grow-loop retry policy shared by persist / usage_scan / reaper_bridge, rehomed to +// core/wire/ext_state_read.h in Q-W6 and still pinned here alongside the decode — covered // against a fake read: absent, small-fit, grow-then-fit, empty-complete (composed with // the decode guard), and the 16 MB overflow give-up. The overflow case is // prune-safety-adjacent (usage_scan folds it to abortPrune), so it is pinned here. #include "../src/core/instrument/map/bridge_marshal.h" +#include "../src/core/wire/ext_state_read.h" // readProjExtStateGrowing (Q-W6 rehome) #include #include @@ -20,6 +22,7 @@ using namespace reasampler; using namespace reasampler::instrument::map; +using namespace reasampler::wire; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ From cd12b97631e229fb8997c4413f8854557882d6fb Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Wed, 29 Jul 2026 13:51:35 -0400 Subject: [PATCH 2/2] Q-W6 review follow-ups: drop stale wav_trim clause, fix stale include comment, idempotent action-table clear, reflow ragged comment, align session.h trailing comments --- CLAUDE.md | 2 +- src/app/main.cpp | 2 +- src/shell/actions/action_registry.cpp | 1 + src/shell/actions/design_view_actions.cpp | 2 +- src/shell/capture/capture_batch.cpp | 2 +- src/shell/capture/capture_orchestrator.cpp | 2 +- src/shell/panel/panel_input.cpp | 12 ++++++------ src/shell/panel/panel_layout.cpp | 2 +- src/shell/panel/panel_render.cpp | 2 +- 9 files changed, 14 insertions(+), 13 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 02c078e..61cc31c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,7 +64,7 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde - `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. - `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. - `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. -- `wav_codec` — chunk walker + layout parse + float32 build + size-field patch + content hashes; the single pure RIFF/WAV owner. (`wav_trim` is now a transitional forwarding alias onto `wav_codec`, kept only so the Q-W2v TUs it feeds compile untouched; retire it once that wave lands.) +- `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). - `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.** - `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. - `prune_button` — pure layout/hit-test for the `bank_panel` footer Prune button. diff --git a/src/app/main.cpp b/src/app/main.cpp index 1a5bc47..a721162 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -30,7 +30,7 @@ #include #include "core/capture/render_settings.h" // captureActionTable -#include "core/version/app_version.h" // channelCommandId / appVersion +#include "core/version/app_version.h" // appVersion #include "ingest.h" #include "shell/actions/action_registry.h" // the Q-W6 registration table #include "shell/actions/bank_actions.h" // multi-bank action family (B3; Q-W4 home) diff --git a/src/shell/actions/action_registry.cpp b/src/shell/actions/action_registry.cpp index 2112111..00ece1a 100644 --- a/src/shell/actions/action_registry.cpp +++ b/src/shell/actions/action_registry.cpp @@ -61,6 +61,7 @@ int registerAction(reaper_plugin_info_t* rec, const char* suffix, void registerActionTable(reaper_plugin_info_t* rec, const ActionTableRow* rows, std::size_t count) { + g_table.clear(); // idempotent-by-construction: a re-register never doubles-up rows for (std::size_t i = 0; i < count; ++i) { g_table.push_back(TableEntry{rows[i]}); TableEntry& e = g_table.back(); diff --git a/src/shell/actions/design_view_actions.cpp b/src/shell/actions/design_view_actions.cpp index 91b76e9..a05dfb9 100644 --- a/src/shell/actions/design_view_actions.cpp +++ b/src/shell/actions/design_view_actions.cpp @@ -29,7 +29,7 @@ #include "core/view/lane_keys.h" // view::isOnManualLane — the single managed/manual predicate #include "core/view/view_mode_model.h" -#include "shell/persist/session.h" // ReaSamplerSession (owns view() model) +#include "shell/persist/session.h" // ReaSamplerSession (owns view() model) #include "shell/capture/item_read.h" // shared MediaItem* -> GUID + fixed-lane-name reads (D2 W3-B) #include "shell/capture/track_guid.h" // shared MediaTrack* -> canonical GUID key #include "shell/panel/panel_window.h" // bankPanelInvalidate — footer toggle repaint diff --git a/src/shell/capture/capture_batch.cpp b/src/shell/capture/capture_batch.cpp index fc15bb5..e22e4f9 100644 --- a/src/shell/capture/capture_batch.cpp +++ b/src/shell/capture/capture_batch.cpp @@ -19,7 +19,7 @@ #include "core/capture/batch_capture.h" // planCaptureUnits / BatchOutcome #include "core/model/bank_book.h" // BankBook / Bank #include "core/model/provenance.h" // recipe parse/build, fingerprint -#include "shell/persist/session.h" // ReaSamplerSession +#include "shell/persist/session.h" // ReaSamplerSession #include "shell/capture/capture_orchestrator.h" // captureAndIndexOne / renderOffline #include "shell/capture/provenance_shell.h" // fxChainIdentity* / trackByGuid #include "shell/capture/scope_resolve.h" // ResolvedSource diff --git a/src/shell/capture/capture_orchestrator.cpp b/src/shell/capture/capture_orchestrator.cpp index 486cb6e..030e146 100644 --- a/src/shell/capture/capture_orchestrator.cpp +++ b/src/shell/capture/capture_orchestrator.cpp @@ -17,7 +17,7 @@ #include "core/capture/tail_control.h" // TailSetting #include "core/model/provenance.h" // model::Provenance #include "ingest.h" // ingestAssignActiveInstance -#include "shell/persist/session.h" // ReaSamplerSession +#include "shell/persist/session.h" // ReaSamplerSession #include "shell/capture/insert.h" // runInsert / InsertRequest #include "shell/capture/realtime_lifecycle.h" // the in-flight realtime state diff --git a/src/shell/panel/panel_input.cpp b/src/shell/panel/panel_input.cpp index 7edade5..8a2a1b4 100644 --- a/src/shell/panel/panel_input.cpp +++ b/src/shell/panel/panel_input.cpp @@ -18,7 +18,7 @@ #include "shell/panel/panel_input.h" #include "shell/actions/bank_actions.h" // bankPruneCommandId — the footer Prune dispatch (R3) -#include "shell/persist/session.h" // ReaSamplerSession — view/tail reads + mutation +#include "shell/persist/session.h" // ReaSamplerSession — view/tail reads + mutation #include "core/view/view_mode_model.h" // autoTagNewContent / NewItem / AutoTag (D2 Wave 2) #include "shell/capture/item_read.h" // itemGuid / itemLaneName — shared item-read seam (D2 W3-B) #include "shell/capture/track_guid.h" // guidString — canonical track GUID key (D2 Wave 2) @@ -156,11 +156,11 @@ void enumerateLiveGuids(ReaProject* proj, std::set& allGuids, // membership index. // // INTENTIONAL: membership mutation happens OUTSIDE any Undo block. Auto-tag is a -// background metadata update (like setting a label), not a destructive project edit. -// the persist shell (ext_state_io.cpp) writes it on the next project save alongside the -// bank and view state, the -// same way an action-driven tag is persisted. Wrapping this in an Undo block would flood -// the REAPER undo history with a new entry for every timer tick that sees new content. +// background metadata update (like setting a label), not a destructive project edit. The +// persist shell (ext_state_io.cpp) writes it on the next project save alongside the bank +// and view state, the same way an action-driven tag is persisted. Wrapping this in an Undo +// block would flood the REAPER undo history with a new entry for every timer tick that sees +// new content. // Returns true iff this tick tagged at least one new GUID into a mode — the signal the // caller uses to decide whether to run the lane-minting pass (a track can only newly // become multi-mode when auto-tag just placed content on it). No tag ⇒ nothing to mint. diff --git a/src/shell/panel/panel_layout.cpp b/src/shell/panel/panel_layout.cpp index 0a1eceb..34d9311 100644 --- a/src/shell/panel/panel_layout.cpp +++ b/src/shell/panel/panel_layout.cpp @@ -20,7 +20,7 @@ #include "shell/panel/panel_state.h" #include "shell/panel/panel_layout.h" -#include "shell/persist/session.h" // ReaSamplerSession — mode/view reads +#include "shell/persist/session.h" // ReaSamplerSession — mode/view reads #include "core/view/view_mode_model.h" // ViewModeModel — modes()/activeModeId() // Action-trigger buttons (M11): resolve each button's command id at runtime from the diff --git a/src/shell/panel/panel_render.cpp b/src/shell/panel/panel_render.cpp index 8e19795..e111169 100644 --- a/src/shell/panel/panel_render.cpp +++ b/src/shell/panel/panel_render.cpp @@ -18,7 +18,7 @@ #include "shell/panel/panel_state.h" #include "shell/panel/draw_kit.h" // kit text()/fillSurface/drawButton/drawWaveform (L1) -#include "shell/persist/session.h" // ReaSamplerSession — mode/view/tail reads +#include "shell/persist/session.h" // ReaSamplerSession — mode/view/tail reads #include "core/view/view_mode_model.h" // ViewModeModel / Mode — the footer toggle's model namespace reasampler::panel {