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:
2026-07-26 15:29:54 -04:00
parent 5595ba42f9
commit 3c2a7c45b2
17 changed files with 1256 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
// bridge_marshal.cpp — see bridge_marshal.h. Pure; no host types.
#include "bridge_marshal.h"
namespace reasampler::vst {
std::optional<std::string> decodeGetProjExtState(int apiReturn,
const std::string& buffer) {
// REAPER returns the length of the stored value; 0 means the key is absent. Guard
// both the return AND the buffer: a caller that reused a dirty buffer must not
// surface stale bytes as a value when the API reported nothing.
if (apiReturn <= 0 || buffer.empty()) return std::nullopt;
return buffer;
}
std::optional<std::string> extractJsonStringField(const std::string& json,
const std::string& key) {
// Find the member token: "key" followed (after optional whitespace) by ':' then a
// quoted string. Scan for each candidate occurrence of the quoted key so a value
// that happens to contain the key text can't produce a false match.
const std::string needle = "\"" + key + "\"";
size_t searchFrom = 0;
while (true) {
const size_t keyPos = json.find(needle, searchFrom);
if (keyPos == std::string::npos) return std::nullopt;
size_t i = keyPos + needle.size();
searchFrom = i; // next candidate starts after this key token
// Skip whitespace to the ':'.
while (i < json.size() &&
(json[i] == ' ' || json[i] == '\t' || json[i] == '\n' ||
json[i] == '\r')) {
++i;
}
if (i >= json.size() || json[i] != ':') continue; // not a member — keep looking
++i;
// Skip whitespace to the value.
while (i < json.size() &&
(json[i] == ' ' || json[i] == '\t' || json[i] == '\n' ||
json[i] == '\r')) {
++i;
}
if (i >= json.size() || json[i] != '"') return std::nullopt; // value not a string
++i;
// Read the string body, honoring the common JSON escapes.
std::string out;
while (i < json.size()) {
const char c = json[i];
if (c == '\\') {
if (i + 1 >= json.size()) return std::nullopt; // dangling escape
const char e = json[i + 1];
switch (e) {
case '"': out.push_back('"'); break;
case '\\': out.push_back('\\'); break;
case '/': out.push_back('/'); break;
case 'n': out.push_back('\n'); break;
case 't': out.push_back('\t'); break;
case 'r': out.push_back('\r'); break;
case 'b': out.push_back('\b'); break;
case 'f': out.push_back('\f'); break;
default: out.push_back(e); break; // pass through unknown escapes
}
i += 2;
continue;
}
if (c == '"') return out; // closing quote — done
out.push_back(c);
++i;
}
return std::nullopt; // unterminated string
}
}
} // namespace reasampler::vst