98 lines
3.8 KiB
C++
98 lines
3.8 KiB
C++
// 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 (reasampler_uid.h).
|
|
|
|
#include "core/wire/instrument_drop.h"
|
|
|
|
#include <cstdio>
|
|
|
|
#include "core/wire/reasampler_uid.h" // REASAMPLER_ACTIVE_UID_* — the FROZEN, channel-selected class UID
|
|
#include "core/instrument/map/component_state_io.h" // ComponentState + serializeComponentState (the SHARED writer)
|
|
#include "core/wire/bytes.h" // putLE — the ONE LE byte codec
|
|
|
|
namespace reasampler::wire {
|
|
|
|
using instrument::map::ComponentState;
|
|
using instrument::map::serializeComponentState;
|
|
|
|
namespace {
|
|
|
|
// The .vstpreset container stores its integers little-endian on disk (public.sdk
|
|
// vstpresetfile.cpp swaps only on big-endian hosts) — putLE is exactly that byte order.
|
|
|
|
void appendFourCC(std::vector<std::uint8_t>& out, const char id[4]) {
|
|
out.insert(out.end(), id, id + 4);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::string vstClassIdHex() {
|
|
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");
|
|
putLE<std::uint32_t>(out, 1); // kFormatVersion
|
|
out.insert(out.end(), classIdHex32.begin(), classIdHex32.end());
|
|
putLE<std::uint64_t>(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");
|
|
putLE<std::uint32_t>(out, 1);
|
|
appendFourCC(out, "Comp");
|
|
putLE<std::uint64_t>(out, kHeaderSize);
|
|
putLE<std::uint64_t>(out, compSize);
|
|
return out;
|
|
}
|
|
|
|
std::vector<std::uint8_t> instrumentDropStateBytes(const std::string& sampleId) {
|
|
// Everything but selectionId stays at fresh-instance defaults (no zones,
|
|
// implicit channel mode, generation 0) — same as a browser click.
|
|
ComponentState cs;
|
|
cs.selectionId = sampleId;
|
|
return serializeComponentState(cs);
|
|
}
|
|
|
|
std::vector<std::uint8_t> buildInstrumentDropPreset(const std::string& sampleId) {
|
|
return buildVstPresetBytes(vstClassIdHex(), instrumentDropStateBytes(sampleId));
|
|
}
|
|
|
|
DropOutcome decideDropOutcome(const DropAttempt& attempt) {
|
|
DropOutcome out;
|
|
if (attempt.addedFxIndex < 0) return out; // add failed — nothing exists to roll back
|
|
if (!attempt.presetApplied) {
|
|
out.rollbackFxIndex = attempt.addedFxIndex;
|
|
return out;
|
|
}
|
|
out.loaded = true;
|
|
return out;
|
|
}
|
|
|
|
bool infoNamesFxHotspot(const std::string& info) {
|
|
// See the header contract for the prefix rule and the embed-strip exclusion.
|
|
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
|