fix(bridge): 3 bug âm thanh Qin/Nexus standalone

1) wavesynth tự phát khi mở app: probe calibrate() render vào live mix
   (renderAll process cùng instance) -> thêm calibratingCh_[16], renderAll
   bỏ qua channel đang calibrate; plugin_host panic (sustain off + all
   notes off + 128 noteOff) ngay sau preset apply để startup im lặng.
2) bấm GUI mở 2 cửa sổ Qin: sandbox VST2 child tự sở hữu top-level window
   -> ownsGuiWindow() + main.cpp forward OPEN_GUI control, không tạo native
   parent / không registry double-open.
3) play phát nhạc cụ sai: restore clobber — seed preset đè patch thật trong
   state file (ch3 63ba3667 -> 7f07473c) do saveStateJob chạy lúc child còn
   seed. Fix: queuePreset(waitApply=true) chờ child apply xong (presetOut
   memcmp), consume-before-apply (zero presetInLen trước khi apply), deferred
   double-load re-check trong main.cpp skip assign trùng path, watchdog
   respawn re-queue lastPreset_.
This commit is contained in:
2026-08-21 08:50:28 +07:00
parent b91e6d3875
commit a7b01d3484
7 changed files with 143 additions and 23 deletions
+35
View File
@@ -1129,6 +1129,24 @@ int main(int argc, char* argv[]) {
return;
}
}
// G4.6: child-own GUI (sandbox VST2 like Qin). The child process
// opens its OWN top-level window on control 4 — no native parent
// window to create, no g_guiWindows registry, no same-path
// silencing, no closing of other editors (separate process;
// nothing to serialize). Just forward the OPEN_GUI control.
if (auto* inst0 = instruments.get(guiCh)) {
if (inst0->ownsGuiWindow()) {
std::cerr << "[NativeBridge] GUI via child-own ch=" << guiCh
<< " plugin=" << arg2 << std::endl;
try {
inst0->attachView(nullptr);
} catch (...) {
std::cerr << "[NativeBridge] GUI child-own attach EXCEPTION ch=" << guiCh
<< std::endl;
}
return;
}
}
void* hwnd = (void*)arg1;
#ifdef _WIN32
HWND nativeHwnd = nullptr;
@@ -1577,6 +1595,23 @@ int main(int argc, char* argv[]) {
auto doAssign = std::function<void()>(
[&instruments, t, ch, path, sampleRate, block, spOld, spNew]() {
std::lock_guard<std::mutex> lg(g_loadMutex);
// G4.6: deferred double-load re-check. Restore's
// doRestore (also deferred via runOnMain) can assign
// the SAME path FIRST — a second assign here replaces
// the restored instance with a seed-only one (doAssign
// applies NO preset) and clobbers the real patch in
// the state file (ch3 63ba3667 -> 7f07473c observed).
// Skip like the job-level guard; undo the silence.
if (!path.empty() && !lower_plugin_path(ch).empty() &&
lower_plugin_path(ch) == lower_path_str(path)) {
std::cout << "[NativeBridge] LOAD skip (deferred re-check) ch=" << ch
<< " path=" << path << std::endl;
g_engine->setReloading(ch, false);
for (uint32_t y : spOld) unmute_if_not_closing(y, "load-skip");
for (uint32_t y : spNew) unmute_if_not_closing(y, "load-skip");
g_stateDirty = true;
return;
}
bool ok = false;
try {
ok = instruments.assign(ch, t, path, sampleRate, block);