V45.2 fix Qin silence (20s audio-thread stall) + SF loop downbeat delay

- SandboxVst2Host::loadSerializedState: queuePreset non-blocking (byte-identical
  echo never matched -> waitApply always burned 20s TIMEOUT on the audio thread;
  VST2 restore runs via runOnMain so every startup silenced ALL audio ~20s = Qin
  'failed that su'). Bridge now holds reloading + defers saveStateJob until the
  child republishes a different chunk (clobber protection, no audio block).
- sf-fx-realtime clear_queue('loop'): truncate old tail to KEEP=4 blocks
  (~21ms) instead of keeping FILL 24 (~128ms) -> wrap downbeat no longer ~110ms
  late; skipFade crossfade masks the truncation jump, seam crossfade recount.
- bump ?v= 202608291721 (app.jsx x2, index.html x2); deploy 3 _internal + dist.
This commit is contained in:
2026-08-29 17:25:32 +07:00
parent 6b26d5db0e
commit f3fd0109a7
8 changed files with 56 additions and 9 deletions
+28 -3
View File
@@ -1421,9 +1421,34 @@ int main(int argc, char* argv[]) {
if (!inst2) {
std::cerr << "[NativeBridge] restore lost inst ch=" << ch << std::endl;
} else {
if ((t == InstrumentType::VST3 || t == InstrumentType::VST2) && preset.size() >= 100)
inst2->loadSerializedState(preset);
else
if ((t == InstrumentType::VST3 || t == InstrumentType::VST2) && preset.size() >= 100) {
// V45.2: sandbox VST2 child applies presetIn ASYNCHRONOUSLY.
// Queue non-blocking (see SandboxVst2Host) and hold reloading
// until the child republishes a different chunk — otherwise a
// saveStateJob in the ~2s republish window persists the seed
// over the real preset (restore clobber). Never block the
// audio thread for the echo (byte-identical never matches).
if (t == InstrumentType::VST2) {
std::string before = inst2->serializeState();
instruments.setReloading(ch, true);
inst2->loadSerializedState(preset);
if (g_uiWorker) g_uiWorker->post([&instruments, ch, before]() {
auto t0 = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - t0 < std::chrono::seconds(5)) {
bool changed = false;
instruments.withInstrument(ch, [&](INativeInstrument* i) {
if (i && i->serializeState() != before) changed = true;
return changed;
});
if (changed) break;
Sleep(20);
}
instruments.setReloading(ch, false);
});
} else {
inst2->loadSerializedState(preset);
}
} else
inst2->selectProgram(ch, bank, program);
// Re-calibrate AFTER the preset exists: assign()
// calibrated the empty instance (VST2 like Qin