162 lines
8.3 KiB
C++
162 lines
8.3 KiB
C++
#include "core/namespaces.h"
|
|
// 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/capture/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
|
|
// 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 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. Channel-derived (Phase V, V4): the accessor returns "reasampler" (stable) or
|
|
// "reasampler_beta" (beta), matching whatever the extension wrote. 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;
|
|
setProjExtState_ = nullptr;
|
|
getTrackGuid_ = nullptr;
|
|
guidToString_ = 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"));
|
|
// 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"));
|
|
// pS-usage: the (prefix-guarded) usage publish write + the track-identity pair the
|
|
// usage record stamps. All degrade to null gracefully — an old REAPER just never
|
|
// publishes usage (the extension then protects by bank references only).
|
|
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;
|
|
|
|
// 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; 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
|
|
}
|
|
|
|
bool ReaperBridge::writeUsageExtState(const std::string& usageKey,
|
|
const std::string& value) {
|
|
if (!setProjExtState_ || !hostApp_) return false;
|
|
// STRUCTURAL read-only-bank guard: this module writes usage keys and nothing else.
|
|
// A non-"rsusage_" key is a programming error upstream — refuse rather than widen
|
|
// 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 state for this extname" (SDK ~6288) —
|
|
// after storing our non-empty value the namespace state is necessarily > 0, so a
|
|
// <= 0 return means the write did not land. Reported to the caller (the publish
|
|
// path retries on the next reload tick); a silently-dropped record would leave the
|
|
// instance's holds unprotected.
|
|
const int rv =
|
|
setProjExtState_(proj, kProjExtNamespace(), usageKey.c_str(), value.c_str());
|
|
// Deliberately NO MarkProjectDirty: a usage change always accompanies a component-
|
|
// state change that already dirties the project; an idempotent load-time republish
|
|
// must not flag an untouched project as modified.
|
|
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 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
|