Ξ-W2-T1: the resample bake chain — instrument renders, extension banks, one click re-points and resets

This commit is contained in:
2026-08-01 16:26:28 -04:00
parent 6c982cd617
commit 60308a3655
52 changed files with 2212 additions and 55 deletions
+4
View File
@@ -69,6 +69,10 @@ sits on.
- `bank_model``Sample` 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.
- `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
+4
View File
@@ -9,6 +9,10 @@ reasampler_pure_library(bank_book
LINK PUBLIC bank_model slot_map PRIVATE json)
reasampler_test(bank_book LINK bank_book)
# Links nothing: a display name is a string rule, not a bank operation.
reasampler_pure_library(resample_name SOURCES resample_name.cpp)
reasampler_test(resample_name LINK resample_name)
reasampler_pure_library(provenance SOURCES provenance.cpp LINK PRIVATE wire)
# bank_model: the test proves the recorded recipe survives the Sample-JSON round-trip.
reasampler_test(provenance LINK provenance bank_model)
+49
View File
@@ -0,0 +1,49 @@
// See resample_name.h.
#include "core/model/resample_name.h"
#include <cctype>
namespace reasampler::model {
namespace {
constexpr const char* kFallbackStem = "resample";
constexpr int kFirstIteration = 2; // the source itself is iteration 1
// The " r<digits>" tail, if the name ends in one and the digits are a whole number > 0.
// Returns 0 (no tail) otherwise; `stemLength` is then left untouched.
int trailingIteration(const std::string& name, std::size_t& stemLength) {
std::size_t digitsBegin = name.size();
while (digitsBegin > 0 && std::isdigit(static_cast<unsigned char>(name[digitsBegin - 1])))
--digitsBegin;
if (digitsBegin == name.size()) return 0; // no digits at the end
if (digitsBegin < 2) return 0; // no room for " r"
if (name[digitsBegin - 1] != 'r' || name[digitsBegin - 2] != ' ') return 0;
// Overflow-safe accumulate: a pathological digit run stops counting rather than
// wrapping into a small number and silently reusing an existing name.
int value = 0;
for (std::size_t i = digitsBegin; i < name.size(); ++i) {
if (value > 1000000) return 0;
value = value * 10 + (name[i] - '0');
}
if (value <= 0) return 0;
stemLength = digitsBegin - 2;
return value;
}
} // namespace
std::string nextIterationName(const std::string& sourceName) {
if (sourceName.empty())
return std::string(kFallbackStem) + " r" + std::to_string(kFirstIteration);
std::size_t stemLength = sourceName.size();
const int current = trailingIteration(sourceName, stemLength);
if (current > 0)
return sourceName.substr(0, stemLength) + " r" + std::to_string(current + 1);
return sourceName + " r" + std::to_string(kFirstIteration);
}
} // namespace reasampler::model
+19
View File
@@ -0,0 +1,19 @@
#pragma once
// resample_name — the display name a resample's new bank entry takes, so a repeated bake
// reads as one iteration chain in the browser rather than as N unrelated captures.
//
// The chain is legible in the NAME only; the machine-readable lineage is
// OriginRecord::parentSampleId, written at birth. This is presentation, and deliberately
// not parsed back into a lineage by anything.
#include <string>
namespace reasampler::model {
// "Kick" -> "Kick r2" -> "Kick r3". A name already carrying an " r<N>" tail increments it
// rather than stacking a second one; an empty name becomes "resample r2". A tail that is
// not a whole positive number (" r", " r0", " rx") is left alone and a fresh " r2"
// appended, because it was never one of ours.
std::string nextIterationName(const std::string& sourceName);
} // namespace reasampler::model