G1.4: bo mute-when-editor-open - channelQuiet chi con reloading; xoa editorOpen machinery (g_editorOpen/g_editorPathCount/clear_editor_open/setEditorOpenPredicate); audio loop process() binh thuong khi editor mo

This commit is contained in:
2026-08-15 21:28:16 +07:00
parent b92c85ec73
commit 7629783298
4 changed files with 1212 additions and 1282 deletions
Binary file not shown.
@@ -110,7 +110,6 @@ public:
// plugin DLL path has an open editor count as quiet (same-plugin channels // plugin DLL path has an open editor count as quiet (same-plugin channels
// too -- the worker pumps the editor's window proc inside the DLL while the // too -- the worker pumps the editor's window proc inside the DLL while the
// audio loop process()es other instances of the same DLL). // audio loop process()es other instances of the same DLL).
void setEditorOpenPredicate(std::function<bool(const std::string& lowerPath)> fn);
// Flush every sounding note on every assigned channel. // Flush every sounding note on every assigned channel.
void allNotesOff(); void allNotesOff();
@@ -137,7 +136,6 @@ private:
// is in progress): real-time code drops MIDI and skips rendering. Lowercased // is in progress): real-time code drops MIDI and skips rendering. Lowercased
// compare inside -- Windows plugin paths are case-insensitive. // compare inside -- Windows plugin paths are case-insensitive.
bool channelQuiet(uint32_t ch) const; bool channelQuiet(uint32_t ch) const;
std::function<bool(const std::string&)> editorOpenFn_;
std::atomic<bool> isBypassed_{false}; std::atomic<bool> isBypassed_{false};
}; };
+4 -16
View File
@@ -260,24 +260,12 @@ bool InstrumentEngineManager::isReloading(uint32_t channel) const {
return channel < 16 && reloadingCh_[channel]; return channel < 16 && reloadingCh_[channel];
} }
void InstrumentEngineManager::setEditorOpenPredicate(std::function<bool(const std::string&)> fn) { // Quiet = reloading (mid-rebuild) only. G1.4: bo mute-when-editor-open —
std::lock_guard<std::mutex> lock(mu_); // editor co the mo trong luc PLAY, audio loop van process() binh thuong.
editorOpenFn_ = std::move(fn); // Called with mu_ held by the real-time dispatch.
}
// Quiet = reloading OR an editor attached for the channel's plugin DLL path.
// Called with mu_ held by the real-time dispatch (the predicate locks
// g_editorMutex -- lock order mu_ -> g_editorMutex; g_editorMutex is never
// held while taking mu_).
bool InstrumentEngineManager::channelQuiet(uint32_t ch) const { bool InstrumentEngineManager::channelQuiet(uint32_t ch) const {
if (ch >= 16 || reloadingCh_[ch]) return true; if (ch >= 16 || reloadingCh_[ch]) return true;
if (!editorOpenFn_) return false; return false;
auto it = paths_.find(ch);
if (it == paths_.end() || it->second.empty()) return false;
std::string lp = it->second;
std::transform(lp.begin(), lp.end(), lp.begin(),
[](unsigned char c) { return (char)::tolower(c); });
return editorOpenFn_(lp);
} }
// Real-time MIDI dispatch: hold mu_ for the WHOLE call so assign()/unload() // Real-time MIDI dispatch: hold mu_ for the WHOLE call so assign()/unload()
+1 -57
View File
@@ -125,19 +125,6 @@ static std::mutex g_attachMutex;
static std::vector<std::string> g_attachPaths; static std::vector<std::string> g_attachPaths;
static bool g_closeInFlight[16] = { false }; static bool g_closeInFlight[16] = { false };
// Editor-open tracking: while a channel's VST editor (native window) is
// attached, EVERY channel assigned the same plugin DLL path must stay quiet —
// the plugin's window proc runs on the editor channel's worker while the
// audio loop calls process() on other instances of the same DLL (2 threads
// in one DLL -> Nexus USER32 crash). g_editorPathCount = refcount per
// lowercase plugin path; g_editorOpenPath/g_editorOpen per channel.
// Lock order: g_editorMutex is taken while holding the engine mutex
// (channelQuiet) or standalone in non-rt jobs — never the reverse.
static std::mutex g_editorMutex;
static std::map<std::string, int> g_editorPathCount;
static std::string g_editorOpenPath[16];
static bool g_editorOpen[16] = { false };
static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (uMsg == WM_CLOSE) { if (uMsg == WM_CLOSE) {
uint32_t ch = UINT32_MAX; uint32_t ch = UINT32_MAX;
@@ -442,22 +429,6 @@ static void unmute_if_not_closing(uint32_t y, const char* why) {
g_engine->setReloading(y, false); g_engine->setReloading(y, false);
} }
// Editor closed / instrument replaced: drop the channel's editor-open state
// (refcounted per lowercase plugin path). Called from non-rt jobs only —
// never from the audio loop.
static void clear_editor_open(uint32_t ch) {
std::lock_guard<std::mutex> lk(g_editorMutex);
if (!g_editorOpen[ch]) return;
g_editorOpen[ch] = false;
std::string pth = g_editorOpenPath[ch];
g_editorOpenPath[ch].clear();
if (!pth.empty()) {
auto it = g_editorPathCount.find(pth);
if (it != g_editorPathCount.end() && --it->second <= 0)
g_editorPathCount.erase(it);
}
}
// closeGUI() MUST run on the UiThread (its COM STA apartment) — the view was // closeGUI() MUST run on the UiThread (its COM STA apartment) — the view was
// attached there. Calling view->removed() from another thread is a // attached there. Calling view->removed() from another thread is a
// cross-apartment COM call that corrupts the plugin; Nexus then hangs on the // cross-apartment COM call that corrupts the plugin; Nexus then hangs on the
@@ -544,9 +515,6 @@ static void close_editor_now(uint32_t ch, HWND hwnd) {
<< " — plugin threw (createInstance)" << std::endl; << " — plugin threw (createInstance)" << std::endl;
} }
} }
// Editor detached + fresh instance loaded: drop editor-open
// state (channelQuiet falls back to reloading flags only).
clear_editor_open(ch);
// createInstance is done: unmuting is safe again. But if an // createInstance is done: unmuting is safe again. But if an
// attach for the SAME plugin path is still running (its // attach for the SAME plugin path is still running (its
// createView must not race process() on any same-path // createView must not race process() on any same-path
@@ -570,7 +538,6 @@ static void close_editor_now(uint32_t ch, HWND hwnd) {
} else { } else {
// Instrument already gone (unloaded) — nothing to rebuild. // Instrument already gone (unloaded) — nothing to rebuild.
g_engine->setReloading(ch, false); g_engine->setReloading(ch, false);
clear_editor_open(ch);
std::lock_guard<std::mutex> lk(g_attachMutex); std::lock_guard<std::mutex> lk(g_attachMutex);
g_closeInFlight[ch] = false; g_closeInFlight[ch] = false;
} }
@@ -672,14 +639,6 @@ int main(int argc, char* argv[]) {
#ifdef _WIN32 #ifdef _WIN32
g_engine = &instruments; g_engine = &instruments;
#endif #endif
// Editor-open predicate: channelQuiet() checks whether ANY editor is
// attached for the channel's plugin DLL path (refcounted in
// g_editorPathCount, updated by the open/close GUI jobs).
instruments.setEditorOpenPredicate([](const std::string& lp) {
std::lock_guard<std::mutex> lk(g_editorMutex);
auto it = g_editorPathCount.find(lp);
return it != g_editorPathCount.end() && it->second > 0;
});
// Per-channel persistent workers: loadPlugin + openGUI run on the SAME // Per-channel persistent workers: loadPlugin + openGUI run on the SAME
// thread whose COM STA apartment stays alive for the channel's lifetime // thread whose COM STA apartment stays alive for the channel's lifetime
// (see ChannelWorker comment — a dead apartment hangs Nexus attached()). // (see ChannelWorker comment — a dead apartment hangs Nexus attached()).
@@ -1015,23 +974,11 @@ int main(int argc, char* argv[]) {
for (uint32_t y : samePathSilenced) for (uint32_t y : samePathSilenced)
unmute_if_not_closing(y, "openGUI"); unmute_if_not_closing(y, "openGUI");
if (ok) { if (ok) {
// Editor attached: mark the plugin path editor-open so the
// audio loop stops processing EVERY channel assigned this
// DLL while the editor lives (its window proc runs on this
// worker; process() elsewhere on the same DLL -> Nexus crash).
{
std::lock_guard<std::mutex> lk(g_editorMutex);
if (!gp.empty()) {
g_editorOpen[guiCh] = true;
g_editorOpenPath[guiCh] = gp;
++g_editorPathCount[gp];
}
}
disable_ime_contexts((HWND)hwnd); disable_ime_contexts((HWND)hwnd);
std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd
<< " (channel muted while editor open)" << std::endl; << " plugin=" << arg2 << " ch=" << guiCh
<< std::endl; << std::endl;
instruments.setReloading(guiCh, false); instruments.setReloading(guiCh, false);
} }
@@ -1166,9 +1113,6 @@ int main(int argc, char* argv[]) {
std::cerr << "[NativeBridge] LOAD close-editor EXCEPTION ch=" << ch std::cerr << "[NativeBridge] LOAD close-editor EXCEPTION ch=" << ch
<< " — plugin window proc threw" << std::endl; << " — plugin window proc threw" << std::endl;
} }
// Editor detached: drop editor-open state before the
// instrument is replaced (assign).
clear_editor_open(ch);
ShowWindow(hToHide, SW_HIDE); ShowWindow(hToHide, SW_HIDE);
} }
#endif #endif