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
+3 -1
View File
@@ -45,7 +45,8 @@ subdirectories:
- **File seam (audio, permanent).** The sample **audio** is the on-disk 32-bit-float
WAVs — project-relative, travelling with the `.rpp`. The instrument resolves those
paths the same way `persist` does (a shared convention, not a re-implementation).
paths the same way `persist` does (a shared convention, not a re-implementation),
against the project the instance lives in (`map/sample_roots`).
There is no live PCM stream across the bridge, by design.
- **Live-state seam (the mapping, via the bridge).** For everything that is not raw
audio — the bank index, the mapping, which project is active — the instrument reads
@@ -367,6 +368,7 @@ anything for a trigger shape.
- `params_payload` — the PARAMS-PAYLOAD half of that codec, split from the envelope half on the axis the format already has: the payload carries its own version and grows independently, so the two version ladders are two responsibilities. An INTERNAL seam — the public entry points stay `serialize`/`deserializeComponentState`. The prose ladder and every version constant stay in `component_state_io.h`, their one home.
- `bank_sync` — generation change-detection + assignment-request consume: owns the yes/no decision logic so the rules are provable without a host. The processor shell owns cadence and side effects.
- `bridge_marshal` — pure marshalling helper for the REAPER VST-host bridge read: interprets the `GetProjExtState` int return against its filled buffer.
- `sample_roots` — where a bank-relative sample path resolves: `ownProjectDir` picks the instance's own project out of the shell's enumeration BY IDENTITY (no fallback to any other project), `sampleRootCandidates` orders the roots to try, and `readFromFirstRoot` reads the first that yields bytes. The shell half and its rationale are `shell/instrument`'s.
- `trigger_seam` — the shared Trigger play-SPAN formula: how a %-length becomes the source-frame span the overlay draws over and the bake's window holds, threading `startFrame` correctly and clamping the fraction the same way `Voice::start` does (the engine evaluates the same formula inline rather than depending on `map/`). The spline fold every consumer must apply first — `effectiveLengthFraction` — is `play_params.h`'s, beside the rest of that rule family. (Its fade frames↔fraction converters retired with the fade pair itself.)
### `ui/`
+3
View File
@@ -1,6 +1,9 @@
reasampler_pure_library(bridge_marshal SOURCES bridge_marshal.cpp)
reasampler_test(bridge_marshal LINK bridge_marshal)
reasampler_pure_library(sample_roots SOURCES sample_roots.cpp LINK PRIVATE capture_paths)
reasampler_test(sample_roots LINK sample_roots)
reasampler_pure_library(trigger_seam SOURCES trigger_seam.cpp)
# Plain frame arithmetic over doubles: no engine, no value layer, no editor geometry. The
# %-length fold it used to host lives with its siblings in play_params.h.
+34
View File
@@ -0,0 +1,34 @@
// sample_roots.cpp — see sample_roots.h.
#include "core/instrument/map/sample_roots.h"
#include "core/capture/capture_paths.h" // projectDirOfRpp / resolveBankFile (persist's convention)
namespace reasampler::instrument::map {
std::string ownProjectDir(const void* own, const std::vector<ProjectTab>& open) {
if (!own) return {};
for (const ProjectTab& tab : open) {
if (tab.project == own) return capture::projectDirOfRpp(tab.rppPath);
}
return {};
}
std::vector<std::string> sampleRootCandidates(const std::string& projectDir) {
if (projectDir.empty()) return {};
return {projectDir};
}
std::vector<std::uint8_t> readFromFirstRoot(const std::vector<std::string>& roots,
const std::string& relativePath,
const FileReader& read) {
for (const std::string& root : roots) {
const std::string abs = capture::resolveBankFile(root, relativePath);
if (abs.empty()) continue;
std::vector<std::uint8_t> bytes = read(abs);
if (!bytes.empty()) return bytes;
}
return {};
}
} // namespace reasampler::instrument::map
+41
View File
@@ -0,0 +1,41 @@
// sample_roots — where the instrument looks for a bank-relative sample: the directory of
// the project it LIVES IN, found by identity among the open projects, never the focused
// tab's. Pure: the shell enumerates REAPER's projects and reads files; this decides which
// project, which roots, and in what order.
#pragma once
#include <cstdint>
#include <functional>
#include <string>
#include <vector>
namespace reasampler::instrument::map {
// One enumerated project: its identity (an opaque ReaProject*, compared, never
// dereferenced) and its .rpp path, empty for a never-saved project.
struct ProjectTab {
const void* project = nullptr;
std::string rppPath;
};
// The directory of the project whose identity is `own`; empty when `own` is null, is not
// among `open`, or was never saved. Deliberately no fallback to any other project: another
// project's bank folder can hold a same-named file, and playing it is the wrong-project
// bug this exists to close.
std::string ownProjectDir(const void* own, const std::vector<ProjectTab>& open);
// The roots a bank-relative path is tried against, in order. Only the project directory
// today — the ordered list is the seam a later root (tried after it) extends. Empty when
// there is no project directory, which every caller treats as nothing to play.
std::vector<std::string> sampleRootCandidates(const std::string& projectDir);
using FileReader = std::function<std::vector<std::uint8_t>(const std::string& path)>;
// The bytes of `relativePath` resolved under the first root whose read yields any (a read
// that comes back empty falls through to the next root); empty when none does.
std::vector<std::uint8_t> readFromFirstRoot(const std::vector<std::string>& roots,
const std::string& relativePath,
const FileReader& read);
} // namespace reasampler::instrument::map