Ξ-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
+70 -18
View File
@@ -77,6 +77,18 @@ std::optional<DecodedPcm> decodeRelative(const std::string& projectDir,
return out;
}
// The ONE decode + build the reload and the bake snapshot share, so a baked render can
// never be built from a different fold than the one the user is hearing.
std::optional<SampleData> buildFromRef(const SelectedSample& sel,
const InstrumentParams& params,
const std::string& projectDir, ChannelMode mode) {
std::optional<DecodedPcm> pcm = decodeRelative(projectDir, sel.relativePath, mode);
if (!pcm) return std::nullopt;
SampleData sample = buildSampleData(resolveCapture(sel, params), std::move(*pcm));
if (!sample.playable()) return std::nullopt;
return sample;
}
} // namespace
std::string ReaSamplerProcessor::reloadInstrument() {
@@ -144,25 +156,23 @@ std::string ReaSamplerProcessor::reloadInstrument() {
channelModeExplicit_);
mode = channelMode_;
}
std::optional<DecodedPcm> pcm = decodeRelative(projectDir, sel->relativePath, mode);
if (pcm) {
sample = buildSampleData(resolveCapture(*sel, params), std::move(*pcm));
havePlayable = sample.playable();
if (havePlayable) {
// Point the built snapshot at the instance's ONE live block and seed it from
// the very PlayParams the voices latch, so an untouched knob folds to the same
// frames the build resolved and a note-on with a live block sounds identical
// to one without.
sample.live = &liveParams_;
{
// reloadMutex_ (held for this whole function) nests livePublishMutex_ here;
// publishLiveParams never holds reloadMutex_, so this is the only nesting.
std::lock_guard<std::mutex> lp(livePublishMutex_);
liveParams_.publish(instrument::engine::foldLive(sample.play));
}
builtSampleRate_.store(sample.sampleRate, std::memory_order_relaxed);
resolvedId = selId; // the concrete pick that resolved
if (std::optional<SampleData> decoded =
buildFromRef(*sel, params, projectDir, mode)) {
sample = std::move(*decoded);
havePlayable = true;
// Point the built snapshot at the instance's ONE live block and seed it from
// the very PlayParams the voices latch, so an untouched knob folds to the same
// frames the build resolved and a note-on with a live block sounds identical
// to one without.
sample.live = &liveParams_;
{
// reloadMutex_ (held for this whole function) nests livePublishMutex_ here;
// publishLiveParams never holds reloadMutex_, so this is the only nesting.
std::lock_guard<std::mutex> lp(livePublishMutex_);
liveParams_.publish(instrument::engine::foldLive(sample.play));
}
builtSampleRate_.store(sample.sampleRate, std::memory_order_relaxed);
resolvedId = selId; // the concrete pick that resolved
}
}
@@ -190,6 +200,48 @@ std::string ReaSamplerProcessor::reloadInstrument() {
return resolvedId;
}
std::string ReaSamplerProcessor::usageInstanceGuid() {
std::lock_guard<std::mutex> lock(usageMutex_);
if (instanceGuid_.empty()) instanceGuid_ = mintUsageInstanceGuid();
return instanceGuid_;
}
std::optional<SampleData> ReaSamplerProcessor::bakeSnapshot() {
const std::string selId = selectedSampleId();
if (selId.empty()) return std::nullopt;
const InstrumentParams params = instrumentParams();
SampleRefs refs;
{
std::lock_guard<std::mutex> rl(refsMutex_);
refs = sampleRefs_;
}
const SelectedSample* sel = findRef(refs, selId);
if (!sel) return std::nullopt;
// No live block is attached: the bake renders the stored parameter set, which is what
// every knob has already written, rather than whatever the audio thread is observing.
return buildFromRef(*sel, params, bridge_.activeProjectDir(), channelMode());
}
void ReaSamplerProcessor::adoptBakedCapture(const SampleRefEntry& entry,
const InstrumentParams& reset,
double masterGainLinear) {
{
std::lock_guard<std::mutex> rl(refsMutex_);
bool replaced = false;
for (SampleRefEntry& existing : sampleRefs_) {
if (existing.sampleId == entry.sampleId) { existing = entry; replaced = true; break; }
}
if (!replaced) sampleRefs_.push_back(entry);
}
setSelectedSampleId(entry.sampleId);
setInstrumentParams(reset);
setMasterGainLinear(masterGainLinear);
// ONE reload for the re-point and the reset together: it decodes the new file and
// publishes the neutral parameters in the same swap, so no block is ever rendered with
// one of the two applied and not the other.
reloadInstrument();
}
void ReaSamplerProcessor::publishUsage(const SampleRefs& refs,
const std::vector<std::string>& ids) {
if (!bridge_.isConnected()) return; // non-REAPER host / no ext-state — nothing to do