Files
reasampler/src/core/wire/CLAUDE.md
T

9.5 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.h is the FOREVER-FROZEN VST3 class-UID header shared by the runtime FUID (reasampler_vst.h) and the .vstpreset hex string (instrument_drop) — so binary identity and preset identity cannot diverge.
  • assignment_request and sample_usage are 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 in video_processor.h and the reaper_plugin_functions.h GetProjExtState/SetProjExtState/EnumProjExtState entries). The plugin can also fetch its host context — the track/take/project it was instantiated in (opcode 0xdeadf00e). Consequence: the instrument reads the same live "reasampler" ext-state that persist writes, 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 vendored vendor/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 shell usage_scan and the prune-abort behavior are owned by the shell/persist layer, a parallel dispatch). Each VST3 instance holds a per-instance GUID persisted in ComponentState v11 (instanceGuid field; pre-v11 blobs mint the guid on first publish). At the tail of every reloadInstrument call (off audio thread) the processor publishes its held SampleRefs paths to the ext-state key rsusage_<instanceGuid> in the "reasampler" namespace via reaper_bridge::writeUsageExtState — an entry point that is prefix-guarded (accepts only rsusage_-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's rsbake_ request key is the other), a deliberate exception analogous to assignment_request on 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 reports counted — the live records still attributed to their rsusage_* keys — because the flattened path list cannot answer "who holds this"; counted is empty whenever abortPrune is 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: ownerNonce is 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 in docs/TODO.md ("Persist ReaSampler 9000 instance identity…").

Modules

  • wire (core/wire) — the ONE length-prefixed ext-state wire codec (Q-W1): putField/parseUnsignedDecimal + the bounds-checked Cursor (field/fieldInt/fieldInt64/fieldSizeT/fieldDouble), replacing four near-identical copies (provenance / assignment_request / sample_usage / bank_sync). core/wire/bytes.h is the sibling little-endian byte codec (putLE, ByteReader, doubleToBits/bitsToDouble) that component_state_io is the biggest consumer of. core/wire/ext_state_read.h owns the GetProjExtState grow-loop retry policy (Absent/Complete/Overflow) shared by persist, usage_scan, and reaper_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 the REASAMPLER_ACTIVE_UID_* channel-selector macros. Split out of reasampler_vst.h so the pure extension side (instrument_drop) can derive the .vstpreset class-ID hex string without pulling in the VST3 SDK. Both reasampler_vst.h (runtime FUID) and instrument_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 the ingest shell through to the VST3 bridge.
  • bake_wire — the resample bake's request/outcome pair on ONE per-instance key (rsbake_<guid>): the instrument writes a BakeRequest, invokes the extension's action synchronously, and reads the extension's BakeOutcome back 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 underscore NamedCommandLookup needs but rec->Register("command_id", …) does not, so both artifacts name one action. BakeStatus values are WIRE INTEGERS: never renumber, only append, and an unrecognized value decodes as Failed rather than as the numeric default Ok.
  • instrument_drop — pure FX-drop payload builder: constructs a Steinberg-format .vstpreset image (channel-active class ID + the instrument's own component state, capture pre-selected) the shell applies via TrackFX_SetPreset; owns classifyReaperSurface, the prefix classifier mapping a GetThingFromPoint (info token, track-present) pair onto core/ui/drag_out's ReaperSurface. Classifier ordering is load-bearing: the embed strip is matched before the tcp/mcp panel family, which now claims the WHOLE track panel rather than just its FX sub-elements. All-or-nothing contract — caller rolls back via TrackFX_Delete on 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; counted carries key-attributed live records), identityMatches (ReaSampler 9000 FX identity). REAPER-free, unit-tested. The mirror of assignment_request on 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 by core/tracking's authority, which owns every consumer-facing decision built on it.

Gotchas

  • bytes.h, ext_state_read.h, and reasampler_uid.h are header-only (no sibling .cpp / no dedicated _tests target 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_usage is deliberately silent on liveness enumeration (which FX instances are currently live) — that scan lives in shell/persist's usage_scan, not here. This directory owns only the wire format and the two pure fold/collision decisions.