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
+36 -3
View File
@@ -7,9 +7,10 @@
#include <string>
#include <vector>
#include "bank_sync.h" // parseBankGeneration (S9 dirty-guard over the per-paint refresh)
#include "editor_geometry.h" // Rect (shared with embed_strip)
#include "embed_strip.h" // the pure strip layout + hit-test
#include "ext_keys.h" // kProjExtBanksKey
#include "ext_keys.h" // kProjExtBanksKey / kProjExtBankGenKey
#include "reaper_bridge.h"
#include "reasampler_processor.h"
@@ -91,6 +92,36 @@ void ReaSamplerEmbed::refresh() {
if (selectedZone_ >= static_cast<int>(map_.zones.size())) selectedZone_ = -1;
}
void ReaSamplerEmbed::maybeRefresh() {
if (!processor_) { refresh(); return; } // clears state; cheap
// The performance map is a cheap in-process accessor (mutex + copy), and the editor may
// have edited zones with NO bank-content change — always re-snapshot it so a zone edit
// reflects immediately.
map_ = processor_->performanceMap();
if (selectedZone_ >= static_cast<int>(map_.zones.size())) selectedZone_ = -1;
// The EXPENSIVE part is the bank-blob bridge read (samples_). Gate it on the S9 bank-
// generation stamp (a small ext-state read): only re-read the bank when the generation
// changed since the last paint (a recapture / ingest / remove), or on the first paint
// (lastSeenBankGeneration_ == -1). A pre-S9 project reads generation 0; the first paint
// folds it and subsequent idle paints skip the bank read entirely.
std::int64_t currentGen = lastSeenBankGeneration_;
if (auto rawGen =
processor_->bridge().readReasamplerExtState(reasampler::kProjExtBankGenKey)) {
currentGen = parseBankGeneration(*rawGen);
} else if (lastSeenBankGeneration_ < 0) {
currentGen = 0; // unprimed + no stamp (pre-S9): treat as generation 0 for the first read
}
if (lastSeenBankGeneration_ < 0 || currentGen != lastSeenBankGeneration_) {
auto banks =
processor_->bridge().readReasamplerExtState(reasampler::kProjExtBanksKey);
samples_ = banks ? listSamples(*banks) : std::vector<SampleChoice>{};
lastSeenBankGeneration_ = currentGen;
}
}
TPtrInt ReaSamplerEmbed::embed_message(int msg, TPtrInt parm2, TPtrInt parm3) {
switch (msg) {
case REAPER_FXEMBED_WM_IS_SUPPORTED:
@@ -141,8 +172,10 @@ bool ReaSamplerEmbed::paint(TPtrInt bitmap, TPtrInt drawInfo) {
if (w <= 0 || h <= 0) return false;
// Re-read live state each paint (UI thread) so the strip reflects keymap edits + bank
// changes without its own timer — REAPER repaints the embed surface on its cadence.
refresh();
// changes without its own timer — REAPER repaints the embed surface on its cadence. S9
// dirty-guard: maybeRefresh does the EXPENSIVE bank-blob read only when the bank generation
// changed (the flagged S6 follow-up), always refreshing the cheap performance map.
maybeRefresh();
// REAPER hands us its own bitmap sized to the embed area; draw directly into it (unlike
// the editor, which owns a LICE_SysBitmap and BitBlt's). Origin is the bitmap's (0,0).