Restore the bank fold and usage publish to the resume path, guard setActive against repeats, and make the meter fold's bound literal

The resume also hands back to a full reload when the fold moves the loaded capture's decode source, so the refs table and the audio cannot skew.
This commit is contained in:
2026-08-02 13:15:47 -04:00
parent 5c6525fb91
commit da14509ab5
15 changed files with 218 additions and 76 deletions
+39 -4
View File
@@ -268,8 +268,7 @@ void ReaSamplerProcessor::publishUsage(const SampleRefs& refs,
}
void ReaSamplerProcessor::publishBuiltLocked(std::unique_ptr<LoadedInstrument> built) {
// REQUIRES reloadMutex_ held. Shared by reloadInstrument and rebuildVoiceEngine — the
// one safety-critical swap dance (see the header's drain-slot proof).
// REQUIRES reloadMutex_ held (see the header's drain-slot proof).
const std::uint64_t seen = processGeneration_.load(std::memory_order_acquire);
graveyard_.erase(
std::remove_if(graveyard_.begin(), graveyard_.end(),
@@ -347,9 +346,45 @@ bool ReaSamplerProcessor::resumeDormantInstrument() {
// park. Rebuilding here would displace a correct instrument into the drain slot.
if (live_.load(std::memory_order_acquire)) return true;
if (!dormantSample_) return false;
// The activation is still where a bank change made with NO EDITOR OPEN is picked up:
// pollBankSync, the only other route to either of the two calls below, runs off the
// editor's sync tick and nothing else. Both are a GetProjExtState plus a parse — no disk
// and no decode, which is what lets them stay on a path whose whole point is skipping
// those two.
const std::string selId = selectedSampleId();
const std::vector<std::string> ids = referencedSampleIds(selId);
SampleRefs refs;
bool sourceMoved = false;
{
std::optional<std::string> banksJson =
bridge_.readReasamplerExtState(kProjExtBanksKey);
std::lock_guard<std::mutex> rl(refsMutex_);
if (banksJson) {
const SelectedSample* before = findRef(sampleRefs_, selId);
const std::optional<SelectedSample> was =
before ? std::optional<SelectedSample>(*before) : std::nullopt;
refreshRefsFromBank(sampleRefs_, *banksJson, ids);
const SelectedSample* now = findRef(sampleRefs_, selId);
sourceMoved = !was || !now || !sameDecodeSource(*was, *now);
}
refs = sampleRefs_;
}
// A recapture that landed while this instance was inactive makes the park the WRONG audio,
// and refreshing the refs without re-decoding would leave the table naming one file while
// the voices play another. Hand back to the full reload, which decodes the new one.
if (sourceMoved) return false;
// MOVED, not copied: the park exists for this one handoff, and keeping it would hold a
// second copy of the PCM for the whole active lifetime.
publishBuiltLocked(buildInstrumentLocked(std::move(*dormantSample_)));
// second copy of the PCM for the whole active lifetime. Disengaged BEFORE the build so a
// throwing build leaves nothing to resume — the next activation then takes the reload
// rather than publishing an empty sample as permanent silence.
SampleData resumed = std::move(*dormantSample_);
dormantSample_.reset();
publishBuiltLocked(buildInstrumentLocked(std::move(resumed)));
// The prune-protection republish reloadInstrument owes on every publish: it is also what
// heals an rsusage_ key whose write failed when this instance last set its state.
publishUsage(refs, ids);
return true;
}