8.9 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 — the one sanctioned instrument→ext-state write, 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). Over-protection is the accepted residual; under-protection is a data-loss bug. 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 (TODO.md, deliberately NOT absorbed by the tracking
consolidation):
ownerNonceis not persisted, so after save→reopen an instance cannot recognize its own prior-session usage record — it unions and marks the recordunionedforever, so prune stops reclaiming captures the instance once held but no longer uses (safe, but the bank folder grows unbounded). This is a completeness wart, not a safety one; every candidate fix examined so far trades it for a new under-protection window, which the consolidation's own safety mandate forbids. Seedocs/TODO.mdfor the constraint and the rejected session-epoch candidate.
- 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.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; owns theinfoNamesFxHotspotprefix classifier forGetThingFromPointtokens. 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.