Ξ-W2-T1: the resample bake chain — instrument renders, extension banks, one click re-points and resets
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user