fix: VST3 teardown under lock + re-attach hang + editor window on replace
- InstrumentEngineManager::assign: destroy replaced instrument OUTSIDE mu_ (VST3 terminate/removed can block -> audio loop stall -> bridge Not Responding, transport stop hangs, notes never off) - main.cpp LOAD job: DestroyWindow any open editor window of the channel before assign (replace VSTi -> SF2 with editor alive hung in removed()) - Vst3Instrument: reload() fresh plugin instance on second openGUI after close (Nexus etc. hang at view->attached() twice on same component)
This commit is contained in:
@@ -36,6 +36,8 @@ private:
|
|||||||
uint32_t maxBlockSize_;
|
uint32_t maxBlockSize_;
|
||||||
bool loaded_;
|
bool loaded_;
|
||||||
bool guiAttached_;
|
bool guiAttached_;
|
||||||
|
bool hasAttachedOnce_;
|
||||||
|
bool reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VST3_INSTRUMENT_H
|
#endif // VST3_INSTRUMENT_H
|
||||||
|
|||||||
@@ -174,10 +174,18 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type,
|
|||||||
// Replacing an existing instrument drops its voices with the old engine.
|
// 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
|
// 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.
|
// 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<INativeInstrument> oldInst;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mu_);
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
auto it = channels_.find(channel);
|
||||||
|
if (it != channels_.end()) oldInst = std::move(it->second);
|
||||||
channels_[channel] = std::move(inst);
|
channels_[channel] = std::move(inst);
|
||||||
}
|
}
|
||||||
|
oldInst.reset();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,8 @@ Vst3Instrument::Vst3Instrument()
|
|||||||
sampleRate_(44100.0),
|
sampleRate_(44100.0),
|
||||||
maxBlockSize_(256),
|
maxBlockSize_(256),
|
||||||
loaded_(false),
|
loaded_(false),
|
||||||
guiAttached_(false) {}
|
guiAttached_(false),
|
||||||
|
hasAttachedOnce_(false) {}
|
||||||
|
|
||||||
Vst3Instrument::~Vst3Instrument() {
|
Vst3Instrument::~Vst3Instrument() {
|
||||||
#ifdef HAVE_VST3SDK
|
#ifdef HAVE_VST3SDK
|
||||||
@@ -489,6 +490,15 @@ bool Vst3Instrument::openGUI(void* parentWindowHandle) {
|
|||||||
<< " controller=" << (s ? (s->controller ? 1 : 0) : -1) << std::endl;
|
<< " controller=" << (s ? (s->controller ? 1 : 0) : -1) << std::endl;
|
||||||
if (!s || !s->controller || !parentWindowHandle) return false;
|
if (!s || !s->controller || !parentWindowHandle) return false;
|
||||||
if (s->view && guiAttached_) return true;
|
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<Vst3HostState*>(state_);
|
||||||
|
if (!s || !s->controller) return false;
|
||||||
|
}
|
||||||
IPlugView* rawView = nullptr;
|
IPlugView* rawView = nullptr;
|
||||||
tresult qi = s->controller->queryInterface(IPlugView::iid, (void**)&rawView);
|
tresult qi = s->controller->queryInterface(IPlugView::iid, (void**)&rawView);
|
||||||
{
|
{
|
||||||
@@ -539,6 +549,7 @@ bool Vst3Instrument::openGUI(void* parentWindowHandle) {
|
|||||||
#endif
|
#endif
|
||||||
s->view = view;
|
s->view = view;
|
||||||
guiAttached_ = true;
|
guiAttached_ = true;
|
||||||
|
hasAttachedOnce_ = true;
|
||||||
// ponytail: the bridge loop is a worker thread without a Windows message
|
// ponytail: the bridge loop is a worker thread without a Windows message
|
||||||
// pump — some editors may not repaint until the first native event; a
|
// pump — some editors may not repaint until the first native event; a
|
||||||
// future version can spin a dedicated UI thread + pump.
|
// future version can spin a dedicated UI thread + pump.
|
||||||
@@ -546,6 +557,31 @@ bool Vst3Instrument::openGUI(void* parentWindowHandle) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Vst3Instrument::reload() {
|
||||||
|
#ifndef HAVE_VST3SDK
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
if (!state_) return false;
|
||||||
|
closeGUI();
|
||||||
|
auto* s = static_cast<Vst3HostState*>(state_);
|
||||||
|
if (s->component) {
|
||||||
|
FUnknownPtr<IAudioProcessor> 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() {
|
void Vst3Instrument::closeGUI() {
|
||||||
#ifndef HAVE_VST3SDK
|
#ifndef HAVE_VST3SDK
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -299,6 +299,26 @@ int main(int argc, char* argv[]) {
|
|||||||
uint32_t ch = c.channel & 0xF;
|
uint32_t ch = c.channel & 0xF;
|
||||||
if (!workers[ch]) workers[ch] = std::make_unique<ChannelWorker>();
|
if (!workers[ch]) workers[ch] = std::make_unique<ChannelWorker>();
|
||||||
workers[ch]->post([&instruments, t, ch, path, sampleRate, block]() {
|
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<std::mutex> 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
|
std::cerr << "[dbg] load thread start ch=" << ch
|
||||||
<< " type=" << (int)t << " path=" << path << std::endl;
|
<< " type=" << (int)t << " path=" << path << std::endl;
|
||||||
bool ok = instruments.assign(ch, t, path, sampleRate, block);
|
bool ok = instruments.assign(ch, t, path, sampleRate, block);
|
||||||
|
|||||||
Reference in New Issue
Block a user