A no-answer stays a failure; it now says whether the extension never ran the landing, answered a stale generation, spoke a wire this build cannot read, cleared the request, or refused it.
10 KiB
src/core/wire — pure ext-state and wire-format codecs, cross-artifact contracts
Scope
The lexical/wire layer underneath the domain grammars: length-prefixed ext-state field
codecs, the little-endian byte codec, the GetProjExtState grow-loop retry policy, the
FOREVER-FROZEN VST3 class-UID macros, and the two mirror-image cross-artifact wires
(assignment_request extension→instrument, sample_usage instrument→extension) plus
the FX-drop payload builder (instrument_drop). Domain grammars themselves (what the
fields mean) stay in their own modules (bank_model, sample_map, provenance,
bank_sync, …) — this directory owns lexing/emitting/marshalling only, not domain
semantics.
This directory owns two cross-artifact contracts specifically:
reasampler_uid.his the FOREVER-FROZEN VST3 class-UID header shared by the runtimeFUID(reasampler_vst.h) and the.vstpresethex string (instrument_drop) — so binary identity and preset identity cannot diverge.assignment_requestandsample_usageare mirror-image wires: the former carries the drop payload extension→instrument, the latter carries usage records instrument→extension.
Invariants
-
The VST-host bridge (the integration mechanism, stated once here). A VST3 hosted inside REAPER can call back into REAPER's own API by resolving function pointers by name over the host callback (
hostcb(&effect, 0xdeadbeef, 0xdeadf00d, 0, "FunctionName", 0.0)— the same string-keyed API table the extension uses; verified invideo_processor.hand thereaper_plugin_functions.hGetProjExtState/SetProjExtState/EnumProjExtStateentries). The plugin can also fetch its host context — the track/take/project it was instantiated in (opcode0xdeadf00e). Consequence: the instrument reads the same live"reasampler"ext-state thatpersistwrites, follows the active project, and needs no "point me at the bank folder" wiring — it asks REAPER which project it is in. Confirm the bridge opcodes and by-name resolution against the vendoredvendor/reaper-sdk/sdk/headers (reaper_plugin.h,video_processor.h,reaper_plugin_functions.h) before relying on new opcodes. -
Instance-usage wire —
rsusage_<instanceGuid>(pS-usage; pure portions only — the extension-side scan shellusage_scanand the prune-abort behavior are owned by theshell/persistlayer, a parallel dispatch). Each VST3 instance holds a per-instance GUID persisted inComponentStatev11 (instanceGuidfield; pre-v11 blobs mint the guid on first publish). At the tail of everyreloadInstrumentcall (off audio thread) the processor publishes its heldSampleRefspaths to the ext-state keyrsusage_<instanceGuid>in the"reasampler"namespace viareaper_bridge::writeUsageExtState— an entry point that is prefix-guarded (accepts onlyrsusage_-prefixed keys, refuses all others), so the read-only-bank invariant is structurally enforced. Direction: the instrument writes usage keys; the extension reads them — one of the two sanctioned instrument→ext-state writes (bake_wire'srsbake_request key is the other), a deliberate exception analogous toassignment_requeston the other wire. Usage records are never cleared by the instrument at teardown (REAPER destroys the plugin instance when an FX chain is set offline, including Design View's CPU-park, so a terminate-time clear would strip a still-live instance's record); liveness is decided extension-side at prune-scan time.- The pure fold (
sample_usage::foldUsageRecords/usageHeldPaths). A record counts iff its publishing track still hosts at least one instance (offline FX included); a record with no track context counts while any instance exists; and when records exist but zero instances were identified, every record's paths are protected (identity-failure net — a matcher failure must never degrade toward delete). The guarantee: a capture held by any live instance can never be deleted; if the prune cannot determine with certainty which captures are held, it aborts entirely (deletes nothing). The fold reportscounted— the live records still attributed to theirrsusage_*keys — because the flattened path list cannot answer "who holds this";countedis empty wheneverabortPruneis set, since attribution is exactly what an unreadable record destroys. - Collision safety (
planUsagePublish). A persisted GUID is copyable (FX copy / track duplication).ownerNonce— a per-lifetime nonce minted fresh in memory at instance creation, never persisted — proves "exactly this incarnation wrote the key last."unioned— a sticky multi-writer poison: once a same-track sibling is detected, the key enters union-forever mode (holds only accumulate, never drop). Resolution always leans over-protect: same-nonce + not-unioned → clean replace; same-track foreign nonce or unioned → union; cross-track foreign nonce → remint under a fresh key. None of the three directions can under-protect. - Deferred follow-up:
ownerNonceis not persisted, so after save→reopen an instance cannot recognize its own prior-session usage record and unions forever. Reasoning, constraints, and the rejected candidates live indocs/TODO.md("Persist ReaSampler 9000 instance identity…").
- The pure fold (
Modules
wire(core/wire) — the ONE length-prefixed ext-state wire codec (Q-W1):putField/parseUnsignedDecimal+ the bounds-checkedCursor(field/fieldInt/fieldInt64/fieldSizeT/fieldDouble), replacing four near-identical copies (provenance/assignment_request/sample_usage/bank_sync).core/wire/bytes.his the sibling little-endian byte codec (putLE,ByteReader,doubleToBits/bitsToDouble) thatcomponent_state_iois the biggest consumer of.core/wire/ext_state_read.howns theGetProjExtStategrow-loop retry policy (Absent/Complete/Overflow) shared bypersist,usage_scan, andreaper_bridge.core/wire/reasampler_uid.h(the FOREVER-FROZEN VST3 class-UID macros) also lives in this directory.reasampler_uid.h— SDK-free header owning the FOREVER-FROZEN VST3 class-UID integer macros (stable + beta pairs,REASAMPLER_PROC_UID_*/REASAMPLER_PROC_UID_BETA_*) and theREASAMPLER_ACTIVE_UID_*channel-selector macros. Split out ofreasampler_vst.hso the pure extension side (instrument_drop) can derive the.vstpresetclass-ID hex string without pulling in the VST3 SDK. Bothreasampler_vst.h(runtimeFUID) andinstrument_drop(preset hex string) source from this single header — the binary identity and the preset-file identity cannot diverge.assignment_request— pure ingest-assign wire: typed request record carrying the drop payload from theingestshell through to the VST3 bridge.bake_wire— the resample bake's request/outcome pair on ONE per-instance key (rsbake_<guid>): the instrument writes aBakeRequest, invokes the extension's action synchronously, and reads the extension'sBakeOutcomeback over the same key inside that one call. Not a handshake — a call and a return, and it must not grow a claim protocol. Also the ONE home of the bake action's command-id suffix and of the leading underscoreNamedCommandLookupneeds butrec->Register("command_id", …)does not, so both artifacts name one action.BakeStatusvalues are WIRE INTEGERS: never renumber, only append, and an unrecognized value decodes asFailedrather than as the numeric defaultOk. It owns BOTH ends' reading of that key, since the key's contents are the only evidence either side gets:classifyBakeAnswer(instrument side — six kinds, of whichUnanswered, the request still sitting there untouched, is what separates an extension that never ran the landing from one that refused) andclassifyBakeScan+kMaxRequestAgeSeconds(extension side — the per-key Land / RefuseWrongProject / ClearStale / Ignore verdict over every open tab, stated without a REAPER type so the multi-tab matrix is unit-provable).instrument_drop— pure FX-drop payload builder: constructs a Steinberg-format.vstpresetimage (channel-active class ID + the instrument's own component state, capture pre-selected) the shell applies viaTrackFX_SetPreset; ownsclassifyReaperSurface, the prefix classifier mapping aGetThingFromPoint(info token, track-present) pair ontocore/ui/drag_out'sReaperSurface. Classifier ordering is load-bearing: the embed strip is matched before thetcp/mcppanel family, which now claims the WHOLE track panel rather than just its FX sub-elements. All-or-nothing contract — caller rolls back viaTrackFX_Deleteon any failure.sample_usage— instance-usage wire:UsageRecord,planUsagePublish(fresh/heal/clean-replace/union/remint publish plan),foldUsageRecords/usageHeldPaths(liveness fold — protect-all when records exist but no instance is live; abort→protect-all on unreadable record;countedcarries key-attributed live records),identityMatches(ReaSampler 9000 FX identity). REAPER-free, unit-tested. The mirror ofassignment_requeston the instrument→extension direction: the wire format and the two safety-critical decisions (what to write on publish, which records count at prune time) are pure so they are provable without a DAW. It lives here because it is a wire format with an instrument-side writer; the fold's output is consumed bycore/tracking's authority, which owns every consumer-facing decision built on it.
Gotchas
bytes.h,ext_state_read.h, andreasampler_uid.hare header-only (no sibling.cpp/ no dedicated_teststarget of their own) — they are consumed directly by the modules named in their bullets above; don't go looking for a standalone build target for them.sample_usageis deliberately silent on liveness enumeration (which FX instances are currently live) — that scan lives inshell/persist'susage_scan, not here. This directory owns only the wire format and the two pure fold/collision decisions.