docs: scope MIDI-playback instrument as Phase S (native VST3, second artifact)
Settle forks D-A..D-D: bare Steinberg VST3 SDK + LICE editor; split seam with root-note/loop-points on Sample now; Tier 0-1 committed; embedded TCP/MCP UI scheduled. Phase S added to PLAN.md and CONTEXT.md spec.
This commit is contained in:
+285
@@ -1043,3 +1043,288 @@ No new REAPER *audio* API. New surfaces to verify before use:
|
||||
|
||||
**Build-time residual (not a fork):** the owned-file manifest's exact persistence
|
||||
shape (sibling `"reasampler"` ext-state key vs. folded into the `banks` blob).
|
||||
|
||||
---
|
||||
|
||||
# MIDI-playback instrument — additive phase spec (Phase S — Sampler)
|
||||
|
||||
> **New pillar, its own lettered phase, and — uniquely — its own build artifact.**
|
||||
> Every prior phase (M / D / B / R / V) ships inside the one `reaper_reasampler`
|
||||
> extension binary. Phase S does **not**: a REAPER extension *cannot* be a
|
||||
> MIDI-triggered instrument (it is not a node in any track's signal chain), so the
|
||||
> instrument is a **second, separate binary** — a native **VST3** plugin the user
|
||||
> instantiates on an instrument track — that reads ReaSampler's banks and plays them
|
||||
> MIDI-triggered. Namespaced **`S` (Sampler)** rather than "D" (which would collide
|
||||
> with Design View). The `M`/`D`/`B`/`R`/`V` extension pillars are untouched. Product
|
||||
> framing, the plugin-format reasoning, the bare-VST3-vs-JUCE assessment, and the
|
||||
> settled decision record: `docs/product/midi-playback.md`. Same standing discipline:
|
||||
> **verify every Steinberg VST3 SDK and REAPER/SWELL API name/signature against the
|
||||
> vendored headers before use.**
|
||||
|
||||
## What it is
|
||||
|
||||
A native **VST3 sampler instrument** — a separate product/artifact from the
|
||||
extension — that maps ReaSampler's captured bank samples across a MIDI keyboard and
|
||||
plays them back with a real voice engine (polyphony, velocity, envelopes). The
|
||||
extension stays the sole owner of **capture + organization**; the instrument is the
|
||||
**playback surface**. The two are *tightly integrated but distinct acts*: the
|
||||
extension captures and organizes; the instrument plays. Neither crosses into the
|
||||
other's role — the instrument never captures, the extension never becomes an
|
||||
instrument.
|
||||
|
||||
**Why a VST3 and not the extension (load-bearing, settled — see D1/D5/D6 below).** An
|
||||
instrument track's "read live MIDI, emit audio per-voice, in REAPER's
|
||||
routing/record/render path" contract belongs to VST/VST3/CLAP/JSFX plugins, hosted
|
||||
through an entirely different mechanism than the extension API. The extension SDK's
|
||||
audio-adjacent surfaces (`Audio_RegHardwareHook`, `kbd_OnMidiEvent`, `PlayPreview`,
|
||||
`pcmsrc` subclassing) are each the wrong tool for a live-MIDI instrument — the full
|
||||
reasoning is in `docs/product/midi-playback.md` §1. The instrument is therefore a
|
||||
standard VST3 plugin; this is not an engineering-around-able limitation, it is what
|
||||
the plugin *format* is.
|
||||
|
||||
## The three locked decisions this spec assumes
|
||||
|
||||
Settled by Daniel (2026-07-26); everything below assumes them. Reasoning preserved in
|
||||
`docs/product/midi-playback.md` §4.
|
||||
|
||||
- **D1 — native VST3.** Not JSFX. Full sampler sophistication, clean integration, and
|
||||
access to the REAPER VST-host bridge. JSFX retired (cross-platform-for-free is
|
||||
worthless under D5, and JSFX gets no bridge).
|
||||
- **D5 — Windows-only, VST3-only, REAPER-only.** No cross-platform DSP/build/signing
|
||||
matrix, no multi-format wrapper, no standalone-in-other-hosts concern. REAPER-coupling
|
||||
via the bridge is intended. This is the single biggest simplifier — it deletes most of
|
||||
what makes VST3 painful.
|
||||
- **D6 — two products, tightly integrated.** A separate artifact, but **not** a
|
||||
divorced file-only companion: via the VST-host bridge it reads the live
|
||||
`"reasampler"` project ext-state and is project-aware.
|
||||
|
||||
## The VST-host bridge (the integration mechanism, stated once)
|
||||
|
||||
A VST3 *hosted inside REAPER* can call back into REAPER's own API by resolving
|
||||
function pointers by name over the host callback
|
||||
(`hostcb(&effect, 0xdeadbeef, 0xdeadf00d, 0, "FunctionName", 0.0)` — the same
|
||||
string-keyed API table the extension uses; verified in `video_processor.h` and the
|
||||
`reaper_plugin_functions.h` `GetProjExtState`/`SetProjExtState`/`EnumProjExtState`
|
||||
entries). The plugin can also fetch its **host context** — the track/take/project it
|
||||
was instantiated in (opcode `0xdeadf00e`). **Consequence:** the instrument reads the
|
||||
*same* live `"reasampler"` ext-state that `persist` writes, follows the active
|
||||
project, and needs no "point me at the bank folder" wiring — it asks REAPER which
|
||||
project it is in. This capability exists *because* the plugin is hosted in REAPER; it
|
||||
is the technical affordance D6 leaned on. **Must-verify before build:** confirm the
|
||||
bridge opcodes and the by-name resolution against the vendored
|
||||
`vendor/reaper-sdk/sdk/` headers (`reaper_plugin.h`, `video_processor.h`,
|
||||
`reaper_plugin_functions.h`) — the framing doc's opcode citations are verified from
|
||||
those headers but the exact call marshalling should be confirmed at the spike.
|
||||
|
||||
## The two seams (audio via files, mapping via live state)
|
||||
|
||||
- **File seam (audio, permanent).** The sample **audio** is the on-disk 32-bit-float
|
||||
WAVs — project-relative, travelling with the `.rpp` via the M4 machinery. The
|
||||
instrument resolves those paths **the same way `persist` does** (a shared convention,
|
||||
not a re-implementation). There is no live PCM stream across the bridge, by design.
|
||||
- **Live-state seam (the mapping, via the bridge).** For everything that is *not* raw
|
||||
audio — the bank index, the mapping, which project is active — the instrument reads
|
||||
the live `"reasampler"` ext-state via the bridge. It sees what `persist` last wrote
|
||||
and follows the active project.
|
||||
|
||||
## The seam fields — what becomes a bank intrinsic (D-B, settled 2026-07-26)
|
||||
|
||||
**The split model (option iii) is the settled answer.** It mirrors the
|
||||
capture/placement separation:
|
||||
|
||||
- **Bank intrinsics (facts about the captured file) live on `Sample`.** **Root note**
|
||||
(the MIDI note the sample was recorded at, so it can be repitched across the
|
||||
keyboard — distinct from the existing optional *musical key* field) and **loop
|
||||
points** (sustain-loop start/end for held notes; sample-accurate, zero-crossing-aware)
|
||||
are *facts about the file*, analogous to sample rate, length, and peaks. They are added
|
||||
to `Sample` as an **additive field extension** — the same shape as how `provenance`
|
||||
was added in M1: new optional fields with JSON round-trip, populated at/after capture,
|
||||
defaulting cleanly for pre-existing samples. This keeps the bank a clean,
|
||||
tool-agnostic library (WAVs + facts, readable by anything).
|
||||
- **The performance map (a creative arrangement) lives in the instrument.** **Key
|
||||
zones** (low/high note per sample), **velocity layers**, **round-robin groups**,
|
||||
**amplitude envelopes** (ADSR), and per-sample tuning/gain trim are a *performance
|
||||
choice*, not a fact about a file — they belong to the instrument, not the bank. Under
|
||||
the live-state seam the instrument may still *read* performance-map data out of shared
|
||||
`"reasampler"` state, so "who owns which field" is a data-ownership decision, not a
|
||||
transport one.
|
||||
|
||||
**Why the intrinsic fields are added early (D-B, the backfill-cliff reasoning).** The
|
||||
`Sample` field addition is scheduled as an **early Phase S point** even though the
|
||||
instrument that consumes them lands later. Rationale (the *design-the-seam-even-if-you-
|
||||
defer-the-feature* instinct, same as Fork R-D's owned-file manifest): if the fields are
|
||||
added only when the instrument needs them, every sample captured before then lacks a
|
||||
root note / loop points and must be backfilled by hand. Adding the fields now — so
|
||||
capture starts populating them (or at least defaulting them cleanly) — costs almost
|
||||
nothing and closes the cliff. The field addition touches the **extension** codebase
|
||||
(`bank_model` + capture + persist), is independently shippable, and lands before the
|
||||
instrument build leans on it.
|
||||
|
||||
## Scope tiers (D-C, settled 2026-07-26 — Tier 0–1 committed, Tier 2 held, Tier 3 optional-forever)
|
||||
|
||||
Tiers are minimal → sophisticated; **Tier 0 delivers the core promise** and each tier
|
||||
above is optional depth, not a prerequisite for the one below.
|
||||
|
||||
- **Tier 0 — "the bank plays" (committed).** One sample mapped chromatically across
|
||||
the keyboard from its root note; basic polyphony; a simple amp envelope; velocity →
|
||||
volume. The honest MVP: point a bank sample at a MIDI track and play it repitched.
|
||||
On the native path this is the `SingleComponentEffect` skeleton plus a single-voice
|
||||
core, editor deferrable behind a parameters-only default view.
|
||||
- **Tier 1 — "a keymap" (committed).** Multiple samples zoned across the keyboard (key
|
||||
ranges), each with its own root note — a captured *kit* or a *multisampled instrument*
|
||||
plays correctly. This is where the root-note + key-range seam fields earn their place.
|
||||
One sample per key-region.
|
||||
- **Tier 2 — "expressive" (HELD — noted, not specified).** Velocity layers, round-robin
|
||||
(the anti-machine-gun feature), full ADSR, per-sample tuning/gain trim, sustain loops.
|
||||
Where it becomes a tool people reach for. **Explicitly a follow-on** — its points are
|
||||
not drawn up in this spec; it is recorded as the next depth increment once Tier 0–1
|
||||
proves the instrument belongs in ReaSampler's world.
|
||||
- **Tier 3 — "instrument polish" (optional-forever).** Filters, filter/pitch envelopes,
|
||||
LFOs, per-voice pan, choke groups, a modest FX slot. A direction to leave room for,
|
||||
never a commitment. Do **not** let a Tier-3 feature list inflate the build-shape
|
||||
decisions.
|
||||
|
||||
## The build shape (D-A, settled 2026-07-26 — bare Steinberg VST3 SDK + LICE editor)
|
||||
|
||||
**Settled: bare Steinberg VST3 SDK, no JUCE, with the editor drawn in the same
|
||||
LICE/SWELL stack `bank_panel` already uses.** Reasoning (full assessment in
|
||||
`docs/product/midi-playback.md` §1a and §4 D-A):
|
||||
|
||||
- **The audio-processing scaffolding is bounded.** Using `SingleComponentEffect` (the
|
||||
SDK's combined processor+controller base — sanctioned for a non-distributable,
|
||||
REAPER-only plugin under D5/D6) plus the SDK's factory macros, a silent-but-loading
|
||||
VST3 instrument skeleton is order-of-magnitude a few-hundred lines of
|
||||
adapt-from-example ceremony, written once. The AGain / Note Expression Synth SDK
|
||||
examples are the copy-source. Not a tar pit.
|
||||
- **D5 deletes JUCE's biggest justification.** JUCE exists largely for multi-format /
|
||||
cross-platform, both of which D5 removed. Its one genuine remaining pull is the editor
|
||||
UI — and ReaSampler is the atypical case where even that is weak, because it already
|
||||
has a working, docked, custom-drawn LICE UI (`bank_panel`) and a house style. Drawing
|
||||
the editor in a VST3 `IPlugView` that hosts a LICE surface reuses that muscle, keeps
|
||||
the look house-consistent, and avoids JUCE's AGPL-or-pay license posture (the Steinberg
|
||||
SDK is permissive, no revenue gate).
|
||||
- **The one real edge — the `IPlugView`↔LICE bridge** (window lifecycle, sizing, event
|
||||
routing from the host into the draw/hit-test loop) — is *the same class of work*
|
||||
ReaSampler already did to dock `bank_panel`, not a new competence, but it is less
|
||||
trodden than dropping in a JUCE editor. It is therefore the phase's **opening spike**
|
||||
(below), which also converts §1a's experienced-estimates (Windows module-export
|
||||
symbol names, factory-macro spellings, exact bridge marshalling) into verified fact
|
||||
before the engine build leans on them. VSTGUI (the SDK's bundled toolkit) is the noted
|
||||
fallback rung *only if* the LICE bridge proves gnarlier than the panel work suggests;
|
||||
JUCE is the last resort behind that.
|
||||
|
||||
## The pure core (D3 — the load-bearing split, transplanted)
|
||||
|
||||
**The sampler's voice engine, envelope math, key/velocity mapping, repitch/interpolation,
|
||||
and keymap resolution are a pure, REAPER-free, DAW-free, unit-tested module** — the
|
||||
mirror of `bank_model` / `peaks` / `view_mode_model` / `bank_book`, tested in the CTest
|
||||
harness outside any host. This is the heart of the phase; **test it hard.** The VST3
|
||||
wrapper — the `SingleComponentEffect` subclass, bus setup, the `process` call
|
||||
marshalling MIDI→core and core→audio-buffer, the `IPlugView` LICE editor, and the bridge
|
||||
calls that read `"reasampler"` ext-state — is the **thin shell**, the only part that
|
||||
touches VST3 or REAPER at all. Critically, this split is **invariant under the build-shape
|
||||
choice**: whether the shell is bare-SDK or (hypothetically) JUCE, the pure core is
|
||||
identical, REAPER-free, and tested the same way. The format choice is a shell choice; the
|
||||
core is invariant.
|
||||
|
||||
## Module architecture (preserve the pure/shell split — in the new artifact)
|
||||
|
||||
Pure (no REAPER types, no VST3 types, unit-tested — the mirror of `bank_model`):
|
||||
- **Sampler core** — voice allocation/polyphony, amplitude envelope (ADSR), key→sample
|
||||
and velocity→sample mapping (the keymap), repitch/interpolation from root note, and
|
||||
keymap resolution. REAPER-free *and* VST3-free, unit-tested in CTest against known
|
||||
signals (mirror of how `peaks` asserts an envelope). This is D3's pure core and the
|
||||
heart of the phase.
|
||||
|
||||
Shell (VST3-facing / REAPER-facing, thin):
|
||||
- **VST3 wrapper** — `SingleComponentEffect` subclass: `initialize` (declare an event
|
||||
input bus + an audio output bus, no audio input), `setupProcessing`, `setActive`,
|
||||
`setState`/`getState`, and the hot-path `process` that reads MIDI off the event bus,
|
||||
drives the pure core, and writes the core's per-voice audio to the output bus. Plus the
|
||||
module factory (`GetPluginFactory` + Windows `InitDll`/`ExitDll` — verify exact export
|
||||
names at the spike).
|
||||
- **`IPlugView` LICE editor** — hosts a LICE-drawn surface in the VST3 view seat
|
||||
(window creation/sizing, host→draw/hit-test event routing). Reuses the `bank_panel`
|
||||
LICE/SWELL competence and house style.
|
||||
- **Bridge/state reader** — resolves `GetProjExtState`/`EnumProjExtState` by name over
|
||||
the host callback, fetches the host project context, reads the live `"reasampler"`
|
||||
ext-state (bank index + intrinsic fields + performance map), and resolves WAV audio
|
||||
paths the same project-relative way `persist` does.
|
||||
|
||||
## Precision / invariant implications
|
||||
|
||||
- **The bank is one source; the instrument is another view of it (never a fork).** The
|
||||
instrument is a pure *consumer* of the bank — it does not copy samples, does not own a
|
||||
private sample store, and does not mutate the bank. The bank stays the single
|
||||
authoritative artifact (the one-source-multiple-views instinct). Any instrument path
|
||||
that writes back into the bank or keeps its own sample copies is a bug.
|
||||
- **Capture/placement/playback stay distinct acts.** The instrument reads and plays; it
|
||||
never captures and never inserts into the arrange. The capture load-bearing principle
|
||||
is untouched — Phase S adds a *third* distinct act (playback) without weakening the
|
||||
capture↔placement separation.
|
||||
- **`Sample` field addition is additive and lossless.** Root note + loop points are new
|
||||
optional fields with JSON round-trip, defaulting cleanly for samples captured before
|
||||
the addition — the same additive, backward-compatible shape as `provenance` (M1). No
|
||||
existing `Sample` field changes; no `BankIndex` behavior changes.
|
||||
- **Relative-paths-only survives.** The instrument resolves audio via the M4
|
||||
project-relative machinery; it introduces no absolute paths.
|
||||
|
||||
## Embedded TCP/MCP UI (D-D, settled 2026-07-26 — SCHEDULED as a later Phase S point)
|
||||
|
||||
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). A ReaSampler instrument can render a
|
||||
compact keymap/level strip inline in the TCP/MCP, not only in its own window. Because
|
||||
this uses the **same LICE-class drawing as the D-A editor path**, it composes naturally
|
||||
with the bare-SDK-plus-LICE build — the groundwork is the groundwork.
|
||||
|
||||
**Settled: scheduled, not deferred.** This is a real, in-phase later point — it lands
|
||||
**after** the main `IPlugView` editor exists (it composes with that LICE path), not a
|
||||
someday-note. It is polish, not a Tier-0 need, so it sequences last in the phase; but it
|
||||
is on the roadmap. **Must-verify before build:** the `IReaperUIEmbedInterface` contract
|
||||
and embed message/lifecycle against `vendor/reaper-sdk/sdk/reaper_plugin_fx_embed.h`.
|
||||
|
||||
## REAPER / Steinberg API surface (verify all signatures)
|
||||
|
||||
- **VST3 SDK (a new vendored dependency — vendor it at the spike).** `FUnknown` and the
|
||||
`IComponent` / `IAudioProcessor` / `IEditController` interface family; the
|
||||
`SingleComponentEffect` / `EditControllerEx1` / `AudioEffect` base classes; the class
|
||||
factory (`GetPluginFactory` + factory macros); `IPlugView` for the editor;
|
||||
`ProcessData` / `ProcessSetup` for the hot path. **Verify** interface members, the
|
||||
base-class overrides, factory-macro spellings, and the Windows module-export symbol
|
||||
names (`InitDll`/`ExitDll`/`GetPluginFactory`) against the vendored SDK at the spike —
|
||||
the framing doc flags several of these as experienced estimates.
|
||||
- **REAPER VST-host bridge.** `hostcb` opcode `0xdeadf00d` (resolve API function by
|
||||
name) and `0xdeadf00e` (host context); the by-name resolution of
|
||||
`GetProjExtState`/`SetProjExtState`/`EnumProjExtState`. **Verify** against
|
||||
`vendor/reaper-sdk/sdk/reaper_plugin.h` + `video_processor.h` +
|
||||
`reaper_plugin_functions.h`.
|
||||
- **Embedded UI (D-D, later point).** `IReaperUIEmbedInterface` and the embed
|
||||
message/lifecycle contract — verify against
|
||||
`vendor/reaper-sdk/sdk/reaper_plugin_fx_embed.h` before use.
|
||||
- **LICE/SWELL editor.** Reuses the `bank_panel` LICE/SWELL drawing surface; verify the
|
||||
`IPlugView`↔LICE window/bitmap bridge at the spike (window creation, sizing, event
|
||||
routing) — the least-trodden edge of the phase.
|
||||
|
||||
## 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 — reject in review.
|
||||
- **The instrument keeps no private copy of the samples.** It consumes the one
|
||||
authoritative bank; per-instance sample stores are a non-goal (they refork the source
|
||||
the one-source-multiple-views instinct keeps single).
|
||||
- **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 the core is a bug (the D3 split).
|
||||
- **Additive to the extension.** The `Sample` intrinsic-field addition is additive
|
||||
(new optional fields; no existing field or `BankIndex` behavior changes); everything
|
||||
else in Phase S lives in the *second* artifact and does not alter the extension's
|
||||
M/D/B/R/V pillars.
|
||||
- **Do not spec Tier 2/3.** Tier 2 is held (noted, not specified); Tier 3 is
|
||||
optional-forever. Do not let their feature lists drive Tier 0–1's build shape.
|
||||
- **Verify Steinberg SDK, bridge, embed, and LICE-view surfaces** against the vendored
|
||||
headers before use — several §1a claims are experienced estimates until the spike
|
||||
confirms them.
|
||||
|
||||
@@ -475,3 +475,208 @@ CONTEXT.md §Prune (Settled forks).
|
||||
locked in:** a second forever-stable command-id prefix and a second ext-state
|
||||
namespace. **Dev-ops:** the build now produces two named artifacts (stable + beta)
|
||||
per platform.
|
||||
|
||||
---
|
||||
|
||||
# Phase S — MIDI-playback instrument (native VST3 sampler; a second build artifact)
|
||||
|
||||
> **New pillar, own lettered namespace, and — uniquely — a second build artifact.**
|
||||
> Every prior phase ships inside the one `reaper_reasampler` extension binary; Phase
|
||||
> S does not. A REAPER extension *cannot* be a MIDI-triggered instrument (it is not a
|
||||
> node in any track's signal chain), so the instrument is a **separate native VST3
|
||||
> plugin** the user instantiates on an instrument track, reading ReaSampler's banks
|
||||
> and playing them MIDI-triggered. Namespaced **`S` (Sampler)** rather than "D"
|
||||
> (Daniel's call — "D" collides with Design View). Authoritative spec: **CONTEXT.md
|
||||
> §MIDI-playback instrument — additive phase spec (Phase S)**. Product framing +
|
||||
> the settled decision record (D1/D5/D6 locked, D-A..D-D settled 2026-07-26):
|
||||
> `docs/product/midi-playback.md`. When a point lands, doc-keeper moves it to
|
||||
> `COMPLETED.md`.
|
||||
>
|
||||
> **Locked (see `docs/product/midi-playback.md` §4):** D1 native VST3 (not JSFX);
|
||||
> D5 Windows-only / VST3-only / REAPER-only; D6 two products, tightly integrated via
|
||||
> the VST-host bridge (live `"reasampler"` ext-state, project-aware). **Settled forks
|
||||
> (2026-07-26):** D-A bare Steinberg VST3 SDK + LICE editor (no JUCE); D-B split seam
|
||||
> with root-note + loop-points added to `Sample` *now*; D-C Tier 0–1 committed (Tier 2
|
||||
> held, Tier 3 optional-forever); D-D embedded TCP/MCP UI **scheduled** as a later
|
||||
> in-phase point (after the main editor exists).
|
||||
>
|
||||
> **Second build artifact (load-bearing, flagged up front):** Phase S produces a
|
||||
> *separate* VST3 binary alongside `reaper_reasampler`. The Steinberg VST3 SDK is a
|
||||
> **new vendored dependency** (vendor at the spike — an implementation-time
|
||||
> prerequisite, not done here), and CMake grows a second target with Windows VST3
|
||||
> module-export/bundle wiring. Both are established by S1 so nothing downstream leans
|
||||
> on an unbuilt target.
|
||||
|
||||
## S1 — opening spike: VST3 skeleton + `IPlugView`↔LICE bridge (proof + second target)
|
||||
**Goal:** Stand up the second build artifact and prove the two least-trodden
|
||||
unknowns before the engine build leans on them: (1) a silent-but-loading VST3
|
||||
`SingleComponentEffect` skeleton that REAPER hosts, and (2) an `IPlugView` that hosts
|
||||
a LICE-drawn surface. Converts §1a's experienced-estimates (Windows module-export
|
||||
names, factory-macro spellings, exact bridge marshalling) into verified fact.
|
||||
CONTEXT.md §Phase S (build shape, module architecture, API surface).
|
||||
**Prerequisite (implementation-time):** vendor the Steinberg VST3 SDK (a new
|
||||
submodule/dependency alongside `reaper-sdk` / `WDL`); confirm whether VSTGUI is
|
||||
bundled (moot for D-A but resolves the noted fallback rung).
|
||||
**Verify (in DAW):** the VST3 skeleton loads in REAPER on an instrument track,
|
||||
enumerates via `GetPluginFactory`, sets up an event-in + audio-out bus, and runs an
|
||||
empty `process` without error; an `IPlugView` opens and draws a LICE surface with a
|
||||
working hit-test; the VST-host bridge resolves `GetProjExtState` by name and reads a
|
||||
known `"reasampler"` value. Nothing plays yet — this is the loading/drawing/bridge
|
||||
proof.
|
||||
|
||||
- [ ] CMake second target: a separate VST3 module artifact built alongside
|
||||
`reaper_reasampler` (Windows VST3 export/bundle wiring; `GetPluginFactory` +
|
||||
`InitDll`/`ExitDll` — **verify exact export names against the vendored SDK**).
|
||||
- [ ] `SingleComponentEffect` skeleton: factory + class registration, `initialize`
|
||||
declaring an event-input bus + an audio-output bus (no audio input),
|
||||
`setupProcessing`, `setActive`, empty `process`. Loads silently in REAPER.
|
||||
- [ ] `IPlugView`↔LICE bridge spike: open a plugin editor window hosting a LICE-drawn
|
||||
surface (window creation/sizing, host→draw/hit-test event routing), reusing the
|
||||
`bank_panel` LICE/SWELL competence. **The decision's one real unknown — prove it
|
||||
here.** (VSTGUI is the noted fallback only if this proves gnarlier than the panel
|
||||
work suggests.)
|
||||
- [ ] Bridge read spike: resolve `GetProjExtState`/`EnumProjExtState` by name over the
|
||||
host callback (`hostcb` opcode `0xdeadf00d`), fetch host project context
|
||||
(`0xdeadf00e`), and read a known `"reasampler"` ext-state value. **Verify opcodes +
|
||||
marshalling against `reaper_plugin.h` / `video_processor.h` /
|
||||
`reaper_plugin_functions.h`.**
|
||||
|
||||
## S2 — `Sample` intrinsic fields (root note + loop points; in the *extension*)
|
||||
**Goal:** Add the two bank-intrinsic seam fields to `Sample` — **root note** (MIDI
|
||||
note the sample was recorded at; distinct from the existing optional *musical key*)
|
||||
and **loop points** (sustain-loop start/end, sample-accurate, zero-crossing-aware) —
|
||||
as an additive field extension with JSON round-trip, populated at/after capture. This
|
||||
touches the **extension** codebase, is independently shippable, and lands early to
|
||||
close the backfill cliff before the instrument consumes the fields. CONTEXT.md
|
||||
§Phase S (seam fields, D-B). **Same additive shape as `provenance` (M1).**
|
||||
**Verify:** CTest green. Round-trip lossless across the new fields; pre-existing
|
||||
samples (no root note / loop points) deserialize with clean defaults (no loss, no
|
||||
migration break); capture populates root note where derivable and loop points where
|
||||
set; relative-paths-only unaffected; `BankIndex` behavior unchanged (purely
|
||||
additive).
|
||||
**Depends on:** nothing in Phase S (extension-only; can land before or in parallel
|
||||
with S1).
|
||||
|
||||
- [ ] Add `rootNote` (optional MIDI note) + `loopStart`/`loopEnd` (optional
|
||||
sample-accurate loop points) to `Sample`; JSON serialize/deserialize with clean
|
||||
defaults for samples lacking them (additive, backward-compatible — mirror of how
|
||||
`provenance` was added).
|
||||
- [ ] Populate the fields on capture where derivable (root note) / settable (loop
|
||||
points); leave them cleanly empty otherwise. No existing `Sample` field changes.
|
||||
- [ ] Tests: full round-trip lossless including the new fields; a legacy `Sample`
|
||||
JSON (no new fields) parses with defaults and re-serializes without loss; additive
|
||||
invariant (no change to existing fields, dedup, tier, or `BankIndex` behavior).
|
||||
|
||||
## S3 — pure sampler core (voice engine / envelope / keymap / repitch)
|
||||
**Goal:** The REAPER-free **and** VST3-free sampler core — voice allocation/polyphony,
|
||||
amplitude envelope (ADSR), key→sample and velocity→sample mapping (the keymap),
|
||||
repitch/interpolation from root note, keymap resolution — unit-tested in CTest against
|
||||
known signals. **The heart of the phase (D3); the mirror of
|
||||
`bank_model`/`peaks`/`view_mode_model`/`bank_book`; test it hard.** The core is
|
||||
invariant under the build-shape choice — no VST3 or REAPER type at its boundary.
|
||||
CONTEXT.md §Phase S (pure core, module architecture).
|
||||
**Verify:** CTest green. Voice allocation is correct under polyphony (note-on/off,
|
||||
voice stealing where bounded); ADSR shape asserted against a known signal (mirror of
|
||||
`peaks`); repitch from root note produces the expected pitch ratio; keymap resolution
|
||||
maps a (note, velocity) to the correct sample/zone; the core takes and returns only
|
||||
plain data (no VST3/REAPER types) — enforced by the test target linking neither SDK.
|
||||
**Depends on:** S2 (consumes `rootNote` / loop points as core inputs).
|
||||
|
||||
- [ ] Voice engine: polyphonic voice allocation (note-on/off, bounded voice stealing),
|
||||
per-voice state, mono-and-basic-polyphony sufficient for Tier 0.
|
||||
- [ ] Amplitude envelope (ADSR) math — asserted against a known signal.
|
||||
- [ ] Repitch/interpolation from root note (chromatic pitch ratio across the
|
||||
keyboard); loop-point-aware sustain for held notes.
|
||||
- [ ] Keymap model + resolution: key ranges/zones (Tier-1 shape) and the
|
||||
(note, velocity) → sample/zone query; Tier-0 chromatic-from-single-root as the
|
||||
degenerate case.
|
||||
- [ ] Tests: voice allocation under polyphony + stealing; ADSR envelope shape;
|
||||
repitch pitch-ratio correctness; keymap resolution (single-root chromatic + zoned);
|
||||
core boundary is plain-data-only (no VST3/REAPER types).
|
||||
|
||||
## S4 — Tier 0: "the bank plays" (single sample, chromatic)
|
||||
**Goal:** The honest MVP — one bank sample mapped chromatically across the keyboard
|
||||
from its root note, basic polyphony, a simple amp envelope, velocity→volume. Wire the
|
||||
S3 core into the S1 VST3 shell over the live-state seam (bridge-read bank + audio via
|
||||
the M4 project-relative path machinery). Editor deferrable behind a parameters-only
|
||||
default view. CONTEXT.md §Phase S (Tier 0, seams). **Delivers the core promise.**
|
||||
**Verify (in DAW):** on an instrument track, the VST3 plays a chosen bank sample
|
||||
MIDI-triggered, repitched chromatically from its root note, with basic polyphony,
|
||||
an amp envelope, and velocity→volume; it reads the live `"reasampler"` bank via the
|
||||
bridge and resolves the WAV audio the same project-relative way `persist` does;
|
||||
following the active project works; it never captures and never inserts into the
|
||||
arrange (read-only over the bank).
|
||||
**Depends on:** S1, S2, S3.
|
||||
|
||||
- [ ] VST3 `process` marshalling: read MIDI note-on/off/velocity off the event bus,
|
||||
drive the S3 core, write per-voice audio to the output bus.
|
||||
- [ ] Live-state seam: read the bank index + selected sample's root note from
|
||||
`"reasampler"` ext-state via the bridge; resolve the WAV audio path the M4
|
||||
project-relative way (shared convention with `persist`, not re-implemented).
|
||||
- [ ] Sample selection UI (minimal, in the `IPlugView` LICE editor or a
|
||||
parameters-only default view): choose which bank sample this instance plays.
|
||||
- [ ] Tier-0 playback: chromatic-from-root, basic polyphony, amp envelope,
|
||||
velocity→volume — plays in REAPER's routing/record/render path like any VSTi.
|
||||
|
||||
## S5 — Tier 1: "a keymap" (zoned multisamples, per-sample root notes)
|
||||
**Goal:** Multiple bank samples zoned across the keyboard (key ranges), each with its
|
||||
own root note — a captured *kit* (one-shots) or a *multisampled instrument* (same
|
||||
instrument sampled at several pitches) plays correctly. One sample per key-region.
|
||||
CONTEXT.md §Phase S (Tier 1). **Where the root-note + key-range seam fields earn
|
||||
their place.**
|
||||
**Verify (in DAW):** a keymap of several bank samples plays correctly zoned across
|
||||
the keyboard, each repitched from its own root note within its range; a captured kit
|
||||
and a multisampled instrument both play as expected; the keymap is authored in the
|
||||
instrument (performance map) while root notes come from the bank intrinsics (S2);
|
||||
editing the keymap does not touch the bank.
|
||||
**Depends on:** S4.
|
||||
|
||||
- [ ] Keymap editor in the `IPlugView` LICE editor: assign bank samples to key ranges
|
||||
(low/high note per sample), each with its own root note (from S2 intrinsics,
|
||||
overridable in the performance map).
|
||||
- [ ] Tier-1 playback: zoned resolution — a note picks its zone's sample and repitches
|
||||
from that sample's root note; one sample per key-region.
|
||||
- [ ] Performance-map persistence: the keymap (zones, per-sample assignment) is the
|
||||
instrument's own state — held in the instrument (read/written over the live
|
||||
`"reasampler"` seam per D-B's data-ownership split), never written back as a bank
|
||||
intrinsic.
|
||||
|
||||
## S6 — embedded TCP/MCP UI (D-D — scheduled in-phase, after the editor)
|
||||
**Goal:** Render a compact keymap/level strip **inline in the track/mixer control
|
||||
panel** via `reaper_plugin_fx_embed.h` (`IReaperUIEmbedInterface`) — the same
|
||||
Cockos surface REAPER's own embedded FX use — so the instrument draws inline, not only
|
||||
in its own window. Composes with the S1/S5 LICE editor path (same LICE-class drawing).
|
||||
**Scheduled, not deferred (D-D settled 2026-07-26):** a real later point, sequenced
|
||||
last because it is polish over a Tier-0 need — but on the roadmap. CONTEXT.md §Phase S
|
||||
(embedded UI, D-D).
|
||||
**Verify (in DAW):** the instrument draws a compact inline strip in the TCP/MCP (not
|
||||
only its own editor window); the inline surface reflects and (where offered) edits the
|
||||
keymap/levels; the embed lifecycle is clean (open/close/resize); the same LICE drawing
|
||||
as the main editor is reused.
|
||||
**Depends on:** S5 (composes over the existing LICE editor). **Must-verify before
|
||||
build:** the `IReaperUIEmbedInterface` contract + embed message/lifecycle against
|
||||
`vendor/reaper-sdk/sdk/reaper_plugin_fx_embed.h`.
|
||||
|
||||
- [ ] Implement `IReaperUIEmbedInterface` on the VST3; draw a compact keymap/level
|
||||
strip inline in the TCP/MCP using the same LICE surface as the editor.
|
||||
- [ ] Embed lifecycle (open/close/resize/hit-test inline) handled cleanly; reflects
|
||||
the live keymap/levels.
|
||||
|
||||
## Phase S — held and optional-forever (noted, not specified)
|
||||
- **Tier 2 — "expressive" (HELD).** Velocity layers, round-robin (anti-machine-gun),
|
||||
full ADSR, per-sample tuning/gain trim, sustain loops. The next depth increment once
|
||||
Tier 0–1 proves the instrument belongs — **its points are not drawn up here.**
|
||||
- **Tier 3 — "instrument polish" (optional-forever).** Filters, filter/pitch
|
||||
envelopes, LFOs, per-voice pan, choke groups, a modest FX slot. A direction to leave
|
||||
room for, never a commitment.
|
||||
|
||||
## Phase S — must-verify-before-build (carried from CONTEXT.md §Phase S)
|
||||
- **Steinberg VST3 SDK surface** — interface members, base-class overrides,
|
||||
factory-macro spellings, Windows module-export symbol names
|
||||
(`InitDll`/`ExitDll`/`GetPluginFactory`), and whether VSTGUI is bundled. Several are
|
||||
§1a experienced-estimates until S1 confirms them against the vendored SDK.
|
||||
- **VST-host bridge** — opcodes `0xdeadf00d` (resolve-by-name) / `0xdeadf00e` (host
|
||||
context) and the exact call marshalling, against `reaper_plugin.h` /
|
||||
`video_processor.h` / `reaper_plugin_functions.h`.
|
||||
- **`IReaperUIEmbedInterface`** — embed contract + message/lifecycle, against
|
||||
`reaper_plugin_fx_embed.h` (needed only at S6).
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
# MIDI playback — opportunity & design-space framing
|
||||
|
||||
Framing for a **MIDI-triggered audio sampler** that plays back ReaSampler's captured
|
||||
banks. This is a **discussion-shaping doc, not a build plan** — no phase, no PLAN.md
|
||||
points. Several forks that were open in prior drafts are now **locked** (see below);
|
||||
what remains open is re-presented in §4 so Daniel can make the residual calls before
|
||||
anything is scoped.
|
||||
banks. This began as a discussion-shaping doc; with all forks now settled it has become
|
||||
the **product framing behind a scoped phase**. Its build roadmap lives in **PLAN.md
|
||||
§Phase S** and its authoritative spec in **CONTEXT.md §Phase S** — this doc holds the
|
||||
*why* (the plugin-format reasoning, the bare-VST3-vs-JUCE assessment, the settled
|
||||
decision record).
|
||||
|
||||
Status: framed by product-designer (2026-07-26), **revised 2026-07-26 (r3)**. Prior
|
||||
revisions (a) established that a REAPER *extension* cannot be a MIDI instrument, and (b)
|
||||
corrected a material omission — REAPER's **VST-host bridge**, which lets a VST3 plugin
|
||||
*hosted inside REAPER* call back into REAPER's own API by resolving function pointers by
|
||||
name over the host callback. This revision folds in **Daniel's locked decisions** (D1,
|
||||
D5, D6 — see the box below), retires the now-settled forks in place (marked **DECIDED**,
|
||||
with the reasoning preserved, not deleted), and adds a substantive, honest assessment of
|
||||
**what complying with the VST3 surface actually costs without JUCE** (§1a). Grounded in
|
||||
Status: framed by product-designer (2026-07-26), **revised 2026-07-26 (r4)**. The
|
||||
"no PLAN.md footprint" era is **over** — with D-A through D-D settled (below), the
|
||||
instrument was scoped into **Phase S** (codename Daniel's: "S" for Sampler, because "D"
|
||||
collides with the existing Design View phase). **PLAN.md §Phase S is now the
|
||||
authoritative roadmap; CONTEXT.md §Phase S is the authoritative spec.** This doc is the
|
||||
framing/decision record they point back to. Prior revisions (a) established that a REAPER
|
||||
*extension* cannot be a MIDI instrument, (b) corrected a material omission — REAPER's
|
||||
**VST-host bridge**, which lets a VST3 plugin *hosted inside REAPER* call back into
|
||||
REAPER's own API by resolving function pointers by name over the host callback — (c) [r3]
|
||||
folded in **Daniel's locked decisions** (D1, D5, D6) and added the honest **bare-VST3
|
||||
assessment** (§1a). This revision [r4] records **Daniel's calls on the four residual
|
||||
forks D-A..D-D** (all settled 2026-07-26) and points to the now-live phase docs.
|
||||
Grounded in
|
||||
the vendored REAPER SDK headers (`vendor/reaper-sdk/sdk/reaper_plugin.h`,
|
||||
`reaper_plugin_functions.h`, `video_processor.h`, `reaper_plugin_fx_embed.h`), REAPER's
|
||||
published VST-extensions SDK page (`reaper.fm/sdk/vst/vst_ext.php`), and the Steinberg
|
||||
@@ -515,14 +521,27 @@ the thin shell. Confirmed clean for native, and — importantly — **invariant
|
||||
bare-vs-JUCE** (§1a): the shell choice doesn't touch the core. This is the most
|
||||
ReaSampler-native way to build it and it's assumed, not debated, going forward.
|
||||
|
||||
### Residual forks — Daniel's to call, in priority order
|
||||
### Residual forks — SETTLED (Daniel, 2026-07-26; reasoning preserved, not re-opened)
|
||||
|
||||
Four decisions remain live. Ordered by how much they gate the rest.
|
||||
All four residual decisions are now called. Each is marked **SETTLED** with Daniel's
|
||||
choice and the reasoning kept as the record of *why* — do not re-litigate. They are
|
||||
scoped into **PLAN.md §Phase S** / **CONTEXT.md §Phase S**.
|
||||
|
||||
**D-A (highest) — Bare Steinberg VST3 SDK vs. JUCE (with the UI-toolkit sub-question).**
|
||||
This is now *the* central fork, and §1a is the honest assessment feeding it. The
|
||||
sub-question — **who draws the editor?** — is really the whole fork, because §1a shows the
|
||||
audio-processing scaffolding is bounded either way.
|
||||
**D-A — SETTLED: bare Steinberg VST3 SDK + LICE editor (no JUCE).** *(The central fork.
|
||||
§1a is the assessment that fed it. The sub-question — who draws the editor? — was the
|
||||
whole fork, because §1a showed the audio-processing scaffolding is bounded either way.)*
|
||||
Daniel took **Option A**: bare Steinberg SDK, `SingleComponentEffect` for the bounded
|
||||
audio scaffolding, editor drawn in the **same LICE/SWELL stack `bank_panel` already
|
||||
uses** — no JUCE dependency, no AGPL-or-pay posture, house-consistent UI. The
|
||||
`IPlugView`↔LICE bridge (the one real unknown) is **not** a gate on the decision (the
|
||||
decision is made) but remains the right *first implementation step*: it is scheduled as
|
||||
**Phase S's opening spike (S1)** to convert §1a's experienced-estimates — Windows
|
||||
module-export names, factory-macro spellings, exact bridge marshalling — into verified
|
||||
fact before the engine build leans on them. (The stale §1a "is VSTGUI even bundled"
|
||||
question is dropped as moot under Option A; VSTGUI remains a noted fallback rung only if
|
||||
the LICE bridge proves gnarlier than the panel work suggests, with JUCE the last resort
|
||||
behind that.) The full option analysis (A/B/C, the JUCE license posture, the LICE-bridge
|
||||
caveat) is preserved below as the record.
|
||||
|
||||
- *Option A — bare Steinberg SDK + LICE editor.* Take only the VST3 SDK (permissive,
|
||||
royalty-free, no revenue gate). Use `SingleComponentEffect` for the ~200–400 lines of
|
||||
@@ -552,58 +571,66 @@ conceding to a full framework. **Concrete ask: greenlight a small `IPlugView`↔
|
||||
before committing** — it's the one unknown that decides A vs. B, and §1a's LICE-bridge and
|
||||
Windows-export-name claims are experienced estimates that a spike would convert to fact.
|
||||
|
||||
**D-B — The seam fields: what becomes a bank intrinsic (§2, old D4).** Where the mapping
|
||||
lives: (i) bank-owned map, (ii) instrument-owned map, or (iii) split — file-*intrinsics*
|
||||
(root note, loop points) in the bank; *performance-map* (zones, velocity layers,
|
||||
round-robin, envelopes) in the instrument. *My lean, unchanged:* **(iii) split.** It
|
||||
mirrors ReaSampler's capture/placement instinct — root note and loop points are *facts
|
||||
about the captured file* (like sample rate, length, peaks) and belong in the bank;
|
||||
zones/layers/envelopes are a *performance choice* and belong in the instrument. It keeps
|
||||
the bank a clean library and makes the bank-index change **small and additive** (root note
|
||||
+ loop points — the same shape as adding `provenance` in M1). The live-state seam (§2)
|
||||
means the instrument reads even the performance-map out of shared `"reasampler"` state, so
|
||||
"who owns which field" is a data-ownership decision, not a transport one. *The one thing to
|
||||
settle early:* whether root note / loop points get added to `Sample` **now** (so capture
|
||||
starts populating them and no backfill cliff opens later) or deferred — this is the "design
|
||||
the seam even if you defer the feature" instinct, and it's cheap to add the fields early.
|
||||
**D-B — SETTLED: split seam (option iii), and the intrinsic fields are added NOW.**
|
||||
*(The mapping-ownership fork, old D4. Options were: (i) bank-owned map, (ii)
|
||||
instrument-owned map, or (iii) split.)* Daniel took **(iii) split** — and, critically,
|
||||
called that the `Sample` intrinsic fields land **now**, not deferred. **Root note + loop
|
||||
points become bank intrinsics on `Sample`** (facts about the captured file, like sample
|
||||
rate/length/peaks) — a small additive change, the same shape as adding `provenance` in
|
||||
M1. **Zones, velocity layers, round-robin, envelopes are the instrument's performance
|
||||
map** (a creative arrangement, not a file fact). The live-state seam (§2) means the
|
||||
instrument reads even the performance-map out of shared `"reasampler"` state, so "who owns
|
||||
which field" is a data-ownership decision, not a transport one. Adding the fields now
|
||||
closes the **backfill cliff** (the *design-the-seam-even-if-you-defer-the-feature*
|
||||
instinct, same as Fork R-D's owned-file manifest): every sample captured before the
|
||||
fields exist would otherwise lack a root note / loop points and need hand-backfilling. The
|
||||
field addition touches the **extension** codebase, is independently shippable, and is
|
||||
scheduled as an **early Phase S point (S2)** ahead of the instrument that consumes it.
|
||||
|
||||
**D-C — Tier scope to commit to first (§3).** Tier 0 ("the bank plays" — one sample,
|
||||
chromatic, amp envelope, velocity→volume) / Tier 1 ("a keymap" — zoned multisamples with
|
||||
per-sample root notes) / Tier 2 ("expressive" — velocity layers, round-robin, ADSR, loops)
|
||||
/ Tier 3 ("polish" — filters, LFOs, choke groups). *My lean:* **scope Tier 0–1 first** as
|
||||
the "does this belong in ReaSampler's world" proof; hold Tier 2 as the "becomes a tool
|
||||
people reach for" follow-on; treat Tier 3 as optional-forever. Don't let a Tier-3 feature
|
||||
list inflate the D-A decision — the editor and DSP needs of Tier 0–1 are modest, which
|
||||
further weakens the "need JUCE" pull.
|
||||
**D-C — SETTLED: Tier 0–1 scoped now; Tier 2 held; Tier 3 optional-forever.** *(The
|
||||
tier-scope fork, §3. Tiers: 0 "the bank plays" / 1 "a keymap" / 2 "expressive" / 3
|
||||
"polish".)* Daniel took the recommended scope: **Tier 0** ("the bank plays" — one sample,
|
||||
chromatic, amp envelope, velocity→volume) then **Tier 1** ("a keymap" — zoned multisamples
|
||||
with per-sample root notes) are the committed scope and the "does this belong in
|
||||
ReaSampler's world" proof (Phase S points S4/S5). **Tier 2** ("expressive" — velocity
|
||||
layers, round-robin, ADSR, loops) is held as an explicit follow-on — **noted, not
|
||||
specified** (its points are not drawn up). **Tier 3** ("polish" — filters, LFOs, choke
|
||||
groups) is optional-forever. The Tier-0–1 editor and DSP needs are modest, which is part of
|
||||
why the D-A "no JUCE" call is comfortable.
|
||||
|
||||
**D-D (deferred by default) — Embedded TCP/MCP UI.** A REAPER-hosted VST3 can draw its own
|
||||
UI *inline in the track/mixer control panel* (`reaper_plugin_fx_embed.h`: VST3 implements
|
||||
`IReaperUIEmbedInterface`; the same Cockos surface REAPER's own JS/embedded FX use). A
|
||||
native ReaSampler instrument could render a compact keymap/level strip inline in the
|
||||
TCP/MCP, not only in its own window — and since this uses the same LICE-class drawing as
|
||||
Option A above, it composes naturally with the bare-SDK-plus-LICE path. **Pure polish, not
|
||||
a Tier-0 need — deferred by default.** Flagged only because it's a lever that exists on
|
||||
this exact (native-in-REAPER, LICE-drawing) path and nowhere else, so if "deeply woven into
|
||||
REAPER's UI" ever becomes the vision, the groundwork is already the groundwork.
|
||||
**D-D — SETTLED: embedded TCP/MCP UI is SCHEDULED (not deferred).** *(The
|
||||
inline-in-REAPER-UI lever.)* A REAPER-hosted VST3 can draw its own UI *inline in the
|
||||
track/mixer control panel* (`reaper_plugin_fx_embed.h`: VST3 implements
|
||||
`IReaperUIEmbedInterface`; the same Cockos surface REAPER's own JS/embedded FX use) — a
|
||||
compact keymap/level strip inline in the TCP/MCP, not only in its own window. Because it
|
||||
uses the **same LICE-class drawing as the D-A editor path**, it composes naturally with the
|
||||
bare-SDK-plus-LICE build. **Daniel's call: schedule this, don't defer it** — it is a real,
|
||||
in-phase later point on the Phase S roadmap (**S6**), sequenced *after* the main
|
||||
`IPlugView` editor exists (it composes with that LICE path), not a someday-note. It is
|
||||
polish rather than a Tier-0 need, so it sequences last — but it is on the roadmap.
|
||||
|
||||
---
|
||||
|
||||
## What this doc is asking for
|
||||
## Where this landed
|
||||
|
||||
With D1/D5/D6 locked, the decision this doc now puts to Daniel is a single ordered set:
|
||||
With D1/D5/D6 locked and **D-A..D-D all settled (2026-07-26)**, the instrument is scoped
|
||||
into **Phase S** — a native VST3 sampler as a **second build artifact** alongside the
|
||||
`reaper_reasampler` extension. The settled set:
|
||||
|
||||
1. **D-A — bare Steinberg SDK + LICE editor, or JUCE?** (My lean: bare SDK + LICE, gated on
|
||||
a small `IPlugView`↔LICE spike. This is the one that decides the instrument's whole
|
||||
build shape and dependency posture.)
|
||||
2. **D-B — split seam, and do root note / loop points get added to `Sample` now?** (My
|
||||
lean: yes, split; add the intrinsic fields early to avoid a backfill cliff.)
|
||||
3. **D-C — commit Tier 0–1 first?** (My lean: yes; hold 2, defer 3.)
|
||||
4. **D-D — embedded TCP/MCP UI:** deferred by default; note only.
|
||||
1. **D-A → bare Steinberg VST3 SDK + LICE editor** (no JUCE). The `IPlugView`↔LICE bridge
|
||||
is the opening implementation spike (**S1**), not a decision gate.
|
||||
2. **D-B → split seam; root note + loop points added to `Sample` now** (**S2**, in the
|
||||
extension) to close the backfill cliff.
|
||||
3. **D-C → Tier 0–1 committed** (**S4/S5**); Tier 2 held (noted, not specified); Tier 3
|
||||
optional-forever.
|
||||
4. **D-D → embedded TCP/MCP UI scheduled** (**S6**), after the main editor exists — on the
|
||||
roadmap, not deferred.
|
||||
|
||||
Once D-A and D-B are called, this becomes concrete enough to write an actual phase spec
|
||||
(the instrument as its own artifact, with the pure sampler core as a testable module
|
||||
mirroring `bank_model`). Until then it stays a framing doc with **no PLAN.md footprint** —
|
||||
deliberately, so nothing is scoped until the build shape (D-A) is chosen.
|
||||
**Authoritative from here:** **PLAN.md §Phase S** is the roadmap (S1–S6, sequenced by
|
||||
dependency order: spike → `Sample` fields → pure sampler core → Tier 0 → Tier 1 → embedded
|
||||
UI); **CONTEXT.md §Phase S** is the spec (seam-field semantics, scope contracts, the
|
||||
pure/shell split in the new artifact, the must-verify SDK/bridge surfaces). This doc is the
|
||||
framing/decision record they point back to. The "no PLAN.md footprint" era is over.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user