feat(vst): stand up VST3 instrument spike (S1) — skeleton, IPlugView↔LICE editor, REAPER bridge read
Vendor Steinberg VST3 SDK (v3.7.9_build_61); add reasampler_vst.vst3 as a second, additive build artifact with pure editor-geometry + bridge-marshal helpers under CTest.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
// reaper_bridge.cpp — see reaper_bridge.h. The DAW-facing edge; keep it thin.
|
||||
|
||||
#include "reaper_bridge.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "bridge_marshal.h"
|
||||
|
||||
// The VST3 base types must be included before REAPER's VST3 interface header, which
|
||||
// uses FUnknown / CStringA / uint32 / DECLARE_CLASS_IID / PLUGIN_API from
|
||||
// pluginterfaces/base — all in namespace Steinberg.
|
||||
#include "pluginterfaces/base/funknown.h"
|
||||
#include "pluginterfaces/base/ftypes.h"
|
||||
|
||||
// REAPER's VST3-side bridge interface (vendored). IReaperHostApplication is what REAPER
|
||||
// passes (as an IHostApplication) to IComponent::initialize; it exposes getReaperApi
|
||||
// (resolve-by-name) and getReaperParent (host context). The header uses UNQUALIFIED
|
||||
// Steinberg types (FUnknown, CStringA, uint32, FUID, DECLARE_CLASS_IID, PLUGIN_API), so
|
||||
// it must be pulled into the Steinberg namespace — the same way REAPER's own VST3
|
||||
// examples include it.
|
||||
namespace Steinberg {
|
||||
#include "reaper_vst3_interfaces.h"
|
||||
} // namespace Steinberg
|
||||
|
||||
// DECLARE_CLASS_IID in the REAPER header only DECLARES IReaperHostApplication::iid; some
|
||||
// TU must DEFINE it. We do it here — this is the only place that queries for the
|
||||
// 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";
|
||||
}
|
||||
|
||||
namespace reasampler::vst {
|
||||
|
||||
bool ReaperBridge::connect(Steinberg::FUnknown* context) {
|
||||
getProjExtState_ = nullptr;
|
||||
enumProjExtState_ = nullptr;
|
||||
hostApp_ = nullptr;
|
||||
if (!context) return false;
|
||||
|
||||
// Query the host context for REAPER's bridge interface. In a non-REAPER host this
|
||||
// query fails and we stay unconnected — the instrument still loads.
|
||||
Steinberg::FUnknownPtr<Steinberg::IReaperHostApplication> reaper(context);
|
||||
if (!reaper) return false;
|
||||
hostApp_ = reaper.get();
|
||||
|
||||
// Resolve the ext-state functions by name. getReaperApi returns the same function
|
||||
// pointers the extension resolves via rec->GetFunc; a null return means the symbol
|
||||
// is unavailable (very old REAPER) — degrade gracefully.
|
||||
getProjExtState_ = reinterpret_cast<GetProjExtStateFn>(
|
||||
reaper->getReaperApi("GetProjExtState"));
|
||||
enumProjExtState_ = reinterpret_cast<EnumProjExtStateFn>(
|
||||
reaper->getReaperApi("EnumProjExtState"));
|
||||
|
||||
return getProjExtState_ != nullptr;
|
||||
}
|
||||
|
||||
std::optional<std::string> ReaperBridge::readReasamplerExtState(const std::string& key) {
|
||||
if (!getProjExtState_ || !hostApp_) return std::nullopt;
|
||||
|
||||
// Fetch the host project (getReaperParent(3) — project). Reads that live "reasampler"
|
||||
// ext-state against the ACTIVE project the instrument was instantiated in, so it
|
||||
// follows project switches for free (D6).
|
||||
auto* reaper = static_cast<Steinberg::IReaperHostApplication*>(hostApp_);
|
||||
void* proj = reaper->getReaperParent(3);
|
||||
// A null project is legitimate (e.g. instantiated before a project context exists);
|
||||
// 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()));
|
||||
|
||||
// Marshal the raw result through the pure decoder (handles the absent-key case).
|
||||
return decodeGetProjExtState(rv, std::string(buf.data()));
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
Reference in New Issue
Block a user