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 3ee084725a..d8e662fa46 100644 --- a/framework/vst/internal/vstplugininstance.cpp +++ b/framework/vst/internal/vstplugininstance.cpp @@ -39,6 +39,17 @@ 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()) { @@ -47,12 +58,18 @@ VstPluginInstance::VstPluginInstance(const muse::audio::AudioResourceId& resourc m_id = ++s_lastId; m_componentHandlerPtr->pluginParamsChanged().onNotify(this, [this]() { - rescanParams(); + 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 (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); @@ -107,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); @@ -128,7 +143,6 @@ void VstPluginInstance::load() } m_pluginProvider = std::make_unique(factory, classInfo); - m_classInfo = classInfo; break; } @@ -157,6 +171,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,9 +199,9 @@ void VstPluginInstance::syncControllerToComponentState() void VstPluginInstance::rescanParams() { - ONLY_AUDIO_OR_MAIN_THREAD(threadSecurer); + ONLY_MAIN_THREAD(threadSecurer); - if (!m_isLoaded || m_updatingState) { + if (!m_isLoaded) { return; } @@ -232,97 +248,14 @@ 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 +void VstPluginInstance::setPluginConfig(const audio::AudioUnitConfig& config) { ONLY_MAIN_THREAD(threadSecurer); - std::lock_guard lock(m_mutex); - - if (!m_pluginProvider) { - return nullptr; - } - - PluginControllerPtr controller = m_pluginProvider->controller(); - if (!controller) { - return nullptr; - } - - return owned(controller->createView(PluginEditorViewType::kEditor)); -} - -PluginControllerPtr VstPluginInstance::controller() const -{ - ONLY_AUDIO_THREAD(threadSecurer); - - std::lock_guard lock(m_mutex); - - if (!m_pluginProvider) { - return nullptr; - } - - return m_pluginProvider->controller(); -} - -PluginComponentPtr VstPluginInstance::component() const -{ - ONLY_AUDIO_THREAD(threadSecurer); - - std::lock_guard lock(m_mutex); - - if (!m_pluginProvider) { - return nullptr; - } - - return m_pluginProvider->component(); -} - -PluginMidiMappingPtr VstPluginInstance::midiMapping() const -{ - ONLY_AUDIO_THREAD(threadSecurer); - - std::lock_guard lock(m_mutex); - - if (!m_pluginProvider) { - return nullptr; + if (!m_isLoaded) { + return; } - 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 muse::audio::AudioUnitConfig& config) -{ - ONLY_AUDIO_THREAD(threadSecurer); - - std::lock_guard lock(m_mutex); - if (!m_pluginProvider) { LOGE() << "Plugin provider is not initialized"; return; @@ -343,9 +276,9 @@ void VstPluginInstance::updatePluginConfig(const muse::audio::AudioUnitConfig& c return; } - m_updatingState = true; + m_componentHandlerPtr->setSuppressNotify(true); DEFER { - m_updatingState = false; + m_componentHandlerPtr->setSuppressNotify(false); }; try { @@ -372,27 +305,70 @@ void VstPluginInstance::updatePluginConfig(const muse::audio::AudioUnitConfig& c } } -void VstPluginInstance::refreshConfig() +PluginViewPtr VstPluginInstance::createView() const { ONLY_MAIN_THREAD(threadSecurer); - std::lock_guard lock(m_mutex); + if (!m_isLoaded || !m_pluginProvider) { + return nullptr; + } + + PluginControllerPtr controller = m_pluginProvider->controller(); + if (!controller) { + return nullptr; + } - rescanParams(); + return owned(controller->createView(PluginEditorViewType::kEditor)); } -bool VstPluginInstance::isValid() const +PluginControllerPtr VstPluginInstance::controller() const { ONLY_AUDIO_THREAD(threadSecurer); - std::lock_guard lock(m_mutex); + if (!m_isLoaded || !m_pluginProvider) { + return nullptr; + } + + return m_pluginProvider->controller(); +} - if (!m_module - || !m_pluginProvider) { - return false; +PluginComponentPtr VstPluginInstance::component() const +{ + // TODO: Audio engine or process thread + // ONLY_AUDIO_THREAD(threadSecurer); + + if (!m_isLoaded || !m_pluginProvider) { + return nullptr; } - return true; + return m_pluginProvider->component(); +} + +PluginMidiMappingPtr VstPluginInstance::midiMapping() const +{ + ONLY_AUDIO_THREAD(threadSecurer); + + if (!m_isLoaded || !m_pluginProvider) { + return nullptr; + } + + return m_pluginProvider->midiMapping(); +} + +void VstPluginInstance::updatePluginConfig(const audio::AudioUnitConfig& config) +{ + ONLY_AUDIO_THREAD(threadSecurer); + + Async::call(this, [this, config]() { + setPluginConfig(config); + }, threadSecurer()->mainThreadId()); +} + +void VstPluginInstance::refreshConfig() +{ + ONLY_MAIN_THREAD(threadSecurer); + + rescanParams(); } bool VstPluginInstance::isLoaded() const diff --git a/framework/vst/internal/vstplugininstance.h b/framework/vst/internal/vstplugininstance.h index 0a891cff4e..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; @@ -74,16 +70,15 @@ class VstPluginInstance : public IVstPluginInstance, public async::Asyncable async::Channel pluginSettingsChanged() const override; private: - void rescanParams(); - void stateBufferFromString(VstMemoryStream& buffer, char* strData, const size_t strSize) const; void syncControllerToComponentState(); + void rescanParams(); + void setPluginConfig(const muse::audio::AudioUnitConfig& config); VstPluginInstanceId m_id = 0; muse::audio::AudioResourceId m_resourceId; PluginModulePtr m_module = nullptr; std::unique_ptr m_pluginProvider; - ClassInfo m_classInfo; Steinberg::FUnknownPtr m_componentHandlerPtr = nullptr; @@ -93,9 +88,5 @@ 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; }; }