Resolve ReaSampler 9000 samples against the instance's own project, not the focused tab

sampleRoots matches getReaperParent(3) among EnumProjects' open projects; every instrument sample read goes through an ordered root list (one root today) so a later temp-bank root slots in after it.
This commit is contained in:
2026-09-25 14:55:50 -04:00
parent c3e7556261
commit 0976ea13a8
11 changed files with 304 additions and 42 deletions
+13 -15
View File
@@ -17,10 +17,10 @@
#include <utility>
#include <vector>
#include "core/capture/capture_paths.h" // resolveBankFile (shared path resolution)
#include "core/capture/wav_codec.h" // parseWavLayout, extractFloatFrames (shared WAV parse)
#include "core/instrument/map/bank_sync.h" // pure decisions: parseBankGeneration, consumeDecision
#include "core/instrument/map/sample_map.h" // refs resolve, buildSampleData (self-contained)
#include "core/instrument/map/sample_roots.h" // readFromFirstRoot (own-project resolution)
#include "core/util/file_bytes.h" // shared whole-file loader
#include "core/wire/assignment_request.h" // decodeAssignmentRequest (request wire parse)
#include "core/wire/sample_usage.h" // usage publish plan + wire (prune-protection seam)
@@ -32,7 +32,6 @@ using namespace instrument::map; // resolution + bank-sync vocabulary this TU
using namespace reasampler::wire; // assignment_request + sample_usage wire records
using capture::extractFloatFrames;
using capture::parseWavLayout;
using capture::resolveBankFile;
using capture::WavLayout;
using util::readFileBytes;
@@ -58,16 +57,15 @@ std::string mintUsageInstanceGuid() {
return std::string(buf);
}
// Resolves a project-relative WAV path, reads + decodes it (file I/O, off-thread only),
// and applies the cross-mode channel policy for `mode` (mono downmix; stereo -> dual-mono
// for a mono source, L/R for a stereo source — see decodeChannels). Returns nullopt on any
// resolve/read/decode failure — the caller plays silence.
std::optional<DecodedPcm> decodeRelative(const std::string& projectDir,
// Resolves a project-relative WAV path against the instance's sample roots, reads + decodes
// it (file I/O, off-thread only), and applies the cross-mode channel policy for `mode` (mono
// downmix; stereo -> dual-mono for a mono source, L/R for a stereo source — see
// decodeChannels). Returns nullopt on any resolve/read/decode failure — the caller plays
// silence.
std::optional<DecodedPcm> decodeRelative(const std::vector<std::string>& roots,
const std::string& relativePath,
ChannelMode mode) {
const std::string abs = resolveBankFile(projectDir, relativePath);
if (abs.empty()) return std::nullopt;
const std::vector<std::uint8_t> bytes = readFileBytes(abs);
const std::vector<std::uint8_t> bytes = readFromFirstRoot(roots, relativePath, readFileBytes);
const WavLayout layout = parseWavLayout(bytes);
if (!layout.valid) return std::nullopt;
std::vector<AudioSample> interleaved =
@@ -82,9 +80,9 @@ std::optional<DecodedPcm> decodeRelative(const std::string& projectDir,
// never be built from a different fold than the one the user is hearing.
BuiltCapture buildFromRef(const SelectedSample& sel,
const InstrumentParams& params,
const std::string& projectDir,
const std::vector<std::string>& roots,
ChannelMode mode, int renderRate) {
std::optional<DecodedPcm> pcm = decodeRelative(projectDir, sel.relativePath, mode);
std::optional<DecodedPcm> pcm = decodeRelative(roots, sel.relativePath, mode);
if (!pcm) return {};
if (!sourceRateSupported(pcm->sampleRate, renderRate)) return {std::nullopt, true};
SampleData sample =
@@ -125,7 +123,7 @@ std::string ReaSamplerProcessor::reloadInstrument() {
// (getState filters via retainRefs to what the instance plays).
refs = sampleRefs_; // snapshot for the decode below (outside the refs lock)
}
const std::string projectDir = bridge_.activeProjectDir();
const std::vector<std::string> roots = bridge_.sampleRoots();
// Governs how the WAV decodes (mono downmix vs 2-channel); auto-defaulted from the
// capture's own channel count below, before the decode.
ChannelMode mode = channelMode();
@@ -152,7 +150,7 @@ std::string ReaSamplerProcessor::reloadInstrument() {
channelModeExplicit_);
mode = channelMode_;
}
BuiltCapture decoded = buildFromRef(*sel, params, projectDir, mode, hostRenderRate());
BuiltCapture decoded = buildFromRef(*sel, params, roots, mode, hostRenderRate());
rateRefused = decoded.rateRefused;
if (decoded.sample) {
sample = std::move(*decoded.sample);
@@ -220,7 +218,7 @@ BuiltCapture ReaSamplerProcessor::bakeSnapshot(int renderRate) {
if (!sel) return {};
// 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(), renderRate);
return buildFromRef(*sel, params, bridge_.sampleRoots(), channelMode(), renderRate);
}
void ReaSamplerProcessor::adoptBakedCapture(const SampleRefEntry& entry,