S10: capture-first ReaSampler 9000 editor — browser, single-capture setup, zones toggle
Reverse S4 auto-select (empty=silence+empty state), add peak-thumbnail capture browser + bank filter + keyboard-strip drag machine, v3 component state (selection + zones), and the S-NAME-1 filename/display rename (UID locked). MSVC min/max macro collisions fixed post-implementation; 26/26 tests green.
This commit is contained in:
@@ -65,8 +65,8 @@ std::vector<std::uint8_t> readFileBytes(const std::string& path) {
|
||||
// Resolve a project-relative WAV path (the M4 way persist does), read + decode it (file
|
||||
// I/O — off-thread only), and downmix to the core's mono contract. Returns nullopt when
|
||||
// the path fails to resolve, the file is unreadable, the WAV is malformed, or the decode
|
||||
// yields no frames — the caller drops the zone (Tier 1) or plays silence (Tier 0). Shared
|
||||
// by the zoned build and the Tier-0 fallback so both decode identically.
|
||||
// yields no frames — the caller drops the zone (zoned map) or plays silence (single capture).
|
||||
// Shared by the zoned build and the single-capture path so both decode identically.
|
||||
std::optional<DecodedZonePcm> decodeRelative(const std::string& projectDir,
|
||||
const std::string& relativePath) {
|
||||
const std::string abs = resolveBankFile(projectDir, relativePath);
|
||||
@@ -165,14 +165,16 @@ tresult PLUGIN_API ReaSamplerProcessor::setState(IBStream* state) {
|
||||
while (state->read(chunk, sizeof(chunk), &got) == kResultOk && got > 0) {
|
||||
bytes.insert(bytes.end(), chunk, chunk + got);
|
||||
}
|
||||
// Component state IS the performance map (Tier 1, D-B). deserializePerformance lifts a
|
||||
// v1 (S4 single-selection) blob to a one-zone map, so already-saved Tier-0 instances
|
||||
// restore cleanly. When the restored map is empty, the instrument falls back to the
|
||||
// Tier-0 first-sample in reloadFromBank; if any single-zone restored map is present we
|
||||
// seed the fallback selection from it so the editor reflects the restored pick.
|
||||
const PerformanceMap map = deserializePerformance(bytes);
|
||||
setPerformanceMap(map);
|
||||
if (map.zones.size() == 1) setSelectedSampleId(map.zones.front().sampleId);
|
||||
// Component state (v3, S10) is {single-capture selection id, opt-in zones}. The
|
||||
// selection and the zones are DISTINCT — the default face is one picked capture, zones
|
||||
// are a demoted overlay — so both are restored explicitly (no more inferring a selection
|
||||
// from a lone zone). deserializeComponentState lifts older blobs cleanly: a v2 zones-only
|
||||
// blob restores {"", zones}; a v1 S4 single-selection blob restores {id, one-zone map} so
|
||||
// the old pick survives as both; an empty/unknown blob restores {"", no zones} — the S10
|
||||
// silent empty state (no first-sample fallback in reloadFromBank).
|
||||
const ComponentState cs = deserializeComponentState(bytes);
|
||||
setSelectedSampleId(cs.selectionId);
|
||||
setPerformanceMap(cs.map);
|
||||
// Rebuild from the restored state (off-thread — setState is a load-time call).
|
||||
reloadFromBank();
|
||||
return kResultOk;
|
||||
@@ -180,10 +182,15 @@ tresult PLUGIN_API ReaSamplerProcessor::setState(IBStream* state) {
|
||||
|
||||
tresult PLUGIN_API ReaSamplerProcessor::getState(IBStream* state) {
|
||||
if (!state) return kResultFalse;
|
||||
// Persist the performance map (the instrument's own Tier-1 state, D-B — NEVER written
|
||||
// to the "reasampler" bank ext-state). An empty map serializes to just the version +
|
||||
// zero-count header (restores as empty -> Tier-0 fallback).
|
||||
const std::vector<std::uint8_t> bytes = serializePerformance(performanceMap());
|
||||
// Persist the full instance state (v3, S10): the single-capture selection id AND the
|
||||
// opt-in zones — the instrument's own state (D-B), NEVER written to the "reasampler"
|
||||
// bank ext-state. An instance with no pick and no zones serializes to {"", no zones}
|
||||
// and restores as the S10 empty state (silence + "pick a capture"), never auto-playing
|
||||
// sample #1.
|
||||
ComponentState state_out;
|
||||
state_out.selectionId = selectedSampleId();
|
||||
state_out.map = performanceMap();
|
||||
const std::vector<std::uint8_t> bytes = serializeComponentState(state_out);
|
||||
if (!bytes.empty()) {
|
||||
const tresult wr = state->write(const_cast<std::uint8_t*>(bytes.data()),
|
||||
static_cast<int32>(bytes.size()), nullptr);
|
||||
@@ -260,9 +267,13 @@ std::string ReaSamplerProcessor::reloadFromBank() {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Tier-0 fallback: an empty (or fully-unresolvable) performance map plays the
|
||||
// selected fallback sample chromatically across the whole keyboard — preserving
|
||||
// the S4 "the bank plays" behavior for an un-authored instrument.
|
||||
// 3. Single-capture fast path (S10): an empty performance map plays the ONE
|
||||
// deliberately-selected capture chromatically across the whole keyboard. This is
|
||||
// the default face — one picked capture, repitched from its root. NO first-
|
||||
// sample fallback: an EMPTY selection (or a stale id) resolves to nullopt in
|
||||
// selectSample, so an un-picked instrument stays SILENT (the editor shows its
|
||||
// "pick a capture" empty state) rather than auto-playing sample #1 (S10 policy
|
||||
// reversal of the S4 convenience default).
|
||||
if (!haveKeymap) {
|
||||
std::optional<SelectedSample> sel =
|
||||
selectSample(*banksJson, selectedSampleId());
|
||||
@@ -273,9 +284,7 @@ std::string ReaSamplerProcessor::reloadFromBank() {
|
||||
km = buildTier0Keymap(std::move(pcm->monoFrames), pcm->sampleRate,
|
||||
sel->rootNote, sel->loop);
|
||||
haveKeymap = true;
|
||||
// Record which id actually resolved so a first-sample fallback
|
||||
// (empty stored id) becomes the concrete selection.
|
||||
resolvedId = selectedSampleId();
|
||||
resolvedId = selectedSampleId(); // the concrete pick that resolved
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user