From 24cbfa221e1263993fdee6a850ecb5b7b3e6dde3 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 May 2026 14:47:35 +0300 Subject: [PATCH 1/4] VST: call rescanParams() on the main thread (required by VST) --- framework/vst/internal/vstplugininstance.cpp | 38 ++++++++++---------- framework/vst/internal/vstplugininstance.h | 1 - 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/framework/vst/internal/vstplugininstance.cpp b/framework/vst/internal/vstplugininstance.cpp index 3ee084725a..af7b7565a9 100644 --- a/framework/vst/internal/vstplugininstance.cpp +++ b/framework/vst/internal/vstplugininstance.cpp @@ -39,16 +39,23 @@ static const std::string_view CONTROLLER_STATE_KEY = "controllerState"; static VstPluginInstanceId s_lastId = 0; +static void stateBufferFromString(VstMemoryStream& buffer, char* strData, const size_t strSize) +{ + if (strSize == 0) { + return; + } + + buffer.setSize(0); + buffer.write(strData, static_cast(strSize), nullptr); + buffer.seek(0, Steinberg::IBStream::kIBSeekSet, nullptr); +} + VstPluginInstance::VstPluginInstance(const muse::audio::AudioResourceId& resourceId) : m_resourceId(resourceId), m_componentHandlerPtr(new VstComponentHandler()) { ONLY_AUDIO_THREAD(threadSecurer); m_id = ++s_lastId; - - m_componentHandlerPtr->pluginParamsChanged().onNotify(this, [this]() { - rescanParams(); - }); } VstPluginInstance::~VstPluginInstance() @@ -150,6 +157,10 @@ void VstPluginInstance::load() controller->setComponentHandler(m_componentHandlerPtr); syncControllerToComponentState(); + m_componentHandlerPtr->pluginParamsChanged().onNotify(this, [this]() { + rescanParams(); + }); + m_isLoaded = true; m_loadingCompleted.notify(); }, threadSecurer()->mainThreadId()); @@ -157,6 +168,8 @@ void VstPluginInstance::load() void VstPluginInstance::syncControllerToComponentState() { + ONLY_MAIN_THREAD(threadSecurer); + // Synchronize controller to the component's default state. // Some plugins (e.g. Roland Cloud ZENOLOGY) rely on this to // fully initialize internal data structures; without it the @@ -183,12 +196,14 @@ void VstPluginInstance::syncControllerToComponentState() void VstPluginInstance::rescanParams() { - ONLY_AUDIO_OR_MAIN_THREAD(threadSecurer); + ONLY_MAIN_THREAD(threadSecurer); if (!m_isLoaded || m_updatingState) { return; } + std::lock_guard lock(m_mutex); + if (!m_pluginProvider) { LOGE() << "Plugin provider is not initialized"; return; @@ -232,17 +247,6 @@ void VstPluginInstance::rescanParams() m_pluginSettingsChanges.send(updatedConfig); } -void VstPluginInstance::stateBufferFromString(VstMemoryStream& buffer, char* strData, const size_t strSize) const -{ - if (strSize == 0) { - return; - } - - buffer.setSize(0); - buffer.write(strData, static_cast(strSize), nullptr); - buffer.seek(0, Steinberg::IBStream::kIBSeekSet, nullptr); -} - PluginViewPtr VstPluginInstance::createView() const { ONLY_MAIN_THREAD(threadSecurer); @@ -376,8 +380,6 @@ void VstPluginInstance::refreshConfig() { ONLY_MAIN_THREAD(threadSecurer); - std::lock_guard lock(m_mutex); - rescanParams(); } diff --git a/framework/vst/internal/vstplugininstance.h b/framework/vst/internal/vstplugininstance.h index 0a891cff4e..2598273938 100644 --- a/framework/vst/internal/vstplugininstance.h +++ b/framework/vst/internal/vstplugininstance.h @@ -75,7 +75,6 @@ class VstPluginInstance : public IVstPluginInstance, public async::Asyncable private: void rescanParams(); - void stateBufferFromString(VstMemoryStream& buffer, char* strData, const size_t strSize) const; void syncControllerToComponentState(); VstPluginInstanceId m_id = 0; From dfe53a73837eef115d63142b2dec5dd25f1f1390 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 May 2026 14:59:45 +0300 Subject: [PATCH 2/4] VST: apply config on the main thread (required by VST) --- .../vst/internal/vstcomponenthandler.cpp | 31 ++++- framework/vst/internal/vstcomponenthandler.h | 11 ++ framework/vst/internal/vstplugininstance.cpp | 129 ++++++++++-------- framework/vst/internal/vstplugininstance.h | 5 +- 4 files changed, 114 insertions(+), 62 deletions(-) diff --git a/framework/vst/internal/vstcomponenthandler.cpp b/framework/vst/internal/vstcomponenthandler.cpp index 281413c165..8eba5be83b 100644 --- a/framework/vst/internal/vstcomponenthandler.cpp +++ b/framework/vst/internal/vstcomponenthandler.cpp @@ -55,6 +55,11 @@ Notification VstComponentHandler::pluginParamsChanged() const return m_paramsChangedNotify; } +void VstComponentHandler::setSuppressNotify(bool suppress) +{ + m_advancedHandler->setSuppressNotify(suppress); +} + Steinberg::tresult VstComponentHandler::beginEdit(Steinberg::Vst::ParamID /*id*/) { return Steinberg::kResultOk; @@ -62,14 +67,18 @@ Steinberg::tresult VstComponentHandler::beginEdit(Steinberg::Vst::ParamID /*id*/ Steinberg::tresult VstComponentHandler::performEdit(Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized) { - m_paramChanged.send(std::move(id), std::move(valueNormalized)); + if (!m_advancedHandler->suppressNotify()) { + m_paramChanged.send(id, valueNormalized); + } return Steinberg::kResultOk; } Steinberg::tresult VstComponentHandler::endEdit(Steinberg::Vst::ParamID /*id*/) { - m_paramsChangedNotify.notify(); + if (!m_advancedHandler->suppressNotify()) { + m_paramsChangedNotify.notify(); + } return Steinberg::kResultOk; } @@ -84,9 +93,21 @@ VstAdvancedHandler::VstAdvancedHandler(async::Notification notifier) { } +void VstAdvancedHandler::setSuppressNotify(bool suppress) +{ + m_suppressNotify.store(suppress, std::memory_order_relaxed); +} + +bool VstAdvancedHandler::suppressNotify() const +{ + return m_suppressNotify.load(std::memory_order_relaxed); +} + Steinberg::tresult VstAdvancedHandler::setDirty(Steinberg::TBool /*state*/) { - m_paramsChanged.notify(); + if (!suppressNotify()) { + m_paramsChanged.notify(); + } return Steinberg::kResultOk; } @@ -103,7 +124,9 @@ Steinberg::tresult VstAdvancedHandler::startGroupEdit() Steinberg::tresult VstAdvancedHandler::finishGroupEdit() { - m_paramsChanged.notify(); + if (!suppressNotify()) { + m_paramsChanged.notify(); + } return Steinberg::kResultOk; } diff --git a/framework/vst/internal/vstcomponenthandler.h b/framework/vst/internal/vstcomponenthandler.h index fdcee7a786..b7a70426b9 100644 --- a/framework/vst/internal/vstcomponenthandler.h +++ b/framework/vst/internal/vstcomponenthandler.h @@ -22,6 +22,8 @@ #ifndef MUSE_VST_VSTCOMPONENTHANDLER_H #define MUSE_VST_VSTCOMPONENTHANDLER_H +#include + #include "async/notification.h" #include "async/channel.h" @@ -35,12 +37,17 @@ class VstAdvancedHandler : public IAdvancedComponentHandler VstAdvancedHandler(async::Notification notifier); virtual ~VstAdvancedHandler() = default; + void setSuppressNotify(bool suppress); + bool suppressNotify() const; + Steinberg::tresult setDirty(Steinberg::TBool state) override; Steinberg::tresult requestOpenEditor(Steinberg::FIDString name) override; Steinberg::tresult startGroupEdit() override; Steinberg::tresult finishGroupEdit() override; + private: async::Notification m_paramsChanged; + std::atomic_bool m_suppressNotify = false; }; class VstComponentHandler : public IComponentHandler @@ -53,6 +60,10 @@ class VstComponentHandler : public IComponentHandler async::Channel pluginParamChanged() const; async::Notification pluginParamsChanged() const; + // Suppress pluginParamsChanged() while applying config programmatically, + // to avoid triggering a rescan of the state we are currently writing + void setSuppressNotify(bool suppress); + private: Steinberg::tresult beginEdit(Steinberg::Vst::ParamID id) override; Steinberg::tresult performEdit(Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized) override; diff --git a/framework/vst/internal/vstplugininstance.cpp b/framework/vst/internal/vstplugininstance.cpp index af7b7565a9..ec21dfad33 100644 --- a/framework/vst/internal/vstplugininstance.cpp +++ b/framework/vst/internal/vstplugininstance.cpp @@ -56,10 +56,20 @@ VstPluginInstance::VstPluginInstance(const muse::audio::AudioResourceId& resourc ONLY_AUDIO_THREAD(threadSecurer); m_id = ++s_lastId; + + m_componentHandlerPtr->pluginParamsChanged().onNotify(this, [this]() { + Async::call(this, [this]() { + rescanParams(); + }, threadSecurer()->mainThreadId()); + }); } VstPluginInstance::~VstPluginInstance() { + //! NOTE: Signal early so any rescanParams() already queued to the main thread + //! returns before touching members (mutex, provider, etc.) + m_isLoaded = false; + muse::audio::AudioResourceId resourceId = m_resourceId; std::shared_ptr provider = std::move(m_pluginProvider); PluginModulePtr module = std::move(m_module); @@ -157,10 +167,6 @@ void VstPluginInstance::load() controller->setComponentHandler(m_componentHandlerPtr); syncControllerToComponentState(); - m_componentHandlerPtr->pluginParamsChanged().onNotify(this, [this]() { - rescanParams(); - }); - m_isLoaded = true; m_loadingCompleted.notify(); }, threadSecurer()->mainThreadId()); @@ -198,7 +204,7 @@ void VstPluginInstance::rescanParams() { ONLY_MAIN_THREAD(threadSecurer); - if (!m_isLoaded || m_updatingState) { + if (!m_isLoaded) { return; } @@ -247,6 +253,65 @@ void VstPluginInstance::rescanParams() m_pluginSettingsChanges.send(updatedConfig); } +void VstPluginInstance::setPluginConfig(const audio::AudioUnitConfig& config) +{ + ONLY_MAIN_THREAD(threadSecurer); + + if (!m_isLoaded) { + return; + } + + std::lock_guard lock(m_mutex); + + if (!m_pluginProvider) { + LOGE() << "Plugin provider is not initialized"; + return; + } + + PluginControllerPtr controller = m_pluginProvider->controller(); + PluginComponentPtr component = m_pluginProvider->component(); + + if (!controller || !component) { + LOGE() << "Unable to update settings for VST plugin"; + return; + } + + auto componentState = config.find(COMPONENT_STATE_KEY.data()); + auto controllerState = config.find(CONTROLLER_STATE_KEY.data()); + + if (componentState == config.end() && controllerState == config.end()) { + return; + } + + m_componentHandlerPtr->setSuppressNotify(true); + DEFER { + m_componentHandlerPtr->setSuppressNotify(false); + }; + + try { + if (componentState != config.end() && !componentState->second.empty()) { + stateBufferFromString(m_componentStateBuffer, const_cast(componentState->second.c_str()), componentState->second.size()); + + if (component->setState(&m_componentStateBuffer) == Steinberg::kResultOk) { + m_componentStateBuffer.seek(0, Steinberg::IBStream::kIBSeekSet, nullptr); + controller->setComponentState(&m_componentStateBuffer); + } + } + } catch (...) { + LOGW() << "Component state restore failed: " << m_resourceId; + } + + try { + if (controllerState != config.end() && !controllerState->second.empty()) { + stateBufferFromString(m_controllerStateBuffer, const_cast(controllerState->second.data()), + controllerState->second.size()); + controller->setState(&m_controllerStateBuffer); + } + } catch (...) { + LOGW() << "Controller state restore failed: " << m_resourceId; + } +} + PluginViewPtr VstPluginInstance::createView() const { ONLY_MAIN_THREAD(threadSecurer); @@ -321,59 +386,13 @@ bool VstPluginInstance::isAbleForInput() const return search != m_classInfo.subCategories().cend(); } -void VstPluginInstance::updatePluginConfig(const muse::audio::AudioUnitConfig& config) +void VstPluginInstance::updatePluginConfig(const audio::AudioUnitConfig& config) { ONLY_AUDIO_THREAD(threadSecurer); - std::lock_guard lock(m_mutex); - - if (!m_pluginProvider) { - LOGE() << "Plugin provider is not initialized"; - return; - } - - PluginControllerPtr controller = m_pluginProvider->controller(); - PluginComponentPtr component = m_pluginProvider->component(); - - if (!controller || !component) { - LOGE() << "Unable to update settings for VST plugin"; - return; - } - - auto componentState = config.find(COMPONENT_STATE_KEY.data()); - auto controllerState = config.find(CONTROLLER_STATE_KEY.data()); - - if (componentState == config.end() && controllerState == config.end()) { - return; - } - - m_updatingState = true; - DEFER { - m_updatingState = false; - }; - - try { - if (componentState != config.end() && !componentState->second.empty()) { - stateBufferFromString(m_componentStateBuffer, const_cast(componentState->second.c_str()), componentState->second.size()); - - if (component->setState(&m_componentStateBuffer) == Steinberg::kResultOk) { - m_componentStateBuffer.seek(0, Steinberg::IBStream::kIBSeekSet, nullptr); - controller->setComponentState(&m_componentStateBuffer); - } - } - } catch (...) { - LOGW() << "Component state restore failed: " << m_resourceId; - } - - try { - if (controllerState != config.end() && !controllerState->second.empty()) { - stateBufferFromString(m_controllerStateBuffer, const_cast(controllerState->second.data()), - controllerState->second.size()); - controller->setState(&m_controllerStateBuffer); - } - } catch (...) { - LOGW() << "Controller state restore failed: " << m_resourceId; - } + Async::call(this, [this, config]() { + setPluginConfig(config); + }, threadSecurer()->mainThreadId()); } void VstPluginInstance::refreshConfig() diff --git a/framework/vst/internal/vstplugininstance.h b/framework/vst/internal/vstplugininstance.h index 2598273938..8aa119c42f 100644 --- a/framework/vst/internal/vstplugininstance.h +++ b/framework/vst/internal/vstplugininstance.h @@ -74,8 +74,9 @@ class VstPluginInstance : public IVstPluginInstance, public async::Asyncable async::Channel pluginSettingsChanged() const override; private: - void rescanParams(); void syncControllerToComponentState(); + void rescanParams(); + void setPluginConfig(const muse::audio::AudioUnitConfig& config); VstPluginInstanceId m_id = 0; muse::audio::AudioResourceId m_resourceId; @@ -93,8 +94,6 @@ class VstPluginInstance : public IVstPluginInstance, public async::Asyncable std::atomic_bool m_isLoaded = false; async::Notification m_loadingCompleted; - std::atomic_bool m_updatingState = false; - mutable std::mutex m_mutex; }; } From e7f337cb8a36d69738c3686ac0a9007049e66805 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 May 2026 15:42:52 +0300 Subject: [PATCH 3/4] Temporarily disable thread check, as it is also called from the process thread --- framework/vst/internal/vstplugininstance.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/vst/internal/vstplugininstance.cpp b/framework/vst/internal/vstplugininstance.cpp index ec21dfad33..672ce6d772 100644 --- a/framework/vst/internal/vstplugininstance.cpp +++ b/framework/vst/internal/vstplugininstance.cpp @@ -345,7 +345,8 @@ PluginControllerPtr VstPluginInstance::controller() const PluginComponentPtr VstPluginInstance::component() const { - ONLY_AUDIO_THREAD(threadSecurer); + // TODO: Audio engine or process thread + // ONLY_AUDIO_THREAD(threadSecurer); std::lock_guard lock(m_mutex); From d782e78e973d999c512648317458544cb252a906 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 May 2026 17:31:07 +0300 Subject: [PATCH 4/4] VST: code clean up --- framework/vst/internal/vstplugininstance.cpp | 56 ++------------------ framework/vst/internal/vstplugininstance.h | 7 --- 2 files changed, 5 insertions(+), 58 deletions(-) diff --git a/framework/vst/internal/vstplugininstance.cpp b/framework/vst/internal/vstplugininstance.cpp index 672ce6d772..d8e662fa46 100644 --- a/framework/vst/internal/vstplugininstance.cpp +++ b/framework/vst/internal/vstplugininstance.cpp @@ -67,7 +67,7 @@ VstPluginInstance::VstPluginInstance(const muse::audio::AudioResourceId& resourc VstPluginInstance::~VstPluginInstance() { //! NOTE: Signal early so any rescanParams() already queued to the main thread - //! returns before touching members (mutex, provider, etc.) + //! returns before touching members (provider, etc.) m_isLoaded = false; muse::audio::AudioResourceId resourceId = m_resourceId; @@ -124,8 +124,6 @@ void VstPluginInstance::load() Async::call(this, [this]() { ONLY_MAIN_THREAD(threadSecurer); - std::lock_guard lock(m_mutex); - m_module = modulesRepo()->pluginModule(m_resourceId); if (!m_module) { modulesRepo()->addPluginModule(m_resourceId); @@ -145,7 +143,6 @@ void VstPluginInstance::load() } m_pluginProvider = std::make_unique(factory, classInfo); - m_classInfo = classInfo; break; } @@ -208,8 +205,6 @@ void VstPluginInstance::rescanParams() return; } - std::lock_guard lock(m_mutex); - if (!m_pluginProvider) { LOGE() << "Plugin provider is not initialized"; return; @@ -261,8 +256,6 @@ void VstPluginInstance::setPluginConfig(const audio::AudioUnitConfig& config) return; } - std::lock_guard lock(m_mutex); - if (!m_pluginProvider) { LOGE() << "Plugin provider is not initialized"; return; @@ -316,9 +309,7 @@ PluginViewPtr VstPluginInstance::createView() const { ONLY_MAIN_THREAD(threadSecurer); - std::lock_guard lock(m_mutex); - - if (!m_pluginProvider) { + if (!m_isLoaded || !m_pluginProvider) { return nullptr; } @@ -334,9 +325,7 @@ PluginControllerPtr VstPluginInstance::controller() const { ONLY_AUDIO_THREAD(threadSecurer); - std::lock_guard lock(m_mutex); - - if (!m_pluginProvider) { + if (!m_isLoaded || !m_pluginProvider) { return nullptr; } @@ -348,9 +337,7 @@ PluginComponentPtr VstPluginInstance::component() const // TODO: Audio engine or process thread // ONLY_AUDIO_THREAD(threadSecurer); - std::lock_guard lock(m_mutex); - - if (!m_pluginProvider) { + if (!m_isLoaded || !m_pluginProvider) { return nullptr; } @@ -361,32 +348,13 @@ PluginMidiMappingPtr VstPluginInstance::midiMapping() const { ONLY_AUDIO_THREAD(threadSecurer); - std::lock_guard lock(m_mutex); - - if (!m_pluginProvider) { + if (!m_isLoaded || !m_pluginProvider) { return nullptr; } return m_pluginProvider->midiMapping(); } -bool VstPluginInstance::isAbleForInput() const -{ - ONLY_AUDIO_THREAD(threadSecurer); - - std::lock_guard lock(m_mutex); - - auto search = std::find_if(m_classInfo.subCategories().begin(), - m_classInfo.subCategories().end(), [](const std::string& subCategoryStr) { - return subCategoryStr == PluginSubCategory::Synth - || subCategoryStr == PluginSubCategory::Piano - || subCategoryStr == PluginSubCategory::Drum - || subCategoryStr == PluginSubCategory::External; - }); - - return search != m_classInfo.subCategories().cend(); -} - void VstPluginInstance::updatePluginConfig(const audio::AudioUnitConfig& config) { ONLY_AUDIO_THREAD(threadSecurer); @@ -403,20 +371,6 @@ void VstPluginInstance::refreshConfig() rescanParams(); } -bool VstPluginInstance::isValid() const -{ - ONLY_AUDIO_THREAD(threadSecurer); - - std::lock_guard lock(m_mutex); - - if (!m_module - || !m_pluginProvider) { - return false; - } - - return true; -} - bool VstPluginInstance::isLoaded() const { ONLY_AUDIO_OR_MAIN_THREAD(threadSecurer); diff --git a/framework/vst/internal/vstplugininstance.h b/framework/vst/internal/vstplugininstance.h index 8aa119c42f..56ec9753c5 100644 --- a/framework/vst/internal/vstplugininstance.h +++ b/framework/vst/internal/vstplugininstance.h @@ -22,7 +22,6 @@ #pragma once -#include #include #include "../ivstplugininstance.h" @@ -59,14 +58,11 @@ class VstPluginInstance : public IVstPluginInstance, public async::Asyncable PluginComponentPtr component() const override; PluginMidiMappingPtr midiMapping() const override; - bool isAbleForInput() const; - void updatePluginConfig(const muse::audio::AudioUnitConfig& config) override; void refreshConfig() override; void load(); - bool isValid() const; bool isLoaded() const override; async::Notification loadingCompleted() const override; @@ -83,7 +79,6 @@ class VstPluginInstance : public IVstPluginInstance, public async::Asyncable PluginModulePtr m_module = nullptr; std::unique_ptr m_pluginProvider; - ClassInfo m_classInfo; Steinberg::FUnknownPtr m_componentHandlerPtr = nullptr; @@ -93,7 +88,5 @@ class VstPluginInstance : public IVstPluginInstance, public async::Asyncable std::atomic_bool m_isLoaded = false; async::Notification m_loadingCompleted; - - mutable std::mutex m_mutex; }; }