feat(S9/S8): bank-generation change-detection + instrument-side assignment reader

Extension stamps a monotonic bank_generation counter, bumped at content
mutations; the VST3 instrument polls it off-thread on an editor timer and
consumes assignment requests, refreshing playback hands-free.
This commit is contained in:
2026-07-26 23:46:06 -04:00
parent 725f3e7d3c
commit 66d47fb410
21 changed files with 838 additions and 36 deletions
+22 -4
View File
@@ -216,7 +216,10 @@ static void CommitRealtimeResult(const reasampler::CaptureResult& res)
// index AddResult — even a hash-collapse still WROTE a file the tool owns, and the
// manifest dedups a repeat path itself (Phase R prune reconciles manifest vs index).
g_session.owned().add(res.sample.relativePath);
g_session.saveToActiveProject(); // persist book + manifest + MarkProjectDirty (travels with .rpp)
// S9: a capture add changes what a live instance could play (a new sample landed in the
// active bank) -> bump before the persist so the stamped generation refreshes instances.
g_session.bumpBankGeneration();
g_session.saveToActiveProject(); // persist book + manifest + generation + MarkProjectDirty (travels with .rpp)
}
// Advance any in-flight realtime capture one tick. Cheap when none is running (a
@@ -852,6 +855,9 @@ static std::string RunCapture(const reasampler::CaptureActionDef& def)
// `banks` + `owned_files` keys) so the capture survives Save / close+reopen (M4) and
// travels with the .rpp. saveToActiveProject also clears the retired legacy key and
// calls MarkProjectDirty. Non-destructive: writes only our own ext-state keys.
// S9: a capture add is a bank-content change -> bump before the persist so an assigned
// live instance refreshes hands-free (the S8 capture+assign path builds on this).
g_session.bumpBankGeneration();
g_session.saveToActiveProject();
// Hand the LANDED bank-index id back to the assign path (S8): captureAndIndexOne
@@ -1042,8 +1048,12 @@ static void RunBatchCaptureItems()
} // selGuard restores the original selection here, on every path
// Persist ONCE for the whole batch (one ext-state write) — only if something landed.
if (anyAdded)
// S9: one bump for the whole batch (coalesced) — the counter is monotonic, not per-sample,
// so a single increment past the last-seen value is enough to trigger one instance reload.
if (anyAdded) {
g_session.bumpBankGeneration();
g_session.saveToActiveProject();
}
ShowConsoleMsg((outcome.summaryLine("item") + "\n").c_str());
}
@@ -1159,8 +1169,11 @@ static void RunBatchCaptureRazor()
}
} // selGuard restores the original track selection here, on every path
if (anyAdded)
// S9: one coalesced bump for the whole razor batch (see the item-batch note above).
if (anyAdded) {
g_session.bumpBankGeneration();
g_session.saveToActiveProject();
}
ShowConsoleMsg((outcome.summaryLine("razor area") + "\n").c_str());
}
@@ -1344,7 +1357,12 @@ static void RunRecaptureFromSource()
// Record the regenerated file in the owned manifest (a new file the tool wrote);
// the superseded old file becomes an orphan reclaimed by Phase R prune.
g_session.owned().add(updated.relativePath);
const bool persisted = g_session.saveToActiveProject(); // book + manifest + MarkProjectDirty
// S9: re-capture-in-place regenerates the SAME id's audio — the exact case the
// hands-free refresh exists for (an instance referencing this id keeps playing the
// OLD audio until it reloads). Bump inside the undo block so undo rolls back the
// generation with the rest of the blob.
g_session.bumpBankGeneration();
const bool persisted = g_session.saveToActiveProject(); // book + manifest + generation + MarkProjectDirty
Undo_EndBlock2(nullptr, persisted ? "ReaSampler: re-capture from source" : "",
persisted ? UNDO_STATE_MISCCFG : 0);
}