Thread-safety for receiving parameters

This commit is contained in:
Jean Pierre Cimalando 2021-04-27 17:38:48 +02:00
parent 7ca9c15a28
commit 9580ca84d5
2 changed files with 97 additions and 43 deletions

View file

@ -18,6 +18,7 @@
#include "X11RunLoop.h" #include "X11RunLoop.h"
#endif #endif
#include <ghc/fs_std.hpp> #include <ghc/fs_std.hpp>
#include <atomic>
using namespace VSTGUI; using namespace VSTGUI;
@ -93,6 +94,10 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p
for (FObject* update : triggerUpdates_) for (FObject* update : triggerUpdates_)
update->addDependent(this); update->addDependent(this);
threadChecker_ = Vst::ThreadChecker::create();
parametersToUpdate_.clear();
Steinberg::IdleUpdateHandler::start(); Steinberg::IdleUpdateHandler::start();
for (FObject* update : continuousUpdates_) for (FObject* update : continuousUpdates_)
@ -192,6 +197,7 @@ CMessageResult SfizzVstEditor::notify(CBaseObject* sender, const char* message)
if (message == CVSTGUITimer::kMsgTimer) { if (message == CVSTGUITimer::kMsgTimer) {
processOscQueue(); processOscQueue();
processNoteEventQueue(); processNoteEventQueue();
processParameterUpdates();
updateEditorIsOpenParameter(); // Note(jpc) for Reaper, it can fail at open time 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<Vst::RangeParameter>(changedUnknown)) { if (Vst::RangeParameter* param = Steinberg::FCast<Vst::RangeParameter>(changedUnknown)) {
const Vst::ParamValue value = param->getNormalized(); // Note(jpc) some hosts send us the parameters in the wrong thread...
const Vst::ParamID id = param->getInfo().id; // store these parameters thread-safely and let the idle
const SfizzRange range = SfizzRange::getForParameter(id); // callback process them later
switch (id) { if (threadChecker_->test())
case kPidVolume: updateParameter(param);
uiReceiveValue(EditId::Volume, range.denormalize(value)); else {
break; static std::atomic_bool warn_once_flag { false };
case kPidNumVoices: if (!warn_once_flag.exchange(true))
uiReceiveValue(EditId::Polyphony, range.denormalize(value)); fprintf(stderr, "[sfizz] using a thread-safety workaround for parameter updates\n");
break; const Vst::ParamID id = param->getInfo().id;
case kPidOversampling: std::lock_guard<std::mutex> lock(parametersToUpdateMutex_);
uiReceiveValue(EditId::Oversampling, float(1u << (int32)range.denormalize(value))); parametersToUpdate_.insert(id);
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;
} }
return; return;
} }
@ -368,6 +343,73 @@ void SfizzVstEditor::processNoteEventQueue()
queue->clear(); queue->clear();
} }
void SfizzVstEditor::processParameterUpdates()
{
auto extractNextParamID = [this]() -> Vst::ParamID {
Vst::ParamID id = Vst::kNoParamId;
std::lock_guard<std::mutex> 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<Vst::RangeParameter>(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) void SfizzVstEditor::uiSendValue(EditId id, const EditValue& v)
{ {

View file

@ -8,8 +8,10 @@
#include "SfizzVstController.h" #include "SfizzVstController.h"
#include "editor/EditorController.h" #include "editor/EditorController.h"
#include "public.sdk/source/vst/vstguieditor.h" #include "public.sdk/source/vst/vstguieditor.h"
#include "public.sdk/source/common/threadchecker.h"
#include <absl/types/span.h> #include <absl/types/span.h>
#include <mutex> #include <mutex>
#include <set>
class Editor; class Editor;
#if !defined(__APPLE__) && !defined(_WIN32) #if !defined(__APPLE__) && !defined(_WIN32)
namespace VSTGUI { class RunLoop; } namespace VSTGUI { class RunLoop; }
@ -48,6 +50,8 @@ public:
private: private:
void processOscQueue(); void processOscQueue();
void processNoteEventQueue(); void processNoteEventQueue();
void processParameterUpdates();
void updateParameter(Vst::Parameter* parameterToUpdate);
protected: protected:
// EditorController // EditorController
@ -84,4 +88,12 @@ private:
// subscribed updates // subscribed updates
std::vector<IPtr<FObject>> continuousUpdates_; std::vector<IPtr<FObject>> continuousUpdates_;
std::vector<IPtr<FObject>> triggerUpdates_; std::vector<IPtr<FObject>> triggerUpdates_;
// thread safety
std::unique_ptr<Vst::ThreadChecker> 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<Vst::ParamID> parametersToUpdate_;
std::mutex parametersToUpdateMutex_;
}; };