// 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 "instrument_drop.h" #include #include "vst/reasampler_uid.h" // REASAMPLER_ACTIVE_UID_* — the FROZEN, channel-selected class UID #include "vst/sample_map.h" // ComponentState + serializeComponentState (the SHARED writer) namespace reasampler { 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& out, std::uint32_t v) { out.push_back(static_cast(v & 0xFF)); out.push_back(static_cast((v >> 8) & 0xFF)); out.push_back(static_cast((v >> 16) & 0xFF)); out.push_back(static_cast((v >> 24) & 0xFF)); } void appendU64LE(std::vector& out, std::uint64_t v) { for (int i = 0; i < 8; ++i) out.push_back(static_cast((v >> (8 * i)) & 0xFF)); } void appendFourCC(std::vector& 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(REASAMPLER_ACTIVE_UID_1), static_cast(REASAMPLER_ACTIVE_UID_2), static_cast(REASAMPLER_ACTIVE_UID_3), static_cast(REASAMPLER_ACTIVE_UID_4)); return std::string(buf, 32); } std::vector buildVstPresetBytes( const std::string& classIdHex32, const std::vector& componentState) { std::vector 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(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 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 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