Its false gated six undo points, so an unverified byte-equality assumption could have silently removed Ctrl-Z for a bank mutation that landed.
12 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, and its peerextStateWriteLanded— the ONLY verdict on whether aSetProjExtStatewrite took, because that API's return is the size of the WHOLE extname's state and cannot speak for one key. Two shells bind it, BOTH on the instrument's per-instance keys: the bake landing's answer/clear write-back and the instrument bridge's prefix-guarded writes. Deliberately NOT the persist, whose return is control flow over undo points — this verdict is a proof value reported to the user, and an observational false there would discard the Ctrl-Z for a bank mutation that landed. Itsnulloptmeans ABSENT specifically, so every caller must route an Overflow read to its own "could not check" answer rather than folding it in.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, separates "nothing wrote an outcome over our key" from a refusal — it does NOT identify a landing that never ran, since a skipped key and a rejected answer-write look identical from here) andclassifyBakeScan+kMaxRequestAgeSeconds(extension side — the per-key Land / RefuseWrongProject / ClearStale / IgnoreUnreadable / IgnoreNotARequest verdict over every open tab, stated without a REAPER type so the multi-tab matrix is unit-provable).BakeScanTally+describeBakeScan+describeBakeKeyare that same reading counted and spoken — the rationale lives at the types. The report's absence is NOT evidence the landing never ran, and no sentence either artifact prints may say it is:answeredis pass-wide and counts a QUEUED write, so a pass can answer some other key while skipping ours, or have our own answer'sSetProjExtStaterejected. The summary is therefore silent only when the pass answered somebody, left no key unanswered, and every answer was READ BACK from its own key;describeBakeKeyprints one line per enumerated key regardless, which is the only thing that names WHICH key — the counts cannot. The read-back verdict behindBakeWriteProofisext_state_read.h'sextStateWriteLanded, above.bakeLandingAfterPersistis the ONE route to aBankedlanding: everyLandverdict is assignedUnpersistedand passed through it, so no shell path — a dedup hit least of all, since its target may be an entry the same pass just added — can claim the word without the pass's persist having reported success. WhatBankedclaims is exactly thatpersistedinput — a saved project was active and the bank write was issued — and the enum comment is that claim's one home.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.