A VST hosted in REAPER can call back into REAPER's own API (name-resolved via audioMaster), so a native ReaSampler instrument reads live project ext-state directly. Re-frames the seam, D6, and the D1/D2 calculus.
30 KiB
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, no settled forks yet. It exists so Daniel and the-boss can react to an honest map of the option space before anything is scoped.
Status: framed by product-designer (2026-07-26), revised 2026-07-26 to correct a
material omission — the earlier draft missed REAPER's VST-host bridge: a VST/VST3
plugin hosted inside REAPER can call back into REAPER's own API from within the
plugin, resolving API function pointers by name over the host audioMaster callback.
That single fact changes the integration seam (§2), the "one product or two" question
(D6), and the JSFX-vs-native calculus (D1/D2). The corrections are made plainly
in-place, not appended as a footnote; where the prior draft was too pessimistic about
what a native instrument can reach, it is fixed and the correction is flagged. 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) and
REAPER's published VST-extensions SDK page (reaper.fm/sdk/vst/vst_ext.php) — the
plugin-format claims below are checked against those, not asserted. The genuine forks
are flagged as Daniel's to decide; nothing here pre-decides them.
The bridge, stated once, up front (the correction). A hosted VST is not limited to scraping bank WAVs + JSON off disk. REAPER hands the plugin its host callback; calling
hostcb(&effect, 0xdeadbeef, 0xdeadf00d, 0, "FunctionName", 0.0)resolves any REAPER API function by name — the same string-keyed API surface the extension uses (verified:video_processor.hline 44 importsvideo_CreateVideoProcessorexactly this way;GetProjExtState/SetProjExtState/EnumProjExtStateare ordinary entries in that same string-keyed table,reaper_plugin_functions.hlines 8376/8796/9969). The plugin can also fetch its host context — the track/take/project it's instantiated in (sibling opcode0xdeadf00e,video_processor.hline 40; CLAP hasclap_get_reaper_context,reaper_plugin.hline 142). Consequence: a native ReaSampler instrument can read the same"reasampler"project ext-state thatpersistwrites — live, project-aware, following the active project — not a file it re-parses off disk. This is a REAPER-VST-specific capability (it exists because the plugin is hosted in REAPER), and it is the thumb on the scale the prior draft failed to weigh. The cost of leaning on it is stated honestly in D1/D2: it makes the native plugin REAPER-coupled.
The need, stated plainly
ReaSampler today captures and organizes audio into per-project banks (on-disk
32-bit float WAVs + a JSON index, multi-bank via bank_book). It has no way to
play those samples back from a MIDI keyboard. The panel can audition a sample on
click — but a one-shot, one-voice audition is not an instrument. There is no
key→sample mapping, no velocity response, no polyphony, no envelope, no round-robin.
The ask: what would let me play my captured banks MIDI-triggered, and what does the REAPER surface actually allow toward that?
The rest of this doc is organized as the task framed it: (1) the plugin-format reality, honestly; (2) the integration seam between capture and playback; (3) product shape and scope tiers; (4) the forks that are Daniel's to call.
1. The plugin-format reality (the honest part)
The load-bearing fact: a REAPER extension cannot be a MIDI-triggered instrument.
This is not a limitation we can engineer around by trying harder — it is what the
plugin format is. ReaSampler today is a reaper_*.dll loaded at startup via the
extension API (ReaperPluginEntry). That format gives an extension enormous reach
over the project — tracks, items, actions, rendering, project state — but it is
not a node in any track's audio signal chain. An instrument track's "read live
MIDI, emit audio per-voice" contract belongs to VST/VST3/CLAP/JSFX plugins, which
REAPER hosts through an entirely different mechanism.
What the extension SDK does expose near audio/MIDI, verified in the headers, and why none of it is "be an instrument":
-
Audio_RegHardwareHook+audio_hook_register_t::OnAudioBuffer(reaper_plugin.h~1228). This is a real-time callback into the audio thread, called pre- and post-REAPER-processing, handing youGetBuffer(isOutput, idx)for the hardware I/O channels. It is a hardware tap, not a track insert. You could technically mix sample playback into the hardware output buffer here — but you'd be bypassing REAPER's entire routing, metering, recording, and rendering path. Output from this hook cannot be recorded, rendered, routed, or FX-processed as track audio. It is the wrong tool for an instrument by construction, and it runs in the audio thread with all the real-time-safety hazards that implies. Rejected on sight for this purpose — noting it only to be complete. -
kbd_OnMidiEvent/kbd_OnMidiList(reaper_plugin_functions.h~4138). REAPER informs the extension of MIDI events (for action-triggering — this is how MIDI can fire the capture/insert actions we already plan to bind). It is notification, not a routable MIDI input feeding a synth voice allocator. Great for "MIDI note fires the capture-to-slot action"; useless as an instrument's note input. -
PlayPreview/PlayTrackPreview+preview_register_t(reaper_plugin.h~1302). This is REAPER's preview-playback mechanism, and ReaSampler already uses it —bank_panel.cppstreams a bank WAV through a caller-ownedpreview_register_tfor click-audition (PCM_Source_CreateFromFile+PlayPreview). This is the one audio-emitting surface the extension already drives, and it is load-bearing for the hybrid option below. But note its shape: it plays a source at a position with a volume — it is a fire-and-forget preview, not a polyphonic, per-note, velocity-scaled, envelope-shaped voice engine. You do not get MIDI→voice allocation for free; you'd be building a sampler engine on top of a preview primitive. -
pcmsrcregistration +PCM_sourcesubclassing (reaper_plugin.h~804, thePCM_SOURCE_EXT_*surface). An extension can register a customPCM_sourcetype. This is the surface REAPER itself and extensions like SWS use for custom media. It is a real seam — but it produces a media source (something an item plays), not a live-MIDI instrument. Interesting for "a bank-backed media source" but not the keyboard-instrument ask.
Conclusion, stated for the record: to get a genuine MIDI-triggered instrument — play a note, hear the mapped sample, velocity-scaled, polyphonic, with an envelope — the playback engine must be a standard instrument plugin (VST3 / CLAP / JSFX), not the extension. The extension SDK is the wrong format for that job, and this is the single most important thing for Daniel to internalize before scoping anything.
The three honest options
Option A — a real VSTi/instrument plugin (JUCE or bare VST3/CLAP SDK) that reads ReaSampler's banks. A separate build artifact: a VST3 (and/or CLAP) sampler plugin that the user instantiates on an instrument track. It maps samples across the keyboard and plays them MIDI-triggered with a real voice engine. This is the "sophisticated sampler" answer.
- Gives you: everything an instrument is — polyphony, velocity, envelopes, the works,
fully integrated into REAPER's routing/render/record path like any VSTi. And,
because it's hosted in REAPER, it reaches ReaSampler's project state directly via the
VST-host bridge — it doesn't merely parse bank JSON off disk, it can read the live
"reasampler"ext-statepersistwrites, know its own host project, and follow the active project. The prior draft treated Option A as a divorced file-reader; that was the omission. A native ReaSampler instrument can be natively, tightly integrated with the extension's project state, not loosely coupled through a file format alone. - Costs: a second codebase in a second plugin format, almost certainly a new dependency (JUCE is the pragmatic choice; bare VST3 SDK is more code, CLAP is leaner but younger). It is a real DSP/voice-engine build, not a weekend. Cross-platform DSP, its own build/release/signing story, its own UI toolkit. This is a product-sized commitment, not a feature. And leaning on the bridge for state means the instrument is REAPER-coupled — it stops being a portable VST that runs in any host and becomes a REAPER companion. That may be exactly right (see D6), but it is a real narrowing and must be a conscious choice, not a side effect.
Option B — a JSFX sampler. JSFX is REAPER's built-in scriptable plugin format
(text .jsfx files, JIT-compiled by REAPER, hostable as an instrument). A JSFX
instrument can receive live MIDI and emit audio on a track. It can load samples
(Xen-style file reads / the JSFX file/serialize API) and play them back.
- Gives you: a real in-track instrument with zero new binary, zero new SDK, zero JUCE — ships as a text file alongside the extension, cross-platform for free (REAPER runs the JIT everywhere it runs).
- Costs: JSFX is a constrained DSP scripting language, not C++. A polyphonic multisample engine with round-robin/velocity-layers/streaming is doable but you're writing DSP in JSFX's idiom, and large-sample streaming / disk I/O is more awkward than in a native plugin. Reading ReaSampler's JSON index from JSFX is friction (JSFX is not a general-purpose file parser). Best fit for a minimal-to-mid sampler, a real ceiling for a sophisticated one.
- What JSFX can't match, now that the bridge is on the table: the VST-host bridge is a
VST/VST3/CLAP capability — it is how a hosted plugin resolves REAPER API pointers.
JSFX is not a hosted VST; it has REAPER integration of its own kind (
ext_noinit,file_*ops, the sharedgmem[],slider/parameter plumbing), but it does not get the same string-keyed REAPER-API surface a native VST does, and in particular has no clean, native path to readpersist's"reasampler"project ext-state the way a native VST can. On the JSFX path, the seam is genuinely "read a file ReaSampler wrote next to the bank" (hence the sidecar note in §2). This asymmetry is new information: the bridge makes native meaningfully more integratable than the prior draft assumed — it's not just "native is more powerful DSP," it's "native can be part of ReaSampler's state model, JSFX stays a file-coupled consumer."
Option C — the hybrid (recommended framing to explore first). Keep the extension as the sole owner of capture + organization (which is its whole existing identity and the load-bearing "capture and placement are separate acts" principle). Add a separate instrument (Option A or B) that consumes the banks as a shared-artifact contract. The extension never becomes an instrument; the instrument never captures. Each does what its format is good at. This is the honest shape of the whole thing — the two options above are really "which instrument technology" within the hybrid, because the extension is staying regardless.
The real fork, then, is not "extension vs. plugin" (the extension stays either way) — it is "which instrument format consumes the banks: JSFX or native VSTi/CLAP," and "how much sampler do we actually want." Those are §3 and §4.
2. The integration seam
The prior draft assumed one seam: the bank folder (project-relative WAVs) + the bank/index JSON on disk, with the playback engine as a file-reader. That's still the seam for sample audio (WAVs are files; there's no getting a live PCM stream across the bridge, nor would you want to). But for everything that isn't the raw audio — the index, the mapping data, which project's bank is active — the bridge opens a second, richer seam the prior draft missed:
- File seam (audio + a portable snapshot). WAVs on disk; index/mapping as JSON or a sidecar. Tool-agnostic, host-agnostic, portable. Any instrument (JSFX or native, in REAPER or any other host) can consume it. This is the only seam JSFX gets, and the fallback/export seam for native.
- Live-state seam (native-in-REAPER only). A native VST instance reads the
"reasampler"project ext-state directly via the bridge (GetProjExtState/EnumProjExtState, resolved by name overhostcb), and knows its own host project via the context callback. It sees whatpersistlast wrote, follows the active project, and needs no "point me at the right bank folder" wiring — it asks REAPER which project it's in. This is strictly more than the file seam and it's REAPER-coupled by construction.
The design question below — "is the JSON sufficient, or does playback need mapping the index doesn't carry" — is unchanged. What the bridge changes is where that mapping can live and how the instrument gets it: for native, the mapping can be live shared state between extension and instrument, not just a file handed across.
What the current index carries (from bank_model's Sample, per CONTEXT.md §Data
model): id, display name, relative path, source range, channel count, sample rate,
length, capture tempo, an optional key, peak/RMS/LUFS, content hash, tier,
provenance, timestamp. Notably it already has an optional key field and capture
tempo — the seeds of pitch-mapping are there.
What a playback engine additionally needs — none of which the index carries today:
- Root note (the pitch the sample was recorded at, so it can be repitched across
the keyboard). The optional
keyis close but is "musical key," not "root MIDI note" — different thing. - Key range / zone (low note, high note) — which keys trigger this sample.
- Velocity layers (which sample plays at which velocity band; a mapping of samples to velocity zones).
- Round-robin groups (cycle through N samples on repeated same-note hits).
- Loop points (sustain loop start/end for held notes; sample-accurate, zero-crossing-aware — CONTEXT already flags loop/zero-crossing handling as day-one-relevant for wavetable material).
- Amplitude envelope (ADSR) and optionally filter/pitch envelopes.
- Tuning/gain trim per sample.
The design decision this forces: does the mapping (key ranges, velocity layers, round-robin, envelopes) live —
- (i) in the bank index (ReaSampler owns the instrument definition; the plugin is a dumb player of a ReaSampler-authored map), or
- (ii) in the instrument plugin's own state (the plugin owns the map; the bank is just a WAV+metadata pool it maps over), or
- (iii) split — the bank carries per-sample intrinsics (root note, loop points — facts about the file), and the instrument carries the arrangement (zones, layers, envelopes — a performance choice)?
(iii) is the principled answer and worth leading with. It mirrors ReaSampler's own
deepest instinct — the capture/placement separation. Root note and loop points are
facts about the captured file (analogous to sample rate, length, peaks) and belong
in the bank, computed or set at/after capture. Key zones, velocity layers, envelopes,
round-robin are a performance mapping — a creative arrangement of those facts — and
belong in the instrument. This keeps the bank a clean, portable, tool-agnostic library
(a bank is still just WAVs + facts, readable by anything) while the instrument owns the
opinionated part. It also means the bank index grows only by file-intrinsic fields
(root note, loop points), which is a small, safe, additive change — exactly the kind of
seam ReaSampler already knows how to add (mirror of how provenance was added as a
field in M1 and populated later).
A note on format (revised for the bridge): the friction is asymmetric, and more so than the prior draft said.
- Native (Option A): doesn't just "read the bank JSON off disk" — it can read the map
out of live project ext-state via the bridge (whichever of i/ii/iii you choose, the
extension and instrument can share the same
"reasampler"state rather than one writing a file the other re-parses). The.wavaudio is still files; the mapping need not be. Cleanest possible integration. - JSFX (Option B): no bridge — reading arbitrary JSON is friction, so the seam likely
needs a simpler sidecar (a flat
.txt/key-value map ReaSampler writes next to the bank, JSFX-parseable). On the JSFX path the seam is unavoidably file-based. That's a concrete cost of JSFX and a reason the seam design and the format choice stay coupled.
Portability caveat (partly dissolved on the native path). The bank is project-relative
and travels with the .rpp (M4 machinery). Any file-seam consumer must resolve paths
the same way — so a JSFX instrument still needs to be told the current project bank folder
(saved in its own state), a real wiring detail, don't hand-wave it. But a native
instance sidesteps this: via the context callback it asks REAPER which project it's in,
then reads that project's bank location straight from ext-state — no "point me at the
folder" step. What was a shared wiring cost is now a cost only on the JSFX path. Note
the flip side: a native instrument that resolves its bank only through the bridge won't
work outside REAPER at all — so if a portable/exportable instrument is ever a goal, keep
the file seam as the source of truth and treat the live-state seam as an accelerator, not
the sole path.
3. Product shape & scope tiers
Sketched as tiers, minimal → sophisticated. The point of tiering is that Tier 0 delivers the core promise ("play my captured banks from a MIDI keyboard") and each tier above is optional depth, not a prerequisite for the one below.
Tier 0 — "the bank plays." One sample mapped chromatically across the keyboard from a root note; monophonic-or-basic-polyphony; a simple amp envelope; velocity → volume. Point one bank sample at a MIDI track and play it repitched. This is the smallest thing that delivers the promise and is the honest MVP. On the JSFX path this is a genuinely small build; on the native path it's the skeleton of the plugin.
Tier 1 — "a keymap." Multiple samples zoned across the keyboard (key ranges), each with its own root note. Now a captured kit (several one-shots) or a multisampled instrument (same instrument sampled at several pitches) plays correctly. This is where the root-note + key-range seam fields (§2) earn their place. Still one sample per key-region.
Tier 2 — "expressive." Velocity layers (soft vs. hard hits pick different samples), round-robin (repeated hits cycle samples — the anti-machine-gun feature), full ADSR, per-sample tuning/gain trim, sustain loops for held notes. This is a real sampler and where "sophisticated" lives. It's also where the mapping data (§2) gets rich enough that the "who owns the map" fork (i/ii/iii) really bites.
Tier 3 — "instrument polish." Filter + filter envelope, LFOs, per-voice pan, choke groups (hi-hat open/closed), maybe a modest built-in FX slot. Standard sampler feature creep. Explicitly not needed to deliver value; a direction to leave room for, not commit to.
The recommended reading: Tier 0–1 is the "does this even belong in ReaSampler's world" proof. If the answer is yes, Tier 2 is where it becomes a tool people reach for. Tier 3 is optional forever. Scope the decision at Tier 0–1 first and treat 2/3 as held futures — don't let a Tier-3 feature list drive the format choice.
The one-source-multiple-views angle
Worth flagging because it recurs in Daniel's work: the bank is one source (WAVs + facts). The panel is one view (organize/audition). A MIDI instrument is another view of the same source (play). That's a clean framing — the instrument doesn't fork the bank, it consumes it — and it argues for keeping the bank as the single authoritative artifact and the instrument as a pure consumer (seam option ii/iii, never "the instrument keeps its own copy of the samples").
4. Risks & open decisions — Daniel's to call
None of these are pre-decided here. Each is a genuine fork.
D1 — Instrument format: JSFX vs. native VSTi (VST3/CLAP, likely via JUCE). The
central fork. JSFX = no new binary, no JUCE, cross-platform free, real ceiling on
sophistication, awkward bank-JSON reading, and no VST-host bridge (file-seam only).
Native = full sophistication, clean integration, plus the bridge: live "reasampler"
ext-state, project-awareness, embedded TCP/MCP UI (see below) — at the cost of a whole
second codebase in a second format with its own build/release/dependency/signing story,
and REAPER-coupling if it leans on the bridge.
What the bridge does to the native column (the honest update). The prior draft's native pitch was "more DSP power + parses JSON cleanly." The bridge adds a category the prior draft didn't weigh: native can be part of ReaSampler's live state model, not a file-coupled consumer. That is a real pull toward native beyond raw DSP — it's the difference between "an instrument that reads a ReaSampler export" and "an instrument that is ReaSampler's playback surface." It doesn't make JSFX wrong; it makes native's ceiling higher and its integration tighter than I credited. Weigh it against the coupling cost: a bridge-dependent VST is a REAPER-only artifact, no longer a portable VST.
My lean, updated but unchanged in direction, for discussion only: if the goal is Tier 0–1, still prototype in JSFX first — it proves the sampler value and the key/velocity seam with near-zero format commitment, and the file seam you design there is exactly the portable/export seam a native build would keep anyway. The bridge is an integration advantage, not a does-the-sampler-work advantage, so it doesn't change what the cheapest proof is. Reach for native when either (a) Tier 2+ sophistication is a firm goal, or (b) tight live integration with ReaSampler's project state is itself the point — that second trigger is new, and it's the bridge's real contribution to this fork.
D2 — Whether to take a JUCE (or any external plugin-SDK) dependency at all. The project today is a clean C++ extension with two vendored submodules and a proud pure-core/shell discipline. A native instrument means a third major dependency and a second build target of a fundamentally different kind. That's a real architectural weight. JSFX sidesteps it entirely. Flagging it as its own decision because "should we depend on JUCE" is a bigger standing commitment than "should we build a sampler."
The bridge's bearing on D2. Weigh two coupling costs, not one. A native instrument that
uses the bridge takes on both a plugin-SDK dependency (JUCE/VST3/CLAP) and a
REAPER-API coupling (the same reaper_plugin_functions.h surface the extension binds — so
the instrument would vendor and bind against it too, likely resolving pointers via hostcb
rather than the extension's startup path). The upside is that the pure sampler core stays
REAPER-free and testable regardless (D3); it's only the shell that touches the bridge, so
the coupling is contained where the project already puts REAPER coupling. The downside is
plain: choosing the bridge means choosing a REAPER-only instrument. If Daniel wants a
sampler that also runs in other hosts, the bridge is off the table and D1's native pitch
loses its integration edge — collapsing back toward "native = more DSP power only."
D3 — Does the pure-core discipline survive the format boundary? ReaSampler's
identity is pure REAPER-free testable core + thin shells. A sampler's voice engine,
envelope math, key/velocity mapping, and repitch logic are exactly the kind of pure,
testable core this project excels at — they could live in a REAPER-free, DAW-free,
unit-tested module (mirror of bank_model/peaks/view_mode_model) with the plugin
format (JSFX or VST3 wrapper) as the thin shell around it. This is the most
ReaSampler-native way to build it and I'd argue strongly for it regardless of D1: the
sampler DSP core is pure and tested; the format is a shell. The open question is whether
that discipline can hold across a different plugin format — with native it's clean C++
so yes; with JSFX the "pure core" would be JSFX code, harder to unit-test in the CTest
harness. That tension is real and feeds back into D1.
D4 — Where the mapping lives (seam fork i/ii/iii from §2). Bank-owned map, instrument-owned map, or split (file-intrinsics in the bank, performance-map in the instrument). My lean: (iii) split — it's the one that honors ReaSampler's capture/placement instinct and keeps the bank tool-agnostic. But it's Daniel's call whether ReaSampler should author instrument definitions at all, or stay purely a sample library that a separate mapping tool arranges.
D5 — Cross-platform. The extension is already cross-platform (SWELL). JSFX inherits that for free. A native plugin re-opens the full cross-platform DSP + UI + build matrix (Win/mac/Linux, code-signing on mac, etc.) as a separate artifact. A cost that lands entirely on the native path.
D6 — Is this even one product? The reframe worth surfacing: ReaSampler's thesis is "a precision capture/organize tool; capture and placement are separate acts." A MIDI-playback instrument is a different act — playback. There's a legitimate reading where the instrument is a companion product that shares the bank format, not a feature of ReaSampler — the way a sample library and a sampler that reads it are related-but-distinct products. That framing might keep ReaSampler sharp (it stays the capture tool it is) while letting the instrument evolve on its own clock and format. The alternative reading — it's all one integrated sampler-workstation — is also coherent.
Re-argued with the bridge on the scale (the prior draft weighed this fork without it).
The prior draft implicitly assumed the two products could only ever share a file
format — which makes "companion product" feel clean and natural, because a shared file
is exactly what independent products share. The bridge changes the terms. A native
instrument can be tightly, live-integrated with ReaSampler's project state: same
"reasampler" ext-state, same project identity, following the active project — a degree
of coupling that is more than "two products that happen to read the same files" and
starts to look like "two faces of one tool sharing one state model." That is a genuine
thumb toward the one-integrated-product reading. It's not decisive, and I won't
pretend it is:
- It only applies if the instrument is native (JSFX can't reach the bridge, so a JSFX instrument is naturally a file-coupled companion — the format choice and this fork are entangled: pick JSFX and you've half-answered D6 toward "companion").
- It's an available integration, not a required one — a native instrument could still be built to the file seam only, deliberately staying a loose companion for portability. The bridge widens the option, it doesn't force it.
- The tight-integration reading buys REAPER-coupling; the companion reading buys portability and independent evolution. Those are real, opposed goods, and the bridge doesn't resolve which Daniel wants — it just makes the integrated option materially more capable than the prior draft credited, so the companion reading no longer wins by default on "well, they can only share a file anyway."
Net: the bridge strengthens "one integrated product" from a coherent-but-equal option into the one with the stronger technical affordance — if native-in-REAPER is the path. It does not pre-decide the fork. This is still the highest-order question and it's Daniel's to answer, but he should answer it knowing the integrated option is richer than the file-sharing picture implied — and knowing that choosing JSFX quietly tilts it the other way.
D7 — Embedded TCP/MCP UI (new option the bridge surfaces; native/CLAP only). While
verifying the bridge I found a second REAPER-VST-specific affordance the prior draft never
mentioned: a hosted plugin can draw its own embedded UI directly in the track/mixer
control panel (reaper_plugin_fx_embed.h: VST2 answers canDo("hasCockosEmbeddedUI")
with 0xbeef0000 and draws via effVendorSpecific/effEditDraw; VST3 implements
IReaperUIEmbedInterface; CLAP exposes cockos.reaper_embedui). Concretely: a native
ReaSampler instrument could render a compact keymap/level strip inline in the TCP/MCP,
not only in its own plugin window — the same surface REAPER's own JS/embedded FX use. This
is native/CLAP-only (JSFX can't do it) and is pure polish, not a Tier-0 need — but it's
a real integration affordance that only exists on the native-in-REAPER path, and it
compounds the D1/D6 tilt: if "ReaSampler's playback surface, deeply woven into REAPER's UI"
is the vision, this is a lever only native reaches. Flagging it for completeness, not
recommending it — it's a Tier-3-ish nicety, deferred by default.
What this doc is asking for
A direction on the two highest-order forks, in order:
- D6 — one product or two? Is the instrument a feature of ReaSampler, or a companion product sharing the bank format? Everything else sits under this.
- D1 — JSFX-first prototype, or straight to native? Given a target of Tier 0–1 to start, and the pure-core-as-shell discipline (D3) held either way.
Once those two are called, the seam fields (§2, D4) and the tier scope (§3) become concrete enough to write an actual phase spec. Until then this stays a framing doc with no PLAN.md footprint — deliberately, so we don't scope an instrument before deciding whether we're building one.