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,63 @@
|
||||
// reaper_bridge.h — the REAPER VST-host bridge (Phase S1 read spike). THIN shell:
|
||||
// resolves REAPER API functions by name over the host context and reads the live
|
||||
// "reasampler" project ext-state. The fiddly decode lives in bridge_marshal (pure).
|
||||
//
|
||||
// VERIFIED BRIDGE MECHANISM (corrects §1a's estimate). §1a described the VST2-style
|
||||
// hostcb opcode pattern (hostcb(&effect, 0xdeadbeef, 0xdeadf00d, ...)). That is the
|
||||
// VST2 path (video_processor.h documents it for a VST2 aEffect). For a VST3 plugin the
|
||||
// bridge is exposed differently and more cleanly: REAPER passes an IHostApplication as
|
||||
// the `context` to IComponent::initialize(FUnknown* context); querying it for
|
||||
// IReaperHostApplication (vendor/reaper-sdk/sdk/reaper_vst3_interfaces.h) yields:
|
||||
// * getReaperApi(funcname) -> resolve a REAPER API function pointer by name
|
||||
// (the VST3 equivalent of opcode 0xdeadf00d), and
|
||||
// * getReaperParent(3) -> the host ReaProject* (the VST3 equivalent of the
|
||||
// 0xdeadf00e host-context fetch; 1=track, 2=take, 3=project, 4=fxdsp, 5=trackchan).
|
||||
// So a VST3 uses IReaperHostApplication, not the raw hostcb opcodes. Verified against
|
||||
// reaper_vst3_interfaces.h + reaper_plugin_functions.h at the spike.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "pluginterfaces/base/funknown.h"
|
||||
|
||||
namespace reasampler::vst {
|
||||
|
||||
// Wraps the REAPER host bridge for a single plugin instance. Constructed cheaply;
|
||||
// connect() must be called with the initialize() context before any read. All reads
|
||||
// degrade to nullopt (never crash) when the host is not REAPER or a symbol is absent —
|
||||
// the instrument must load in non-REAPER hosts too, just without live state.
|
||||
class ReaperBridge {
|
||||
public:
|
||||
ReaperBridge() = default;
|
||||
|
||||
// Bind to the host. `context` is the FUnknown* REAPER hands IComponent::initialize.
|
||||
// Returns true when the REAPER bridge is available (host is REAPER and the ext-state
|
||||
// API resolved). Safe to call with a null or non-REAPER context — returns false.
|
||||
bool connect(Steinberg::FUnknown* context);
|
||||
|
||||
// True once connect() found the REAPER host application AND resolved the ext-state
|
||||
// functions.
|
||||
bool isConnected() const { return getProjExtState_ != nullptr; }
|
||||
|
||||
// Read a "reasampler" ext-state value by key from the host's active project.
|
||||
// Returns nullopt when unconnected, when the project can't be resolved, or when the
|
||||
// key is absent. This is the S1 read-spike entry point.
|
||||
std::optional<std::string> readReasamplerExtState(const std::string& key);
|
||||
|
||||
private:
|
||||
// Resolved REAPER API function pointers (by name via getReaperApi). Signatures
|
||||
// verified against reaper_plugin_functions.h.
|
||||
using GetProjExtStateFn = int (*)(void* proj, const char* extname, const char* key,
|
||||
char* valOutNeedBig, int valOutNeedBig_sz);
|
||||
using EnumProjExtStateFn = bool (*)(void* proj, const char* extname, int idx,
|
||||
char* keyOut, int keyOut_sz, char* valOut,
|
||||
int valOut_sz);
|
||||
|
||||
void* hostApp_ = nullptr; // IReaperHostApplication* (opaque here; used in .cpp)
|
||||
GetProjExtStateFn getProjExtState_ = nullptr;
|
||||
EnumProjExtStateFn enumProjExtState_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace reasampler::vst
|
||||
Reference in New Issue
Block a user