61 lines
3.4 KiB
C++
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
|