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