Audacity 3.2.0
LV2Validator.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file LV2Validator.cpp
6
7 Paul Licameli split from LV2Effect.cpp
8
9 Audacity(R) is copyright (c) 1999-2008 Audacity Team.
10 License: GPL v2 or later. See License.txt.
11
12**********************************************************************/
13
14#if defined(USE_LV2)
15
16#if defined(__GNUC__)
17#pragma GCC diagnostic ignored "-Wparentheses"
18#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
19#elif defined(__clang__)
20#pragma clang diagnostic ignored "-Wparentheses"
21#pragma clang diagnostic ignored "-Wdeprecated-declarations"
22#endif
23
24#include "LV2Validator.h"
25#include "effects/EffectBase.h"
26#include "LV2EffectMeter.h"
27#include "LV2Instance.h"
28#include "LV2Wrapper.h"
29
30#include "ShuttleGui.h"
31
32#include <wx/button.h>
33#include <wx/checkbox.h>
34#include <wx/choice.h>
35
36#include <wx/sizer.h>
37
38#include <wx/stattext.h>
39#include <wx/textctrl.h>
40
41#include <wx/scrolwin.h>
42
43#include "../../widgets/valnum.h"
44
45#include "../../widgets/NumericTextCtrl.h"
46
47#if defined(__WXGTK__)
48#include <gtk/gtk.h>
49#endif
50
52{
53#ifdef __WXMAC__
54 // Issue 3222: x42-limiter does an unbalanced release somewhere, requiring
55 // the host to add an extra reference. But other plug-ins don't expect
56 // that.
57 // This is how we can accommodate them either way.
58
59 const auto widget = (mNativeWin && mSuilInstance)
60 ? static_cast<WXWidget>(suil_instance_get_widget(mSuilInstance.get()))
61 : nullptr;
62 if (widget) {
63 if (!mJustLeakMemory) {
64 // Bump reference count twice
65 wxCFRetain(widget);
66 wxCFRetain(widget);
67 }
68 else {
69 // Compensates for either an unbalanced release, or (when closing the
70 // project while the dialog is open) dangling pointers remaining after
71 // the last release, by adding one release. In the second case, it
72 // leaks resources.
73 // (We can't detect here, which of the two cases applies.)
74 wxCFRetain(widget);
75 }
76
77 // Do destruction of mNativeWin, which points to widget, and the suil
78 // instance in the scope of an autorelease pool
79 {
80 wxMacAutoreleasePool pool;
81 mNativeWin = nullptr;
82 mSuilInstance.reset();
83 // Deferred releases are forced to happen here, not later up the stack
84 // in the enclosing wxMacAutoreleasePool given by wxWidgets
85 }
86
87 // Two increases of the reference count means there can be one unbalanced
88 // release and yet we can still query the use-count safely
89 if (!mJustLeakMemory) {
90 int count = CFGetRetainCount(widget);
91 wxCFRelease(widget);
92 if (count > 1)
93 // Most plug-ins should come here, but x42-limiter will not!
94 wxCFRelease(widget);
95 }
96 }
97#else
98 mNativeWin = nullptr;
99#endif
100}
101
103 const LilvPlugin &plug, LV2Instance &instance,
104 EffectSettingsAccess &access, const EffectOutputs *pOutputs,
105 double sampleRate,
106 const LV2FeaturesList &features,
107 const LV2Ports &ports, wxWindow *parent, bool useGUI
108) : EffectUIValidator{ effect, access }
109 , mPlug{ plug }
110 , mType{ effect.GetType() }
111 , mInstance{ instance }
112 , mpOutputs{ pOutputs }
113 , mSampleRate{ sampleRate }
114 , mPorts{ ports }
115 , mPortUIStates{ instance.GetPortStates(), ports }
116 , mParent{ parent }
117 , mUseGUI{ useGUI }
118{
119 if (mParent)
120 mParent->PushEventHandler(this);
121}
122
123enum
124{
125 ID_Duration = 10000,
126 ID_Triggers = 11000,
127 ID_Toggles = 12000,
128 ID_Sliders = 13000,
129 ID_Choices = 14000,
130 ID_Texts = 15000,
131};
132
133BEGIN_EVENT_TABLE(LV2Validator, wxEvtHandler)
135 EVT_COMMAND_RANGE(ID_Toggles, ID_Toggles + 999, wxEVT_COMMAND_CHECKBOX_CLICKED, LV2Validator::OnToggle)
136 EVT_COMMAND_RANGE(ID_Sliders, ID_Sliders + 999, wxEVT_COMMAND_SLIDER_UPDATED, LV2Validator::OnSlider)
137 EVT_COMMAND_RANGE(ID_Choices, ID_Choices + 999, wxEVT_COMMAND_CHOICE_SELECTED, LV2Validator::OnChoice)
139
140 EVT_IDLE(LV2Validator::OnIdle)
142
143bool LV2Validator::IsGraphicalUI()
144{
145 return mUseGUI;
146}
147
149{
152 settings.extra.SetDuration(mDuration->GetValue());
153 return nullptr;
154 });
155 return true;
156}
157
159{
160 // Disconnect the plain UI output meters
161 if (!mPlainUIControls.empty()) {
162 size_t p = 0;
163 for (auto &port : mPorts.mControlPorts) {
164 if (!port->mIsInput)
165 if (auto &pMeter = mPlainUIControls[p].meter) {
166 pMeter->Disconnect();
167 pMeter = nullptr;
168 }
169 ++p;
170 }
171 }
172 // The idle event handler for the fancy UI output must disconnect too
173 mpOutputs = nullptr;
174 if (mParent) {
175 mParent->PopEventHandler();
176 mParent = nullptr;
177 }
178 mUI.Destroy();
179}
180
182{
183 Disconnect();
184}
185
186std::shared_ptr<SuilHost> LV2Validator::GetSuilHost()
187{
188 // This is a unique_ptr specialization
189 using SuilHostPtr = Lilv_ptr<SuilHost, suil_host_free>;
190 // The host has no dependency on the plug-in and can be shared among
191 // validators
192 static std::weak_ptr<SuilHost> sSuilHost;
193 std::shared_ptr<SuilHost> result = sSuilHost.lock();
194 if (!result)
195 // shared_ptr erases type of custom deleter of SuilHostPtr
196 sSuilHost = result = SuilHostPtr{ suil_host_new(
198 LV2UIFeaturesList::suil_port_index, nullptr, nullptr) };
199 return result;
200}
201
203 std::unique_ptr<LV2Wrapper> pWrapper, const EffectSettings &settings)
204{
205 assert(pWrapper);
206 auto &wrapper = *pWrapper;
207 mpWrapper = move(pWrapper);
208 using namespace LV2Symbols;
209 // Set the native UI type
210 const char *nativeType =
211#if defined(__WXGTK3__)
212 LV2_UI__Gtk3UI;
213#elif defined(__WXGTK__)
214 LV2_UI__GtkUI;
215#elif defined(__WXMSW__)
216 LV2_UI__WindowsUI;
217#elif defined(__WXMAC__)
218 LV2_UI__CocoaUI;
219#endif
220
221 // Determine if the plugin has a supported UI
222 const LilvUI *ui = nullptr;
223 const LilvNode *uiType = nullptr;
224 using LilvUIsPtr = Lilv_ptr<LilvUIs, lilv_uis_free>;
225 LilvUIsPtr uis{ lilv_plugin_get_uis(&mPlug) };
226 if (uis) {
227 if (LilvNodePtr containerType{ lilv_new_uri(gWorld, nativeType) }) {
228 LILV_FOREACH(uis, iter, uis.get()) {
229 ui = lilv_uis_get(uis.get(), iter);
230 if (lilv_ui_is_supported(ui,
231 suil_ui_supported, containerType.get(), &uiType))
232 break;
233 if (lilv_ui_is_a(ui, node_Gtk) || lilv_ui_is_a(ui, node_Gtk3)) {
234 uiType = node_Gtk;
235 break;
236 }
237 ui = nullptr;
238 }
239 }
240 }
241
242 // Check for other supported UIs
243 if (!ui && uis) {
244 LILV_FOREACH(uis, iter, uis.get()) {
245 ui = lilv_uis_get(uis.get(), iter);
246 if (lilv_ui_is_a(ui, node_ExternalUI) || lilv_ui_is_a(ui, node_ExternalUIOld)) {
247 uiType = node_ExternalUI;
248 break;
249 }
250 ui = NULL;
251 }
252 }
253
254 // No usable UI found
255 if (ui == NULL)
256 return false;
257
258 const auto uinode = lilv_ui_get_uri(ui);
259 lilv_world_load_resource(gWorld, uinode);
261 auto &instance = wrapper.GetInstance();
262 auto &features = mUIFeatures.emplace(
263 wrapper.GetFeatures(), &handler, uinode, &instance,
264 (uiType == node_ExternalUI) ? nullptr : mParent);
265 if (!features.mOk)
266 return false;
267
268 const char *containerType;
269 if (uiType == node_ExternalUI)
270 containerType = LV2_EXTERNAL_UI__Widget;
271 else {
272 containerType = nativeType;
273#if defined(__WXGTK__)
274 // Make sure the parent has a window
275 if (!gtk_widget_get_window(GTK_WIDGET(mParent->m_wxwindow)))
276 gtk_widget_realize(GTK_WIDGET(mParent->m_wxwindow));
277#endif
278 }
279
280 // Set before creating the UI instance so the initial size (if any) can be captured
281 mNativeWinInitialSize = wxDefaultSize;
282 mNativeWinLastSize = wxDefaultSize;
283
284 // Create the suil host
285 if (!(mSuilHost = GetSuilHost()))
286 return false;
287
288#if defined(__WXMSW__)
289 // Plugins may have dependencies that need to be loaded from the same path
290 // as the main DLL, so add this plugin's path to the DLL search order.
291 LilvCharsPtr libPath{
292 lilv_file_uri_parse(lilv_node_as_uri(lilv_ui_get_binary_uri(ui)),
293 nullptr)
294 };
295 const auto path = wxPathOnly(libPath.get());
296 SetDllDirectory(path.c_str());
297 auto cleanup = finally([&]{ SetDllDirectory(nullptr); });
298#endif
299
300 LilvCharsPtr bundlePath{
301 lilv_file_uri_parse(lilv_node_as_uri(lilv_ui_get_bundle_uri(ui)), nullptr)
302 };
303 LilvCharsPtr binaryPath{
304 lilv_file_uri_parse(lilv_node_as_uri(lilv_ui_get_binary_uri(ui)), nullptr)
305 };
306
307 // The void* that the instance passes back to our write and index
308 // callback functions, which were given to suil_host_new:
309 UIHandler *pHandler = this;
310
311 // Reassign the sample rate, which is pointed to by options, which are
312 // pointed to by features, before we tell the library the features
313 mUI.mSuilInstance.reset(suil_instance_new(mSuilHost.get(),
314 pHandler, containerType,
315 lilv_node_as_uri(lilv_plugin_get_uri(&mPlug)),
316 lilv_node_as_uri(lilv_ui_get_uri(ui)), lilv_node_as_uri(uiType),
317 bundlePath.get(), binaryPath.get(),
318 features.GetFeaturePointers().data()));
319
320 // Bail if the instance (no compatible UI) couldn't be created
321 if (!mUI.mSuilInstance)
322 return false;
323
324 if (uiType == node_ExternalUI) {
325 mParent->SetMinSize(wxDefaultSize);
327 suil_instance_get_widget(mUI.mSuilInstance.get()));
328 mTimer.Start(20);
330 } else {
331 const auto widget = static_cast<WXWidget>(
332 suil_instance_get_widget(mUI.mSuilInstance.get()));
333
334#if defined(__WXGTK__)
335 // Needed by some plugins (e.g., Invada) to ensure the display is fully
336 // populated.
337 gtk_widget_show_all(widget);
338
339 // See note at size_request()
340 g_signal_connect(widget, "size-request", G_CALLBACK(LV2Validator::size_request), this);
341#endif
342
343 wxWindowPtr< NativeWindow > pNativeWin{ safenew NativeWindow() };
344 if (!pNativeWin->Create(mParent, widget))
345 return false;
346 mUI.mNativeWin = pNativeWin;
347 pNativeWin->Bind(wxEVT_SIZE, &LV2Validator::OnSize, this);
348
349 // The plugin called the LV2UI_Resize::ui_resize function to set the size before
350 // the native window was created, so set the size now.
351 if (mNativeWinInitialSize != wxDefaultSize)
352 pNativeWin->SetMinSize(mNativeWinInitialSize);
353
354 wxSizerItem *si = NULL;
355 auto vs = std::make_unique<wxBoxSizer>(wxVERTICAL);
356 auto hs = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
357 if (features.mNoResize) {
358 si = hs->Add(pNativeWin.get(), 0, wxCENTER);
359 vs->Add(hs.release(), 1, wxCENTER);
360 } else {
361 si = hs->Add(pNativeWin.get(), 1, wxEXPAND);
362 vs->Add(hs.release(), 1, wxEXPAND);
363 }
364 if (!si)
365 return false;
366 mParent->SetSizerAndFit(vs.release());
367 }
368
369 mUIIdleInterface = static_cast<const LV2UI_Idle_Interface *>(
370 suil_instance_extension_data(mUI.mSuilInstance.get(), LV2_UI__idleInterface));
371
372 mUIShowInterface = static_cast<const LV2UI_Show_Interface *>(
373 suil_instance_extension_data(mUI.mSuilInstance.get(), LV2_UI__showInterface));
374
375// if (mUIShowInterface && mUIShowInterface->show) {
376// mUIShowInterface->show(suil_instance_get_handle(mSuilInstance));
377// }
378
379#ifdef __WXMAC__
380#ifdef __WX_EVTLOOP_BUSY_WAITING__
381 wxEventLoop::SetBusyWaiting(true);
382#endif
383#endif
384
385 return true;
386}
387
389{
390 auto &portUIStates = mPortUIStates;
391 auto &settings = access.Get();
394
395 int numCols = 5;
396 wxSizer *innerSizer;
397
398 assert(mParent); // To justify safenew
399 const auto w = safenew wxScrolledWindow(mParent,
400 wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxTAB_TRAVERSAL);
401
402 {
403 auto outerSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
404 w->SetScrollRate(0, 20);
405 // This fools NVDA into not saying "Panel" when the dialog gets focus
406 w->SetName(wxT("\a"));
407 w->SetLabel(wxT("\a"));
408 outerSizer->Add(w, 1, wxEXPAND);
409
410 auto uInnerSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
411 innerSizer = uInnerSizer.get();
412
413 // Add the duration control, if a generator
414 if (mType == EffectTypeGenerate) {
415 auto sizer = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
416 auto item = safenew wxStaticText(w, 0, _("&Duration:"));
417 sizer->Add(item, 0, wxALIGN_CENTER | wxALL, 5);
418 auto &extra = settings.extra;
420 NumericConverter::TIME, extra.GetDurationFormat(),
421 extra.GetDuration(), mSampleRate,
423 mDuration->SetName( XO("Duration") );
424 sizer->Add(mDuration, 0, wxALIGN_CENTER | wxALL, 5);
425 auto groupSizer =
426 std::make_unique<wxStaticBoxSizer>(wxVERTICAL, w, _("Generator"));
427 groupSizer->Add(sizer.release(), 0, wxALIGN_CENTER | wxALL, 5);
428 innerSizer->Add(groupSizer.release(), 0, wxEXPAND | wxALL, 5);
429 }
430
431 // Make other controls, grouped into static boxes that are named
432 // according to certain control port metadata
433 auto groups = mPorts.mGroups; // mutable copy
434 std::sort(groups.begin(), groups.end(), TranslationLess);
435 for (auto &label: groups) {
436 auto gridSizer = std::make_unique<wxFlexGridSizer>(numCols, 5, 5);
437 gridSizer->AddGrowableCol(3);
438 for (auto & p : mPorts.mGroupMap.at(label)) /* won't throw */ {
439 auto &state = portUIStates.mControlPortStates[p];
440 auto &port = state.mpPort;
441 auto &ctrl = mPlainUIControls[p];
442 const auto &value = values[p];
443 auto labelText = port->mName;
444 if (!port->mUnits.empty())
445 labelText += XO("(%s)").Format(port->mUnits).Translation();
446
447 // A "trigger" port gets a row with just a pushbutton
448 if (port->mTrigger) {
449 gridSizer->Add(1, 1, 0);
450
451 assert(w); // To justify safenew
452 auto b = safenew wxButton(w, ID_Triggers + p, labelText);
453 gridSizer->Add(b, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
454 ctrl.button = b;
455
456 gridSizer->Add(1, 1, 0);
457 gridSizer->Add(1, 1, 0);
458 gridSizer->Add(1, 1, 0);
459 continue;
460 }
461
462 // Any other kind of port gets a name text...
463 auto item = safenew wxStaticText(w, wxID_ANY, labelText + wxT(":"),
464 wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT);
465 gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT);
466
467 // ... then appropriate controls and static texts in other columns
468 if (port->mToggle) {
469 // Toggle port gets a checkbox
470 auto c = safenew wxCheckBox(w, ID_Toggles + p, wxT(""));
471 c->SetName(labelText);
472 c->SetValue(value > 0);
473 gridSizer->Add(c, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
474 ctrl.checkbox = c;
475
476 gridSizer->Add(1, 1, 0);
477 gridSizer->Add(1, 1, 0);
478 gridSizer->Add(1, 1, 0);
479 }
480 else if (port->mEnumeration) {
481 // Enumeration port gets a choice control
482 // Discretize the value (all ports hold a float value) to
483 // determine the intial selection
484 auto s = port->Discretize(value);
485 auto c = safenew wxChoice(w, ID_Choices + p);
486 c->SetName(labelText);
487 c->Append(port->mScaleLabels);
488 c->SetSelection(s);
489 gridSizer->Add(c, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
490 ctrl.choice = c;
491
492 gridSizer->Add(1, 1, 0);
493 gridSizer->Add(1, 1, 0);
494 gridSizer->Add(1, 1, 0);
495 }
496 else if (!port->mIsInput) {
497 // Real-valued output gets a meter control
498 gridSizer->Add(1, 1, 0);
499 gridSizer->Add(1, 1, 0);
500
501 static float sink;
502 const auto pOutputValues =
503 static_cast<const LV2EffectOutputs*>(mpOutputs);
504 const auto pValue =
505 pOutputValues ? &pOutputValues->values[p] : &sink;
507 auto m = safenew LV2EffectMeter(w, port, *pValue);
508 gridSizer->Add(m, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND);
509 ctrl.meter = m;
510
511 gridSizer->Add(1, 1, 0);
512 }
513 else {
514 // Numerical input gets a text input, with a validator...
515 auto t = safenew wxTextCtrl(w, ID_Texts + p, wxT(""));
516 t->SetName(labelText);
517 gridSizer->Add(t, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
518 ctrl.mText = t;
519 auto rate = port->mSampleRate ? mSampleRate : 1.0f;
520 state.mLo = port->mMin * rate;
521 state.mHi = port->mMax * rate;
522 state.mTmp = value * rate;
523 if (port->mInteger) {
524 IntegerValidator<float> vld(&state.mTmp);
525 vld.SetRange(state.mLo, state.mHi);
526 t->SetValidator(vld);
527 }
528 else {
529 FloatingPointValidator<float> vld(6, &state.mTmp);
530 vld.SetRange(state.mLo, state.mHi);
531
532 // Set number of decimal places
533 float range = state.mHi - state.mLo;
534 auto style = range < 10
535 ? NumValidatorStyle::THREE_TRAILING_ZEROES
536 : range < 100
537 ? NumValidatorStyle::TWO_TRAILING_ZEROES
538 : NumValidatorStyle::ONE_TRAILING_ZERO;
539 vld.SetStyle(style);
540 t->SetValidator(vld);
541 }
542
543 // ... optional lower-bound static text ...
544 if (port->mHasLo) {
545 wxString str;
546 if (port->mInteger || port->mSampleRate)
547 str.Printf(wxT("%d"), (int) lrintf(state.mLo));
548 else
549 str = Internat::ToDisplayString(state.mLo);
550 item = safenew wxStaticText(w, wxID_ANY, str);
551 gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT);
552 }
553 else
554 gridSizer->Add(1, 1, 0);
555
556 // ... a slider ...
557 auto s = safenew wxSliderWrapper(w, ID_Sliders + p,
558 0, 0, 1000, wxDefaultPosition, { 150, -1 });
559 s->SetName(labelText);
560 gridSizer->Add(s, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND);
561 ctrl.slider = s;
562
563 // ... and optional upper-bound static text
564 if (port->mHasHi) {
565 wxString str;
566 if (port->mInteger || port->mSampleRate)
567 str.Printf(wxT("%d"), (int) lrintf(state.mHi));
568 else
569 str = Internat::ToDisplayString(state.mHi);
570 item = safenew wxStaticText(w, wxID_ANY, str);
571 gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
572 }
573 else
574 gridSizer->Add(1, 1, 0);
575 }
576 }
577
578 auto groupSizer = std::make_unique<wxStaticBoxSizer>(
579 wxVERTICAL, w, label.Translation());
580 groupSizer->Add(gridSizer.release(), 1, wxEXPAND | wxALL, 5);
581 innerSizer->Add(groupSizer.release(), 0, wxEXPAND | wxALL, 5);
582 }
583
584 innerSizer->Layout();
585
587 auto VisitCells = [&, cnt = innerSizer->GetChildren().GetCount()](auto f){
588 for (size_t i = (mType == EffectTypeGenerate); i < cnt; ++i) {
589 // For each group (skipping duration) visit the grid sizer
590 auto groupSizer = innerSizer->GetItem(i)->GetSizer();
591 auto gridSizer = static_cast<wxFlexGridSizer *>(
592 groupSizer->GetItem(size_t{0})->GetSizer());
593 auto items = gridSizer->GetChildren().GetCount();
594 size_t cols = gridSizer->GetCols();
595 for (size_t j = 0; j < items; ++j) {
596 // For each grid item
597 auto item = gridSizer->GetItem(j);
598 f(item, j, cols);
599 }
600 }
601 };
602
603 // Calculate the maximum width of all columns (bypass Generator sizer)
604 std::vector<int> widths(numCols);
605 VisitCells([&](wxSizerItem *item, size_t j, size_t cols){
606 auto &width = widths[j % cols];
607 width = std::max(width, item->GetSize().GetWidth());
608 });
609
610 // Set each column in all of the groups to the same width.
611 VisitCells([&](wxSizerItem *item, size_t j, size_t cols){
612 int flags = item->GetFlag();
613 if (flags & wxEXPAND)
614 return;
615 if (flags & wxALIGN_RIGHT)
616 flags = (flags & ~wxALL) | wxLEFT;
617 else
618 flags = (flags & ~wxALL) | wxRIGHT;
619 item->SetFlag(flags);
620 item->SetBorder(widths[j % cols] - item->GetMinSize().GetWidth());
621 });
622
623 w->SetSizer(uInnerSizer.release());
624
625 mParent->SetSizer(outerSizer.release());
626 } // scope of unique_ptrs of sizers
627
628 // Try to give the window a sensible default/minimum size
629 wxSize sz1 = innerSizer->GetMinSize();
630 wxSize sz2 = mParent->GetMinSize();
631 w->SetMinSize( { -1, std::min(sz1.y, sz2.y) } );
632
633 // And let the parent reduce to the NEW minimum if possible
634 mParent->SetMinSize(w->GetMinSize());
635
636 return true;
637}
638
640{
641 const auto &mySettings = GetSettings(mAccess.Get());
642 auto pMaster = mInstance.GetMaster();
643
644 if (pMaster && mySettings.mpState) {
645 // Maybe there are other important side effects on the instance besides
646 // changes of port values
647 lilv_state_restore(mySettings.mpState.get(), &pMaster->GetInstance(),
648 nullptr, nullptr, 0, nullptr);
649 // Destroy the short lived carrier of preset state
650 mySettings.mpState.reset();
651 }
652
653 auto &values = mySettings.values;
654 {
655 size_t index = 0; for (auto & state : mPortUIStates.mControlPortStates) {
656 auto &port = state.mpPort;
657 if (port->mIsInput)
658 state.mTmp =
659 values[index] * (port->mSampleRate ? mSampleRate : 1.0);
660 ++index;
661 }
662 }
663
664 if (mUseGUI) {
665 // fancy UI
666 if (mUI.mSuilInstance) {
667 size_t index = 0;
668 for (auto & port : mPorts.mControlPorts) {
669 if (port->mIsInput)
670 suil_instance_port_event(mUI.mSuilInstance.get(),
671 port->mIndex, sizeof(float),
672 /* Means this event sends a float: */ 0,
673 &values[index]);
674 ++index;
675 }
676 }
677 return true;
678 }
679
680 // else plain UI
681 // Visiting controls by groups
682 for (auto & group : mPorts.mGroups) {
683 const auto & params = mPorts.mGroupMap.at(group); /* won't throw */
684 for (auto & param : params) {
685 auto &state = mPortUIStates.mControlPortStates[param];
686 auto &port = state.mpPort;
687 auto &ctrl = mPlainUIControls[param];
688 auto &value = values[param];
689 if (port->mTrigger)
690 continue;
691 else if (port->mToggle)
692 ctrl.checkbox->SetValue(value > 0);
693 else if (port->mEnumeration) // Check before integer
694 ctrl.choice->SetSelection(port->Discretize(value));
695 else if (port->mIsInput) {
696 state.mTmp = value * (port->mSampleRate ? mSampleRate : 1.0f);
697 SetSlider(state, ctrl);
698 }
699 }
700 }
701 if (mParent && !mParent->TransferDataToWindow())
702 return false;
703 return true;
704}
705
707 const LV2ControlPortState &state, const PlainUIControl &ctrl)
708{
709 float lo = state.mLo;
710 float hi = state.mHi;
711 float val = state.mTmp;
712 if (state.mpPort->mLogarithmic) {
713 lo = logf(lo);
714 hi = logf(hi);
715 val = logf(val);
716 }
717 ctrl.slider->SetValue(lrintf((val - lo) / (hi - lo) * 1000.0));
718}
719
721 LV2EffectSettings& settings, size_t controlPortIndex, float value)
722{
723 const auto currentValue = settings.values[controlPortIndex];
724
725 // LV2 implementation allows to edit the values
726 // using text boxes too. Provide sufficiently small epsilon
727 // to distinguish between the values.
728 // (for example, conversion from the text representation
729 // is always lossy, so direct comparison of the values
730 // is not possible, nor should it be used for float values)
731 const auto epsilon = 1e-5f;
732
733 if (std::abs(currentValue - value) < epsilon)
734 return;
735
736 settings.values[controlPortIndex] = value;
737 Publish({ mPorts.mControlPorts[controlPortIndex]->mIndex, value });
738}
739
740void LV2Validator::OnTrigger(wxCommandEvent &evt)
741{
742 size_t idx = evt.GetId() - ID_Triggers;
743 auto & port = mPorts.mControlPorts[idx];
746 return nullptr;
747 });
748}
749
750void LV2Validator::OnToggle(wxCommandEvent &evt)
751{
752 size_t idx = evt.GetId() - ID_Toggles;
755 GetSettings(settings), idx, evt.GetInt() ? 1.0 : 0.0);
756 return nullptr;
757 });
758}
759
760void LV2Validator::OnChoice(wxCommandEvent &evt)
761{
762 size_t idx = evt.GetId() - ID_Choices;
763 auto & port = mPorts.mControlPorts[idx];
766 GetSettings(settings), idx, port->mScaleValues[evt.GetInt()]);
767 return nullptr;
768 });
769}
770
771void LV2Validator::OnText(wxCommandEvent &evt)
772{
773 size_t idx = evt.GetId() - ID_Texts;
774 auto &state = mPortUIStates.mControlPortStates[idx];
775 auto &port = state.mpPort;
776 auto &ctrl = mPlainUIControls[idx];
777 if (ctrl.mText->GetValidator()->TransferFromWindow()) {
780 GetSettings(settings), idx,
781 port->mSampleRate ? state.mTmp / mSampleRate : state.mTmp);
782 return nullptr;
783 });
784 SetSlider(state, ctrl);
785 }
786}
787
788void LV2Validator::OnSlider(wxCommandEvent &evt)
789{
790 size_t idx = evt.GetId() - ID_Sliders;
791 auto &state = mPortUIStates.mControlPortStates[idx];
792 auto &port = state.mpPort;
793 float lo = state.mLo;
794 float hi = state.mHi;
795 if (port->mLogarithmic) {
796 lo = logf(lo);
797 hi = logf(hi);
798 }
799 state.mTmp = (((float) evt.GetInt()) / 1000.0) * (hi - lo) + lo;
800 state.mTmp = std::clamp(state.mTmp, lo, hi);
801 state.mTmp = port->mLogarithmic ? expf(state.mTmp) : state.mTmp;
804 GetSettings(settings), idx,
805 port->mSampleRate ? state.mTmp / mSampleRate : state.mTmp);
806 return nullptr;
807 });
808 mPlainUIControls[idx].mText->GetValidator()->TransferToWindow();
809}
810
812{
813 if (mExternalWidget)
815}
816
817void LV2Validator::OnIdle(wxIdleEvent &evt)
818{
819 evt.Skip();
820 if (!mUI.mSuilInstance)
821 return;
822
823 if (mExternalUIClosed) {
824 mExternalUIClosed = false;
825 if (mDialog)
826 mDialog->Close();
827 return;
828 }
829
830 if (mUIIdleInterface) {
831 const auto handle = suil_instance_get_handle(mUI.mSuilInstance.get());
832 if (mUIIdleInterface->idle && mUIIdleInterface->idle(handle)) {
834 mUIShowInterface->hide(handle);
835 if (mDialog)
836 mDialog->Close();
837 return;
838 }
839 }
840
841 auto &portUIStates = mPortUIStates;
842
843 if (auto &atomState = portUIStates.mControlOut) {
844 atomState->SendToDialog([&](const LV2_Atom *atom, uint32_t size){
845 suil_instance_port_event(mUI.mSuilInstance.get(),
846 atomState->mpPort->mIndex, size,
847 // Means this event sends some structured data:
848 LV2Symbols::urid_EventTransfer, atom);
849 });
850 }
851
852 // Is this idle time polling for changes of input redundant with
853 // TransferDataToWindow or is it really needed? Probably harmless.
854 // In case of output control port values though, it is needed for metering.
855 mAccess.Flush();
857 auto pOutputValues = static_cast<const LV2EffectOutputs *>(mpOutputs);
858
859 size_t index = 0; for (auto &state : portUIStates.mControlPortStates) {
860 auto &port = state.mpPort;
861
862 const auto pValue = port->mIsInput
863 ? &values[index]
864 : pOutputValues ? &pOutputValues->values[index]
865 : nullptr;
866 if (pValue) {
867 auto &value = *pValue;
868 // Let UI know that a port's value has changed
869 if (value != state.mLst) {
870 suil_instance_port_event(mUI.mSuilInstance.get(),
871 port->mIndex, sizeof(value),
872 /* Means this event sends a float: */ 0,
873/*
874 Quoting what suil.h says about the next argument (which is good):
875
876 The `buffer` must be valid only for the duration of this call, the UI must
877 not keep a reference to it.
878 */
879 &value);
880 state.mLst = value;
881 }
882 }
883 ++index;
884 }
885}
886
887void LV2Validator::OnSize(wxSizeEvent & evt)
888{
889 evt.Skip();
890
891 // Don't do anything here if we're recursing
892 if (mResizing)
893 return;
894
895 // Indicate resizing is occurring
896 mResizing = true;
897
898 // Can only resize AFTER the dialog has been completely created and
899 // there's no need to resize if we're already at the desired size.
900 if (mDialog && evt.GetSize() != mNativeWinLastSize) {
901 // Save the desired size and set the native window to match
902 mNativeWinLastSize = evt.GetSize();
903 mUI.mNativeWin->SetMinSize(mNativeWinLastSize);
904
905 // Clear the minimum size of the parent window to allow the following
906 // Fit() to make proper adjustments
907 mParent->SetMinSize(wxDefaultSize);
908
909#if defined(__WXGTK__)
910 // If the user resized the native window, then we need to also
911 // clear the dialogs minimum size. If this isn't done, the dialog
912 // will not resize properly when going from a larger size to a smaller
913 // size (due to the minimum size constraint).
914 //
915 // In this case, mResized has been set by the "size_request()" function
916 // to indicate that this is a plugin generated resize request.
917 if (mResized)
918 mDialog->SetMinSize(wxDefaultSize);
919
920 // Resize dialog
921 mDialog->Fit();
922
923 // Reestablish the minimum (and maximum) now that the dialog
924 // has is desired size.
925 if (mResized) {
926 mDialog->SetMinSize(mDialog->GetSize());
927 if (mUIFeatures && mUIFeatures->mNoResize)
928 mDialog->SetMaxSize(mDialog->GetSize());
929 }
930
931 // Tell size_request() that the native window was just resized.
932 mResized = true;
933#else
934 // Resize the dialog to fit its content.
935 mDialog->Fit();
936#endif
937 }
938
939 // No longer resizing
940 mResizing = false;
941}
942
943// ============================================================================
944// Feature handlers
945// ============================================================================
946
947int LV2Validator::ui_resize(int width, int height)
948{
949 // Queue a wxSizeEvent to resize the plugins UI
950 if (mUI.mNativeWin) {
951 wxSizeEvent sw{ wxSize{ width, height } };
952 sw.SetEventObject(mUI.mNativeWin.get());
953 mUI.mNativeWin->GetEventHandler()->AddPendingEvent(sw);
954 }
955 else
956 // The window hasn't been created yet, so record the desired size
957 mNativeWinInitialSize = { width, height };
958 return 0;
959}
960
962{
963 mExternalUIClosed = true;
964}
965
966// Foreign UI code wants to send a value or event to me, the host
967void LV2Validator::suil_port_write(uint32_t port_index,
968 uint32_t buffer_size, uint32_t protocol, const void *buffer)
969{
970 // Handle implicit floats
971 if (protocol == 0 && buffer_size == sizeof(float)) {
972 if (auto it = mPorts.mControlPortMap.find(port_index);
973 it != mPorts.mControlPortMap.end())
974 {
975 const auto value = *static_cast<const float*>(buffer);
978 {
979 GetSettings(settings).values[it->second] = value;
980 return nullptr;
981 });
982
983
984 Publish({ size_t(port_index), value });
985 }
986 }
987 // Handle event transfers
988 else if (protocol == LV2Symbols::urid_EventTransfer) {
989 auto &portUIStates = mPortUIStates;
990 auto &atomPortState = portUIStates.mControlIn;
991 if (atomPortState && port_index == atomPortState->mpPort->mIndex)
992 atomPortState->ReceiveFromDialog(buffer, buffer_size);
993 }
994}
995
996uint32_t LV2Validator::suil_port_index(const char *port_symbol)
997{
998 for (size_t i = 0, cnt = lilv_plugin_get_num_ports(&mPlug); i < cnt; ++i) {
999 const auto port = lilv_plugin_get_port_by_index(&mPlug, i);
1000 if (strcmp(port_symbol,
1001 lilv_node_as_string(lilv_port_get_symbol(&mPlug, port))) == 0)
1002 return lilv_port_get_index(&mPlug, port);
1003 }
1004 return LV2UI_INVALID_PORT_INDEX;
1005}
1006
1007#if defined(__WXGTK__)
1008// static callback
1009//
1010// Need to queue a wxSizeEvent when the native window gets resized outside of
1011// WX control. Many of the x42 LV2 plugins can resize themselves when changing
1012// the scale factor. (e.g., open "x42-dpl" effect and right click to change scaling)
1013void LV2Validator::size_request(GtkWidget *widget, GtkRequisition *requisition,
1014 LV2Validator *pValidator)
1015{
1016 pValidator->SizeRequest(widget, requisition);
1017}
1018
1019void LV2Validator::SizeRequest(GtkWidget *widget, GtkRequisition *requisition)
1020{
1021 // Don't do anything if the OnSize() method is active
1022 if (!mResizing) {
1023 // If the OnSize() routine has processed an event, mResized will be true,
1024 // so just set the widgets size.
1025 if (mResized) {
1026 gtk_widget_set_size_request(widget,
1028 mResized = false;
1029 }
1030 // Otherwise, the plugin has resized the widget and we need to let WX know
1031 // about it.
1032 else if (mUI.mNativeWin) {
1033 mResized = true;
1034 wxSizeEvent se(wxSize(requisition->width, requisition->height));
1035 se.SetEventObject(mUI.mNativeWin.get());
1036 mUI.mNativeWin->GetEventHandler()->AddPendingEvent(se);
1037 }
1038 }
1039}
1040#endif
1041#endif
wxEVT_COMMAND_BUTTON_CLICKED
wxT("CloseDown"))
END_EVENT_TABLE()
int min(int a, int b)
#define str(a)
EffectDistortionSettings params
Definition: Distortion.cpp:75
@ EffectTypeGenerate
const wxChar * values
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:75
LV2EffectSettings & GetSettings(EffectSettings &settings)
Definition: LV2Ports.h:215
std::unique_ptr< Type, Lilv_deleter< Type, f > > Lilv_ptr
Generate classes of smart pointers to lv2 resources.
Definition: LV2Utils.h:26
Lilv_ptr< LilvNode, lilv_node_free > LilvNodePtr
Definition: LV2Utils.h:33
Lilv_ptr< char, free_chars > LilvCharsPtr
Definition: LV2Utils.h:29
@ ID_Triggers
@ ID_Texts
@ ID_Duration
@ ID_Choices
@ ID_Sliders
@ ID_Toggles
#define safenew
Definition: MemoryX.h:10
wxEVT_COMMAND_TEXT_UPDATED
Definition: Nyquist.cpp:132
EVT_COMMAND_RANGE(ID_Slider, ID_Slider+99, wxEVT_COMMAND_SLIDER_UPDATED, NyquistEffect::OnSlider) EVT_COMMAND_RANGE(ID_Text
TranslatableString label
Definition: TagsEditor.cpp:164
static Settings & settings()
Definition: TrackInfo.cpp:87
bool TranslationLess(const TranslatableString &a, const TranslatableString &b)
A commonly needed sort comparator, which depends on the language setting.
Base class for many of the effects in Audacity.
Definition: EffectBase.h:33
Hold values to send to effect output meters.
void ModifySettings(Function &&function)
Do a correct read-modify-write of settings.
virtual const EffectSettings & Get()=0
virtual void Flush()=0
Make the last Set changes "persistent" in underlying storage.
Interface for transferring values from a panel of effect controls.
Definition: EffectPlugin.h:239
EffectSettingsAccess & mAccess
Definition: EffectPlugin.h:297
static wxString ToDisplayString(double numberToConvert, int digitsAfterDecimalPoint=-1)
Convert a number to a string, uses the user's locale's decimal separator.
Definition: Internat.cpp:161
UI widget that watches a floating point location and then updates a bar.
const LV2Wrapper * GetMaster() const
Definition: LV2Instance.h:46
LV2AtomPortStatePtr mControlIn
Definition: LV2Ports.h:302
LV2ControlPortStateArray mControlPortStates
Definition: LV2Ports.h:304
std::unordered_map< TranslatableString, std::vector< int > > mGroupMap
Definition: LV2Ports.h:285
TranslatableStrings mGroups
Definition: LV2Ports.h:284
LV2ControlPortArray mControlPorts
Definition: LV2Ports.h:283
std::unordered_map< uint32_t, size_t > mControlPortMap
Definition: LV2Ports.h:288
void OnSize(wxSizeEvent &evt)
NumericTextCtrl * mDuration
Definition: LV2Validator.h:160
const LV2UI_Idle_Interface * mUIIdleInterface
Definition: LV2Validator.h:158
void SetSlider(const LV2ControlPortState &state, const PlainUIControl &ctrl)
const LV2Ports & mPorts
Definition: LV2Validator.h:106
const double mSampleRate
Definition: LV2Validator.h:105
std::vector< PlainUIControl > mPlainUIControls
Array in correspondence with the control ports.
Definition: LV2Validator.h:128
bool BuildFancy(std::unique_ptr< LV2Wrapper > pWrapper, const EffectSettings &settings)
bool BuildPlain(EffectSettingsAccess &access)
const EffectOutputs * mpOutputs
Definition: LV2Validator.h:104
std::shared_ptr< SuilHost > mSuilHost
Definition: LV2Validator.h:111
void Disconnect() override
On the first call only, may disconnect from further event handling.
wxWindow * mParent
Definition: LV2Validator.h:112
bool ValidateUI() override
Get settings data from the panel; may make error dialogs and return false.
bool UpdateUI() override
Update appearance of the panel for changes in settings.
~LV2Validator() override
void OnSlider(wxCommandEvent &evt)
void suil_port_write(uint32_t port_index, uint32_t buffer_size, uint32_t protocol, const void *buffer) override
LV2PortUIStates mPortUIStates
Definition: LV2Validator.h:109
void OnIdle(wxIdleEvent &evt)
wxSize mNativeWinInitialSize
Definition: LV2Validator.h:142
struct LV2Validator::UI mUI
static std::shared_ptr< SuilHost > GetSuilHost()
void OnTrigger(wxCommandEvent &evt)
int ui_resize(int width, int height) override
LV2Validator::Timer mTimer
void OnText(wxCommandEvent &evt)
void OnChoice(wxCommandEvent &evt)
wxSize mNativeWinLastSize
Definition: LV2Validator.h:143
LV2Validator(EffectBase &effect, const LilvPlugin &plug, LV2Instance &instance, EffectSettingsAccess &access, const EffectOutputs *pOutputs, double sampleRate, const LV2FeaturesList &features, const LV2Ports &ports, wxWindow *parent, bool useGUI)
void ui_closed() override
void UpdateControlPortValue(LV2EffectSettings &settings, size_t controlPortIndex, float value)
LV2Instance & mInstance
Definition: LV2Validator.h:103
std::optional< const LV2UIFeaturesList > mUIFeatures
Definition: LV2Validator.h:108
const EffectType mType
Definition: LV2Validator.h:102
const LilvPlugin & mPlug
Definition: LV2Validator.h:101
const LV2UI_Show_Interface * mUIShowInterface
Definition: LV2Validator.h:159
void OnToggle(wxCommandEvent &evt)
static void size_request(GtkWidget *widget, GtkRequisition *requisition, LV2Validator *pValidator)
std::unique_ptr< LV2Wrapper > mpWrapper
Definition: LV2Validator.h:107
void SizeRequest(GtkWidget *widget, GtkRequisition *requisition)
wxWeakRef< wxDialog > mDialog
Definition: LV2Validator.h:149
uint32_t suil_port_index(const char *port_symbol) override
bool mExternalUIClosed
Definition: LV2Validator.h:150
void SetName(const TranslatableString &name)
CallbackReturn Publish(const EffectSettingChanged &message)
Send a message to connected callbacks.
Definition: Observer.h:207
wxString Translation() const
#define lrintf(flt)
Definition: float_cast.h:170
#define LV2_EXTERNAL_UI_SHOW(ptr)
#define LV2_EXTERNAL_UI_RUN(ptr)
#define LV2_EXTERNAL_UI__Widget
LilvWorld * gWorld
Definition: LV2Symbols.cpp:31
Externalized state of a plug-in.
Other UI related state of an instance of an LV2 Control port.
Definition: LV2Ports.h:237
float mLo
Lower bound, as scaled by sample rate if that is required.
Definition: LV2Ports.h:250
float mHi
Upper bound, as scaled by sample rate if that is required.
Definition: LV2Ports.h:252
float mTmp
Value of UI control, as scaled by sample rate if that is required.
Definition: LV2Ports.h:248
const LV2ControlPortPtr mpPort
Definition: LV2Ports.h:244
Carry output control port information back to main thread.
Definition: LV2Ports.h:228
std::vector< float > values
vector of values in correspondence with the control ports
Definition: LV2Ports.h:233
Storage locations to be connected to LV2 control ports.
Definition: LV2Ports.h:206
std::vector< float > values
vector of values in correspondence with the control ports
Definition: LV2Ports.h:208
Abstraction of host services that a plug-ins native UI needs.
static uint32_t suil_port_index(SuilController controller, const char *port_symbol)
static void suil_port_write(SuilController controller, uint32_t port_index, uint32_t buffer_size, uint32_t protocol, const void *buffer)
void Notify() override
LV2_External_UI_Widget * mExternalWidget
Definition: LV2Validator.h:154
wxWindowPtr< NativeWindow > mNativeWin
Definition: LV2Validator.h:136
SuilInstancePtr mSuilInstance
Definition: LV2Validator.h:135
Options & AutoPos(bool enable)