feat(S8): ingest through the bank — capture/import/drop into bank + assign to active instance
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
// assignment_request — the pure core of the S8 ingest assignment-request seam.
|
||||
//
|
||||
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO VST3,
|
||||
// NO vendor/ includes. Standard library only. Unit-tested outside the DAW — the same
|
||||
// "small pure type + length-prefixed round-trip" pattern as provenance / owned_manifest.
|
||||
//
|
||||
// -- What it is --------------------------------------------------------------
|
||||
//
|
||||
// When the EXTENSION ingests a sample (S8: arrange capture / Media-Explorer import /
|
||||
// drop-onto-panel) it writes an ASSIGNMENT REQUEST to its own "reasampler" ext-state
|
||||
// namespace: "the active sampler instance should now play THIS sample." The value
|
||||
// names the ingested sample by (bankId, sampleId) plus a monotonic `generation` the
|
||||
// reader compares to decide the request is NEW (a fresh ingest, even of the same id).
|
||||
//
|
||||
// This module owns ONLY the value's WIRE FORMAT — build/parse round-trip. Writing it
|
||||
// to ext-state is the persist shell's job; READING it is the instrument's job in a
|
||||
// LATER dispatch (S8 instrument-side follow-up, after S10 merges). This is why the
|
||||
// format is documented here in the header, not just in code: the reader lands elsewhere
|
||||
// and must decode exactly what this writer produced.
|
||||
//
|
||||
// -- The data-ownership boundary (load-bearing) ------------------------------
|
||||
//
|
||||
// The EXTENSION writes this; the instrument only READS it. That does not violate the
|
||||
// instrument's read-only-over-the-bank rule: the assignment request is the extension
|
||||
// writing its OWN namespace (a request FROM the extension TO the instrument), never the
|
||||
// instrument writing back into the bank. The instrument, on reading a new generation,
|
||||
// updates its OWN component-state selection (the same selection S4 persists) and reloads.
|
||||
//
|
||||
// -- Why `generation` -------------------------------------------------------
|
||||
//
|
||||
// Instances reference sample IDs, so re-assigning the SAME id (e.g. a recapture, or a
|
||||
// re-drop of the same file) would be indistinguishable from a stale value without a
|
||||
// changing field. `generation` is a monotonic disambiguator (the ingest writer supplies
|
||||
// a wall-clock unix-epoch stamp today — see the writer shell) so the reader can tell
|
||||
// "assigned again just now" from "already saw this." It is DELIBERATELY the same shape
|
||||
// the S9 bank-generation counter will use, but it is NOT that counter — S9 is a separate
|
||||
// point; this field is self-contained to the request and does not depend on S9 landing.
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
// One assignment request: the ingested sample's identity + a monotonic disambiguator.
|
||||
// bankId — the bank the sample was ingested into (the active/target bank).
|
||||
// sampleId — the ingested Sample's stable id (BankIndex key).
|
||||
// generation — a monotonic value the reader compares to detect a NEW request. The
|
||||
// writer supplies a unix-epoch-seconds stamp; the reader treats it as an
|
||||
// opaque "did this change?" token, not a wall-clock it interprets.
|
||||
struct AssignmentRequest {
|
||||
std::string bankId;
|
||||
std::string sampleId;
|
||||
std::int64_t generation = 0;
|
||||
|
||||
bool operator==(const AssignmentRequest& o) const {
|
||||
return bankId == o.bankId && sampleId == o.sampleId &&
|
||||
generation == o.generation;
|
||||
}
|
||||
bool operator!=(const AssignmentRequest& o) const { return !(*this == o); }
|
||||
};
|
||||
|
||||
// Encode an assignment request to the wire string. Length-prefixed fields behind a
|
||||
// magic+version tag ("rsassign1"), so arbitrary bytes in an id (a GUID, a display-
|
||||
// derived id) round-trip whole with no escaping ambiguity — the same idiom provenance
|
||||
// uses. Deterministic: the same request always yields the same string.
|
||||
//
|
||||
// FORMAT (documented for the LATER instrument-side reader):
|
||||
// "rsassign1" <len>':'<bankId> <len>':'<sampleId> <len>':'<generation-decimal>
|
||||
// where each <len> is the decimal byte length of the field that follows the ':'.
|
||||
std::string encodeAssignmentRequest(const AssignmentRequest& req);
|
||||
|
||||
// Parse a wire string produced by encodeAssignmentRequest. std::nullopt on any
|
||||
// malformed / truncated / trailing-garbage input (never UB, never a partial value) —
|
||||
// the reader shell treats absence/malformed as "no pending request." Round-trips:
|
||||
// decodeAssignmentRequest(encodeAssignmentRequest(x)) == x.
|
||||
std::optional<AssignmentRequest> decodeAssignmentRequest(const std::string& wire);
|
||||
|
||||
} // namespace reasampler
|
||||
Reference in New Issue
Block a user