docs: 1.0 documentation restructure
Split root CLAUDE.md into 19 per-directory files scoped to their source area. Roll v0 history into docs/ARCHIVE.md; retire CONTEXT.md, CONTEXT-ARCHIVE.md, PLAN.md, COMPLETED.md. Move plan docs under docs/. Rescue 9 live deferrals into docs/TODO.md.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# src/shell/actions — bindable REAPER actions, drag/drop shells, ingest
|
||||
|
||||
## Scope
|
||||
|
||||
The bindable action families routed through REAPER's `command_id`/`gaccel`/
|
||||
`hookcommand` contract (Design View toggle actions, bank actions, the prune
|
||||
action, and the shared registration plumbing/table), plus the OS drag-out and
|
||||
FX-drop shells, plus the extension-side ingest-through-the-bank shell. This is
|
||||
where user-facing REAPER actions and OS-level drag/drop live; the underlying
|
||||
mutation logic (bank verbs, prune's orphan computation, view-mode reconciliation)
|
||||
is owned by other directories and only skinned here.
|
||||
|
||||
## Invariants
|
||||
|
||||
- **Ingest is an extension act; the instrument is a read-only bank consumer.** Any
|
||||
instrument code path that captures, imports, inserts a timeline item, or writes
|
||||
back into the bank is a bug — the instrument reads and plays only.
|
||||
- **Ingest NEVER inserts a timeline item.** Arrange capture→bank→assign reuses the
|
||||
existing capture add-path and assigns the resulting `Sample` id to the target
|
||||
instance; it never places anything on the timeline — capture/placement
|
||||
separation is load-bearing here same as everywhere else. Only the arrange-capture
|
||||
surface writes the `assignment_request` wire; Media-Explorer import and file-drop
|
||||
onto the bank panel do not, and neither affects a live instance's selection.
|
||||
- **Every bank index verb wraps its mutation in a batched REAPER undo point**
|
||||
(`Undo_BeginBlock2`/`EndBlock2`, `UNDO_STATE_MISCCFG`) so one bank operation is
|
||||
one Ctrl-Z.
|
||||
- **The prune action is the ONLY file-deletion action in the system**; it opens no
|
||||
undo point (file deletion is not REAPER-undoable). It halts on
|
||||
`abortedUnreadableUsage` and prints the offending `rsusage_*` key names.
|
||||
|
||||
## Modules
|
||||
|
||||
- `shell/actions` (`action_registry` / `design_view_actions` / `bank_actions` / `prune_action`) — the bindable action families, all routed via the `command_id`/`gaccel`/`hookcommand` contract. `action_registry` owns the shared registration plumbing (interned channel-qualified id strings; register and mirror-unregister present the identical pointer) **and the Q-W6 registration TABLE**: `main.cpp`'s own family (capture scopes, panel toggle, insert, batch, realtime, recapture, version) is one `ActionTableRow` array — suffix, phrase, flat function-pointer handler — that registration, hookcommand dispatch, and the unload mirror-unregister all iterate, so adding an action touches the table only (OCP). Bank mutations flow through the promptless `shell/bank_ops` verbs (`bankOp*` + `persistBankOp`, taking `ReaSamplerSession&`), which the panel menus and `bank_actions` consume as thin UX skins. **Every bank index verb wraps its mutation in a batched REAPER undo point (`Undo_BeginBlock2`/`EndBlock2`, `UNDO_STATE_MISCCFG`) so one bank operation is one Ctrl-Z.** The prune action (`prune_action`, `BANK_PRUNE_FOLDER`) is **the ONLY file-deletion action in the system**; it opens no undo point (file deletion is not REAPER-undoable). **pS-usage:** `BANK_PRUNE_FOLDER` halts on `abortedUnreadableUsage` and prints the offending `rsusage_*` key names with clear instructions.
|
||||
- `drag_out_win` — OS drag-out shell: Windows OLE `DoDragDrop`/`CF_HDROP`, copy-only (`DROPEFFECT_MOVE` not offered); macOS/Linux via `SWELL_InitiateDragDropOfFileList`.
|
||||
- `instrument_drop_win` — FX-button drop shell: resolves a screen point to a track + FX-surface hotspot, then adds a ReaSampler 9000 instance and applies the dragged capture's state via a transient `.vstpreset` + `TrackFX_SetPreset` (the former `TrackFX_SetNamedConfigParm` "vst_chunk" write was silently unappliable for VST3). Exposes `loadInstrumentOntoTrack` (inner half, no own undo block) and `performInstrumentDrop` (wraps in its own undo block). **Never captures, never writes the bank, never inserts a timeline item.**
|
||||
- `ingest` — ingest-through-the-bank shell on the EXTENSION side: three surfaces — (1) arrange capture→bank→assign (bindable action), (2) Media-Explorer import→bank→instrument on the selected track, (3) file drop onto the bank panel→bank only. Only surface (1) writes the `assignment_request` ext-state wire. **ingest NEVER inserts a timeline item.**
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Structural wart, not yet fixed:** `ingest.cpp` / `ingest.h`, plus `ext_keys.h`
|
||||
and `resource.h`, physically live at `src/` root rather than under
|
||||
`shell/actions/` — Phase Q's reorg did not re-home these files into
|
||||
`core/`/`shell/`/`app/`. `ingest` is documented here as its nearest sibling by
|
||||
role, but the files themselves are not in this directory. This is a code
|
||||
organization issue, not a documentation one — see Open questions in the
|
||||
originating dispatch report.
|
||||
- Media-Explorer import is single-file, pull-on-action (`OpenMediaExplorer` +
|
||||
`MediaExplorerGetLastPlayedFileInfo`) — there is no enumerate-selected-files or
|
||||
register-a-drop-handler API on the Media Explorer surface.
|
||||
- REAPER exposes no drag-drop registration API; drop handling is only on
|
||||
ReaSampler's own HWNDs (`WM_DROPFILES`/`IDropTarget` on the docked `bank_panel`).
|
||||
A drop onto the VST3 editor window relaying to the extension is an unproven
|
||||
spike, not a shipped path.
|
||||
@@ -0,0 +1,42 @@
|
||||
# src/shell/bank_ops — promptless bank-mutation verbs
|
||||
|
||||
## Scope
|
||||
|
||||
The single home for bank-mutation logic: create/rename/delete/evacuate/activate/
|
||||
transfer/remove a sample or bank, plus the undo-batched persist that follows a
|
||||
mutation. No prompts, no message boxes, no panel-state reads — this is the verb
|
||||
seam that `shell/panel/panel_bank_ops` (menu/prompt UX) and `shell/actions/
|
||||
bank_actions` (bindable-action UX) both consume as thin skins, so the mutation
|
||||
logic has exactly one home. The in-model enforcement of bank rules (pool
|
||||
privileges, uniqueness, etc.) lives in `core/model` (`bank_book`) — this directory
|
||||
calls into that model, it does not reimplement its rules.
|
||||
|
||||
## Invariants
|
||||
|
||||
- **Pool privileges are inviolable.** The pool always exists, is un-deletable, is
|
||||
un-renamable (fixed id + fixed display name "Pool"), and a project can never be
|
||||
left with zero banks. No verb in this directory may delete or rename the pool,
|
||||
or evacuate it (the pool is evacuation's destination, not a source).
|
||||
- **Delete drops members; evacuate returns them.** Deleting a named bank drops its
|
||||
member index entries only — files are never deleted by a bank op (file lifecycle
|
||||
stays owned by the capture/prune path). Evacuate moves all of a bank's members
|
||||
back to the pool (index-only, same destination-collapse-by-hash as move), leaving
|
||||
the bank empty. A plain delete of a non-empty bank orphans those members out of
|
||||
every index until prune reclaims them — an accepted, designed window, not a bug.
|
||||
- **Movement is index-only.** Moving/copying a sample between banks removes/adds
|
||||
the index entry only; the underlying file never moves on disk. No bank operation
|
||||
writes, moves, or deletes a file.
|
||||
- **One bank operation is one Ctrl-Z.** Every bank index verb wraps its mutation in
|
||||
a batched REAPER undo point (`Undo_BeginBlock2`/`EndBlock2`, `UNDO_STATE_MISCCFG`)
|
||||
via `persistBankOp` — this is enforced in this directory, not left to callers.
|
||||
|
||||
## Modules
|
||||
|
||||
- `bank_ops` (`shell/bank_ops`) — the promptless bank-mutation verb seam (Q-W6 lift out of `panel_bank_ops`): `bankOpCreate`/`Rename`/`Delete`/`Evacuate`/`Activate`/`Transfer`/`Remove` + `persistBankOp` (the undo-batched ext-state persist), each taking a `ReaSamplerSession&` and returning whether the model accepted the mutation — no prompts, no message boxes, no panel-state reads. `shell/panel/panel_bank_ops` (menu/prompt UX) and `shell/actions/bank_actions` (bindable-action UX) both consume these as thin skins, so the mutation logic has exactly one home.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- The pool-privilege rules and uniqueness rules are enforced in the pure
|
||||
`core/model` `bank_book` layer, not re-checked here defensively — if a mutation
|
||||
looks like it should be rejected but isn't, the bug is more likely in `bank_book`
|
||||
than in this seam. Reference `core/model`, do not duplicate its rules here.
|
||||
@@ -0,0 +1,55 @@
|
||||
# src/shell/capture — REAPER-facing capture backends and action bodies
|
||||
|
||||
## Scope
|
||||
|
||||
Everything that turns a capture request into a rendered file + populated `Sample`,
|
||||
plus the action bodies that drive capture from REAPER's UI/action list: the two
|
||||
concrete capture backends (offline render, realtime record), scope/source
|
||||
resolution, the realtime in-flight state machine, single- and batch-capture
|
||||
orchestration, insert-to-timeline, provenance stamping, and the shared
|
||||
`MediaItem*`/`MediaTrack*` GUID-read helpers. Pure decision logic (what counts as
|
||||
an orphan, how a range maps to capture units, etc.) lives in the corresponding
|
||||
`core/` modules this shell calls into — this directory is the REAPER API surface
|
||||
only.
|
||||
|
||||
## Invariants
|
||||
|
||||
This directory implements, but does not restate, the repo-wide capture precision
|
||||
invariants (null test, bit-identical repeats, non-destructive, exact bounds,
|
||||
relative-paths-only) and the load-bearing capture/placement separation — see root
|
||||
`CLAUDE.md` §Precision invariants and §The load-bearing principle. Shell-specific
|
||||
detail not covered there:
|
||||
|
||||
- **FX-bypass guard ordering.** `scope_resolve` reads the M10 provenance-assembly
|
||||
inputs (track/item selection, FX-chain identity) BEFORE the FX-bypass guard
|
||||
neutralizes the in-scope chain — provenance must see the chain as it really is,
|
||||
not as capture temporarily leaves it.
|
||||
- **Realtime capture drives off REAPER's transport across timer ticks** —
|
||||
`capture_realtime_shell` cannot block REAPER's UI for the duration of a realtime
|
||||
record, so `begin`/`tick`/`abort` are async by construction and the temp-track +
|
||||
send recipe lives in the shell, not the pure core.
|
||||
- **`RunInsertSelected` is the one deliberate exception to capture-never-places**
|
||||
(see `capture_orchestrator` below) — every other capture entry point writes only
|
||||
a file + index entry.
|
||||
|
||||
## Modules
|
||||
|
||||
- `capture` — two CONCRETE backends with deliberately different lifecycles (no shared interface — the former `ICaptureBackend` was deleted in Q-W3, T4-26: one deriver, zero polymorphic call sites): `OfflineRenderBackend` (deterministic default, synchronous) and `RealtimeRecordBackend` (async begin/tick/abort). Input: `CaptureRequest`. Output: finished file + populated `Sample` handed to `bank_model`.
|
||||
- `scope_resolve` (`shell/capture`) — scope/source resolution shared by every capture entry point (Q-W3 hoist out of `main.cpp`): razor-else-time range inference, selected-track/selected-item-owning-track collection with canonical GUIDs, and the M10 provenance-assembly inputs (read BEFORE the FX-bypass guard neutralizes the in-scope chain).
|
||||
- `capture_orchestrator` (`shell/capture`) — single-capture orchestration + the realtime/insert action bodies (Q-W3 hoist, T4-02): `renderOffline` (one offline render under the scope's FX-bypass guard), `captureAndIndexOne` (render + provenance stamp + bank add + owned-manifest record, unpersisted), `RunCapture`/`RunCaptureItemAssign`, `RunCaptureRealtimeTrack`/`RunCancelRealtime` (the realtime action bodies — the in-flight state lives in `realtime_lifecycle`), and `RunInsertSelected` (the ONE deliberate exception to capture-never-places).
|
||||
- `capture_batch` (`shell/capture`) — the batch-capture family + re-capture-from-source (Q-W3 hoist, T4-02): `RunBatchCaptureItems` (one sample per selected item), `RunBatchCaptureRazor` (one sample per razor area), `RunRecaptureFromSource` (regenerate a provenanced sample from its recorded source's current state, bank-only). Every unit routes through `capture_orchestrator` so every precision invariant holds; persist is batched to one ext-state write per action.
|
||||
- `realtime_lifecycle` (`shell/capture`) — the in-flight realtime-capture state machine + globals (Q-W3 hoist): the action starts it, `OnTimer` drives it per tick via `DriveRealtimeCapture` (a single-pointer-test idle fast path — load-bearing hot-path guardrail), `CommitRealtimeResult` lands a finished capture in the bank, `AbortRealtimeCaptureForUnload` tears down cleanly on extension unload.
|
||||
- `capture_realtime_shell` (`shell/capture`) — the async realtime-record backend surface (Q-W6 split of the former fat `capture.h`): `RealtimeRecordBackend::begin`/`tick`/`abort`, transport-driven across timer ticks (a realtime record cannot block REAPER's UI for its own duration). Deliberately shares NO interface with the offline backend — the lifecycles genuinely differ (the former `ICaptureBackend` interface was deleted in Q-W3, T4-26).
|
||||
- `capture_realtime_finalize` (`shell/capture`) — the file-side half of the realtime-record shell (Q-W3, T4-08): discovers the file REAPER actually recorded, moves it into the bank, runs the Auto-tail PCM decay-scan trim, and populates the finished `Sample`.
|
||||
- `insert` — placement via `InsertMedia`. **Conform-to-project-tempo is an explicit opt-in flag, never silent stretching.**
|
||||
- `provenance_shell` — FX-chain identity queries via `TrackFX_*`/`TakeFX_*` APIs; feeds the pure `provenance` fingerprint builder. Stamps `Sample.provenance` on capture; ambiguous/mixed cases record nothing conservatively.
|
||||
- `track_guid` — shared `MediaTrack*` → canonical GUID-string formatter; single source of truth for membership keys.
|
||||
- `item_read` — the ONE place a `MediaItem*` is read for its canonical GUID string (`itemGuid`) and for the durable `P_LANENAME` of the fixed lane it sits on (`itemLaneName`); extracted from previously-duplicated `itemGuid`/`itemLaneName` pairs in `view.cpp` and `bank_panel.cpp` — the item-read analog of `track_guid`'s single `MediaTrack*`→GUID-key formatter. Callers must already know the track is fixed-lane (`I_FREEMODE==2`) before calling `itemLaneName`; the pure `isOnManualLane` predicate handles the non-fixed-lane case separately.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- This directory's governing precision invariants are the repo-wide capture
|
||||
invariants in root `CLAUDE.md`, not a standalone spec block here.
|
||||
- `capture` and `capture_realtime_shell` deliberately share NO common interface with
|
||||
each other (the former `ICaptureBackend` was removed) — do not reintroduce one
|
||||
without a real second polymorphic call site.
|
||||
@@ -0,0 +1,105 @@
|
||||
# src/shell/instrument — ReaSampler 9000 VST3 shells
|
||||
|
||||
## Scope
|
||||
|
||||
The REAPER/VST3-facing shells for the ReaSampler 9000 instrument: the read-only bank
|
||||
bridge, the processor, the editor, the embed strip, and the VST3 entry point — plus
|
||||
two small identity/helper headers this directory owns outright
|
||||
(`reasampler_vst.h`, `editor_internal.h`).
|
||||
|
||||
The pure engine/geometry core this shell wraps (`sampler_core`, `pitch_shift`,
|
||||
`sample_map`, `component_state_io`, `zone_params.h`, `editor_geometry`,
|
||||
`keyboard_strip`, `waveform_view`, `capture_browser`, `browser_scroll`, `note_entry`,
|
||||
`param_slider`, `trigger_seam`, `velocity_curve`, `embed_strip`, `knob_deck`,
|
||||
`curve_popup`, `master_gain`, `reasampler_uid.h`) lives in `core/instrument/*` and
|
||||
`core/wire` and is documented there — this directory consumes it but does not own it.
|
||||
|
||||
## Invariants
|
||||
|
||||
**The build shape (D-A, settled 2026-07-26 — bare Steinberg VST3 SDK + LICE editor).**
|
||||
Bare Steinberg VST3 SDK, no JUCE, with the editor drawn in the same LICE/SWELL stack
|
||||
`bank_panel` already uses. `SingleComponentEffect` (the SDK's combined
|
||||
processor+controller base) plus the SDK's factory macros is the audio-processing
|
||||
scaffolding. Drawing the editor in a VST3 `IPlugView` that hosts a LICE surface reuses
|
||||
the `bank_panel` docking muscle, keeps the look house-consistent, and avoids JUCE's
|
||||
AGPL-or-pay license posture. The `IPlugView`↔LICE bridge (window lifecycle, sizing,
|
||||
event routing from the host into the draw/hit-test loop) is the same class of work as
|
||||
docking `bank_panel`, not a new competence.
|
||||
|
||||
**Embedded TCP/MCP UI (D-D) — `reasampler_embed`.** A REAPER-hosted VST3 can draw its
|
||||
own UI inline in the track/mixer control panel via `reaper_plugin_fx_embed.h` (the
|
||||
plugin implements `IReaperUIEmbedInterface` — the same Cockos surface REAPER's own
|
||||
embedded FX use). Because this uses the same LICE-class drawing as the main editor
|
||||
path, it composes naturally with the bare-SDK-plus-LICE build. **Must-verify:** the
|
||||
`IReaperUIEmbedInterface` contract and embed message/lifecycle against
|
||||
`vendor/reaper-sdk/sdk/reaper_plugin_fx_embed.h`.
|
||||
|
||||
**Channel mode (D-E) — current reality.** An earlier design (D-E, settled
|
||||
2026-07-26) specified a per-instance mono/stereo toggle negotiating the REAPER
|
||||
audio bus via `setBusArrangements`/`getBusArrangement`, with mono-source+stereo-mode
|
||||
→ dual-mono and stereo-source+mono-mode → downmix as the cross-mode policy. **This
|
||||
was superseded by the GA post-launch DAW-fix pass**: the output bus is now
|
||||
permanently stereo, `ChannelMode` is decode-only (the dynamic mono↔stereo bus
|
||||
renegotiation from the earlier design was deleted), and channel mode auto-defaults
|
||||
from the loaded capture via `ComponentState` v9's `channelModeExplicit` flag + the
|
||||
pure `channelModeFor` helper. Root `CLAUDE.md` is authoritative for this behavior —
|
||||
do not reintroduce per-instance bus renegotiation.
|
||||
|
||||
**VST3 channel identity — the UID pair + the pairing surface (S18).** A beta-built VST
|
||||
pairs with the beta extension only, a stable VST with stable only, both installable
|
||||
side-by-side in one REAPER — one channel per binary; all channel identity derives from
|
||||
the ONE `REASAMPLER_CHANNEL_IS_BETA` bit via the pure `app_version` module (no
|
||||
scattered `#ifdef`s in the VST shell, except the one described below).
|
||||
- **The UID-pair invariant is a permanent commitment.** The VST3 class UID is the
|
||||
plugin's identity — a saved REAPER project records it and rebinds a saved instance
|
||||
by it. BOTH channel UIDs (`reasampler_uid.h`'s stable + beta pairs) are frozen
|
||||
forever once shipped; the channel bit selects which one is compiled into this
|
||||
binary (one `DEF_CLASS2`, one class per binary — never both classes in one binary).
|
||||
The UID selection is the ONLY channel `#ifdef` in the VST shell, because an
|
||||
`INLINE_UID` needs literal brace-init tokens and cannot route through
|
||||
`app_version`'s runtime string accessors.
|
||||
- **Binary + display identity are channel-derived**, sourced from `app_version`'s
|
||||
VST-name accessors — never a literal in `reasampler_vst.h`/`vst_entry.cpp`.
|
||||
- **The complete pairing surface is structural, not per-key.** Plugin identity
|
||||
(UID + filename + display) is channel-forked, and all wire keys live under the
|
||||
channel-derived ext-state namespace — the two together make pairing complete: no
|
||||
per-key or per-seam isolation work is ever needed for a new wire key.
|
||||
- **Verify** all identity/factory wiring against the vendored Steinberg SDK
|
||||
(`DEF_CLASS2` / `INLINE_UID` / `FUID` from `pluginfactory.h` + `funknown.h`).
|
||||
|
||||
**Non-goals / guardrails.**
|
||||
- The instrument never captures and never inserts into the arrange. Playback is a
|
||||
read-only act over the bank. Any instrument path that captures, places a timeline
|
||||
item, or writes back into the bank is a bug.
|
||||
- The instrument never ingests. Capture, import, and drop-ingest are *extension*
|
||||
acts; the instrument only reads and plays. A drop onto the editor window (if ever
|
||||
shipped) is relayed to the extension as an ingest request — the instrument never
|
||||
writes the bank itself.
|
||||
- No cross-platform / multi-format. Windows-only, VST3-only, REAPER-only (D5). Do not
|
||||
add an AU/AAX/VST2/CLAP wrapper, a mac/Linux build, or a standalone host target.
|
||||
- The pure core stays REAPER-free *and* VST3-free — the voice engine / envelope /
|
||||
keymap / repitch module takes no VST3 or REAPER type at its boundary; the shell
|
||||
marshals. Any VST3 or REAPER type leaking into `core/instrument` is a bug.
|
||||
- Verify Steinberg SDK, bridge, embed, and LICE-view surfaces against the vendored
|
||||
headers before use.
|
||||
|
||||
## Modules
|
||||
|
||||
- `reaper_bridge` — READ-ONLY bank consumer: receives bank snapshots from the extension and exposes them as a read-only view. **Never writes to the extension's bank** — this is a load-bearing invariant; no mutation path exists in this module. **pS-usage:** gains `writeUsageExtState` (prefix-guarded — accepts only `rsusage_`-prefixed keys, refuses all others) so the processor can publish usage without weakening the read-only-bank invariant.
|
||||
- `reasampler_processor` (`shell/instrument/`: `reasampler_processor.cpp` lifecycle + `process()`, `processor_state.cpp` component-state I/O + UI-thread parameter accessors, `processor_reload.cpp` the off-audio-thread `reloadInstrument`/publish family — Q-W2v, T4-12 split; `process()` and its per-block work stay ONE TU on purpose, no cross-TU call on the per-sample path) — VST3 `SingleComponentEffect` shell: declares event-input bus + **permanently stereo** output (GA fix: dynamic mono↔stereo bus renegotiation deleted; `ChannelMode` is now decode-only), marshals MIDI note-on/off into the VoiceEngine, renders audio; owns off-audio-thread `reloadInstrument` + atomic pointer swap so `process()` does no allocation, no file I/O, no bridge calls. **Self-contained playback (pS):** `ComponentState` v10 adds a `SampleRefs` table — per referenced sample, a project-relative path + decode intrinsics (root, loop, channels, displayName); `reloadInstrument` decodes directly from `SampleRefs`, bank-free (plays with the extension absent). The bank/bridge is a browser source: loading a capture copies its reference in; the reopen-heal timer + poll-to-play apparatus are removed. `retireIdleDrain()` retires fully-idle drain snapshots on the UI-timer cadence. Voice-param edits (`setVoiceCount`/`setVoiceMode`/`setMonoTrigger`) rebuild the engine from the already-decoded keymap via the drain-slot swap — no bank re-read, no WAV re-decode, no audible cut to ringing tails. **FB1:** applies the post-mixer `masterGainLinear` (from `ComponentState` v8) as a per-sample ramp over the summed output — no zipper noise. **GA v9:** `channelModeExplicit_` flag persisted; `channelModeFor()` auto-defaults the mode from the loaded capture's channel count when the flag is not set. **pS:** `ComponentState` bumped v9→v10 (`SampleRefs` table); pre-v10 blobs lift to empty refs and re-save self-contained. **pS-usage:** publishes instance usage (held `SampleRefs` paths) to `rsusage_<instanceGuid>` at the tail of `reloadInstrument` (off audio thread) via `reaper_bridge::writeUsageExtState`; `ComponentState` bumped v10→**v11** (`instanceGuid` field); pre-v11 blobs mint guid on first publish.
|
||||
- `reasampler_editor` (`shell/instrument/`: eight face-axis TUs — `editor_session` session/bridge state, `editor_controls` parameter plumbing, `editor_paint_sample`/`editor_paint_browse_zone` paint, `editor_input_sample`/`editor_input_browse_zone` input, `editor_platform` IPlugView/Win32 window plumbing, plus the pure `editor_geometry` layout hoist as the eighth axis; shared internals in `editor_internal.h`, no TU of its own — Q-W2v, T4-11 split of the former god-TU) — VST3 `IPlugView` LICE editor shell: hosts a LICE-drawn child window; default face is the capture browser, then single-capture setup, with opt-in zones panel. Drop-onto-editor ingest is NOT shipped (deferred).
|
||||
- `reasampler_embed` — implements `IReaperUIEmbedInterface` so the instrument draws inline in the TCP/MCP without a plugin-owned HWND; delegates layout/hit-test to `embed_strip`.
|
||||
- `vst_entry` — VST3 entry point: `GetPluginFactory` export, class registration, channel-forked class UIDs.
|
||||
- `editor_internal.h` — INTERNAL shared helpers for the `reasampler_editor` TU family (Q-W2v split), included only by the editor's own shell TUs (`editor_session` / `editor_controls` / `editor_paint_*` / `editor_input_*` / `editor_platform`), never a public seam: the `Rect`↔kit adapters, small draw primitives (knob face / spectral strip / root marker / title band), label helpers, deck group ids, and the velocity-curve box derivation — the former god-TU's anonymous-namespace helpers that more than one split TU needs. *(Newly authored per this dispatch's brief — no existing root-CLAUDE.md bullet; verified by reading `src/shell/instrument/editor_internal.h`'s own header comment and body.)*
|
||||
- `reasampler_vst.h` — shared identity constants for the ReaSampler VST3 instrument (Phase S): the plugin's class UID (the channel-selected `Steinberg::FUID`, built from the FOREVER-FROZEN macros in `core/wire/reasampler_uid.h`), vendor name/URL/email, so the processor, factory, and editor agree. A class UID is FOREVER-STABLE once shipped — minted once, never regenerated. *(Newly authored per this dispatch's brief — no existing root-CLAUDE.md bullet; verified by reading `src/shell/instrument/reasampler_vst.h` directly.)*
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `editor_internal.h` is include-only — it has no TU of its own and must never become
|
||||
a public seam; only the eight `reasampler_editor` face-axis TUs include it.
|
||||
- The two VST3 class UIDs (`core/wire/reasampler_uid.h`, consumed via
|
||||
`reasampler_vst.h`) are FOREVER-FROZEN — never regenerate an already-shipped UID.
|
||||
- The UID selection `#ifdef` in `reasampler_vst.h` is the one deliberate exception to
|
||||
"channel identity derives from `app_version` accessors, no scattered `#ifdef`s" —
|
||||
`INLINE_UID` needs literal brace-init tokens, so it can't route through a runtime
|
||||
string accessor.
|
||||
@@ -0,0 +1,70 @@
|
||||
# src/shell/panel — the docked bank-panel shell + the shared LICE draw kit
|
||||
|
||||
## Scope
|
||||
|
||||
The REAPER-facing shell for the docked bank panel: the eight `bank_panel` split TUs
|
||||
(`panel_window` / `panel_layout` / `panel_render` / `panel_input` / `panel_drag` /
|
||||
`panel_thumbnails` / `panel_audition` / `panel_bank_ops`, sharing state via
|
||||
`panel_state.h`), plus `draw_kit`, the shared LICE draw shell also consumed by the
|
||||
VST3 editor (`shell/instrument/`).
|
||||
|
||||
Pure layout/hit-test/palette modules the panel draws through (`theme`,
|
||||
`component_geometry`, `bank_grid`, `tab_strip`, `mode_switch`, `action_bar`,
|
||||
`footer_bar`, `overflow_menu`, `prune_button`, `mode_enable`, `tooltip`, `card_drag`,
|
||||
`card_meta`) live in `core/ui` / `core/model` and are documented there — this
|
||||
directory consumes them but does not own them. The promptless bank-mutation verbs
|
||||
(`bankOpCreate`/`Rename`/`Delete`/… + `persistBankOp`) that `panel_bank_ops` skins
|
||||
live in `shell/bank_ops`, a sibling directory, not here.
|
||||
|
||||
## Invariants
|
||||
|
||||
- **Draw through the kit, by palette ROLE, not hardcoded hue.** The shared LICE-based
|
||||
drawing kit is the one source of drawing for the whole system — a button, row,
|
||||
slider, or waveform looks identical in the bank panel, the embed strip, and the VST
|
||||
editor because it is the same kit function, drawn against palette roles (`bg/base`,
|
||||
`bg/panel`, `bg/cell`, `accent/primary`, `accent/secondary`, `accent/tertiary`,
|
||||
`accent/hot`, `text/primary`, `text/dim`, `line/hairline`, `warn`) rather than a
|
||||
literal color. The kit palette stays abstract (role→color, one constants block), so
|
||||
a whole visual direction is a single-file change (Phase L, DS-1/DS-2).
|
||||
- **Dock-panel layout is a thorough redesign, not a light re-skin (DS-3).** The panel
|
||||
lays out the full button/affordance inventory intuitively and uncluttered, then
|
||||
applies the kit — but this does **not** restructure the panel bones: the
|
||||
vertical-split / grid / tab structure is sound and stays as-is; a redesign designs
|
||||
the layout of the button inventory *around* it, not through it.
|
||||
- **What Phase L does not change (Precision / invariant implications):**
|
||||
- The pure/shell split holds — all layout/hit-test stays in pure CTest-covered
|
||||
geometry modules; the kit's *draw* half is shell, its *geometry* half is pure,
|
||||
even where a WDL piece is reused. No hit-test math moves into untestable code.
|
||||
- RT discipline is untouched — the kit is draw-thread only; nothing here touches
|
||||
`process` or any off-thread reload handoff (a VST3-instrument concern).
|
||||
- Read-only-over-bank is untouched — this is look-and-feel; no data-ownership
|
||||
change.
|
||||
- The capture/placement load-bearing principle is untouched — Phase L draws; it
|
||||
does not capture, place, or mutate the bank.
|
||||
- VST3 class UID / component-state contract is unchanged.
|
||||
- Windows-only (D5) — font/GDI/HFONT choices assume Windows; no cross-platform
|
||||
font-fallback concern.
|
||||
|
||||
## Modules
|
||||
|
||||
- `bank_panel` (`shell/panel/`: `panel_window` / `panel_layout` / `panel_render` / `panel_input` / `panel_drag` / `panel_thumbnails` / `panel_audition` / `panel_bank_ops`, sharing state via `panel_state.h` — Q-W2 split of the former god-module into eight TUs) — docked LICE-drawn grid with three-zone layout: top toolbar (Capture → Maintenance → Placement via `action_bar`, short labels, More (⋯) overflow menu via `overflow_menu`), bottom toolbar (four opposite-mode tag buttons + Show Both), and footer (`[Arrange|Design]` toggle, Tail button, Prune via `footer_bar`). Grid renders in sparse slot order with gap cells, drop dispatch, metadata overlay, and selection via `accent/tertiary` purple border. Draws through the L1 kit by palette role; OS drag-out via `drag_out` + `drag_out_win`. `panel_window` owns the SWELL dialog lifecycle + dialog proc + drop-target opt-in; `panel_layout` the toolbar/footer/menu rects + vertical-split geometry (the one geometry source both paint and hit-test read); `panel_render` the WM_PAINT draw; `panel_input` click/wheel/keyboard routing + the new-content auto-tag timer; `panel_drag` the hover + card-drag state machine + drop dispatch; `panel_thumbnails` the PCM→envelope thumbnail cache + the bank-change fingerprint pass; `panel_audition` the preview-playback engine; `panel_bank_ops` the menu/prompt UX skin over the promptless `shell/bank_ops` verbs. `draw_kit` (shared with the VST3 editor) stays a separate TU.
|
||||
- `panel_window` — SWELL dialog lifecycle + dialog proc + drop-target opt-in.
|
||||
- `panel_layout` — toolbar/footer/menu rects + vertical-split geometry (the one geometry source both paint and hit-test read).
|
||||
- `panel_render` — the WM_PAINT draw.
|
||||
- `panel_input` — click/wheel/keyboard routing + the new-content auto-tag timer.
|
||||
- `panel_drag` — the hover + card-drag state machine + drop dispatch.
|
||||
- `panel_thumbnails` — the PCM→envelope thumbnail cache + the bank-change fingerprint pass.
|
||||
- `panel_audition` — the preview-playback engine.
|
||||
- `panel_bank_ops` — the menu/prompt UX skin over the promptless `shell/bank_ops` verbs.
|
||||
- `draw_kit` — shared LICE draw shell: `fillSurface`, `drawButton`/`drawSlider`/`drawListRow`/`drawWaveform`, cached-font `text()`, full interaction-state model, double-buffer preserved. Consumes `theme` + `component_geometry`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **No external UI framework.** iPlug2 / JUCE / VSTGUI are rejected (DS-1). LICE +
|
||||
reused WDL pieces are the toolkit; reject any path that pulls in a new framework.
|
||||
- **No hit-test geometry in untestable shell code.** Even when reusing a WDL piece
|
||||
(e.g. a `vwnd` control for a long scroll list), layout/hit-test math stays in pure
|
||||
CTest-covered modules — do not import a WDL control's retained-mode object model
|
||||
wholesale, since its controls own their hit-test internally and that would move
|
||||
geometry into untestable shell code.
|
||||
- Verify every LICE/WDL/SWELL API name/signature against `vendor/WDL` before use.
|
||||
@@ -0,0 +1,60 @@
|
||||
# src/shell/persist — project ext-state persistence, prune filesystem I/O, usage scan
|
||||
|
||||
## Scope
|
||||
|
||||
The persist seam: project ext-state read/write (`session` / `ext_state_io`), the
|
||||
prune path's filesystem half (`prune_fs`), and the extension-side instance-usage
|
||||
scan (`usage_scan`) that feeds prune's referenced-set. Internal helpers shared only
|
||||
within the persist TU family live in `persist_internal.h`. The pure orphan
|
||||
computation is owned elsewhere (`core/reclaim`); the pure usage wire is owned
|
||||
elsewhere (`core/wire`) — this directory is the REAPER/filesystem-facing half only.
|
||||
|
||||
## Invariants
|
||||
|
||||
- **Prune is the single, exclusive file-deletion authority.** No bank op, no
|
||||
capture op, no Design View op deletes a file; if any path other than prune
|
||||
deletes a bank file, reject it in review.
|
||||
- **Dry-run first, always; no silent deletion.** Prune reports before it deletes
|
||||
(orphan count, reclaimed size, and — for a small set — the files); actual
|
||||
deletion is a confirmed second step. No periodic/background sweep.
|
||||
- **Referenced-set is the union across ALL banks, pool included**, further unioned
|
||||
(pS-usage) with every live instance's held paths via `usage_scan` →
|
||||
`prune_reconcile::mergeReferenced`. A file is an orphan iff no bank AND no live
|
||||
instance references it.
|
||||
- **Safest platform deletion available.** Trash-preferred, unlink fallback — Windows
|
||||
routes through `SHFileOperationW` (`FOF_ALLOWUNDO`, verified against SDK
|
||||
10.0.26100); macOS/Linux fall back to unlink (no portable SWELL trash surface).
|
||||
`prune_fs` is the only module that calls this.
|
||||
- **Manual, explicit trigger only** — a bindable action + a `bank_panel` button,
|
||||
never a silent background sweep.
|
||||
- **Instance-usage fail-safe (pS-usage):** a capture held by any live ReaSampler
|
||||
9000 instance can never be deleted by prune. If any `rsusage_*` record is
|
||||
unreadable or ambiguous, prune **aborts entirely and deletes nothing** —
|
||||
over-protection is the accepted residual, under-protection is a data-loss bug.
|
||||
`usage_scan` decodes every `rsusage_*` key, enumerates every ReaSampler 9000 FX
|
||||
instance (all tracks incl. master, normal + record/input chains, containers
|
||||
recursively, take FX), and folds via the pure `sample_usage::foldUsageRecords` /
|
||||
`usageHeldPaths` (a record with no live instance context protects all its paths —
|
||||
identity-failure net, never degrades toward delete). This is read-only at
|
||||
prune-scan time: `usage_scan` writes no ext-state.
|
||||
- **`PruneReport` carries `abortedUnreadableUsage` + `offendingUsageKeys`**; dry-run,
|
||||
orphan-set, and reclaim each independently abort (delete nothing) when usage
|
||||
state is unreadable. `BANK_PRUNE_FOLDER` (in `shell/actions`) halts on this flag
|
||||
and prints the offending keys.
|
||||
|
||||
## Modules
|
||||
|
||||
- `shell/persist` (`session` / `ext_state_io` / `prune_fs`) — the persist seam, split by responsibility (Q-W5; the former `persist.cpp` god-TU and its `persist.h` compatibility umbrella are both retired — callers include `shell/persist/session.h` / `ext_state_io.h` directly). `session` owns the `ReaSamplerSession` lifecycle: the poll identity-transition detection (load / Save-As / forked sibling / recycled pointer) and the `projectconfig`-driven deferred undo/redo reload. `ext_state_io` owns project ext state (`SetProjExtState`/`GetProjExtState`, namespace `"reasampler"`) ↔ `BankBook` JSON, `ViewModeModel` JSON, `TailSetting` JSON, `OwnedManifest` JSON, the writing-version stamp, GUID minting, and bank-folder relocation. `prune_fs` hosts the prune dry-run / full-set orphan queries (supplying `referencedPaths()` + `owned().paths()` to the `prune_reconcile` pure core) and is **the single file-deletion authority over user files in the bank folder** (`deleteOrphanFile` via `SHFileOperationW`); nothing else in the system deletes bank-folder bytes. **pS-usage:** the prune scan unions instance usage via `usage_scan`; `PruneReport` carries `abortedUnreadableUsage` + `offendingUsageKeys`; dry-run / orphan-set / reclaim each independently abort (delete nothing) when usage state is unreadable.
|
||||
- `usage_scan` — extension-side prune-scan shell (pS-usage): at prune-scan time, enumerates every `rsusage_*` ext-state key, decodes each `sample_usage` wire record, enumerates every ReaSampler 9000 FX instance across all tracks + master / normal + record chains / containers (recursive) / take FX, and folds with `sample_usage::foldUsageRecords` / `usageHeldPaths` to produce the set of held paths — or `abortPrune` when any record is unreadable (fail-safe: an unreadable record may protect anything, so the prune halts). Feeds `prune_reconcile::mergeReferenced`. Read-only: writes no ext-state.
|
||||
- `persist_internal.h` — internal-only shared helpers for the persist TU family (`session` / `ext_state_io` / `prune_fs`); included only by those three TUs, never a public seam (mirror of the panel's `panel_state.h` / the editor's `editor_internal.h` precedent). Holds the former anonymous-namespace helpers more than one split TU needs (active-project + `.rpp` path lookup, project-dir derivation, growing `GetProjExtState` read, project-GUID minting, bank-folder relocation) — all definitions live in `ext_state_io.cpp`. REAPER-free header: the project handle crosses this seam as the same opaque `void*` the public `session` header already uses.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- The pure orphan computation (`prune_reconcile`, `(owned ∩ present) − referenced`)
|
||||
is documented under `core/reclaim`, not here — do not duplicate its spec in this
|
||||
file.
|
||||
- The pure usage wire (`sample_usage`: `UsageRecord`, `planUsagePublish`,
|
||||
`foldUsageRecords`/`usageHeldPaths`, `identityMatches`) is documented under
|
||||
`core/wire`, not here.
|
||||
- `persist_internal.h` is an internal seam, not a public header — do not include it
|
||||
outside `session.cpp` / `ext_state_io.cpp` / `prune_fs.cpp`.
|
||||
@@ -0,0 +1,85 @@
|
||||
# src/shell/view — Design View mode application shell
|
||||
|
||||
## Scope
|
||||
|
||||
The REAPER-facing half of Design View: applying a mode's visibility/processing
|
||||
state to live tracks (park/restore), snapshotting flag values before parking, and
|
||||
restoring from snapshot on toggle-back. The mode registry, membership derivation,
|
||||
and the pure park/restore planner are owned by `core/view` (`view_mode_model`) —
|
||||
this directory is the shell that reads/writes REAPER track flags, it does not
|
||||
decide membership or mode rules.
|
||||
|
||||
## Invariants
|
||||
|
||||
- **Never touches master or `B_MUTE`/`I_SOLO`.** The tool owns only visibility,
|
||||
`B_MAINSEND`, `I_FXEN`, and per-FX offline, on every managed leaf, tagged or
|
||||
untagged. User mute/solo survives every toggle untouched; the master track's
|
||||
visibility flags are never driven (the SDK forbids `B_SHOWINTCP`/`B_SHOWINMIXER`
|
||||
on master).
|
||||
- **Parking a track** (inactive-mode leaf) drives `B_SHOWINTCP=0`, `B_SHOWINMIXER=0`
|
||||
(hide both panels), `B_MAINSEND=0` (out of mix), `I_FXEN=0` (FX bypassed), and
|
||||
`TrackFX_SetOffline(track, fx, true)` for each FX (reclaim CPU) — full CPU-park,
|
||||
not mix-removal-only.
|
||||
- **Non-destructive restore.** For every flag the tool drives, snapshot the prior
|
||||
value BEFORE parking; on toggle-back restore FROM the snapshot, never to a
|
||||
hardcoded "on." Round-trip (snapshot → park → restore) returns every driven flag
|
||||
to its captured value — the phase's trust anchor, the analog of the capture null
|
||||
test.
|
||||
- **GUID-keyed, reorder-safe.** Membership/snapshot keys on track GUID
|
||||
(`GetTrackGUID`), never track index; tolerates unknown/stale GUIDs (pruned on
|
||||
reconcile).
|
||||
- **Relative/portable state only** in the persisted view section (GUID strings,
|
||||
mode ids — no absolute paths, no index positions).
|
||||
- **Documented caveat:** offlined FX re-instantiate when a track returns to the
|
||||
active mode — stateful plugins (convolution, loaded samplers, tail-holding
|
||||
effects) re-initialize on return (possible load hitch, un-persisted internal
|
||||
state lost). Accepted cost of the CPU reclaim; surfaced at the toggle affordance
|
||||
(tooltip).
|
||||
- **Show-both semantics:** a per-track "pin visible across modes" flag re-enables
|
||||
processing whenever shown. A show-both leaf appears in every mode's visible set
|
||||
and is never parked — its driven flags stay at snapshot/restored values, FX
|
||||
online, in the mix. ("Show but keep parked" is not offered.) Stored on the
|
||||
membership record; persists; togglable per selection.
|
||||
- **No literal second canvas.** A literal second arrange surface, a second window,
|
||||
or a duplicated project stays rejected — reject any such path in review.
|
||||
|
||||
**Two-canvas sub-phase (Phase D2/E) — settled and landed parts, DAW-application half:**
|
||||
|
||||
- **Fixed-lane item-level separation mechanics.** Map mode → lane; toggle drives
|
||||
per-lane play/show so only the active mode's lane is present. Items keep their
|
||||
real position and real track — nothing is moved in time or deleted. SDK surface
|
||||
(verified present in `vendor/reaper-sdk`): track-side `I_FREEMODE = 2`,
|
||||
`I_NUMFIXEDLANES`, `C_LANEPLAYS:N`; item-side `I_FIXEDLANE`, `C_LANEPLAYS`,
|
||||
`B_FIXEDLANE_HIDDEN`. `I_FREEMODE` changes require `UpdateTimeline()` to take
|
||||
visible effect.
|
||||
- **Inactive-mode content is hidden AND silenced.** The off-mode lane is set
|
||||
`C_LANEPLAYS = 0` — neither shown nor played — consistent with exclusive
|
||||
membership and with D1's "flipping modes is a real change, not cosmetic."
|
||||
Show-both is the deliberate opt-out for a lane that must stay audible across
|
||||
modes.
|
||||
- **Capture placement is mode-aware.** An explicit placement while in Design mode
|
||||
— including capture-and-place — lands the item in the Design lane; the same
|
||||
rule governs manual insertion. The capture load-bearing principle is untouched:
|
||||
capture still writes a file + index entry and never auto-inserts; this governs
|
||||
only *where* an explicit placement lands.
|
||||
- **REAPER floor: v7** for this sub-phase (fixed lanes shipped in v7); no
|
||||
version-gate branch — below v7 the sub-phase is simply unavailable.
|
||||
|
||||
Item→lane membership rules (the adoption rule, exclusive-per-item membership, the
|
||||
managed/manual lane distinction, and the lane-ownership index) are model concepts
|
||||
owned by `core/view` — see that directory's Invariants; this directory only
|
||||
applies the resulting lane state to live tracks.
|
||||
|
||||
## Modules
|
||||
|
||||
- `view` — Design View shell: snapshots flag values before parking, drives hide + CPU-park on inactive-mode leaves (`B_SHOWINTCP`/`B_SHOWINMIXER`/`B_MAINSEND`/`I_FXEN` + per-FX offline), restores from snapshot. **Never touches master or `B_MUTE`/`I_SOLO`.**
|
||||
|
||||
## Gotchas
|
||||
|
||||
- The pure mode model (`ViewModeModel`, membership, `reconcile(liveGuids)`, the
|
||||
snapshot-based park/restore planner) lives in `core/view` — reference it, do not
|
||||
duplicate its spec here.
|
||||
- The Two-canvas sub-phase (Phase D2/E)'s settled DAW-application rules
|
||||
(fixed-lane mechanics, mode-aware capture placement, hidden-AND-silenced) are
|
||||
reflected in Invariants above; the membership/lane-ownership model concepts
|
||||
it also covers live in `core/view`'s Invariants.
|
||||
Reference in New Issue
Block a user