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,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.
|
||||
Reference in New Issue
Block a user