// 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 #include #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. // // NOT REAL-TIME SAFE (it allocates a read buffer and calls into REAPER): callers on // the audio thread MUST NOT invoke it. The S4 instrument reads on the main/UI thread // and hands a snapshot to the process path (see reasampler_processor.cpp). std::optional readReasamplerExtState(const std::string& key); // The active project's directory (the folder holding its .rpp), forward-slashed, // no trailing slash — the M4 convention persist uses to place the bank alongside // the .rpp. Empty for an unsaved project or when unconnected. The instrument // resolves relative sample paths against this the SAME way persist does // (capture_paths::projectDirOfRpp over EnumProjects(-1)'s .rpp path). Not RT-safe. std::string activeProjectDir(); 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); // EnumProjects(-1, projfnOut, sz) -> active project + its .rpp path (SDK line // ~1264). The instrument uses idx=-1 (current tab) so it follows the active project, // and reads the .rpp path from the out-buffer exactly as persist.cpp does. using EnumProjectsFn = void* (*)(int idx, char* projfnOut, int projfnOut_sz); void* hostApp_ = nullptr; // IReaperHostApplication* (opaque here; used in .cpp) GetProjExtStateFn getProjExtState_ = nullptr; EnumProjExtStateFn enumProjExtState_ = nullptr; EnumProjectsFn enumProjects_ = nullptr; }; } // namespace reasampler::vst