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

6.0 KiB

src/core/model — the pure bank/sample index and its multi-bank container

Scope

Pure (REAPER-free, unit-tested outside the DAW) sample-index models: the single-bank index, the multi-bank registry that wraps it, its JSON codec, the gap-preserving per-bank slot carrier, and the capture-recipe fingerprint. No REAPER types, no filesystem I/O — see root CLAUDE.md for the pure-core/shell split this directory sits on.

Invariants

Pool privileges (multi-bank).

  • The pool is privileged, not special-cased: structurally one BankIndex among many in bank_book; semantically it always exists, is un-deletable, and un-renamable (fixed id + fixed display name "Pool"). New projects and migrated single-bank projects start with the pool and zero named banks. Enforced in the pure rules layer, not just the UI.
  • No action path may delete or rename the pool, leave a project with zero banks, or evacuate the pool (the pool is evacuation's destination, not a source).

Bank identity, movement, dedup.

  • Bank id is the stable key (GUID-style, minted on create); display name and ordinal are mutable. Display names are unique — trimmed + case-insensitive (ASCII) — enforced by createBank/renameBank; the pool's reserved name "Pool" is protected by the same check.
  • Movement moves the index entry, not the file: move/copy between banks is index-only (remove from A's BankIndex, add to B's); the underlying file stays in the shared project bank folder. Per-bank subfolders on disk are an explicit non-goal.
  • Dedup-by-hash is per-bank. Moving a sample whose hash already exists in the destination bank collapses onto the existing entry there. Cross-bank dedup is not enforced — the same hash may exist in the pool and a named bank simultaneously.
  • Move is the default (removes from source, adds to destination); copy is the deliberate secondary act (adds to destination, leaves source intact).
  • Delete drops members (files are not deleted); evacuate returns all of a bank's members to the pool (index-only, same destination-collapse rule). Evacuate cannot be applied to the pool. A plain delete of a non-empty bank orphans those members out of every index until prune reclaims their files — the UI confirms on non-empty delete and offers evacuate as the alternative.

Sample removal.

  • Remove is index-only: drops one Sample entry from one BankIndex; mutates only index + ext-state, no file written/moved/deleted, no timeline item touched.
  • Remove can orphan a file — the same designed orphaned-until-prune state a non-empty delete-bank produces — when it drops the last index reference to a file. Reclaimed later by prune, never by remove.
  • The pool's contents are removable; the pool container is not. Remove-from-pool is allowed.
  • Remove scope is this-bank only (settled 2026-07-24): drops the entry from this bank, leaving copies in other banks untouched. scope: this-bank | all-banks is a latent seam; only this-bank is a surfaced verb.
  • Removes are silent — no confirm dialog. Recoverability comes from batched REAPER undo (Undo_BeginBlock/Undo_EndBlock): one Ctrl-Z restores the index entry. This undo-batching is Phase-B-wide (create/rename/reorder/delete-bank, move, copy, evacuate, and remove all batch this way). hashReferencedElsewhere is a tested model API retained for Phase R prune; it has no shell caller in the remove path.

Precision implications.

  • Relative-paths-only survives unchanged: every BankIndex in the book keeps the relative-path invariant at its add boundary; movement is index-only so files never relocate.
  • Non-destructive: bank create/rename/delete/activate/evacuate and sample move/copy/remove mutate only index + ext-state; no file is written, moved, or deleted, and no timeline item is touched.

Modules

  • bank_modelSample metadata struct + BankIndex (add/remove/query/tier/dedup-by-hash + JSON round-trip). Test it hard — it is the heart.
  • bank_book — multi-bank registry: an ordered set of banks each wrapping a BankIndex. Pool privileges (un-deletable/un-renamable/un-evacuable, never zero banks) enforced in-model. Owns create/rename/reorder/delete of named banks, active-bank id, and index-only move/copy/remove of a sample between banks. The JSON round-trip lives in the sibling bank_book_json TU (Q-W5 split; serialize/deserialize via a private static nameKey seam) — one model, one codec, same public surface.
  • slot_map (core/model) — the gap-preserving display-position carrier for ONE bank (sample id → slot, ≥0), extracted from bank_book (Q-W1): append/remove/reorder (insert-before-and-shift)/reconcile against live membership, resetDense migration seed, JSON round-trip. Wrapped (not merged) by bank_book.
  • resample_name — the display name a resample's new bank entry takes, so repeated bakes read as one iteration chain (Kick -> Kick r2 -> Kick r3) rather than as N unrelated captures. Presentation only: the machine-readable lineage is the ledger's parentSampleId, and nothing parses this name back into one. The rule is per-NAME, not per-bank — two add-distinct bakes of the same source both land as "<name> r2", so display names are not unique and a browser reading the chain must read the ledger.
  • provenance — capture-recipe fingerprint: build/encode/compare a rsprov1 fingerprint of scope, range, tail, rate/channels, track GUIDs, and FX-chain identity. A thin reproducibility fingerprint — NOT a serialized chain to restore. It is the recipe facet of the tracking system whose authority lives in core/tracking; the lineage facet (which file derives from which) is the ledger's, not the Sample's.

Gotchas

  • bank_book wraps BankIndex; it does not modify it (additive — no bank-id field on Sample). Do not add per-bank subfolders on disk or a global cross-bank dedup — both are rejected-in-review non-goals.
  • bank_book_json is a sibling TU, not a separate module — its round-trip is part of bank_book's public surface, not a distinct thing to describe separately.