Audacity 3.2.0
VST3ParametersWindow.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file VST3ParametersWindow.cpp
6
7 @author Vitaly Sverchinsky
8
9 @brief Part of Audacity VST3 module
10
11**********************************************************************/
12
13
15
16#include <pluginterfaces/vst/ivsteditcontroller.h>
17
18#include <wx/sizer.h>
19#include <wx/stattext.h>
20#include <wx/checkbox.h>
21#include <wx/choice.h>
22#include <wx/slider.h>
23
24#include "MemoryX.h"
25#include "VST3Utils.h"
26#if wxUSE_ACCESSIBILITY
27#include "WindowAccessible.h"
28#endif
29
30
31//Interface between wx control and IEditController parameter.
33{
34 const Steinberg::Vst::ParamID mParameterId;
35public:
36 VST3ParameterControl(Steinberg::Vst::ParamID id) : mParameterId(id) { }
37
39 //Read parameter value from the controller and update UI
40 virtual void SetNormalizedValue(Steinberg::Vst::IEditController&, Steinberg::Vst::ParamValue value) = 0;
41 //Convert current control value to normalized parameter value
42 virtual Steinberg::Vst::ParamValue GetNormalizedValue(Steinberg::Vst::IEditController& editController) const = 0;
43 //Update a control's accessible object if necessary
44 virtual void UpdateAccessible(Steinberg::Vst::IEditController& editController, Steinberg::Vst::ParamValue value) {}
45
46 Steinberg::Vst::ParamID GetParameterId() const noexcept { return mParameterId; }
47};
48
49namespace
50{
51 class VST3ValueText final : public wxStaticText, public VST3ParameterControl
52 {
53 const wxString mUnits;
54 public:
55 VST3ValueText(wxWindow *parent,
56 wxWindowID id,
57 Steinberg::Vst::ParamID paramId,
58 const wxString& units,
59 const wxPoint& pos = wxDefaultPosition,
60 const wxSize& size = wxDefaultSize,
61 long style = 0,
62 const wxString& name = wxStaticTextNameStr)
63 : wxStaticText(parent, id, wxEmptyString, pos, size, style, name)
64 , VST3ParameterControl(paramId)
65 , mUnits(units) { }
66
67 void SetNormalizedValue(Steinberg::Vst::IEditController& editController, Steinberg::Vst::ParamValue value) override
68 {
69 Steinberg::Vst::String128 str { };
70 editController.getParamStringByValue(GetParameterId(), value, str);
71 if(mUnits.empty())
72 SetLabel(VST3Utils::ToWxString(str));
73 else
74 SetLabel(wxString::Format("%s %s", VST3Utils::ToWxString(str), mUnits));
75 }
76
77 Steinberg::Vst::ParamValue GetNormalizedValue(Steinberg::Vst::IEditController& editController) const override
78 {
79 return editController.getParamNormalized(GetParameterId());
80 }
81 };
82
83 class VST3ListParameter final : public wxChoice, public VST3ParameterControl
84 {
85 public:
86 VST3ListParameter(wxWindow *parent,
87 wxWindowID id,
88 Steinberg::Vst::ParamID paramId,
89 const wxPoint& pos = wxDefaultPosition,
90 const wxSize& size = wxDefaultSize,
91 long style = 0,
92 const wxValidator& validator = wxDefaultValidator,
93 const wxString& name = wxChoiceNameStr)
94 : wxChoice(parent, id, pos, size, 0, nullptr, style, validator, name)
95 , VST3ParameterControl(paramId) { }
96
97 void SetNormalizedValue(Steinberg::Vst::IEditController& editController, Steinberg::Vst::ParamValue value) override
98 {
99 SetSelection(static_cast<int>(editController.normalizedParamToPlain(GetParameterId(), value)));
100 }
101
102 Steinberg::Vst::ParamValue GetNormalizedValue(Steinberg::Vst::IEditController& editController) const override
103 {
104 return editController.plainParamToNormalized(GetParameterId(), GetSelection());
105 }
106 };
107
108 class VST3ContinuousParameter final : public wxSlider, public VST3ParameterControl
109 {
110 const wxString mTitle;
111 const wxString mUnits;
112
113 public:
114 static constexpr auto Step = 0.01;
115
116 VST3ContinuousParameter(wxWindow *parent,
117 wxWindowID id,
118 Steinberg::Vst::ParamID paramId,
119 const wxString& title,
120 const wxString& units,
121 const wxPoint& pos = wxDefaultPosition,
122 const wxSize& size = wxDefaultSize,
123 long style = wxSL_HORIZONTAL,
124 const wxValidator& validator = wxDefaultValidator,
125 const wxString& name = wxSliderNameStr)
126 : wxSlider(parent, id, 0, 0, static_cast<int>(1.0 / Step), pos, size, style, validator, name)
127 , VST3ParameterControl(paramId)
128 , mTitle(title)
129 , mUnits(units)
130 {
131#if wxUSE_ACCESSIBILITY
132 SetAccessible(safenew WindowAccessible(this));
133#endif
134 }
135
136 void SetNormalizedValue(Steinberg::Vst::IEditController& editController, Steinberg::Vst::ParamValue value) override
137 {
138 SetValue(static_cast<int>(value / Step));
139 UpdateAccessible(editController, value);
140 }
141
142 void UpdateAccessible(Steinberg::Vst::IEditController& editController, Steinberg::Vst::ParamValue value) override
143 {
144 Steinberg::Vst::String128 str{};
145 editController.getParamStringByValue(GetParameterId(), value, str);
146 SetName(wxString::Format("%s %s %s",
147 mTitle, VST3Utils::ToWxString(str), mUnits));
148 }
149
150 Steinberg::Vst::ParamValue GetNormalizedValue(Steinberg::Vst::IEditController&) const override
151 {
152 return GetValue() * Step;
153 }
154 };
155
156 class VST3DiscreteParameter final : public wxSlider, public VST3ParameterControl
157 {
158 const wxString mTitle;
159 const wxString mUnits;
160 const int mStepCount{};
161 bool mUsePluginConversions{ true };
162
163 public:
164 VST3DiscreteParameter(wxWindow *parent,
165 wxWindowID id,
166 Steinberg::Vst::ParamID paramId,
167 int maxValue,
168 const wxString& title,
169 const wxString& units,
170 Steinberg::Vst::IEditController& editController,
171 const wxPoint& pos = wxDefaultPosition,
172 const wxSize& size = wxDefaultSize,
173 long style = wxSL_HORIZONTAL,
174 const wxValidator& validator = wxDefaultValidator,
175 const wxString& name = wxSliderNameStr)
176 : wxSlider(parent, id, 0, 0, maxValue, pos, size, style, validator, name)
177 , VST3ParameterControl(paramId)
178 , mTitle(title)
179 , mUnits(units)
180 , mStepCount(maxValue)
181 {
182#if wxUSE_ACCESSIBILITY
183 SetAccessible(safenew WindowAccessible(this));
184#endif
185 // See Issue #4763.
186 // For discrete parmeters, it appears that some plugins do not provide
187 // correct implementations of the functions for converting between plain
188 // and normalized values. When the correct implementations are not provided
189 // the conversion functions just return the input value.
190 // So, to workaround this, test for this, and when valid implementations
191 // are not provided, use the formulas given by Steinberg in their documentation.
192 mUsePluginConversions = mStepCount != editController.plainParamToNormalized(paramId, mStepCount);
193 }
194
195 void SetNormalizedValue(Steinberg::Vst::IEditController& editController, Steinberg::Vst::ParamValue value) override
196 {
197 int plainValue = mUsePluginConversions ?
198 static_cast<int>(editController.normalizedParamToPlain(GetParameterId(), value)) :
199 static_cast<int>(std::min(static_cast<double>(mStepCount), value * (mStepCount + 1)));
200 SetValue(plainValue);
201 UpdateAccessible(editController, value);
202 }
203
204 void UpdateAccessible(Steinberg::Vst::IEditController& editController, Steinberg::Vst::ParamValue value) override
205 {
206 Steinberg::Vst::String128 str{ };
207 editController.getParamStringByValue(GetParameterId(), value, str);
208 SetName(wxString::Format("%s %s %s", mTitle, VST3Utils::ToWxString(str), mUnits));
209 }
210
211 Steinberg::Vst::ParamValue GetNormalizedValue(Steinberg::Vst::IEditController& editController) const override
212 {
213 return mUsePluginConversions ?
214 editController.plainParamToNormalized(GetParameterId(), GetValue()) :
215 GetValue() / static_cast<double>(mStepCount);
216 }
217 };
218
219 class VST3ToggleParameter final : public wxCheckBox, public VST3ParameterControl
220 {
221 public:
222
223 VST3ToggleParameter(wxWindow *parent,
224 wxWindowID id,
225 const wxString& label,
226 Steinberg::Vst::ParamID paramId,
227 const wxPoint& pos = wxDefaultPosition,
228 const wxSize& size = wxDefaultSize,
229 long style = wxALIGN_RIGHT,
230 const wxValidator& validator = wxDefaultValidator,
231 const wxString& name = wxCheckBoxNameStr)
232 : wxCheckBox(parent, id, label, pos, size, style, validator, name)
233 , VST3ParameterControl(paramId) { }
234
235 void SetNormalizedValue(Steinberg::Vst::IEditController& editController, Steinberg::Vst::ParamValue value) override
236 {
237 SetValue(value != .0);
238 }
239
240 Steinberg::Vst::ParamValue GetNormalizedValue(Steinberg::Vst::IEditController&) const override
241 {
242 return GetValue() ? 1. : .0;
243 }
244 };
245}
246
248 Steinberg::Vst::IEditController& editController,
249 Steinberg::Vst::IComponentHandler& handler,
250 wxWindowID id,
251 const wxPoint& pos,
252 const wxSize& size,
253 long style,
254 const wxString& name)
255 : wxScrolledWindow(parent, id, pos, size, style, name)
256 , mEditController(&editController)
257 , mComponentHandler(&handler)
258{
259 using namespace Steinberg;
260
261 auto sizer = std::make_unique<wxGridSizer>(3, 5, 5);
262
263 for(int i = 0, count = editController.getParameterCount(); i < count; ++i)
264 {
265 Vst::ParameterInfo parameterInfo { };
266 if(editController.getParameterInfo(i, parameterInfo) != kResultOk)
267 continue;
268
269 if(parameterInfo.flags & (Vst::ParameterInfo::kIsHidden | Vst::ParameterInfo::kIsProgramChange))
270 continue;
271
272 if((parameterInfo.flags & (Vst::ParameterInfo::kCanAutomate | Vst::ParameterInfo::kIsBypass | Vst::ParameterInfo::kIsReadOnly)) == 0)
273 continue;
274
275 {
276 // Hide proxy parameters with name that starts with "MIDI CC "
277 // That prevents Plain UI from creating many useless controls
278 static_assert(sizeof(Steinberg::tchar) == sizeof(char16_t));
279 static const std::basic_string_view<tchar> MIDI_CC { reinterpret_cast<const tchar*>(u"MIDI CC ") };
280 if(std::basic_string_view<tchar>(parameterInfo.title).rfind(MIDI_CC, 0) == 0)
281 continue;
282 }
283
284 if (parameterInfo.stepCount != 1) // not a toggle
285 sizer->Add(safenew wxStaticText(
286 this,
287 wxID_ANY,
288 VST3Utils::ToWxString(parameterInfo.title),
289 wxDefaultPosition,
290 wxDefaultSize,
291 wxALIGN_RIGHT), 0, wxEXPAND
292 );
293
294 if(parameterInfo.flags & Vst::ParameterInfo::kIsReadOnly)
295 {
296 auto text = safenew VST3ValueText(
297 this,
298 wxID_ANY,
299 parameterInfo.id,
300 VST3Utils::ToWxString(parameterInfo.units)
301 );
302 sizer->Add(text);
303 sizer->AddStretchSpacer();
305 }
306 //toggle
307 else if(parameterInfo.stepCount == 1)
308 {
309 const auto toggle = safenew VST3ToggleParameter (this, wxID_ANY,
310 VST3Utils::ToWxString(parameterInfo.title), parameterInfo.id);
311 toggle->Bind(wxEVT_CHECKBOX, &VST3ParametersWindow::OnParameterValueChanged, this);
312 sizer->Add(toggle, 0, wxEXPAND);
313 sizer->AddStretchSpacer();
314 sizer->AddStretchSpacer();
316 }
317 //list
318 else if(parameterInfo.stepCount != 0 && (parameterInfo.flags & Vst::ParameterInfo::kIsList))
319 {
320 const auto list = safenew VST3ListParameter(this, wxID_ANY, parameterInfo.id);
321
322 for(auto j = 0; j <= parameterInfo.stepCount; ++j)
323 {
324 Vst::String128 displayValue = { 0 };
325 editController.getParamStringByValue(
326 parameterInfo.id,
327 editController.plainParamToNormalized(parameterInfo.id, j),
328 displayValue
329 );
330 list->AppendString(VST3Utils::ToWxString(displayValue));
331 }
332 list->Bind(wxEVT_CHOICE, &VST3ParametersWindow::OnParameterValueChanged, this);
333 sizer->Add(list, 0, wxEXPAND);
334 sizer->AddStretchSpacer();
336 }
337 else
338 {
339 //continuous
340 if(parameterInfo.stepCount == 0)
341 {
342 auto slider = safenew VST3ContinuousParameter(this, wxID_ANY, parameterInfo.id,
343 VST3Utils::ToWxString(parameterInfo.title), VST3Utils::ToWxString(parameterInfo.units));
344 sizer->Add(slider, 0, wxEXPAND);
345 slider->Bind(wxEVT_SLIDER, &VST3ParametersWindow::OnParameterValueChanged, this);
347 }
348 //discrete
349 else
350 {
351 auto slider = safenew VST3DiscreteParameter(this, wxID_ANY, parameterInfo.id, parameterInfo.stepCount,
352 VST3Utils::ToWxString(parameterInfo.title), VST3Utils::ToWxString(parameterInfo.units),
353 editController);
354 sizer->Add(slider, 0, wxEXPAND);
355 slider->Bind(wxEVT_SLIDER, &VST3ParametersWindow::OnParameterValueChanged, this);
357 }
358 const auto label = safenew VST3ValueText(this, wxID_ANY, parameterInfo.id, VST3Utils::ToWxString(parameterInfo.units));
359 sizer->Add(label);
361 }
362 }
363
364 SetSizer(sizer.release());
365}
366
368{
369 for(auto& p : mControls)
370 p.second->SetNormalizedValue(*mEditController, mEditController->getParamNormalized(p.second->GetParameterId()));
371 for(auto& p : mLabels)
372 p.second->SetNormalizedValue(*mEditController, mEditController->getParamNormalized(p.second->GetParameterId()));
373}
374
375void VST3ParametersWindow::UpdateParameter(Steinberg::Vst::ParamID paramId)
376{
377 {
378 auto it = mControls.find(paramId);
379 if(it != mControls.end())
380 it->second->SetNormalizedValue(*mEditController, mEditController->getParamNormalized(it->second->GetParameterId()));
381 }
382 {
383 auto it = mLabels.find(paramId);
384 if(it != mLabels.end())
385 it->second->SetNormalizedValue(*mEditController, mEditController->getParamNormalized(it->second->GetParameterId()));
386 }
387}
388
390{
391 mControls[control->GetParameterId()] = control;
392 control->SetNormalizedValue(*mEditController, mEditController->getParamNormalized(control->GetParameterId()));
393}
394
396{
397 mLabels[label->GetParameterId()] = label;
398 label->SetNormalizedValue(*mEditController, mEditController->getParamNormalized(label->GetParameterId()));
399}
400
402{
403 if(auto control = dynamic_cast<VST3ParameterControl*>(evt.GetEventObject()))
404 {
405 const auto paramId = control->GetParameterId();
406 const auto normalizedValue = control->GetNormalizedValue(*mEditController);
407
408 mEditController->setParamNormalized(paramId, normalizedValue);
409 if(mComponentHandler->beginEdit(paramId) == Steinberg::kResultOk)
410 {
411 auto cleanup = finally([=] { mComponentHandler->endEdit(paramId); });
412 mComponentHandler->performEdit(paramId, normalizedValue);
413 }
414 auto it = mLabels.find(paramId);
415 if (it != mLabels.end()) {
416 it->second->SetNormalizedValue(*mEditController, normalizedValue);
417 control->UpdateAccessible(*mEditController, normalizedValue);
418 }
419 }
420}
421
422VST3ParametersWindow* VST3ParametersWindow::Setup(wxWindow& parent, Steinberg::Vst::IEditController& editController,
423 Steinberg::Vst::IComponentHandler& componentHandler)
424{
425 constexpr int WindowBorder { 5 };
426 constexpr int WindowMaxHeight { 450 };
427
428 auto parametersWindow = safenew VST3ParametersWindow(&parent,
429 editController,
430 componentHandler,
431 wxID_ANY,
432 wxDefaultPosition,
433 wxDefaultSize,
434 wxVSCROLL | wxTAB_TRAVERSAL);
435 // This fools NVDA into not saying "Panel" when the dialog gets focus
436 parametersWindow->SetName(wxT("\a"));
437 parametersWindow->SetLabel(wxT("\a"));
438
439 auto mainSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
440
441 mainSizer->Add(parametersWindow, 1, wxEXPAND | wxALL, WindowBorder);
442
443 auto minSize = parametersWindow->GetSizer()->CalcMin();
444 if(minSize.GetHeight() > (WindowMaxHeight - WindowBorder * 2))
445 {
446 minSize.SetHeight(WindowMaxHeight);
447 parametersWindow->SetScrollRate(0, 20);
448 }
449 else
450 minSize.y += WindowBorder * 2;
451
452 parent.SetMinSize(minSize);
453 parent.SetSizer(mainSizer.release());
454
455 return parametersWindow;
456}
wxT("CloseDown"))
int min(int a, int b)
#define str(a)
const TranslatableString name
Definition: Distortion.cpp:76
#define safenew
Definition: MemoryX.h:10
static const auto title
TranslatableString label
Definition: TagsEditor.cpp:165
int id
virtual void UpdateAccessible(Steinberg::Vst::IEditController &editController, Steinberg::Vst::ParamValue value)
Steinberg::Vst::ParamID GetParameterId() const noexcept
VST3ParameterControl(Steinberg::Vst::ParamID id)
const Steinberg::Vst::ParamID mParameterId
virtual Steinberg::Vst::ParamValue GetNormalizedValue(Steinberg::Vst::IEditController &editController) const =0
virtual void SetNormalizedValue(Steinberg::Vst::IEditController &, Steinberg::Vst::ParamValue value)=0
"Plain" plugin UI, contains a list of parameter controls and values.
void OnParameterValueChanged(const wxCommandEvent &evt)
void RegisterParameterLabel(VST3ParameterControl *label)
std::unordered_map< Steinberg::Vst::ParamID, VST3ParameterControl * > mControls
void UpdateParameter(Steinberg::Vst::ParamID paramId)
const Steinberg::IPtr< Steinberg::Vst::IComponentHandler > mComponentHandler
VST3ParametersWindow(wxWindow *parent, Steinberg::Vst::IEditController &editController, Steinberg::Vst::IComponentHandler &handler, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxScrolledWindowStyle, const wxString &name=wxPanelNameStr)
static VST3ParametersWindow * Setup(wxWindow &parent, Steinberg::Vst::IEditController &editController, Steinberg::Vst::IComponentHandler &componentHandler)
Creates VST3ParametersWindow inside parent.
std::unordered_map< Steinberg::Vst::ParamID, VST3ParameterControl * > mLabels
void RegisterParameterControl(VST3ParameterControl *control)
const Steinberg::IPtr< Steinberg::Vst::IEditController > mEditController
static wxString ToWxString(const Steinberg::Vst::TChar *str)
Definition: VST3Utils.cpp:93
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
Steinberg::Vst::ParamValue GetNormalizedValue(Steinberg::Vst::IEditController &) const override
VST3ContinuousParameter(wxWindow *parent, wxWindowID id, Steinberg::Vst::ParamID paramId, const wxString &title, const wxString &units, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxSL_HORIZONTAL, const wxValidator &validator=wxDefaultValidator, const wxString &name=wxSliderNameStr)
void UpdateAccessible(Steinberg::Vst::IEditController &editController, Steinberg::Vst::ParamValue value) override
void SetNormalizedValue(Steinberg::Vst::IEditController &editController, Steinberg::Vst::ParamValue value) override
Steinberg::Vst::ParamValue GetNormalizedValue(Steinberg::Vst::IEditController &editController) const override
void SetNormalizedValue(Steinberg::Vst::IEditController &editController, Steinberg::Vst::ParamValue value) override
void UpdateAccessible(Steinberg::Vst::IEditController &editController, Steinberg::Vst::ParamValue value) override
VST3DiscreteParameter(wxWindow *parent, wxWindowID id, Steinberg::Vst::ParamID paramId, int maxValue, const wxString &title, const wxString &units, Steinberg::Vst::IEditController &editController, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxSL_HORIZONTAL, const wxValidator &validator=wxDefaultValidator, const wxString &name=wxSliderNameStr)
Steinberg::Vst::ParamValue GetNormalizedValue(Steinberg::Vst::IEditController &editController) const override
void SetNormalizedValue(Steinberg::Vst::IEditController &editController, Steinberg::Vst::ParamValue value) override
VST3ListParameter(wxWindow *parent, wxWindowID id, Steinberg::Vst::ParamID paramId, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=0, const wxValidator &validator=wxDefaultValidator, const wxString &name=wxChoiceNameStr)
void SetNormalizedValue(Steinberg::Vst::IEditController &editController, Steinberg::Vst::ParamValue value) override
VST3ToggleParameter(wxWindow *parent, wxWindowID id, const wxString &label, Steinberg::Vst::ParamID paramId, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxALIGN_RIGHT, const wxValidator &validator=wxDefaultValidator, const wxString &name=wxCheckBoxNameStr)
Steinberg::Vst::ParamValue GetNormalizedValue(Steinberg::Vst::IEditController &) const override
Steinberg::Vst::ParamValue GetNormalizedValue(Steinberg::Vst::IEditController &editController) const override
VST3ValueText(wxWindow *parent, wxWindowID id, Steinberg::Vst::ParamID paramId, const wxString &units, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=0, const wxString &name=wxStaticTextNameStr)
void SetNormalizedValue(Steinberg::Vst::IEditController &editController, Steinberg::Vst::ParamValue value) override