From 9580ca84d58af925f11d5443670e75675e96c73e Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Tue, 27 Apr 2021 17:38:48 +0200 Subject: [PATCH] Thread-safety for receiving parameters --- plugins/vst/SfizzVstEditor.cpp | 128 ++++++++++++++++++++++----------- plugins/vst/SfizzVstEditor.h | 12 ++++ 2 files changed, 97 insertions(+), 43 deletions(-) diff --git a/plugins/vst/SfizzVstEditor.cpp b/plugins/vst/SfizzVstEditor.cpp index 6c85bfd3..4c9f2565 100644 --- a/plugins/vst/SfizzVstEditor.cpp +++ b/plugins/vst/SfizzVstEditor.cpp @@ -18,6 +18,7 @@ #include "X11RunLoop.h" #endif #include +#include using namespace VSTGUI; @@ -93,6 +94,10 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p for (FObject* update : triggerUpdates_) update->addDependent(this); + threadChecker_ = Vst::ThreadChecker::create(); + + parametersToUpdate_.clear(); + Steinberg::IdleUpdateHandler::start(); for (FObject* update : continuousUpdates_) @@ -192,6 +197,7 @@ CMessageResult SfizzVstEditor::notify(CBaseObject* sender, const char* message) if (message == CVSTGUITimer::kMsgTimer) { processOscQueue(); processNoteEventQueue(); + processParameterUpdates(); updateEditorIsOpenParameter(); // Note(jpc) for Reaper, it can fail at open time } @@ -278,49 +284,18 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message) } if (Vst::RangeParameter* param = Steinberg::FCast(changedUnknown)) { - const Vst::ParamValue value = param->getNormalized(); - const Vst::ParamID id = param->getInfo().id; - const SfizzRange range = SfizzRange::getForParameter(id); - switch (id) { - case kPidVolume: - uiReceiveValue(EditId::Volume, range.denormalize(value)); - break; - case kPidNumVoices: - uiReceiveValue(EditId::Polyphony, range.denormalize(value)); - break; - case kPidOversampling: - uiReceiveValue(EditId::Oversampling, float(1u << (int32)range.denormalize(value))); - break; - case kPidPreloadSize: - uiReceiveValue(EditId::PreloadSize, range.denormalize(value)); - break; - case kPidScalaRootKey: - uiReceiveValue(EditId::ScalaRootKey, range.denormalize(value)); - break; - case kPidTuningFrequency: - uiReceiveValue(EditId::TuningFrequency, range.denormalize(value)); - break; - case kPidStretchedTuning: - uiReceiveValue(EditId::StretchTuning, range.denormalize(value)); - break; - case kPidSampleQuality: - uiReceiveValue(EditId::SampleQuality, range.denormalize(value)); - break; - case kPidOscillatorQuality: - uiReceiveValue(EditId::OscillatorQuality, range.denormalize(value)); - break; - case kPidLeftLevel: - uiReceiveValue(EditId::LeftLevel, range.denormalize(value)); - break; - case kPidRightLevel: - uiReceiveValue(EditId::RightLevel, range.denormalize(value)); - break; - default: - if (id >= kPidCC0 && id <= kPidCCLast) { - int cc = int(id - kPidCC0); - uiReceiveValue(editIdForCC(cc), range.denormalize(value)); - } - break; + // Note(jpc) some hosts send us the parameters in the wrong thread... + // store these parameters thread-safely and let the idle + // callback process them later + if (threadChecker_->test()) + updateParameter(param); + else { + static std::atomic_bool warn_once_flag { false }; + if (!warn_once_flag.exchange(true)) + fprintf(stderr, "[sfizz] using a thread-safety workaround for parameter updates\n"); + const Vst::ParamID id = param->getInfo().id; + std::lock_guard lock(parametersToUpdateMutex_); + parametersToUpdate_.insert(id); } return; } @@ -368,6 +343,73 @@ void SfizzVstEditor::processNoteEventQueue() queue->clear(); } +void SfizzVstEditor::processParameterUpdates() +{ + auto extractNextParamID = [this]() -> Vst::ParamID { + Vst::ParamID id = Vst::kNoParamId; + std::lock_guard lock(parametersToUpdateMutex_); + auto it = parametersToUpdate_.begin(); + if (it != parametersToUpdate_.end()) { + id = *it; + parametersToUpdate_.erase(it); + } + return id; + }; + + for (Vst::ParamID id; (id = extractNextParamID()) != Vst::kNoParamId; ) + updateParameter(getController()->getParameterObject(id)); +} + +void SfizzVstEditor::updateParameter(Vst::Parameter* parameterToUpdate) +{ + if (Vst::RangeParameter* param = FCast(parameterToUpdate)) { + const Vst::ParamID id = param->getInfo().id; + const Vst::ParamValue value = param->getNormalized(); + const SfizzRange range = SfizzRange::getForParameter(id); + switch (id) { + case kPidVolume: + uiReceiveValue(EditId::Volume, range.denormalize(value)); + break; + case kPidNumVoices: + uiReceiveValue(EditId::Polyphony, range.denormalize(value)); + break; + case kPidOversampling: + uiReceiveValue(EditId::Oversampling, float(1u << (int32)range.denormalize(value))); + break; + case kPidPreloadSize: + uiReceiveValue(EditId::PreloadSize, range.denormalize(value)); + break; + case kPidScalaRootKey: + uiReceiveValue(EditId::ScalaRootKey, range.denormalize(value)); + break; + case kPidTuningFrequency: + uiReceiveValue(EditId::TuningFrequency, range.denormalize(value)); + break; + case kPidStretchedTuning: + uiReceiveValue(EditId::StretchTuning, range.denormalize(value)); + break; + case kPidSampleQuality: + uiReceiveValue(EditId::SampleQuality, range.denormalize(value)); + break; + case kPidOscillatorQuality: + uiReceiveValue(EditId::OscillatorQuality, range.denormalize(value)); + break; + case kPidLeftLevel: + uiReceiveValue(EditId::LeftLevel, range.denormalize(value)); + break; + case kPidRightLevel: + uiReceiveValue(EditId::RightLevel, range.denormalize(value)); + break; + default: + if (id >= kPidCC0 && id <= kPidCCLast) { + int cc = int(id - kPidCC0); + uiReceiveValue(editIdForCC(cc), range.denormalize(value)); + } + break; + } + } +} + /// void SfizzVstEditor::uiSendValue(EditId id, const EditValue& v) { diff --git a/plugins/vst/SfizzVstEditor.h b/plugins/vst/SfizzVstEditor.h index d254dbd8..7d3b48c8 100644 --- a/plugins/vst/SfizzVstEditor.h +++ b/plugins/vst/SfizzVstEditor.h @@ -8,8 +8,10 @@ #include "SfizzVstController.h" #include "editor/EditorController.h" #include "public.sdk/source/vst/vstguieditor.h" +#include "public.sdk/source/common/threadchecker.h" #include #include +#include class Editor; #if !defined(__APPLE__) && !defined(_WIN32) namespace VSTGUI { class RunLoop; } @@ -48,6 +50,8 @@ public: private: void processOscQueue(); void processNoteEventQueue(); + void processParameterUpdates(); + void updateParameter(Vst::Parameter* parameterToUpdate); protected: // EditorController @@ -84,4 +88,12 @@ private: // subscribed updates std::vector> continuousUpdates_; std::vector> triggerUpdates_; + + // thread safety + std::unique_ptr threadChecker_; + + // parameters to process, whose values have received changes + // Note(jpc) it's because hosts send us parameter updates in the wrong thread.. + std::set parametersToUpdate_; + std::mutex parametersToUpdateMutex_; };