diff --git a/native_bridge/include/Vst3Instrument.h b/native_bridge/include/Vst3Instrument.h index 9485c90..40235b5 100644 --- a/native_bridge/include/Vst3Instrument.h +++ b/native_bridge/include/Vst3Instrument.h @@ -36,6 +36,8 @@ private: uint32_t maxBlockSize_; bool loaded_; bool guiAttached_; + bool hasAttachedOnce_; + bool reload(); }; #endif // VST3_INSTRUMENT_H diff --git a/native_bridge/src/NativeInstrumentEngine.cpp b/native_bridge/src/NativeInstrumentEngine.cpp index 46ccc5b..3d290fb 100644 --- a/native_bridge/src/NativeInstrumentEngine.cpp +++ b/native_bridge/src/NativeInstrumentEngine.cpp @@ -174,10 +174,18 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type, // Replacing an existing instrument drops its voices with the old engine. // Load may run on a detached thread (VST3 init is slow): only the map // write is under the mutex so renderAll on the audio loop never stalls. + // CRITICAL: the OLD instrument is destroyed AFTER mu_ is released. VST3 + // teardown (setProcessing(false) / terminate / view removed) can block; + // under the lock it would stall renderAll -> bridge Not Responding, + // transport stop hangs, notes never turn off (must kill daw_engine). + std::unique_ptr oldInst; { std::lock_guard lock(mu_); + auto it = channels_.find(channel); + if (it != channels_.end()) oldInst = std::move(it->second); channels_[channel] = std::move(inst); } + oldInst.reset(); return true; } diff --git a/native_bridge/src/Vst3Instrument.cpp b/native_bridge/src/Vst3Instrument.cpp index fa625f8..d8bb647 100644 --- a/native_bridge/src/Vst3Instrument.cpp +++ b/native_bridge/src/Vst3Instrument.cpp @@ -169,7 +169,8 @@ Vst3Instrument::Vst3Instrument() sampleRate_(44100.0), maxBlockSize_(256), loaded_(false), - guiAttached_(false) {} + guiAttached_(false), + hasAttachedOnce_(false) {} Vst3Instrument::~Vst3Instrument() { #ifdef HAVE_VST3SDK @@ -489,6 +490,15 @@ bool Vst3Instrument::openGUI(void* parentWindowHandle) { << " controller=" << (s ? (s->controller ? 1 : 0) : -1) << std::endl; if (!s || !s->controller || !parentWindowHandle) return false; if (s->view && guiAttached_) return true; + if (hasAttachedOnce_ && !guiAttached_) { + // Reopen GUI sau khi dong: mot so plugin (Nexus...) hang o + // view->attached() LAN 2 tren cung component instance. Tao lai inst + // moi tren CUNG worker thread (COM STA con song) roi attach lai. + std::cerr << "[dbg] openGUI: re-attach - reloading fresh plugin instance" << std::endl; + if (!reload()) return false; + s = static_cast(state_); + if (!s || !s->controller) return false; + } IPlugView* rawView = nullptr; tresult qi = s->controller->queryInterface(IPlugView::iid, (void**)&rawView); { @@ -539,6 +549,7 @@ bool Vst3Instrument::openGUI(void* parentWindowHandle) { #endif s->view = view; guiAttached_ = true; + hasAttachedOnce_ = true; // ponytail: the bridge loop is a worker thread without a Windows message // pump — some editors may not repaint until the first native event; a // future version can spin a dedicated UI thread + pump. @@ -546,6 +557,31 @@ bool Vst3Instrument::openGUI(void* parentWindowHandle) { #endif } +bool Vst3Instrument::reload() { +#ifndef HAVE_VST3SDK + return false; +#else + if (!state_) return false; + closeGUI(); + auto* s = static_cast(state_); + if (s->component) { + FUnknownPtr processor(s->component); + if (processor) processor->setProcessing(false); + s->component->setActive(false); + s->component->terminate(); + } + // Single-component plugins: controller == component, already terminated. + if (s->controller && !s->controllerIsComponent) s->controller->terminate(); + s->processData.unprepare(); + delete s; + state_ = nullptr; + loaded_ = false; + guiAttached_ = false; + hasAttachedOnce_ = false; + return loadPlugin(path_, sampleRate_); +#endif +} + void Vst3Instrument::closeGUI() { #ifndef HAVE_VST3SDK return; diff --git a/native_bridge/src/main.cpp b/native_bridge/src/main.cpp index 9c1d76c..72d1485 100644 --- a/native_bridge/src/main.cpp +++ b/native_bridge/src/main.cpp @@ -299,6 +299,26 @@ int main(int argc, char* argv[]) { uint32_t ch = c.channel & 0xF; if (!workers[ch]) workers[ch] = std::make_unique(); workers[ch]->post([&instruments, t, ch, path, sampleRate, block]() { +#ifdef _WIN32 + // DONG cua so editor dang mo cua channel TRUOC khi assign(): + // thay the inst (VST3 -> SF2/inst khac) ma editor con song -> + // old inst destructor goi view->removed() tren HWND con hoat + // dong -> plugin block -> treo bridge. DestroyWindow chay tren + // CUNG worker thread so huu window; WM_DESTROY goi closeGUI() + // dung thu tu. Phai erase map TRUOC DestroyWindow (WM_DESTROY + // handler lay g_guiMutex lai — khong duoc giu lock khi destroy). + HWND hToDestroy = nullptr; + { + std::lock_guard lock(g_guiMutex); + auto git = g_guiWindows.find(ch); + if (git != g_guiWindows.end()) { + hToDestroy = (HWND)git->second; + g_guiWindows.erase(git); + g_hwndToCh.erase(hToDestroy); + } + } + if (hToDestroy && IsWindow(hToDestroy)) DestroyWindow(hToDestroy); +#endif std::cerr << "[dbg] load thread start ch=" << ch << " type=" << (int)t << " path=" << path << std::endl; bool ok = instruments.assign(ch, t, path, sampleRate, block);