Audacity 3.2.0
VSTEditor.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 VSTEditor.cpp
6
7 Dominic Mazzoni
8
9 Paul Licameli split from VSTEffect.cpp
10
11 This class implements a VST Plug-in effect. The plug-in must be
12 loaded in a platform-specific way and passed into the constructor,
13 but from here this class handles the interfacing.
14
15*//********************************************************************/
16
17#include "VSTEditor.h"
18#include "VSTInstance.h"
19
20#include <wx/app.h>
21#include <wx/dialog.h>
22#include <wx/file.h>
23#include <wx/recguard.h>
24#include <wx/sizer.h>
25#include <wx/scrolwin.h>
26#include <wx/stattext.h>
27#include <wx/timer.h>
28#include "ShuttleGui.h"
29#include "../../widgets/NumericTextCtrl.h"
30
31#if wxUSE_ACCESSIBILITY
32#include "WindowAccessible.h"
33#endif
34
35// Put this inclusion last. On Linux it makes some unfortunate pollution of
36// preprocessor macro name space that interferes with other headers.
37#if defined(__WXOSX__)
38#include "VSTControlOSX.h"
39#elif defined(__WXMSW__)
40#include "VSTControlMSW.h"
41#elif defined(__WXGTK__)
42#include "VSTControlGTK.h"
43#endif
44
52class VSTTimer final : public wxTimer
53{
54public:
56 : wxTimer(),
57 mpEditor(pEditor)
58 {
59 }
60
62 {
63 }
64
65 void Notify()
66 {
68 }
69
70private:
72};
73
75//
76// VSTEffect
77//
79enum
80{
81 ID_Duration = 20000,
82 ID_Sliders = 21000,
83};
84
85wxDEFINE_EVENT(EVT_SIZEWINDOW, wxCommandEvent);
86DEFINE_LOCAL_EVENT_TYPE(EVT_UPDATEDISPLAY);
87
88int VSTEditor::ShowDialog(bool nonModal)
89{
90 mDialog->CentreOnParent();
91
92 if (nonModal)
93 {
94 mDialog->Show();
95 return 0;
96 }
97
98 return mDialog->ShowModal();
99}
100
102{
103 return mGui;
104}
105
107{
108 mAccess.Flush();
109}
110
112{
113 wxRecursionGuard guard(mTimerGuard);
114
115 // Ignore it if we're recursing
116 if (guard.IsInside())
117 {
118 return;
119 }
120
121 if (GetInstance().mVstVersion >= 2 && mWantsIdle)
122 {
123 int ret = GetInstance().callDispatcher(effIdle, 0, 0, NULL, 0.0);
124 if (!ret)
125 {
126 mWantsIdle = false;
127 }
128 }
129
130 if (mWantsEditIdle)
131 {
132 GetInstance().callDispatcher(effEditIdle, 0, 0, NULL, 0.0);
133 }
134}
135
137{
138 mWantsIdle = true;
139 mTimer->Start(100);
140}
141
143{
144 wxYieldIfNeeded();
145}
146
148{
149 mWantsEditIdle = state;
150 mTimer->Start(100);
151}
152
153void VSTEditor::NotifyParameterChanged(int index, float value)
154{
156
158 [index, value, &settings, this](const auto& pi)
159 {
160 if (pi.mID != index)
161 return true;
162
163 auto it = settings.mParamsMap.find(pi.mName);
164
165 // For consistency with other plugin families
166 constexpr float epsilon = 1.0e-5f;
167
168 if (
169 it == settings.mParamsMap.end() || !it->second.has_value() ||
170 std::abs(*it->second - value) > epsilon)
171 Publish(EffectSettingChanged { size_t(index), value });
172
173 return false;
174 });
175}
176
177void VSTEditor::OnIdle(wxIdleEvent& evt)
178{
179 evt.Skip();
180 if (!mLastMovements.empty()) {
181 // Be sure the instance has got any messages
182 mAccess.Flush();
184 // Update settings, for stickiness
185 // But don't do a complete FetchSettingsFromInstance
186 for (auto [index, value] : mLastMovements) {
187 if (index >= 0 && index < mParamNames.size()) {
188 const auto &string = mParamNames[index];
189 auto &mySettings = VSTWrapper::GetSettings(settings);
190 mySettings.mParamsMap[string] = value;
191 }
192 }
193 // Succeed but with a null message
194 return nullptr;
195 });
196 for (auto [index, _] : mLastMovements)
197 RefreshParameters(index);
198 mLastMovements.clear();
199 }
200
202
203 if ( GetInstance().OnePresetWasLoadedWhilePlaying() )
204 {
206 }
207
208}
209
210void VSTEditor::SizeWindow(int w, int h)
211{
212 // Queue the event to make the resizes smoother
213 if (mParent)
214 {
215 wxCommandEvent sw(EVT_SIZEWINDOW);
216 sw.SetInt(w);
217 sw.SetExtraLong(h);
218 mParent->GetEventHandler()->AddPendingEvent(sw);
219 }
220
221 return;
222}
223
224static void OnSize(wxSizeEvent & evt)
225{
226 evt.Skip();
227
228 // Once the parent dialog reaches its final size as indicated by
229 // a non-default minimum size, we set the maximum size to match.
230 // This is a bit of a hack to prevent VSTs GUI windows from resizing
231 // there's no real reason to allow it. But, there should be a better
232 // way of handling it.
233 wxWindow *w = (wxWindow *) evt.GetEventObject();
234 wxSize sz = w->GetMinSize();
235
236 if (sz != wxDefaultSize)
237 {
238 w->SetMaxSize(sz);
239 }
240}
241
243{
244 auto& vstEffInstance = dynamic_cast<VSTInstance&>(instance);
245
246 // Turn the power on...some effects need this when the editor is open
247 vstEffInstance.PowerOn();
248
250 if (!control)
251 {
252 return;
253 }
254
255 if (!control->Create(mParent, &vstEffInstance))
256 {
257 return;
258 }
259
260 {
261 auto mainSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
262
263 mainSizer->Add((mControl = control.release()), 0, wxALIGN_CENTER);
264
265 mParent->SetMinSize(wxDefaultSize);
266 mParent->SetSizer(mainSizer.release());
267 }
268
269 NeedEditIdle(true);
270
271 mDialog->Bind(wxEVT_SIZE, OnSize);
272
273
274 BindTo(*mDialog, EVT_SIZEWINDOW, &VSTEditor::OnSizeWindow);
275
276#ifdef __WXMAC__
277#ifdef __WX_EVTLOOP_BUSY_WAITING__
278 wxEventLoop::SetBusyWaiting(true);
279#endif
280#endif
281
282 return;
283}
284
285void VSTEditor::BuildPlain(EffectSettingsAccess &access, EffectType effectType, double projectRate)
286{
287 wxASSERT(mParent); // To justify safenew
288 wxScrolledWindow *const scroller = safenew wxScrolledWindow(mParent,
289 wxID_ANY,
290 wxDefaultPosition,
291 wxDefaultSize,
292 wxVSCROLL | wxTAB_TRAVERSAL);
293
294 {
295 auto mainSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
296
297 // Try to give the window a sensible default/minimum size
298 scroller->SetMinSize(wxSize(wxMax(600, mParent->GetSize().GetWidth() * 2 / 3),
299 mParent->GetSize().GetHeight() / 2));
300 scroller->SetScrollRate(0, 20);
301
302 // This fools NVDA into not saying "Panel" when the dialog gets focus
303 scroller->SetName(wxT("\a"));
304 scroller->SetLabel(wxT("\a"));
305
306 mainSizer->Add(scroller, 1, wxEXPAND | wxALL, 5);
307 mParent->SetSizer(mainSizer.release());
308 }
309
310 mNames.reinit(static_cast<size_t> (mNumParams));
311 mSliders.reinit(static_cast<size_t> (mNumParams));
312 mDisplays.reinit(static_cast<size_t>(mNumParams));
313 mLabels.reinit(static_cast<size_t> (mNumParams));
314
315 {
316 auto paramSizer = std::make_unique<wxStaticBoxSizer>(wxVERTICAL, scroller, _("Effect Settings"));
317
318 {
319 auto gridSizer = std::make_unique<wxFlexGridSizer>(4, 0, 0);
320 gridSizer->AddGrowableCol(1);
321
322 // Add the duration control for generators
323 if (effectType == EffectTypeGenerate)
324 {
325 wxControl *item = safenew wxStaticText(scroller, 0, _("Duration:"));
326 gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
327 auto &extra = access.Get().extra;
330 scroller, ID_Duration,
332 extra.GetDurationFormat(),
333 extra.GetDuration(),
335 .AutoPos(true));
336 mDuration->SetName( XO("Duration") );
337 gridSizer->Add(mDuration, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
338 gridSizer->Add(1, 1, 0);
339 gridSizer->Add(1, 1, 0);
340 }
341
342 // Find the longest parameter name.
343 int namew = 0;
344 int w;
345 int h;
346 for (int i = 0; i < mNumParams; i++)
347 {
348 wxString text = GetInstance().GetString(effGetParamName, i);
349
350 if (text.Right(1) != wxT(':'))
351 {
352 text += wxT(':');
353 }
354
355 scroller->GetTextExtent(text, &w, &h);
356 if (w > namew)
357 {
358 namew = w;
359 }
360 }
361
362 scroller->GetTextExtent(wxT("HHHHHHHH"), &w, &h);
363
364 for (int i = 0; i < mNumParams; i++)
365 {
366 mNames[i] = safenew wxStaticText(scroller,
367 wxID_ANY,
368 wxEmptyString,
369 wxDefaultPosition,
370 wxSize(namew, -1),
371 wxALIGN_RIGHT | wxST_NO_AUTORESIZE);
372 gridSizer->Add(mNames[i], 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
373
374 mSliders[i] = safenew wxSliderWrapper(scroller,
375 ID_Sliders + i,
376 0,
377 0,
378 1000,
379 wxDefaultPosition,
380 wxSize(200, -1));
381 gridSizer->Add(mSliders[i], 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 5);
382#if wxUSE_ACCESSIBILITY
383 // so that name can be set on a standard control
384 mSliders[i]->SetAccessible(safenew WindowAccessible(mSliders[i]));
385#endif
386
387 // Bind the slider to ::OnSlider
388 BindTo(*mSliders[i], wxEVT_COMMAND_SLIDER_UPDATED, &VSTEditor::OnSlider);
389
390 mDisplays[i] = safenew wxStaticText(scroller,
391 wxID_ANY,
392 wxEmptyString,
393 wxDefaultPosition,
394 wxSize(w, -1),
395 wxALIGN_RIGHT | wxST_NO_AUTORESIZE);
396 gridSizer->Add(mDisplays[i], 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
397
398 mLabels[i] = safenew wxStaticText(scroller,
399 wxID_ANY,
400 wxEmptyString,
401 wxDefaultPosition,
402 wxSize(w, -1),
403 wxALIGN_LEFT | wxST_NO_AUTORESIZE);
404 gridSizer->Add(mLabels[i], 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, 5);
405 }
406
407 paramSizer->Add(gridSizer.release(), 1, wxEXPAND | wxALL, 5);
408 }
409 scroller->SetSizer(paramSizer.release());
410 }
411
413
414 mSliders[0]->SetFocus();
415}
416
418{
419 if (!mNames)
420 {
421 return;
422 }
423
424 for (int i = 0; i < mNumParams; i++)
425 {
426 wxString text = GetInstance().GetString(effGetParamName, i);
427
428 text = text.Trim(true).Trim(false);
429
430 wxString name = text;
431
432 if (text.Right(1) != wxT(':'))
433 {
434 text += wxT(':');
435 }
436 mNames[i]->SetLabel(text);
437
438 // For some parameters types like on/off, setting the slider value has
439 // a side effect that causes it to only move when the parameter changes
440 // from off to on. However, this prevents changing the value using the
441 // keyboard, so we skip the active slider if any.
442 if (i != skip)
443 {
444 mSliders[i]->SetValue(GetInstance().callGetParameter(i) * 1000);
445 }
446 name = text;
447
449 if (text.empty())
450 {
451 text.Printf(wxT("%.5g"), GetInstance().callGetParameter(i));
452 }
453 mDisplays[i]->SetLabel(wxString::Format(wxT("%8s"), text));
454 name += wxT(' ') + text;
455
457 if (!text.empty())
458 {
459 text.Printf(wxT("%-8s"), GetInstance().GetString(effGetParamLabel, i));
460 mLabels[i]->SetLabel(wxString::Format(wxT("%8s"), text));
461 name += wxT(' ') + text;
462 }
463
464 mSliders[i]->SetName(name);
465 }
466}
467
468void VSTEditor::OnSizeWindow(wxCommandEvent & evt)
469{
470 if (!mControl)
471 {
472 return;
473 }
474
475 mControl->SetMinSize(wxSize(evt.GetInt(), (int) evt.GetExtraLong()));
476 mControl->SetSize(wxSize(evt.GetInt(), (int) evt.GetExtraLong()));
477
478 // DO NOT CHANGE THE ORDER OF THESE
479 //
480 // Guitar Rig (and possibly others) Cocoa VSTs can resize too large
481 // if the bounds are unlimited.
482 mDialog->SetMinSize(wxDefaultSize);
483 mDialog->SetMaxSize(wxDefaultSize);
484 mDialog->Layout();
485 mDialog->SetMinSize(mDialog->GetBestSize());
486 mDialog->SetMaxSize(mDialog->GetBestSize());
487 mDialog->Fit();
488}
489
490void VSTEditor::OnSlider(wxCommandEvent & evt)
491{
492 wxSlider *s = (wxSlider *) evt.GetEventObject();
493 int i = s->GetId() - ID_Sliders;
494 float value = s->GetValue() / 1000.0;
495
496 NotifyParameterChanged(i, value);
497 // Send changed settings (only) to the worker thread
498 mAccess.Set(GetInstance().MakeMessage(i, value));
499 mLastMovements.emplace_back(i, value);
500}
501
503{
504 // Update the controls on the plain UI
506
507 return true;
508}
509
511{
512 // Just for extra safety
514}
515
517 VSTInstance& instance,
518 EffectType type,
519 bool gui,
520 const EffectUIServices& services,
521 EffectSettingsAccess& access,
522 wxWindow* pParent,
523 int numParams
524)
525 : EffectEditor(services, access),
526 mType{ type },
527 mInstance(instance),
528 mGui{ gui },
529 mParent(pParent),
530 mDialog( static_cast<wxDialog*>(wxGetTopLevelParent(pParent)) ),
531 mNumParams(numParams)
532{
533 // In case of nondestructive processing, put an initial message in the
534 // queue for the instance
537 });
538
539 auto settings = mAccess.Get();
541
544 mParamNames.push_back(pi.mName);
545 return true;
546 } );
547
548 mTimer = std::make_unique<VSTTimer>(this);
549
550 wxTheApp->Bind(wxEVT_IDLE, &VSTEditor::OnIdle, this);
551}
552
553
555{
556 return mInstance;
557}
558
559void VSTEditor::Automate(int index, float value)
560{
561 NotifyParameterChanged(index, value);
562 // Send changed settings (only) to the worker thread
563 mAccess.Set(GetInstance().MakeMessage(index, value));
564 mLastMovements.emplace_back(index, value);
565}
566
568{
570}
571
573{
575}
576
578{
580 {
582 settings.extra.SetDuration(mDuration->GetValue());
583
585
586 return GetInstance().MakeMessage();
587 });
588
589 return true;
590}
591
593{
594
595#ifdef __WXMAC__
596#ifdef __WX_EVTLOOP_BUSY_WAITING__
597 wxEventLoop::SetBusyWaiting(false);
598#endif
599 if (mControl)
600 mControl->Close();
601#endif
602
603 // Tell the instance not to use me anymore - if we do not do this,
604 // hiding the gui and then showing it again *while playing*, would leave
605 // the instance with a dangling pointer to the old owning validator
606 // for a fraction of time, thereby causing a crash.
608
609 NeedEditIdle(false);
610
611 mNames.reset();
612 mSliders.reset();
613 mDisplays.reset();
614 mLabels.reset();
615
616 mParent = NULL;
617 mDialog = NULL;
618
619 mAccess.Flush();
620
621 ValidateUI();
622}
wxT("CloseDown"))
const TranslatableString name
Definition: Distortion.cpp:76
EffectType
@ EffectTypeGenerate
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
#define safenew
Definition: MemoryX.h:9
std::unique_ptr< T, Destroyer< T > > Destroy_ptr
a convenience for using Destroyer
Definition: MemoryX.h:163
const NumericConverterType & NumericConverterType_TIME()
static Settings & settings()
Definition: TrackInfo.cpp:69
DEFINE_LOCAL_EVENT_TYPE(EVT_UPDATEDISPLAY)
wxDEFINE_EVENT(EVT_SIZEWINDOW, wxCommandEvent)
static void OnSize(wxSizeEvent &evt)
Definition: VSTEditor.cpp:224
@ ID_Duration
Definition: VSTEditor.cpp:81
@ ID_Sliders
Definition: VSTEditor.cpp:82
const int effGetParamDisplay
Definition: aeffectx.h:100
const int effEditIdle
Definition: aeffectx.h:108
const int effIdle
Definition: aeffectx.h:127
const int effGetParamLabel
Definition: aeffectx.h:99
const int effGetParamName
Definition: aeffectx.h:101
void reinit(Integral count, bool initialize=false)
Definition: MemoryX.h:58
EffectSettingsAccess & mAccess
Definition: EffectEditor.h:92
void BindTo(wxEvtHandler &src, const EventTag &eventType, void(Class::*pmf)(Event &))
Definition: EffectEditor.h:85
Performs effect computation.
void ModifySettings(Function &&function)
Do a correct read-modify-write of settings.
virtual const EffectSettings & Get()=0
virtual void Set(EffectSettings &&settings, std::unique_ptr< Message > pMessage=nullptr)=0
virtual void Flush()=0
Make the last Set changes "persistent" in underlying storage.
static FormatterContext SampleRateContext(double sampleRate)
void SetName(const TranslatableString &name)
CallbackReturn Publish(const EffectSettingChanged &message)
Send a message to connected callbacks.
Definition: Observer.h:207
void Close()
void BuildPlain(EffectSettingsAccess &access, EffectType effectType, double projectRate)
Definition: VSTEditor.cpp:285
VSTControl * mControl
Definition: VSTEditor.h:108
wxWindow * mParent
Definition: VSTEditor.h:105
ArrayOf< wxStaticText * > mLabels
Definition: VSTEditor.h:102
void NeedIdle() override
Definition: VSTEditor.cpp:136
bool IsGraphicalUI() override
Definition: VSTEditor.cpp:101
bool FetchSettingsFromInstance(EffectSettings &settings)
Definition: VSTEditor.cpp:567
void BuildFancy(EffectInstance &instance)
Definition: VSTEditor.cpp:242
void NotifyParameterChanged(int index, float value)
Definition: VSTEditor.cpp:153
bool mWantsEditIdle
Definition: VSTEditor.h:93
void OnSizeWindow(wxCommandEvent &evt)
Definition: VSTEditor.cpp:468
std::unique_ptr< VSTTimer > mTimer
Definition: VSTEditor.h:58
bool mWantsIdle
Definition: VSTEditor.h:94
void Flush() override
Definition: VSTEditor.cpp:106
std::vector< wxString > mParamNames
Definition: VSTEditor.h:111
bool StoreSettingsToInstance(const EffectSettings &settings)
Definition: VSTEditor.cpp:572
void Automate(int index, float value) override
Definition: VSTEditor.cpp:559
void OnSlider(wxCommandEvent &evt)
Definition: VSTEditor.cpp:490
void NeedEditIdle(bool state)
Definition: VSTEditor.cpp:147
ArrayOf< wxStaticText * > mDisplays
Definition: VSTEditor.h:101
const bool mGui
Definition: VSTEditor.h:81
int mTimerGuard
Definition: VSTEditor.h:91
wxWeakRef< wxDialog > mDialog
Definition: VSTEditor.h:106
void SizeWindow(int w, int h) override
Definition: VSTEditor.cpp:210
int mNumParams
Definition: VSTEditor.h:113
void OnClose() override
Definition: VSTEditor.cpp:592
bool ValidateUI() override
Get settings data from the panel; may make error dialogs and return false.
Definition: VSTEditor.cpp:577
ArrayOf< wxStaticText * > mNames
Definition: VSTEditor.h:99
const EffectType mType
Definition: VSTEditor.h:80
void RefreshParameters(int skip=-1) const
Definition: VSTEditor.cpp:417
void Idle() override
Definition: VSTEditor.cpp:142
int ShowDialog(bool nonModal)
Definition: VSTEditor.cpp:88
void OnIdle(wxIdleEvent &evt)
Definition: VSTEditor.cpp:177
std::vector< std::pair< int, double > > mLastMovements
Definition: VSTEditor.h:97
VSTInstance & GetInstance() const
Definition: VSTEditor.cpp:554
~VSTEditor() override
Definition: VSTEditor.cpp:510
NumericTextCtrl * mDuration
Definition: VSTEditor.h:103
bool UpdateUI() override
Update appearance of the panel for changes in settings.
Definition: VSTEditor.cpp:502
VSTEditor(VSTInstance &instance, EffectType type, bool gui, const EffectUIServices &services, EffectSettingsAccess &access, wxWindow *pParent, int numParams)
Definition: VSTEditor.cpp:516
void OnTimer()
Definition: VSTEditor.cpp:111
VSTInstance & mInstance
Definition: VSTEditor.h:79
ArrayOf< wxSlider * > mSliders
Definition: VSTEditor.h:100
void DeferChunkApplication()
void PowerOn()
std::unique_ptr< Message > MakeMessage() const override
Called on the main thread, in which the result may be cloned.
Definition: VSTInstance.cpp:17
void SetOwningValidator(VSTUIWrapper *vi)
void Notify()
Definition: VSTEditor.cpp:65
VSTEditor * mpEditor
Definition: VSTEditor.cpp:71
VSTTimer(VSTEditor *pEditor)
Definition: VSTEditor.cpp:55
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
Message sent by EffectEditor when a setting is changed by the user.
Definition: EffectEditor.h:26
Externalized state of a plug-in.
EffectSettingsExtra extra
Options & AutoPos(bool enable)
int GetString(wxString &outstr, int opcode, int index=0) const
Definition: VSTWrapper.cpp:651
bool FetchSettings(VSTSettings &vst3Settings, bool doFetch=true) const
static VSTSettings & GetSettings(EffectSettings &settings)
Definition: VSTWrapper.h:103
bool StoreSettings(const VSTSettings &vst3settings) const
void ForEachParameter(ParameterVisitor visitor) const
intptr_t callDispatcher(int opcode, int index, intptr_t value, void *ptr, float opt) override
Definition: VSTWrapper.cpp:682
std::unique_ptr< EffectInstance::Message > MakeMessageFS(const VSTSettings &settings) const