docs(product): lock D1/D5/D6 for MIDI-playback instrument; add bare-VST3-vs-JUCE assessment
Native VST3, Windows-only, two-products-but-integrated. Retire the JSFX fork in place, add grounded assessment of the raw Steinberg VST3 surface without JUCE, and re-present the residual forks in priority order.
This commit is contained in:
+414
-237
@@ -2,22 +2,49 @@
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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
|
||||
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
|
||||
VST3 SDK documentation (portal + class reference — cited inline in §1a). Where a claim is
|
||||
**experienced estimate** rather than a **verified-from-source** fact, it is flagged as
|
||||
such in §1a. The genuine remaining forks are flagged as **Daniel's to decide**; nothing
|
||||
here pre-decides them.
|
||||
|
||||
> ## Locked decisions (SETTLED — do not re-present as open)
|
||||
>
|
||||
> Daniel has nailed these down. Everything downstream assumes them.
|
||||
>
|
||||
> - **D6 → DECIDED: two products, but integrated.** The instrument is a *separate*
|
||||
> product/artifact from the ReaSampler extension, but *tightly integrated* via the
|
||||
> VST-host bridge — it reads live `"reasampler"` project ext-state and is project-aware.
|
||||
> **Not** a divorced, file-only companion. (The old §4 D6 reasoning is preserved below,
|
||||
> marked decided.)
|
||||
> - **D1 → DECIDED: native VST3.** JSFX is off the table. The instrument is a native VST3
|
||||
> plugin. (The old JSFX option and its whole-doc entanglement are retired below, marked
|
||||
> decided; the JSFX reasoning is kept as the record of *why* it was considered and set
|
||||
> aside.)
|
||||
> - **D5 → DECIDED: no cross-platform, no multiformat. REAPER-specific, Windows-only.**
|
||||
> The existing extension is Windows-only; Daniel does not work on other platforms. So:
|
||||
> **no mac/Linux DSP/build/signing matrix, no CLAP-for-portability argument, no
|
||||
> "runs standalone in other hosts" concern.** REAPER-coupling via the bridge is fine and
|
||||
> intended. This collapses several costs the prior draft carried (§1a, §4).
|
||||
>
|
||||
> **What these lock-downs do to the shape:** the doc is no longer weighing "extension vs.
|
||||
> plugin," "JSFX vs. native," or "portable vs. coupled." It is weighing **how to build one
|
||||
> native, Windows-only, REAPER-coupled VST3 instrument** — and the single biggest live
|
||||
> question inside that is now **bare Steinberg VST3 SDK vs. JUCE** (§1a, D-A below).
|
||||
|
||||
> **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
|
||||
@@ -109,94 +136,239 @@ 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
|
||||
### The shape, now that D1/D6 are locked
|
||||
|
||||
**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-state `persist` writes, 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.
|
||||
The old three-way option set (native VSTi / JSFX / hybrid) has collapsed to a single
|
||||
resolved shape:
|
||||
|
||||
**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 shared `gmem[]`, `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 read `persist`'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."
|
||||
**A separate native VST3 instrument that consumes ReaSampler's banks, tightly integrated
|
||||
via the bridge.** The extension stays the sole owner of **capture + organization** (its
|
||||
whole existing identity and the load-bearing "capture and placement are separate acts"
|
||||
principle). The instrument is a *separate build artifact* — a native VST3 sampler the user
|
||||
instantiates on an instrument track — that maps samples across the keyboard and plays them
|
||||
MIDI-triggered with a real voice engine. It reaches ReaSampler's project state directly via
|
||||
the VST-host bridge: it reads the live `"reasampler"` ext-state `persist` writes, knows its
|
||||
own host project, and follows the active project. This is the "two products, but
|
||||
integrated" shape D6 locked in. The extension never becomes an instrument; the instrument
|
||||
never captures.
|
||||
|
||||
**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.
|
||||
*What this gives:* everything an instrument is — polyphony, velocity, envelopes, the works,
|
||||
fully in REAPER's routing/render/record path like any VSTi — **plus** live, project-aware
|
||||
integration with the extension's state, not a divorced file-reader.
|
||||
|
||||
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.
|
||||
*What it costs:* a **second codebase**, the Steinberg VST3 SDK (or JUCE) as a dependency,
|
||||
and a real DSP/voice-engine build — a product-sized commitment, not a feature. The
|
||||
Windows-only + VST3-only + REAPER-only lock-downs (D5) *remove* the costs the prior draft
|
||||
carried around cross-platform DSP, code-signing, and multi-format wrappers — a material
|
||||
simplification. The remaining cost question is almost entirely **"how much scaffolding does
|
||||
the VST3 surface demand, and do we take JUCE to get it"** — answered honestly in §1a.
|
||||
|
||||
> **JSFX — retired (D1 DECIDED: native VST3).** Prior drafts weighed a JSFX sampler
|
||||
> (REAPER's built-in scriptable format: zero new binary, zero SDK, cross-platform free) as
|
||||
> the cheap-prototype path. It is off the table. The reasons it lost, for the record: (1)
|
||||
> JSFX is a constrained DSP scripting language — a polyphonic multisample engine with
|
||||
> round-robin/velocity-layers/streaming is doable but fights the idiom, and large-sample
|
||||
> disk streaming is awkward; (2) JSFX gets **no VST-host bridge**, so it could never read
|
||||
> `persist`'s live `"reasampler"` ext-state — it would be a permanently file-coupled
|
||||
> consumer needing a sidecar seam, which is exactly the *loose* companion D6 rejected; (3)
|
||||
> its "pure core" would be JSFX code, outside the CTest harness (see D3). With D5 locking
|
||||
> Windows-only, JSFX's one real edge — cross-platform-for-free — is worth nothing here.
|
||||
> Native VST3 wins cleanly given the locks.
|
||||
|
||||
---
|
||||
|
||||
## 1a. How crazy is bare VST3 without JUCE? (the honest assessment)
|
||||
|
||||
Daniel's question, directly: *how crazy is it to comply with the VST3 surface without
|
||||
something like JUCE?* Short answer: **not crazy — the audio-processing side is a
|
||||
few-hundred-lines-of-ceremony-you-write-once problem, not a tar pit. The one genuine
|
||||
question is the editor UI, and ReaSampler is unusually well-positioned to answer it
|
||||
without JUCE.** The detail, honestly, with estimate-vs-verified flagged.
|
||||
|
||||
### What the raw Steinberg VST3 SDK actually demands
|
||||
|
||||
A working VST3 instrument must present these interfaces (all VST3 interfaces descend from
|
||||
`FUnknown`, a COM-like base with `queryInterface` / `addRef` / `release` — **verified**,
|
||||
Steinberg VST3 SDK class reference):
|
||||
|
||||
- **`IComponent`** — the plugin's identity and bus/state setup: `initialize`,
|
||||
`setActive`, `getBusCount` / `getBusInfo`, `activateBus`, `setState` / `getState`.
|
||||
- **`IAudioProcessor`** — the DSP contract: `setBusArrangements`, `setupProcessing`
|
||||
(`ProcessSetup`: sample rate, block size, symbolic sample size), `setProcessing`, and
|
||||
the hot path **`process(ProcessData&)`** — where you read MIDI events off the event
|
||||
input bus and write audio to the output bus. For an *instrument* you declare an **event
|
||||
input bus** (MIDI in) and an **audio output bus**, no audio input. (**Verified**:
|
||||
`IAudioProcessor` reference; the instrument bus topology is standard.)
|
||||
- **`IEditController`** — parameter model + editor: `getParameterCount` /
|
||||
`getParameterInfo`, `getState` / `setState`, `setComponentState`, normalized↔plain
|
||||
parameter conversion, and `createView("editor")` returning an `IPlugView` if you have a
|
||||
GUI.
|
||||
- **`IPluginFactory`** (via the module's exported **`GetPluginFactory`**) — enumerates the
|
||||
classes the module offers (the processor and, in the two-component model, the
|
||||
controller), keyed by class UIDs. On Windows the module also exports **`InitDll` /
|
||||
`ExitDll`** (bundle entry points differ per-OS, but D5 makes Windows the only target, so
|
||||
it's just these two plus `GetPluginFactory`). (**Experienced estimate** on the exact
|
||||
Windows export names — the SDK's `dllmain.cpp` / `public.sdk` main glue provides these;
|
||||
I have not re-read the header this session, so treat the exact symbol names as
|
||||
to-verify-against-`public.sdk/source/main/` before build, not as a load-bearing claim.)
|
||||
|
||||
**The COM plumbing is real but bounded.** `queryInterface`/`addRef`/`release` plus the
|
||||
class-factory macros (`BEGIN_FACTORY` / `DEF_CLASS2` / `END_FACTORY`, and the
|
||||
`DECLARE_FUNKNOWN_METHODS` / `IMPLEMENT_REFCOUNT` helper macros) are **provided by the
|
||||
SDK's `pluginterfaces` and `public.sdk` layers** — you do not hand-write refcounting; you
|
||||
invoke macros. (**Experienced estimate** on the exact macro names — these are the
|
||||
long-standing VST3 SDK factory macros; verify spelling against the vendored SDK headers at
|
||||
build time.) This is the "ceremony you write once" — it is copy-adapt-from-the-example
|
||||
work, not design work.
|
||||
|
||||
**And the SDK hands you base classes that absorb most of it.** The critical fact for
|
||||
Daniel's question: you do **not** implement those four interfaces from scratch. The SDK's
|
||||
`public.sdk` layer provides:
|
||||
|
||||
- **`AudioEffect`** (with `Component` / `AudioEffect` base) — implements `IComponent` +
|
||||
`IAudioProcessor` boilerplate; you override `initialize` (declare busses), `setupProcessing`,
|
||||
`setActive`, `setState`/`getState`, and `process`.
|
||||
- **`EditControllerEx1`** — implements `IEditController` boilerplate; you override
|
||||
parameter registration and state.
|
||||
- **`SingleComponentEffect`** — **combines processor and controller into one class**
|
||||
(descends from `EditControllerEx1` and the component hierarchy). You override
|
||||
`initialize` (call `addAudioOutput` + `addEventInput`), `setupProcessing`, and
|
||||
`process`. (**Verified**: Steinberg SDK `SingleComponentEffect` class reference — "default
|
||||
implementation for a non-distributable Plug-in that combines processor and edit
|
||||
controller in one component." The SDK cautions to prefer the two-component split for
|
||||
distributable plugins, but for a **REAPER-only, non-distributable** instrument (D5/D6),
|
||||
`SingleComponentEffect` is exactly the sanctioned shortcut and cuts the interface surface
|
||||
roughly in half.)
|
||||
|
||||
**Honest quantification of the audio side:** with `SingleComponentEffect` + the factory
|
||||
macros, a *silent-but-loading* VST3 instrument skeleton — factory, class registration,
|
||||
module entry, bus setup, empty `process` — is on the order of **~200–400 lines of
|
||||
adapt-from-example ceremony**, written once, then largely untouched. The AGain / Note
|
||||
Expression Synth examples that ship with the SDK are exactly this skeleton and are the
|
||||
copy-source. This is **not** a tar pit. The tar-pit reputation VST3 has comes from (a)
|
||||
multi-format wrappers (AU/AAX/VST2 — **not our problem**, D5) and (b) the GUI, addressed
|
||||
next. (**Experienced estimate** on the line-count band — grounded in the shape of the SDK
|
||||
examples, not a line-counted measurement; treat as an order-of-magnitude honest estimate,
|
||||
not a promise.)
|
||||
|
||||
### What JUCE buys — and whether ReaSampler needs it
|
||||
|
||||
JUCE exists largely to solve problems D5 has already deleted for us. Weighed against the
|
||||
locked constraints:
|
||||
|
||||
| What JUCE provides | Do we need it, given the locks? |
|
||||
|---|---|
|
||||
| **Multi-format wrapper** (one codebase → VST3/AU/AAX/VST2/standalone) | **No.** Single format, VST3 only, REAPER only. This is JUCE's biggest reason to exist and it's moot here. |
|
||||
| **Plugin boilerplate / `AudioProcessor`** | Marginal. The Steinberg SDK's `SingleComponentEffect` already absorbs the VST3 boilerplate; JUCE's abstraction sits *on top of* the same SDK. |
|
||||
| **Parameter management** (`AudioProcessorValueTreeState`) | Nice-to-have. Genuinely convenient, but a sampler's parameter set (envelope, gain, tuning) is small; hand-rolling over `IEditController` is bounded. |
|
||||
| **DSP utilities** (`juce::dsp`, filters, oscillators, interpolators) | Nice-to-have for Tier 3 (filters/LFOs). For Tier 0–2 the sampler DSP — repitch interpolation, envelopes, voice allocation — is exactly the pure core we *want* to write and test ourselves (D3). |
|
||||
| **Editor/UI framework** (`juce::Component`, graphics, widgets) | **The one real pull.** VST3 gives you `IPlugView` and *no toolkit whatsoever*. Something has to draw the editor. This is the crux — see below. |
|
||||
|
||||
**Costs of JUCE, honestly:** it is a large dependency (a whole framework, not a library);
|
||||
it brings its own build system (Projucer / CMake integration) and idioms that would sit
|
||||
oddly beside ReaSampler's lean two-submodule CMake discipline; and its **licensing** is a
|
||||
real standing commitment. JUCE 8 is **dual-licensed AGPLv3 or commercial**. The free tier
|
||||
(Starter) is usable below **~$20K/yr revenue** and — notably in JUCE 8 — **no longer
|
||||
requires a splash screen**. Above that, **Indie (~$200K/yr revenue limit) is a paid
|
||||
license (~$3,500 as of JUCE 8)**, and Pro above that. (**Verified via web**, JUCE forum +
|
||||
license pages — see Sources.) For a personal/ReaSampler-scale tool the free Starter tier
|
||||
likely applies today, but taking JUCE means accepting AGPL-or-pay as a permanent posture
|
||||
on the instrument. That's a bigger standing commitment than the Steinberg SDK, which is a
|
||||
**permissive** (proprietary-but-royalty-free, GPLv3-optional) license with no revenue
|
||||
gate.
|
||||
|
||||
### The UI is the real question — and ReaSampler is unusually well-armed
|
||||
|
||||
Strip everything else away and the honest "reach for JUCE?" decision reduces to one thing:
|
||||
**who draws the editor?** VST3 gives you an `IPlugView` seat and nothing to fill it with.
|
||||
The options on Windows-only (D5):
|
||||
|
||||
1. **JUCE** — for its `Component` graphics stack alone. This is the *usual* reason indie
|
||||
devs take JUCE, and if ReaSampler had no UI competence it'd be the default.
|
||||
2. **VSTGUI** — the UI toolkit that **ships with the Steinberg SDK itself**. Lighter than
|
||||
JUCE, purpose-built for VST editors, no extra dependency beyond the SDK you already
|
||||
took. A real middle option. (**Experienced estimate** — VSTGUI is bundled with the VST3
|
||||
SDK; verify the vendored SDK includes it before relying on it.)
|
||||
3. **Win32 / GDI / Direct2D directly** — Windows-only makes this viable; you own an HWND in
|
||||
the `IPlugView`.
|
||||
4. **The same LICE/SWELL stack the extension already uses.** — **This is the one worth
|
||||
staring at for Daniel specifically.**
|
||||
|
||||
**Why option 4 changes the calculus for *this* project.** ReaSampler's `bank_panel` is
|
||||
**already a LICE-drawn UI** — a real, working, docked, custom-drawn panel (grid,
|
||||
thumbnails, tab strip, hit-testing) built on LICE/SWELL, the same stack REAPER itself and
|
||||
SWS use. That's **67 LICE call-sites in `bank_panel.cpp`** today. The team (Daniel + the
|
||||
implementers) has already paid the learning cost of drawing a custom audio-tool UI in
|
||||
LICE. The usual "take JUCE because hand-rolling a plugin GUI from nothing is miserable"
|
||||
argument is **much weaker here than for a typical indie dev**, because ReaSampler is not
|
||||
starting from nothing — it has demonstrated LICE UI competence and a house style. A VST3
|
||||
`IPlugView` that hosts a LICE-drawn surface would (a) reuse existing UI muscle, (b) keep
|
||||
the instrument's look consistent with the panel, and (c) avoid the JUCE dependency and its
|
||||
license posture entirely. There's even the **embedded-UI affordance** (D7 below) that lets
|
||||
a REAPER-hosted plugin draw inline in the TCP/MCP using this same Cockos surface.
|
||||
|
||||
*The honest caveat:* wiring LICE into a VST3 `IPlugView` (window creation, sizing,
|
||||
event routing from the host into your draw/hit-test loop) is **integration work with real
|
||||
edges** — you're bridging the SDK's view lifecycle to a LICE `HWND`/bitmap. It's not free,
|
||||
and it's less trodden than "drop in a JUCE editor." But it is *the same class of work
|
||||
ReaSampler already did* to dock `bank_panel`, not a new competence. (**Experienced
|
||||
estimate** on the difficulty band — grounded in how `IPlugView` and LICE each work, not a
|
||||
built prototype. Flag: the exact `IPlugView`↔LICE bridge should be spiked before it's
|
||||
promised in a plan.)
|
||||
|
||||
### The pure-core discipline (D3) lands cleanly here
|
||||
|
||||
**Confirmed: the discipline holds, and JUCE-vs-bare doesn't change it.** The sampler's
|
||||
voice engine, envelope math, key/velocity mapping, repitch/interpolation, and keymap
|
||||
resolution are pure DSP + data — **exactly the kind of REAPER-free, DAW-free, unit-tested
|
||||
core** this project already excels at (`bank_model` / `peaks` / `view_mode_model` are the
|
||||
template). They live in a pure module, tested in the CTest harness outside any host. The
|
||||
VST3 wrapper — `SingleComponentEffect` subclass, bus setup, `process` marshalling MIDI→core
|
||||
and core→audio-buffer, the `IPlugView` editor, and the bridge calls that read
|
||||
`"reasampler"` ext-state — is the **thin shell**, the only part that touches VST3 or REAPER
|
||||
at all. This is precisely ReaSampler's load-bearing split, transplanted to a new format.
|
||||
|
||||
Native VST3 makes this *cleaner* than the retired JSFX path would have (JSFX's "core" would
|
||||
be JSFX script, un-unit-testable in CTest). And **JUCE-vs-bare doesn't move it**: whether
|
||||
the shell is a bare-SDK `SingleComponentEffect` or a `juce::AudioProcessor`, the pure core
|
||||
underneath is identical, REAPER-free, and tested the same way. The format choice is a
|
||||
*shell* choice; the core is invariant. That's a reassuring result — it means D-A (bare vs.
|
||||
JUCE, below) can be decided on shell ergonomics and dependency posture alone, without
|
||||
risking the part of the architecture Daniel most cares about.
|
||||
|
||||
### Bottom line for Daniel's question
|
||||
|
||||
Complying with the VST3 surface without JUCE is **reasonable, not crazy** — the
|
||||
audio-processing scaffolding is bounded, example-driven ceremony that
|
||||
`SingleComponentEffect` cuts down further, and the D5 lock-downs delete JUCE's biggest
|
||||
justification (multi-format). The **only** place JUCE earns its weight is the editor UI —
|
||||
and ReaSampler's existing LICE competence makes even *that* argument weaker than it would
|
||||
be for a typical indie. My honest lean (D-A below): **bare Steinberg SDK + LICE editor**,
|
||||
with JUCE as the fallback if the `IPlugView`↔LICE bridge proves gnarlier than the panel
|
||||
work suggests. But it's a genuine fork and it's Daniel's — laid out in §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:
|
||||
There are two seams, and with native VST3 locked (D1) the instrument gets **both**:
|
||||
|
||||
- **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 over `hostcb`), and knows its own host project via
|
||||
the context callback. It sees what `persist` last 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.
|
||||
- **File seam (audio, always).** The sample **audio** is WAVs on disk — there's no getting
|
||||
a live PCM stream across the bridge, nor would you want to. WAVs are project-relative and
|
||||
travel with the `.rpp` (M4 machinery). This is the permanent seam for sample audio.
|
||||
- **Live-state seam (the mapping / index, via the bridge).** For **everything that isn't
|
||||
the raw audio** — the index, the mapping data, which project's bank is active — a native
|
||||
VST instance reads the `"reasampler"` project ext-state directly via the bridge
|
||||
(`GetProjExtState` / `EnumProjExtState`, resolved by name over `hostcb`), and knows its
|
||||
own host project via the context callback. It sees what `persist` last 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 the tight integration D6 locked in.
|
||||
|
||||
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.
|
||||
So: **audio comes across as files; the mapping comes across as live shared state.** The
|
||||
design question below — "is the ext-state/index sufficient, or does playback need mapping
|
||||
it doesn't carry" — is unchanged. What the bridge settles is *where that mapping lives*:
|
||||
between extension and instrument as **live shared `"reasampler"` state**, not a file one
|
||||
writes and the other re-parses.
|
||||
|
||||
**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,
|
||||
@@ -242,29 +414,16 @@ opinionated part. It also means **the bank index grows only by file-intrinsic fi
|
||||
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 `.wav` audio 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.
|
||||
**Wiring note (native, the locked path).** The native instance sidesteps the "point me at
|
||||
the right bank folder" problem entirely: via the context callback it asks REAPER which
|
||||
project it's in, then reads that project's bank location straight from `"reasampler"`
|
||||
ext-state — no user wiring, no saved folder path in the instrument's own state. This is the
|
||||
cleanest possible integration and it's the one D6 chose. The one thing to keep deliberate:
|
||||
the WAV **audio** still resolves via the project-relative path machinery (M4), so the
|
||||
instrument must resolve those paths the same way `persist` does — a shared convention, not a
|
||||
hand-wave. Since D5 locks REAPER-only/Windows-only, the old "but it won't run in other
|
||||
hosts" concern is **moot by design** — the bridge dependency is intended, not a narrowing to
|
||||
regret.
|
||||
|
||||
---
|
||||
|
||||
@@ -277,8 +436,9 @@ 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.
|
||||
thing that delivers the promise* and is the honest MVP. On the native path this is the
|
||||
skeleton of the VST3 plugin — the `SingleComponentEffect` shell (§1a) plus a single-voice
|
||||
core, with the editor deferrable behind a parameters-only default view.
|
||||
|
||||
**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
|
||||
@@ -313,143 +473,160 @@ instrument keeps its own copy of the samples").
|
||||
|
||||
---
|
||||
|
||||
## 4. Risks & open decisions — Daniel's to call
|
||||
## 4. Decisions — settled and residual
|
||||
|
||||
None of these are pre-decided here. Each is a genuine fork.
|
||||
### Settled (DECIDED — reasoning preserved, not re-opened)
|
||||
|
||||
**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.**
|
||||
These are locked. The reasoning is kept as the record of *why*, so the choices don't get
|
||||
silently re-litigated.
|
||||
|
||||
*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.
|
||||
**D1 — Instrument format → DECIDED: native VST3.** JSFX is off the table (retirement
|
||||
reasoning in §1, in the JSFX box). Native VST3 gives full sampler sophistication, clean
|
||||
integration, and the VST-host bridge (live `"reasampler"` ext-state, project-awareness,
|
||||
embedded-UI affordance). The cost — a second codebase in a plugin format — is accepted.
|
||||
The old "JSFX-first cheap prototype" lean is withdrawn: with D5 locking Windows-only,
|
||||
JSFX's one advantage (cross-platform-for-free) is worthless here, and its inability to
|
||||
reach the bridge makes it the wrong tool for the *integrated* product D6 chose.
|
||||
|
||||
*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.
|
||||
**D5 — Cross-platform / multiformat → DECIDED: none. Windows-only, VST3-only,
|
||||
REAPER-only.** The extension is Windows-only; Daniel does not work on other platforms.
|
||||
This *removes* costs prior drafts carried: no mac/Linux DSP/build/signing matrix, no
|
||||
multi-format wrapper, no "runs standalone in other hosts" concern, no CLAP-for-portability
|
||||
argument. REAPER-coupling via the bridge is intended, not a narrowing to regret. This is
|
||||
the single biggest simplifier — it is *why* bare VST3 is reasonable (§1a): most of what
|
||||
makes VST3 painful (multi-format, cross-platform) is deleted.
|
||||
|
||||
**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."
|
||||
**D6 — One product or two → DECIDED: two products, but tightly integrated.** The
|
||||
instrument is a *separate artifact* from the extension (the extension stays the pure
|
||||
capture/organize tool; the instrument is the playback surface — capture and placement, and
|
||||
now playback, stay distinct acts). But it is **not** a divorced file-only companion: via
|
||||
the bridge it reads the live `"reasampler"` project ext-state and is project-aware — "two
|
||||
faces of one tool sharing one state model." The prior draft's open question ("could they
|
||||
only ever share a file?") is resolved: no, and the shared-live-state integration is the
|
||||
chosen shape. The retired reasoning for why the loose-companion reading lost: it only ever
|
||||
looked clean because the prior draft under-weighted the bridge; once the bridge is on the
|
||||
scale, the integrated reading has the stronger technical affordance, and Daniel took it.
|
||||
|
||||
*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 — Pure-core discipline across the format boundary → CONFIRMED holds (see §1a).** Not
|
||||
a fork so much as a checked assumption: the sampler's voice engine, envelope math,
|
||||
key/velocity mapping, and repitch/interpolation live in a pure REAPER-free, DAW-free,
|
||||
unit-tested core (mirror of `bank_model`/`peaks`/`view_mode_model`); the VST3 wrapper is
|
||||
the thin shell. Confirmed clean for native, and — importantly — **invariant under
|
||||
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.
|
||||
|
||||
**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.
|
||||
### Residual forks — Daniel's to call, in priority order
|
||||
|
||||
**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.
|
||||
Four decisions remain live. Ordered by how much they gate the rest.
|
||||
|
||||
**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.
|
||||
**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.
|
||||
|
||||
**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.
|
||||
- *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
|
||||
once-written audio scaffolding, and draw the editor in **LICE/SWELL — the stack
|
||||
`bank_panel` already uses** (67 LICE call-sites today). *Pro:* no JUCE dependency, no
|
||||
AGPL-or-pay posture, house-consistent UI, reuses existing UI muscle, lean CMake
|
||||
discipline preserved. *Con:* the `IPlugView`↔LICE bridge is real integration work with
|
||||
edges (window lifecycle, sizing, event routing) — less trodden than dropping in a JUCE
|
||||
editor, and it should be **spiked before it's promised**.
|
||||
- *Option B — JUCE.* Take JUCE for its editor framework + parameter management + DSP
|
||||
utilities. *Pro:* the editor is a solved problem, `AudioProcessorValueTreeState` is
|
||||
convenient, `juce::dsp` helps at Tier 3. *Con:* a large framework dependency with its own
|
||||
build system and idioms sitting oddly beside the two-submodule discipline; and the
|
||||
**license posture** — AGPLv3-or-commercial, free Starter tier below ~$20K/yr revenue (no
|
||||
splash screen in JUCE 8), Indie ~$3,500 with a ~$200K/yr limit above that. For most of
|
||||
what JUCE solves (multi-format), **D5 already deleted the need.**
|
||||
- *Option C — bare SDK + VSTGUI (the SDK's own bundled toolkit).* A middle path: no JUCE,
|
||||
but a purpose-built VST editor toolkit instead of hand-bridging LICE. Lighter than JUCE,
|
||||
no extra dependency beyond the SDK. Worth a look if the LICE bridge proves gnarly.
|
||||
|
||||
*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."
|
||||
*My honest lean:* **Option A (bare SDK + LICE editor)**, precisely because ReaSampler is
|
||||
the atypical case where the "take JUCE for the GUI" default is weakest — it already has
|
||||
working LICE UI competence and a house style. JUCE's headline value (multi-format) is moot
|
||||
under D5. Fall back to JUCE (Option B) *only if* the `IPlugView`↔LICE spike shows the
|
||||
bridge is genuinely painful; consider VSTGUI (Option C) as the middle rung before
|
||||
conceding to a full framework. **Concrete ask: greenlight a small `IPlugView`↔LICE spike
|
||||
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.
|
||||
|
||||
**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.
|
||||
**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.
|
||||
|
||||
**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.
|
||||
**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-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.
|
||||
|
||||
---
|
||||
|
||||
## What this doc is asking for
|
||||
|
||||
A direction on the two highest-order forks, in order:
|
||||
With D1/D5/D6 locked, the decision this doc now puts to Daniel is a single ordered set:
|
||||
|
||||
1. **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.
|
||||
2. **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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Sources (for §1a's verified claims)
|
||||
|
||||
- Steinberg VST3 SDK — `SingleComponentEffect` class reference (combined processor +
|
||||
controller; "non-distributable" caveat):
|
||||
https://steinbergmedia.github.io/vst3_doc/vstsdk/classSteinberg_1_1Vst_1_1SingleComponentEffect.html
|
||||
- Steinberg VST3 SDK — `IAudioProcessor` class reference (process/setupProcessing/bus
|
||||
contract):
|
||||
https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IAudioProcessor.html
|
||||
- VST3 Developer Portal (overview, tutorials, example plugins — AGain / Note Expression
|
||||
Synth as the skeleton copy-source): https://steinbergmedia.github.io/vst3_dev_portal/
|
||||
- JUCE 8 EULA (dual AGPLv3 / commercial; tier structure):
|
||||
https://juce.com/legal/juce-8-licence/
|
||||
- JUCE forum — revenue limits & JUCE 8 pricing (Starter ~$20K/yr free, Indie ~$200K/yr
|
||||
limit, ~$3,500): https://forum.juce.com/t/revenue-limits-for-juce-tiers/61058
|
||||
|
||||
*Estimate-vs-verified honesty note:* the interface list, `SingleComponentEffect`'s role,
|
||||
the instrument bus topology, and the JUCE license terms are **verified** from the sources
|
||||
above. The **line-count band (~200–400)**, the **exact Windows module-export symbol names**
|
||||
(`InitDll`/`ExitDll`/`GetPluginFactory`), the **factory-macro spellings**
|
||||
(`BEGIN_FACTORY`/`DEF_CLASS2`), **VSTGUI being bundled in the vendored SDK**, and the
|
||||
**`IPlugView`↔LICE bridge difficulty** are **experienced estimates** grounded in how the
|
||||
SDK is shaped — each flagged inline in §1a and each cheap to convert to fact by reading the
|
||||
vendored SDK headers / running a spike before any of it lands in a plan.
|
||||
|
||||
Reference in New Issue
Block a user