S17 drop-and-load onto track FX button; S13 editor drop-accept (relay degraded)

S17: drag_out InstrumentDrop gesture + instrument_drop blob reusing the
instrument's own serializer; bank_panel FX hover-track + add-VST/vst_chunk
inject. S13 relay deferred (read-only bridge) — editor shows drop affordance.
This commit is contained in:
2026-07-27 03:52:26 -04:00
parent 06494c654e
commit 26e2bf2fc6
13 changed files with 779 additions and 54 deletions
+108
View File
@@ -0,0 +1,108 @@
// 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, mono, generation 0) — the same
// ComponentState a browser click would produce. 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));
}
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