122 lines
5.4 KiB
C++
122 lines
5.4 KiB
C++
// instrument_drop — pure implementation. See instrument_drop.h.
|
|
// NO REAPER / SWELL / VST3 SDK / vendor. Reuses sample_map's ComponentState serializer.
|
|
|
|
#include "instrument_drop.h"
|
|
|
|
#include "vst/sample_map.h" // ComponentState + serializeComponentState (the SHARED writer)
|
|
|
|
namespace reasampler {
|
|
|
|
namespace {
|
|
|
|
constexpr char kB64Alphabet[] =
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
// -1 = not a base64 char; index by unsigned byte. Built once.
|
|
int b64Value(unsigned char c) {
|
|
if (c >= 'A' && c <= 'Z') return c - 'A';
|
|
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
|
if (c >= '0' && c <= '9') return c - '0' + 52;
|
|
if (c == '+') return 62;
|
|
if (c == '/') return 63;
|
|
return -1;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::vector<std::uint8_t> instrumentDropStateBytes(const std::string& sampleId) {
|
|
// The ONE fact the drop carries: this capture is the instance's selection. Everything
|
|
// else stays at the fresh-instance defaults (no zones, implicit channel mode, generation
|
|
// 0) — the same ComponentState a browser click would produce. The implicit mode means
|
|
// the GA auto-default will follow the loaded capture's channel count on first reload.
|
|
// serializeComponentState is the instrument's own writer (the single source of truth for
|
|
// the byte layout), so this is NOT a parallel encoder — it IS the instrument's encoder.
|
|
ComponentState cs;
|
|
cs.selectionId = sampleId;
|
|
return serializeComponentState(cs);
|
|
}
|
|
|
|
std::string buildInstrumentDropChunk(const std::string& sampleId) {
|
|
return encodeBase64(instrumentDropStateBytes(sampleId));
|
|
}
|
|
|
|
bool infoNamesFxHotspot(const std::string& info) {
|
|
// See the header contract. Two documented FX-bearing surfaces (SDK §GetThingFromPoint):
|
|
// "fx_chain" / "fx_N" — the FX-chain and floating-FX windows.
|
|
// "tcp.fx" / "mcp.fx" — the TCP / MCP FX button specifically.
|
|
// Bare "tcp" / "mcp" and any other "tcp.*" / "mcp.*" sub-element (e.g. "tcp.mute",
|
|
// "tcp.vol") are track-panel hits that are NOT on the FX button — those must not trigger
|
|
// an instrument drop. Exact-string match for the two .fx tokens; prefix-match for fx_.
|
|
if (info == "tcp.fx" || info == "mcp.fx") return true;
|
|
auto startsWith = [&info](const char* p) { return info.rfind(p, 0) == 0; };
|
|
return startsWith("fx_");
|
|
}
|
|
|
|
std::string encodeBase64(const std::vector<std::uint8_t>& bytes) {
|
|
std::string out;
|
|
out.reserve(((bytes.size() + 2) / 3) * 4);
|
|
std::size_t i = 0;
|
|
const std::size_t n = bytes.size();
|
|
while (i + 3 <= n) {
|
|
const std::uint32_t triple = (static_cast<std::uint32_t>(bytes[i]) << 16) |
|
|
(static_cast<std::uint32_t>(bytes[i + 1]) << 8) |
|
|
static_cast<std::uint32_t>(bytes[i + 2]);
|
|
out.push_back(kB64Alphabet[(triple >> 18) & 0x3F]);
|
|
out.push_back(kB64Alphabet[(triple >> 12) & 0x3F]);
|
|
out.push_back(kB64Alphabet[(triple >> 6) & 0x3F]);
|
|
out.push_back(kB64Alphabet[triple & 0x3F]);
|
|
i += 3;
|
|
}
|
|
const std::size_t rem = n - i;
|
|
if (rem == 1) {
|
|
const std::uint32_t triple = static_cast<std::uint32_t>(bytes[i]) << 16;
|
|
out.push_back(kB64Alphabet[(triple >> 18) & 0x3F]);
|
|
out.push_back(kB64Alphabet[(triple >> 12) & 0x3F]);
|
|
out.push_back('=');
|
|
out.push_back('=');
|
|
} else if (rem == 2) {
|
|
const std::uint32_t triple = (static_cast<std::uint32_t>(bytes[i]) << 16) |
|
|
(static_cast<std::uint32_t>(bytes[i + 1]) << 8);
|
|
out.push_back(kB64Alphabet[(triple >> 18) & 0x3F]);
|
|
out.push_back(kB64Alphabet[(triple >> 12) & 0x3F]);
|
|
out.push_back(kB64Alphabet[(triple >> 6) & 0x3F]);
|
|
out.push_back('=');
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::vector<std::uint8_t> decodeBase64(const std::string& b64) {
|
|
std::vector<std::uint8_t> out;
|
|
if (b64.size() % 4 != 0) return out; // malformed length -> empty (never throws)
|
|
out.reserve((b64.size() / 4) * 3);
|
|
for (std::size_t i = 0; i < b64.size(); i += 4) {
|
|
const char c0 = b64[i], c1 = b64[i + 1], c2 = b64[i + 2], c3 = b64[i + 3];
|
|
const int v0 = b64Value(static_cast<unsigned char>(c0));
|
|
const int v1 = b64Value(static_cast<unsigned char>(c1));
|
|
if (v0 < 0 || v1 < 0) return {}; // illegal char in a non-pad position -> empty
|
|
// Padding is only legal in the last two positions of the last quad.
|
|
const bool pad2 = (c2 == '=');
|
|
const bool pad3 = (c3 == '=');
|
|
if ((pad2 || pad3) && i + 4 != b64.size()) return {}; // pad before the final quad
|
|
if (pad2 && !pad3) return {}; // "=X" is malformed
|
|
std::uint32_t triple = (static_cast<std::uint32_t>(v0) << 18) |
|
|
(static_cast<std::uint32_t>(v1) << 12);
|
|
out.push_back(static_cast<std::uint8_t>((triple >> 16) & 0xFF));
|
|
if (!pad2) {
|
|
const int v2 = b64Value(static_cast<unsigned char>(c2));
|
|
if (v2 < 0) return {};
|
|
triple |= static_cast<std::uint32_t>(v2) << 6;
|
|
out.push_back(static_cast<std::uint8_t>((triple >> 8) & 0xFF));
|
|
if (!pad3) {
|
|
const int v3 = b64Value(static_cast<unsigned char>(c3));
|
|
if (v3 < 0) return {};
|
|
triple |= static_cast<std::uint32_t>(v3);
|
|
out.push_back(static_cast<std::uint8_t>(triple & 0xFF));
|
|
}
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
} // namespace reasampler
|