#pragma once // instrument_drop — the PURE payload-construction core of S17 drop-and-load. // // PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO VST3 SDK, // NO vendor/ includes. Standard library only (+ the pure sample_map it reuses and the // SDK-free UID macros in core/wire/reasampler_uid.h). Unit-tested outside the DAW — the same // "small pure builder + round-trip proof" pattern as assignment_request / provenance. // // -- What it is (the S17 seam, extension side) -------------------------------- // // S17 drops a bank capture onto a track's FX surface, which instantiates ReaSampler 9000 on // that track ALREADY PLAYING that capture. The injection mechanism (S-GA-DropFX revision of // PLAN.md §S17 mechanism (B)): after TrackFX_AddByName creates the instance, the extension // writes a Steinberg-format .vstpreset file whose 'Comp' chunk is the instrument's own // component state (the dragged capture pre-selected) and applies it via // TrackFX_SetPreset(track, fx, ".vstpreset") // which the SDK documents as accepting full .vstpreset paths for VST3 plug-ins. // // WHY NOT vst_chunk (the S-GA-DropFX diagnosis): TrackFX_SetNamedConfigParm's "vst_chunk" // is "base64-encoded VST-specific chunk" — for a VST3 that is REAPER's OWN wrapper framing // of the plugin state (the bytes REAPER round-trips into the RPP #include #include namespace reasampler::wire { // The 32-char uppercase-hex class-ID string of THIS build's channel-active ReaSampler 9000 // VST3 class UID — exactly what Steinberg::FUID::toString renders and what a .vstpreset // header carries (public.sdk vstpresetfile: "ASCII-encoded FUID"). On both COM-compatible // (Windows GUID byte order) and plain layouts, FUID::toString reduces to the four // INLINE_UID uint32 words printed "%08X" in order, so this derivation is platform-stable. // Sourced from the FROZEN macros in core/wire/reasampler_uid.h (the same constants the factory // registers), channel-selected by the one REASAMPLER_CHANNEL_IS_BETA bit — a beta extension // writes presets only the beta VST class accepts, preserving the S18 pairing invariant. std::string vstClassIdHex(); // Build a Steinberg VST3 preset file image (the bytes of a .vstpreset) carrying exactly one // 'Comp' chunk = `componentState`, addressed to class `classIdHex32` (32 hex chars, see // vstClassIdHex). Layout per public.sdk/source/vst/vstpresetfile.cpp, all integers // little-endian on disk: // [0] 'VST3' — header magic // [4] int32 version = 1 // [8] 32-char ASCII class ID // [40] int64 chunk-list offset (= 48 + componentState.size()) // [48] the component-state bytes — the one 'Comp' chunk's data // then 'List', int32 entry count = 1, then the entry: 'Comp', int64 offset 48, int64 size. // No 'Cont' chunk is written: the instrument is a SingleComponentEffect whose whole state is // the component stream; a controller-state chunk is optional in the container format. // Returns an empty vector when classIdHex32 is not exactly 32 chars (contract violation). std::vector buildVstPresetBytes(const std::string& classIdHex32, const std::vector& componentState); // The drop payload: a .vstpreset image for the channel-active class whose component state is // the instrument's default face with just `sampleId` picked — {selectionId = sampleId, no // zones, mono, generation 0}, exactly what a fresh instance would hold after the user // clicked that capture in the browser. The keymap builds under the product defaults (Gate + // Preserve) from the bank's own S2 intrinsics, so the sample plays MIDI-triggered // immediately (the S17 "loaded, selected, playable" verify). // // An EMPTY sampleId yields the empty-state preset ({"", no zones}) — a drop of nothing // selects nothing (the S10 silent empty state); the shell guards against this upstream, but // the pure contract is defined. // // Deterministic: the same sampleId always yields the same bytes. std::vector buildInstrumentDropPreset(const std::string& sampleId); // -- FX-drop-target classification (S-VIEW-BUG-1 / S-GA-DropFX) ---------------- // // Pure classifier for GetThingFromPoint's info string: is the point over a surface where an // instrument drop should instantiate ReaSampler 9000 on the resolved track? This is string // logic (no REAPER types), so it lives here and is unit-tested outside the DAW — the shell // (instrument_drop_win) only supplies the info bytes GetThingFromPoint filled. // // The SDK (reaper_plugin_functions.h §GetThingFromPoint) documents "fx_chain"/"fx_N" for // the FX-chain and floating-FX windows, and "tcp"/"mcp"-prefixed strings with sub-element // tokens ("tcp.mute" is the doc's example) for track-panel hits — WITH the explicit warning // that "future versions may append additional information". The FX-button sub-token itself // is undocumented; the WALTER element family names the TCP/MCP FX surfaces "tcp.fx", // "tcp.fxbyp", "tcp.fxparm", "tcp.fxembed", "mcp.fxlist", ... — all beginning "tcp.fx" / // "mcp.fx". So the hotspot rule is PREFIX-based (S-GA-DropFX: the earlier exact-token match // on "tcp.fx"/"mcp.fx" was too strict for appended info and sibling FX elements): // * "fx_" prefix — the FX-chain and floating-FX windows // * "tcp.fx" / "mcp.fx" prefix — the TCP/MCP FX button + sibling FX sub-elements // EXCEPT "tcp.fxembed" / "mcp.fxembed" — the embed-strip surface where a ReaSampler 9000 // instance draws inside the TCP/MCP. Dropping onto the existing instance's own UI must NOT // add a second instance; the embed surface is explicitly excluded even though it starts // with "tcp.fx". All other "tcp.fx*" / "mcp.fx*" tokens (fxbyp, fxparm, fxlist, ...) are // hotspots — they are FX-chain controls, not a running instance's own surface. // Bare "tcp"/"mcp" and non-FX sub-elements (e.g. "tcp.mute", "tcp.vol") are NOT hotspots. // The exact live token over the FX button remains a DAW-only fact — confirm in REAPER (a // deferred ReaScript around reaper.GetThingFromPoint(reaper.GetMousePosition()) prints it). bool infoNamesFxHotspot(const std::string& info); // The raw component-state bytes the preset carries — exposed so the round-trip test can // decode them back through the instrument's OWN reader (sample_map::deserializeComponentState) // and assert the capture is selected, proving the preset feeds the instrument exactly what // its setState expects. Not called by the shell (which uses the .vstpreset image). std::vector instrumentDropStateBytes(const std::string& sampleId); } // namespace reasampler::wire