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:
@@ -303,7 +303,13 @@ void SandboxVst2Host::loadSerializedState(const std::string& base64) {
|
||||
if (base64.empty()) return;
|
||||
lastPreset_ = base64; // keep for watchdog respawn re-queue
|
||||
if (!alive_.load() || !ipc_) return;
|
||||
queuePreset(base64, true); // wait for apply so early saves see the real chunk
|
||||
// V45.2: non-blocking. The old byte-identical echo wait (memcmp presetOut==input)
|
||||
// NEVER matched for Qin — the child republishes its own round-trip chunk (differs
|
||||
// at byte 144) so waitApply always burned the full 20s TIMEOUT. VST2 restore runs
|
||||
// on the audio thread (runOnMain), so every bridge startup stalled ALL audio 20s —
|
||||
// the user's first play pressed in that window = silence. Bridge now holds
|
||||
// reloading + defers saves until the child republishes (see main.cpp doRestore).
|
||||
queuePreset(base64, false);
|
||||
std::cerr << "[SandboxVst2Host] presetIn queued ch=" << channel_
|
||||
<< " (" << base64.size() << "B)" << std::endl;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user