141 lines
6.7 KiB
C++
141 lines
6.7 KiB
C++
// reaper_bridge.cpp — see reaper_bridge.h. The DAW-facing edge; keep it thin.
|
|
|
|
#include "shell/instrument/reaper_bridge.h"
|
|
|
|
#include <vector>
|
|
|
|
#include "core/instrument/map/bridge_marshal.h"
|
|
#include "core/wire/ext_state_read.h" // readProjExtStateGrowing (grow-loop policy)
|
|
#include "core/capture/capture_paths.h" // projectDirOfRpp (shared project-dir derivation)
|
|
#include "ext_keys.h" // kProjExtNamespace (shared wire contract)
|
|
|
|
// VST3 base types must be included before REAPER's VST3 interface header, which uses
|
|
// unqualified Steinberg types (FUnknown, CStringA, uint32, DECLARE_CLASS_IID, PLUGIN_API).
|
|
#include "pluginterfaces/base/funknown.h"
|
|
#include "pluginterfaces/base/ftypes.h"
|
|
|
|
// REAPER's VST3-side bridge interface (vendored): IReaperHostApplication is the
|
|
// IHostApplication REAPER passes to IComponent::initialize, exposing getReaperApi
|
|
// (resolve-by-name) and getReaperParent (host context). Pulled into namespace Steinberg
|
|
// (the header's unqualified types), 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 the iid; this is the only TU that
|
|
// queries for the interface, so the DEFINE lives with its sole use.
|
|
DEF_CLASS_IID(Steinberg::IReaperHostApplication)
|
|
|
|
// The ext-state namespace is the shared wire contract with the extension — ext_keys.h's
|
|
// kProjExtNamespace() (pure, REAPER-free), channel-derived so both artifacts read one
|
|
// symbol and cannot drift.
|
|
|
|
namespace reasampler::vst {
|
|
|
|
using capture::projectDirOfRpp;
|
|
using instrument::map::decodeGetProjExtState;
|
|
|
|
bool ReaperBridge::connect(Steinberg::FUnknown* context) {
|
|
getProjExtState_ = nullptr;
|
|
enumProjExtState_ = nullptr;
|
|
enumProjects_ = nullptr;
|
|
setProjExtState_ = nullptr;
|
|
getTrackGuid_ = nullptr;
|
|
guidToString_ = nullptr;
|
|
hostApp_ = nullptr;
|
|
if (!context) return false;
|
|
|
|
// 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();
|
|
|
|
// getReaperApi returns the same function pointers the extension resolves via
|
|
// rec->GetFunc; a null return means the symbol is unavailable (very old REAPER).
|
|
getProjExtState_ = reinterpret_cast<GetProjExtStateFn>(
|
|
reaper->getReaperApi("GetProjExtState"));
|
|
enumProjExtState_ = reinterpret_cast<EnumProjExtStateFn>(
|
|
reaper->getReaperApi("EnumProjExtState"));
|
|
// EnumProjects(-1, ...) yields the active project + its .rpp path — same convention
|
|
// the persist shell uses, so the instrument derives the project directory identically.
|
|
enumProjects_ = reinterpret_cast<EnumProjectsFn>(
|
|
reaper->getReaperApi("EnumProjects"));
|
|
// The (prefix-guarded) usage publish write + the track-identity pair it stamps. All
|
|
// degrade to null gracefully — an old REAPER never publishes usage.
|
|
setProjExtState_ = reinterpret_cast<SetProjExtStateFn>(
|
|
reaper->getReaperApi("SetProjExtState"));
|
|
getTrackGuid_ = reinterpret_cast<GetTrackGuidFn>(
|
|
reaper->getReaperApi("GetTrackGUID"));
|
|
guidToString_ = reinterpret_cast<GuidToStringFn>(
|
|
reaper->getReaperApi("guidToString"));
|
|
|
|
return getProjExtState_ != nullptr;
|
|
}
|
|
|
|
std::optional<std::string> ReaperBridge::readReasamplerExtState(const std::string& key) {
|
|
if (!getProjExtState_ || !hostApp_) return std::nullopt;
|
|
|
|
// getReaperParent(3) reads the live "reasampler" ext-state against the active project
|
|
// the instrument was instantiated in, so it follows project switches for free. A null
|
|
// project is legitimate (REAPER treats it as the current project) — pass it through
|
|
// rather than bailing; a fruitless read still yields nullopt to the caller.
|
|
auto* reaper = static_cast<Steinberg::IReaperHostApplication*>(hostApp_);
|
|
void* proj = reaper->getReaperParent(3);
|
|
|
|
// The bank blob can be large, so grow the buffer until it fits rather than risk a
|
|
// silent truncation. The shared wire::readProjExtStateGrowing loop keeps this bridge
|
|
// read and the extension's persist/usage reads from drifting.
|
|
const auto read = wire::readProjExtStateGrowing(
|
|
[&](char* buf, int cap) {
|
|
return getProjExtState_(proj, kProjExtNamespace(), key.c_str(), buf, cap);
|
|
});
|
|
if (read.status != wire::GrowingExtStateRead::Status::Complete)
|
|
return std::nullopt; // absent / empty key, or pathologically large (>16 MB)
|
|
return decodeGetProjExtState(read.apiReturn, read.value);
|
|
}
|
|
|
|
bool ReaperBridge::writeUsageExtState(const std::string& usageKey,
|
|
const std::string& value) {
|
|
if (!setProjExtState_ || !hostApp_) return false;
|
|
// Read-only-bank guard: this module writes usage keys and nothing else. A non-
|
|
// "rsusage_" key is refused rather than widening the instrument's write surface
|
|
// (banks/view/tail/assign stay extension-owned).
|
|
const std::string prefix = kProjExtUsageKeyPrefix;
|
|
if (usageKey.compare(0, prefix.size(), prefix) != 0) return false;
|
|
|
|
auto* reaper = static_cast<Steinberg::IReaperHostApplication*>(hostApp_);
|
|
void* proj = reaper->getReaperParent(3); // null = current project (same as reads)
|
|
// SetProjExtState returns the size of the extname's state — after storing a
|
|
// non-empty value that's necessarily > 0, so <= 0 means the write did not land (the
|
|
// publish path retries next reload tick; a silent drop would leave holds unprotected).
|
|
const int rv =
|
|
setProjExtState_(proj, kProjExtNamespace(), usageKey.c_str(), value.c_str());
|
|
// Deliberately NO MarkProjectDirty: a usage change always rides a component-state
|
|
// change that already dirties the project.
|
|
return rv > 0;
|
|
}
|
|
|
|
std::string ReaperBridge::currentTrackGuid() {
|
|
if (!hostApp_ || !getTrackGuid_ || !guidToString_) return {};
|
|
auto* reaper = static_cast<Steinberg::IReaperHostApplication*>(hostApp_);
|
|
void* track = reaper->getReaperParent(1); // the hosting MediaTrack*
|
|
if (!track) return {}; // no track context (unusual host state)
|
|
void* guid = getTrackGuid_(track);
|
|
if (!guid) return {};
|
|
char buf[64] = {0}; // guidToString's documented destNeed64 contract
|
|
guidToString_(guid, buf);
|
|
return std::string(buf);
|
|
}
|
|
|
|
std::string ReaperBridge::activeProjectDir() {
|
|
if (!enumProjects_) return {};
|
|
// idx=-1 is the current project tab; the out-buffer is empty for a never-saved
|
|
// project. projectDirOfRpp keeps that empty (no default-location fallback).
|
|
std::vector<char> buf(4096, '\0');
|
|
enumProjects_(-1, buf.data(), static_cast<int>(buf.size()));
|
|
return projectDirOfRpp(std::string(buf.data()));
|
|
}
|
|
|
|
} // namespace reasampler::vst
|