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