Files
reasampler/src/core/instrument/map/bank_sync.h
T

61 lines
3.4 KiB
C++

#pragma once
// bank_sync — decision logic for bank-generation change-detection and the instrument-side
// assignment-request consume. The instrument polls two "reasampler" ext-state keys off the
// audio thread: the bank-generation counter (has the bank changed?) and the assignment
// request (should I switch to a just-ingested sample?). The shell reads the raw strings and
// owns cadence (a UI-thread timer, never `process`) + side effects (reloadInstrument,
// setSelectedSampleId); this module owns only the yes/no decisions, so they're provable
// without a host. assignment_request.h owns the wire format; this owns the consume decision.
#include <cstdint>
#include <optional>
#include <string>
#include "core/wire/assignment_request.h" // AssignmentRequest (the decoded request this consumes)
namespace reasampler::instrument::map {
using wire::AssignmentRequest;
// Default for a project with no bank_generation key yet (pre-existing project); the first
// real bump (>= 1) then reads as a change against this.
inline constexpr std::int64_t kBankGenerationAbsent = 0;
// Absent / empty / malformed / negative / overflowing all yield kBankGenerationAbsent (0),
// never a crash or spurious reload. Whole-string parse: trailing garbage rejects the value,
// so a torn/partial write is ignored until the next clean poll.
std::int64_t parseBankGeneration(const std::string& raw);
// Inverse of parseBankGeneration: plain decimal, no sign, no padding — byte-stable across
// writes of the same value.
std::string formatBankGeneration(std::int64_t generation);
// True when `current` differs from `seen` (not just increases — a project switch/reload can
// legitimately lower the value, and the reader should still re-read the bank).
bool bankGenerationChanged(std::int64_t seen, std::int64_t current);
// Verdict of the assignment-request consume decision below. apply and consumedGeneration
// advancing are NOT the same event — a request naming an unresolvable sample is dropped
// (consumed-as-seen) without applying, so the shell doesn't re-evaluate it every poll.
struct AssignConsumeDecision {
bool apply = false; // set this instance's selection to (bankId, sampleId) + reload
std::string bankId; // the request's bank (valid only when apply)
std::string sampleId; // the request's sample (valid only when apply)
std::int64_t consumedGeneration = 0; // the marker to persist (== lastConsumed when nothing new)
};
// `lastConsumed` persists across reopen so a request already applied and manually changed
// away from is not reapplied. `resolves` is whether (bankId, sampleId) exists in the live
// bank right now. `isFocusedTarget` gates thundering-herd (only the focused-editor instance
// applies; others neither apply nor advance their marker, staying eligible if focus moves).
//
// Rules, in order: (1) no request or generation <= lastConsumed -> no-op. (2) new but not
// the target -> no-op, marker unchanged (stays eligible later). (3) new, target, but doesn't
// resolve -> drop silently, marker still advances (consumed-as-seen, never re-evaluated).
// (4) new, target, resolves -> apply + advance marker.
AssignConsumeDecision consumeDecision(const std::optional<AssignmentRequest>& request,
std::int64_t lastConsumed, bool resolves,
bool isFocusedTarget);
} // namespace reasampler::instrument::map