S4 Tier 0: the bank plays — VST3 marshals MIDI to the S3 core, reads the live bank + resolves WAV the M4 way, mono downmix, lock-free load handoff, LICE sample-pick
This commit is contained in:
+39
-17
@@ -5,6 +5,8 @@
|
||||
#include <vector>
|
||||
|
||||
#include "bridge_marshal.h"
|
||||
#include "capture_paths.h" // projectDirOfRpp (shared M4 project-dir derivation)
|
||||
#include "ext_keys.h" // kProjExtNamespace (shared wire contract)
|
||||
|
||||
// The VST3 base types must be included before REAPER's VST3 interface header, which
|
||||
// uses FUnknown / CStringA / uint32 / DECLARE_CLASS_IID / PLUGIN_API from
|
||||
@@ -27,21 +29,17 @@ namespace Steinberg {
|
||||
// interface (FUnknownPtr uses the iid), so the definition lives with its sole use.
|
||||
DEF_CLASS_IID(Steinberg::IReaperHostApplication)
|
||||
|
||||
// The "reasampler" ext-state namespace + bank key. Kept in sync with persist.h by
|
||||
// value (the extension writes them); we only READ here, so we duplicate the two string
|
||||
// constants rather than pull the whole REAPER-facing persist.h into the VST artifact.
|
||||
// If persist.h's kProjExtNamespace / kProjExtBanksKey ever change, these must follow —
|
||||
// they are the shared wire contract between the extension (writer) and instrument
|
||||
// (reader). VERIFY against persist.h.
|
||||
namespace {
|
||||
constexpr const char* kReasamplerNamespace = "reasampler";
|
||||
}
|
||||
// The "reasampler" ext-state namespace is the SHARED wire contract between the
|
||||
// extension (writer) and this instrument (reader); it lives in ext_keys.h (pure,
|
||||
// REAPER-free) — reasampler::kProjExtNamespace — so the two artifacts read one symbol
|
||||
// and cannot drift. The S1 spike duplicated it locally; that duplication is retired.
|
||||
|
||||
namespace reasampler::vst {
|
||||
|
||||
bool ReaperBridge::connect(Steinberg::FUnknown* context) {
|
||||
getProjExtState_ = nullptr;
|
||||
enumProjExtState_ = nullptr;
|
||||
enumProjects_ = nullptr;
|
||||
hostApp_ = nullptr;
|
||||
if (!context) return false;
|
||||
|
||||
@@ -58,6 +56,10 @@ bool ReaperBridge::connect(Steinberg::FUnknown* context) {
|
||||
reaper->getReaperApi("GetProjExtState"));
|
||||
enumProjExtState_ = reinterpret_cast<EnumProjExtStateFn>(
|
||||
reaper->getReaperApi("EnumProjExtState"));
|
||||
// EnumProjects(-1, ...) yields the active project AND its .rpp path — the same call
|
||||
// persist.cpp uses, so the instrument derives the project directory identically.
|
||||
enumProjects_ = reinterpret_cast<EnumProjectsFn>(
|
||||
reaper->getReaperApi("EnumProjects"));
|
||||
|
||||
return getProjExtState_ != nullptr;
|
||||
}
|
||||
@@ -74,15 +76,35 @@ std::optional<std::string> ReaperBridge::readReasamplerExtState(const std::strin
|
||||
// REAPER treats null as the current project for these calls, so we pass it through
|
||||
// rather than bailing — but if the read yields nothing the caller sees nullopt.
|
||||
|
||||
// GetProjExtState writes into a caller buffer; size it generously for a JSON blob
|
||||
// and let the pure decoder interpret the result. The buffer is NUL-terminated by
|
||||
// REAPER on success.
|
||||
std::vector<char> buf(64 * 1024, '\0');
|
||||
const int rv = getProjExtState_(proj, kReasamplerNamespace, key.c_str(), buf.data(),
|
||||
static_cast<int>(buf.size()));
|
||||
// GetProjExtState writes into a caller buffer; the bank blob can be large (many
|
||||
// samples), so grow the buffer until the value fits rather than risk a silent
|
||||
// truncation — mirrors persist.cpp's getProjExtStateString growing strategy. The
|
||||
// return value is the value length; if it fits strictly inside the buffer it is
|
||||
// complete, else grow and retry up to a 16 MB ceiling.
|
||||
for (int cap = 1 << 16; cap <= (1 << 24); cap <<= 2) {
|
||||
std::vector<char> buf(static_cast<std::size_t>(cap), '\0');
|
||||
const int rv = getProjExtState_(proj, kProjExtNamespace, key.c_str(),
|
||||
buf.data(), cap);
|
||||
if (rv <= 0) return std::nullopt; // absent / empty key
|
||||
std::string s(buf.data());
|
||||
if (static_cast<int>(s.size()) + 1 < cap) {
|
||||
return decodeGetProjExtState(rv, s);
|
||||
}
|
||||
// else: possibly truncated -> grow and retry.
|
||||
}
|
||||
return std::nullopt; // pathologically large (>16 MB) — give up rather than loop
|
||||
}
|
||||
|
||||
// Marshal the raw result through the pure decoder (handles the absent-key case).
|
||||
return decodeGetProjExtState(rv, std::string(buf.data()));
|
||||
std::string ReaperBridge::activeProjectDir() {
|
||||
if (!enumProjects_) return {};
|
||||
// idx=-1 is the current project tab; the out-buffer receives the full .rpp path,
|
||||
// EMPTY for a never-saved project. Same call + convention as persist.cpp; the pure
|
||||
// projectDirOfRpp turns the .rpp path into the project directory (parent, forward-
|
||||
// slashed) and keeps an unsaved project's empty path empty (no default-location
|
||||
// fallback — the tool's invariant).
|
||||
std::vector<char> buf(4096, '\0');
|
||||
enumProjects_(-1, buf.data(), static_cast<int>(buf.size()));
|
||||
return projectDirOfRpp(std::string(buf.data()));
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
|
||||
Reference in New Issue
Block a user