Q-W1 pt2: core/shell/app relocation + sub-namespaces; one concrete ui::Rect (LTRB fork retired); slot_map split from bank_book; BankIndex→BankModel; 59/59 green
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
// instrument_drop — pure implementation. See instrument_drop.h.
|
||||
// NO REAPER / SWELL / VST3 SDK / vendor. Reuses sample_map's ComponentState serializer and
|
||||
// the SDK-free UID macros (vst/reasampler_uid.h).
|
||||
|
||||
#include "core/wire/instrument_drop.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "shell/instrument/reasampler_uid.h" // REASAMPLER_ACTIVE_UID_* — the FROZEN, channel-selected class UID
|
||||
#include "core/instrument/map/sample_map.h" // ComponentState + serializeComponentState (the SHARED writer)
|
||||
|
||||
namespace reasampler::wire {
|
||||
|
||||
namespace {
|
||||
|
||||
// Little-endian appenders — the .vstpreset container stores its integers little-endian on
|
||||
// disk (public.sdk vstpresetfile.cpp swaps only on big-endian hosts).
|
||||
void appendU32LE(std::vector<std::uint8_t>& out, std::uint32_t v) {
|
||||
out.push_back(static_cast<std::uint8_t>(v & 0xFF));
|
||||
out.push_back(static_cast<std::uint8_t>((v >> 8) & 0xFF));
|
||||
out.push_back(static_cast<std::uint8_t>((v >> 16) & 0xFF));
|
||||
out.push_back(static_cast<std::uint8_t>((v >> 24) & 0xFF));
|
||||
}
|
||||
|
||||
void appendU64LE(std::vector<std::uint8_t>& out, std::uint64_t v) {
|
||||
for (int i = 0; i < 8; ++i)
|
||||
out.push_back(static_cast<std::uint8_t>((v >> (8 * i)) & 0xFF));
|
||||
}
|
||||
|
||||
void appendFourCC(std::vector<std::uint8_t>& out, const char id[4]) {
|
||||
out.insert(out.end(), id, id + 4);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string vstClassIdHex() {
|
||||
// FUID::toString reduces to the four INLINE_UID words as "%08X" in order on BOTH byte
|
||||
// layouts (see header contract), so rendering the macros directly is the platform-stable
|
||||
// derivation of the string the .vstpreset header must carry.
|
||||
char buf[33];
|
||||
std::snprintf(buf, sizeof(buf), "%08X%08X%08X%08X",
|
||||
static_cast<unsigned>(REASAMPLER_ACTIVE_UID_1),
|
||||
static_cast<unsigned>(REASAMPLER_ACTIVE_UID_2),
|
||||
static_cast<unsigned>(REASAMPLER_ACTIVE_UID_3),
|
||||
static_cast<unsigned>(REASAMPLER_ACTIVE_UID_4));
|
||||
return std::string(buf, 32);
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> buildVstPresetBytes(
|
||||
const std::string& classIdHex32, const std::vector<std::uint8_t>& componentState) {
|
||||
std::vector<std::uint8_t> out;
|
||||
if (classIdHex32.size() != 32) return out; // contract violation -> empty, never throws
|
||||
|
||||
// Header (48 bytes): 'VST3' + int32 version + 32-char class ID + int64 list offset.
|
||||
constexpr std::uint64_t kHeaderSize = 4 + 4 + 32 + 8;
|
||||
const std::uint64_t compSize = componentState.size();
|
||||
const std::uint64_t listOffset = kHeaderSize + compSize;
|
||||
|
||||
out.reserve(static_cast<std::size_t>(listOffset) + 4 + 4 + (4 + 8 + 8));
|
||||
appendFourCC(out, "VST3");
|
||||
appendU32LE(out, 1); // kFormatVersion
|
||||
out.insert(out.end(), classIdHex32.begin(), classIdHex32.end());
|
||||
appendU64LE(out, listOffset);
|
||||
|
||||
// Data area: the one 'Comp' chunk's bytes, at offset kHeaderSize.
|
||||
out.insert(out.end(), componentState.begin(), componentState.end());
|
||||
|
||||
// Chunk list: 'List' + entry count + one entry {'Comp', offset, size}.
|
||||
appendFourCC(out, "List");
|
||||
appendU32LE(out, 1);
|
||||
appendFourCC(out, "Comp");
|
||||
appendU64LE(out, kHeaderSize);
|
||||
appendU64LE(out, compSize);
|
||||
return out;
|
||||
}
|
||||
|
||||
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::vector<std::uint8_t> buildInstrumentDropPreset(const std::string& sampleId) {
|
||||
return buildVstPresetBytes(vstClassIdHex(), instrumentDropStateBytes(sampleId));
|
||||
}
|
||||
|
||||
bool infoNamesFxHotspot(const std::string& info) {
|
||||
// See the header contract. Prefix rule (S-GA-DropFX): "fx_" names the FX-chain /
|
||||
// floating-FX windows; "tcp.fx" / "mcp.fx" prefixes name the TCP/MCP FX button and its
|
||||
// sibling FX sub-elements (fxbyp/fxparm/fxlist...), tolerant of the SDK-documented "may
|
||||
// append additional information". Bare "tcp"/"mcp" and non-FX sub-elements ("tcp.mute",
|
||||
// "tcp.vol") must NOT trigger an instrument drop.
|
||||
//
|
||||
// EXCLUDE the embed-strip sub-element ("tcp.fxembed" / "mcp.fxembed"): that is the
|
||||
// surface where a ReaSampler 9000 embed strip draws inside the TCP/MCP. Dropping a card
|
||||
// there must NOT add a SECOND instance — the surface is the existing instance's own UI,
|
||||
// not an FX-chain drop target. It starts with "tcp.fx" so it must be explicitly excluded.
|
||||
auto startsWith = [&info](const char* p) { return info.rfind(p, 0) == 0; };
|
||||
if (startsWith("tcp.fxembed") || startsWith("mcp.fxembed")) return false;
|
||||
return startsWith("fx_") || startsWith("tcp.fx") || startsWith("mcp.fx");
|
||||
}
|
||||
|
||||
} // namespace reasampler::wire
|
||||
Reference in New Issue
Block a user