25390d5253
SetProjExtState's return describes the whole extname. The persist and the instrument's publish now re-read their key; both refusals can finally fire.
149 lines
8.4 KiB
C++
149 lines
8.4 KiB
C++
// reaper_bridge.h — the REAPER VST-host bridge. 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).
|
|
//
|
|
// Bridge mechanism: REAPER passes an IHostApplication as `context` to
|
|
// IComponent::initialize; querying it for IReaperHostApplication yields getReaperApi
|
|
// (resolve a REAPER API function pointer by name) and getReaperParent(3) (the host
|
|
// ReaProject*; 1=track, 2=take, 3=project, 4=fxdsp, 5=trackchan) — not VST2 hostcb opcodes.
|
|
|
|
#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;
|
|
|
|
// Binds to the host (`context` is the FUnknown* IComponent::initialize hands us).
|
|
// Returns true when the 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);
|
|
|
|
bool isConnected() const { return getProjExtState_ != nullptr; }
|
|
|
|
// Reads a "reasampler" ext-state value by key from the host's active project.
|
|
// Returns nullopt when unconnected, unresolvable, or the key is absent.
|
|
//
|
|
// NOT REAL-TIME SAFE (allocates + calls into REAPER): audio-thread callers MUST NOT
|
|
// invoke this. The instrument reads on the main/UI thread and hands a snapshot to
|
|
// the process path.
|
|
std::optional<std::string> readReasamplerExtState(const std::string& key);
|
|
|
|
// The active project's directory (forward-slashed, no trailing slash) — the same
|
|
// convention persist uses to place the bank alongside the .rpp. Empty for an unsaved
|
|
// project or when unconnected. Not RT-safe.
|
|
std::string activeProjectDir();
|
|
|
|
// The instrument's TWO sanctioned ext-state write surfaces, each accepting exactly one
|
|
// key prefix and refusing every other key. That structural refusal is what keeps the
|
|
// read-only-BANK invariant intact — banks/view/tail/assign stay unwritable from here —
|
|
// and neither payload is bank state. Neither is RT-safe: the call sites are the
|
|
// off-audio-thread reload path and the editor's UI tick.
|
|
//
|
|
// Both return true iff the key READ BACK as exactly the value written (an empty value
|
|
// is a clear, which lands as an absent-or-empty key). SetProjExtState's own return
|
|
// cannot answer that — wire::extStateWriteLanded owns why, and testing it here was a
|
|
// guard that could never fire. A `false` does not distinguish a write that was never
|
|
// issued (unconnected host, refused prefix) from one that did not take or could not be
|
|
// checked; a caller must not name one of the three.
|
|
//
|
|
// Neither marks the project dirty. A usage change always rides a component-state change
|
|
// that already does; a bake request is transient and is cleared in the same tick.
|
|
|
|
// THIS INSTANCE's usage record. `usageKey` MUST carry the "rsusage_" prefix
|
|
// (ext_keys.h's usageKeyFor).
|
|
bool writeUsageExtState(const std::string& usageKey, const std::string& value);
|
|
|
|
// THIS INSTANCE's resample-bake request. `bakeKey` MUST carry the "rsbake_" prefix
|
|
// (ext_keys.h's bakeKeyFor). An empty value clears the key.
|
|
bool writeBakeExtState(const std::string& bakeKey, const std::string& value);
|
|
|
|
// --- Extension action invocation (the bake crossing) ------------------------------
|
|
//
|
|
// `commandName` is the NamedCommandLookup spelling — the registered command_id string
|
|
// with a leading underscore, which the registration string itself does not carry.
|
|
|
|
// Whether the extension is loaded AND has registered this action. Used to paint the
|
|
// affordance: an unavailable action must read Disabled, never enabled-then-refusing.
|
|
bool extensionActionAvailable(const std::string& commandName);
|
|
|
|
// Fires the action with THIS INSTANCE's own project tab (getReaperParent(3)) as
|
|
// Main_OnCommandEx's `proj`, rather than leaving it to whichever tab is focused. What
|
|
// REAPER then makes current for the action's duration is NOT verified in the DAW, so
|
|
// this is a request, not a guarantee — the extension side re-derives which project a
|
|
// request came from and refuses one it cannot safely land. Returns false when the action
|
|
// is unregistered; the invocation itself reports nothing, so a caller learns the result
|
|
// from the state the action wrote, never from here. Must NOT be called from a mouse
|
|
// handler: it runs the extension's whole bake landing synchronously, and the action
|
|
// re-points this instance.
|
|
bool invokeExtensionAction(const std::string& commandName);
|
|
|
|
// The effective project tempo (BPM, quarter notes per minute) at the edit cursor of
|
|
// this instance's own project. 0.0 when unconnected or unresolvable — the caller
|
|
// refuses rather than substituting a tempo (no hardcoded rates or tempos in src/).
|
|
double projectTempoBpm();
|
|
|
|
// The canonical GUID string of the track hosting this FX instance (same rendering as
|
|
// the extension's track_guid::guidString, so usage records compare byte-equal
|
|
// against its live-FX enumeration). Empty when unconnected or no track context (the
|
|
// usage reader then falls back to any-instance liveness). Not RT-safe.
|
|
std::string currentTrackGuid();
|
|
|
|
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. idx=-1 (current
|
|
// tab) follows the active project, same convention as the persist shell.
|
|
using EnumProjectsFn = void* (*)(int idx, char* projfnOut, int projfnOut_sz);
|
|
// Used ONLY by the two prefix-guarded writers — see the read-only-bank note there.
|
|
using SetProjExtStateFn = int (*)(void* proj, const char* extname, const char* key,
|
|
const char* value);
|
|
using NamedCommandLookupFn = int (*)(const char* commandName);
|
|
using MainOnCommandExFn = void (*)(int command, int flag, void* proj);
|
|
using GetCursorPositionExFn = double (*)(void* proj);
|
|
using TimeMapGetTimeSigAtTimeFn = void (*)(void* proj, double time, int* numOut,
|
|
int* denomOut, double* tempoOut);
|
|
// Opaque-pointer signatures so the header stays SDK-type-free; the GUID* is passed
|
|
// straight through, never dereferenced here.
|
|
using GetTrackGuidFn = void* (*)(void* tr);
|
|
using GuidToStringFn = void (*)(const void* g, char* destNeed64);
|
|
|
|
void* hostApp_ = nullptr; // IReaperHostApplication* (opaque here; used in .cpp)
|
|
GetProjExtStateFn getProjExtState_ = nullptr;
|
|
EnumProjExtStateFn enumProjExtState_ = nullptr;
|
|
EnumProjectsFn enumProjects_ = nullptr;
|
|
SetProjExtStateFn setProjExtState_ = nullptr;
|
|
GetTrackGuidFn getTrackGuid_ = nullptr;
|
|
GuidToStringFn guidToString_ = nullptr;
|
|
NamedCommandLookupFn namedCommandLookup_ = nullptr;
|
|
MainOnCommandExFn mainOnCommandEx_ = nullptr;
|
|
GetCursorPositionExFn getCursorPositionEx_ = nullptr;
|
|
TimeMapGetTimeSigAtTimeFn timeMapGetTimeSigAtTime_ = nullptr;
|
|
|
|
// The one prefix guard both public writers route through, so the two cannot diverge
|
|
// in how strictly they refuse a key.
|
|
bool writeGuarded(const std::string& key, const std::string& value,
|
|
const char* requiredPrefix);
|
|
// 0 when the action is not registered (the extension is absent or older). REAPER's
|
|
// documented "not found" return is 0 by convention only — the header does not state
|
|
// it — so every caller treats 0 as unavailable and never as a valid command id.
|
|
int lookupCommand(const std::string& commandName);
|
|
};
|
|
|
|
} // namespace reasampler::vst
|