forked from tim-janik/anklang
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lv2device.cc
2479 lines (2193 loc) · 80.6 KB
/
lv2device.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
#include "processor.hh"
#include "main.hh"
#include "internal.hh"
#include "strings.hh"
#include "loft.hh"
#include "serialize.hh"
#include "storage.hh"
#include "project.hh"
#include "path.hh"
// X11 wrapper
#include "clapplugin.hh"
#include "lv2/atom/atom.h"
#include "lv2/atom/forge.h"
#include "lv2/midi/midi.h"
#include "lv2/options/options.h"
#include "lv2/parameters/parameters.h"
#include "lv2/buf-size/buf-size.h"
#include "lv2/worker/worker.h"
#include "lv2/presets/presets.h"
#include "lv2/resize-port/resize-port.h"
#include "lv2/data-access/data-access.h"
#include "lv2/instance-access/instance-access.h"
#include "lv2/ui/ui.h"
#include "lv2/time/time.h"
#include "lv2/state/state.h"
#include "lv2/units/units.h"
#include "lv2/port-props/port-props.h"
#include "lv2externalui.hh"
#include "lv2evbuf.hh"
#include "lv2device.hh"
#include <lilv/lilv.h>
// #define DEBUG_MAP
// #define DEBUG_WORKER
// #define DEBUG_MISSING_FEATURES
// support building with older version of the lv2 includes that do not yet support free path
#ifndef LV2_STATE__freePath
#define LV2_STATE__freePath LV2_STATE_PREFIX "freePath"
extern "C" {
typedef void *LV2_State_Free_Path_Handle;
typedef struct {
LV2_State_Free_Path_Handle handle;
void (*free_path) (LV2_State_Free_Path_Handle handle, char *path);
} LV2_State_Free_Path;
}
#endif
namespace Ase {
namespace
{
static Gtk2DlWrapEntry *x11wrapper = nullptr;
using std::vector;
using std::string;
using std::set;
using std::map;
using std::max;
using std::min;
class ControlEvent
{
LoftPtr<ControlEvent> loft_ptr_; // keep this object alive
uint32_t port_index_;
uint32_t protocol_;
size_t size_;
LoftPtr<void> data_;
public:
std::atomic<ControlEvent *> next_ = nullptr;
static ControlEvent *
loft_new (uint32_t port_index, uint32_t protocol, size_t size, const void *data = nullptr)
{
LoftPtr<ControlEvent> loft_ptr = loft_make_unique<ControlEvent>();
ControlEvent *new_event = loft_ptr.get();
new_event->loft_ptr_ = std::move (loft_ptr);
new_event->port_index_ = port_index;
new_event->protocol_ = protocol;
new_event->size_ = size;
new_event->data_ = loft_alloc (size);
if (data)
memcpy (new_event->data_.get(), data, size);
return new_event;
}
void
loft_free()
{
loft_ptr_.reset(); // do not access this after this line
}
uint32_t port_index() const { return port_index_; }
uint32_t protocol() const { return protocol_; }
size_t size() const { return size_; }
uint8_t *data() const { return reinterpret_cast<uint8_t *> (data_.get()); }
};
class ControlEventVector
{
AtomicIntrusiveStack<ControlEvent> events_;
public:
template<typename Func>
void
for_each (ControlEventVector& trash_events, Func func)
{
ControlEvent *const events = events_.pop_reversed(), *last = nullptr;
for (ControlEvent *event = events; event; last = event, event = event->next_)
{
const ControlEvent *cevent = event;
func (cevent);
}
if (last)
trash_events.events_.push_chain (events, last);
}
void
free_all()
{
ControlEvent *event = events_.pop_all();
while (event)
{
ControlEvent *old = event;
event = event->next_;
old->loft_free();
}
}
void
push (ControlEvent *event)
{
events_.push (event);
}
~ControlEventVector()
{
free_all();
}
};
static inline std::atomic<ControlEvent*>&
atomic_next_ptrref (ControlEvent *event)
{
return event->next_;
}
class Map
{
std::mutex map_mutex;
LV2_URID next_id;
map<string, LV2_URID> m_urid_map;
map<LV2_URID, String> m_urid_unmap;
LV2_URID_Map lv2_urid_map;
const LV2_Feature lv2_urid_map_feature;
LV2_URID_Unmap lv2_urid_unmap;
const LV2_Feature lv2_urid_unmap_feature;
public:
Map() :
next_id (1),
lv2_urid_map { this, urid_map },
lv2_urid_map_feature { LV2_URID_MAP_URI, &lv2_urid_map },
lv2_urid_unmap { this, urid_unmap },
lv2_urid_unmap_feature { LV2_URID_UNMAP_URI, &lv2_urid_unmap }
{
}
static LV2_URID
urid_map (LV2_URID_Map_Handle handle, const char *str)
{
return static_cast<Map *> (handle)->urid_map (str);
}
static const char *
urid_unmap (LV2_URID_Unmap_Handle handle, LV2_URID id)
{
return static_cast<Map *> (handle)->urid_unmap (id);
}
LV2_URID
urid_map (const char *str)
{
std::lock_guard lg (map_mutex);
LV2_URID& id = m_urid_map[str];
if (id == 0)
id = next_id++;
m_urid_unmap[id] = str;
#ifdef DEBUG_MAP
printerr ("map %s -> %d\n", str, id);
#endif
return id;
}
const char *
urid_unmap (LV2_URID id)
{
std::lock_guard lg (map_mutex);
auto it = m_urid_unmap.find (id);
if (it != m_urid_unmap.end())
return it->second.c_str();
else
return nullptr;
}
const LV2_Feature *
map_feature() const
{
return &lv2_urid_map_feature;
}
const LV2_Feature *
unmap_feature() const
{
return &lv2_urid_unmap_feature;
}
LV2_URID_Map *
lv2_map()
{
return &lv2_urid_map;
}
LV2_URID_Unmap *
lv2_unmap()
{
return &lv2_urid_unmap;
}
};
class PluginHost;
class Options
{
float m_sample_rate_ = 0;
uint32_t m_min_block_length_ = 0;
uint32_t m_max_block_length_ = AUDIO_BLOCK_MAX_RENDER_SIZE;
vector<LV2_Options_Option> const_opts_;
LV2_Feature lv2_options_feature_;
public:
Options (PluginHost& plugin_host, float sample_rate);
const LV2_Feature *
feature() const
{
return &lv2_options_feature_;
}
};
class Worker
{
LV2_Worker_Schedule lv2_worker_sched_ {};
const LV2_Feature lv2_worker_feature_;
const LV2_Worker_Interface *worker_interface_ = nullptr;
LV2_Handle instance_ = nullptr;
ControlEventVector work_events_, response_events_, trash_events_;
std::thread thread_;
std::atomic<int> quit_;
ScopedSemaphore sem_;
void
run()
{
#ifdef DEBUG_WORKER
printerr ("worker thread running\n");
#endif
while (!quit_)
{
sem_.wait();
work_events_.for_each (trash_events_,
[this] (const ControlEvent *event)
{
#ifdef DEBUG_WORKER
printerr ("worker: got work %zd bytes\n", event->size());
#endif
worker_interface_->work (instance_, respond, this, event->size(), event->data());
});
// free both: old worker events and old response events
trash_events_.free_all();
}
}
LV2_Worker_Status
schedule (uint32_t size, const void *data)
{
if (!worker_interface_)
return LV2_WORKER_ERR_UNKNOWN;
work_events_.push (ControlEvent::loft_new (0, 0, size, data));
sem_.post();
return LV2_WORKER_SUCCESS;
}
LV2_Worker_Status
respond (uint32_t size, const void *data)
{
if (!worker_interface_)
return LV2_WORKER_ERR_UNKNOWN;
response_events_.push (ControlEvent::loft_new (0, 0, size, data));
return LV2_WORKER_SUCCESS;
}
static LV2_Worker_Status
schedule (LV2_Worker_Schedule_Handle handle,
uint32_t size,
const void* data)
{
Worker *worker = static_cast<Worker *> (handle);
return worker->schedule (size, data);
}
static LV2_Worker_Status
respond (LV2_Worker_Respond_Handle handle,
uint32_t size,
const void* data)
{
Worker *worker = static_cast<Worker *> (handle);
return worker->respond (size, data);
}
public:
Worker() :
lv2_worker_sched_ { this, schedule },
lv2_worker_feature_ { LV2_WORKER__schedule, &lv2_worker_sched_ },
quit_ (0)
{
thread_ = std::thread (&Worker::run, this);
}
~Worker()
{
// need to call stop before destructor
assert_return (!thread_.joinable());
}
void
stop()
{
quit_ = 1;
sem_.post();
thread_.join();
#ifdef DEBUG_WORKER
printerr ("worker thread joined\n");
#endif
}
void
set_instance (LilvInstance *lilv_instance)
{
instance_ = lilv_instance_get_handle (lilv_instance);
const LV2_Descriptor *descriptor = lilv_instance_get_descriptor (lilv_instance);
if (descriptor && descriptor->extension_data)
worker_interface_ = (const LV2_Worker_Interface *) (*descriptor->extension_data) (LV2_WORKER__interface);
}
void
handle_responses()
{
response_events_.for_each (trash_events_,
[this] (const ControlEvent *event)
{
worker_interface_->work_response (instance_, event->size(), event->data());
});
}
void
end_run()
{
/* to be called after each run cycle */
if (worker_interface_ && worker_interface_->end_run)
worker_interface_->end_run (instance_);
}
const LV2_Feature *
feature() const
{
return &lv2_worker_feature_;
}
};
class Features
{
std::vector<LV2_Feature> features;
std::vector<const LV2_Feature *> null_terminated_ptrs;
public:
const LV2_Feature * const*
get_features()
{
assert_return (null_terminated_ptrs.empty(), nullptr);
for (const auto& f : features)
null_terminated_ptrs.push_back (&f);
null_terminated_ptrs.push_back (nullptr);
return null_terminated_ptrs.data();
}
void
add (const LV2_Feature *lv2_feature)
{
features.push_back (*lv2_feature);
}
void
add (const char *uri, void *data)
{
features.emplace_back (uri, data);
}
};
struct ScalePoint
{
String label;
float value = 0;
};
struct Port
{
LV2_Evbuf *evbuf = nullptr; /* for atom ports */
float control = 0; /* for control ports */
float min_value = 0; /* min control */
float max_value = 0; /* max control */
int control_in_idx = -1; /* for control input ports */
int index = -1; /* lv2 index for this port */
String name;
String symbol;
String unit;
vector<ScalePoint> scale_points; /* for enumerations */
static constexpr uint NO_FLAGS = 0;
static constexpr uint LOGARITHMIC = 1 << 0;
static constexpr uint INTEGER = 1 << 1;
static constexpr uint TOGGLED = 1 << 2;
static constexpr uint ENUMERATION = 1 << 3;
static constexpr uint HIDDEN = 1 << 4; // not visible in generic UI (i.e. bpm port)
// port direction
static constexpr uint INPUT = 1 << 5;
static constexpr uint OUTPUT = 1 << 6;
// port type
static constexpr uint CONTROL = 1 << 7;
static constexpr uint AUDIO = 1 << 8;
static constexpr uint ATOM = 1 << 9;
uint flags = NO_FLAGS;
float
param_to_lv2 (double value) const
{
if (flags & ENUMERATION)
{
int index = std::clamp (irintf (value), 0, int (scale_points.size()) - 1);
return scale_points[index].value;
}
else if (flags & LOGARITHMIC)
{
float f = exp2 (log2 (min_value) + (log2 (max_value) - log2 (min_value)) * value);
return std::clamp (f, min_value, max_value);
}
else if (flags & INTEGER)
{
// TODO: the knob at the UI should also only allow integer values
float f = std::round (value);
return std::clamp (f, min_value, max_value);
}
else
{
return value;
}
}
double
param_from_lv2 (double value) const
{
if (flags & ENUMERATION)
{
double best_diff = 1e10;
size_t best_idx = 0;
for (size_t idx = 0; idx < scale_points.size(); idx++)
{
double diff = std::abs (scale_points[idx].value - value);
if (diff < best_diff)
{
best_idx = idx;
best_diff = diff;
}
}
return best_idx;
}
else if (flags & LOGARITHMIC)
{
double d = (log2 (value) - log2 (min_value)) / (log2 (max_value) - log2 (min_value));
return std::clamp (d, 0.0, 1.0);
}
else if (flags & INTEGER)
{
// TODO: the knob at the UI should also only allow integer values
float f = std::round (value);
return std::clamp (f, min_value, max_value);
}
else
{
return value;
}
}
};
struct PresetInfo
{
String name;
LilvNode *preset = nullptr;
};
struct PortRestoreHelper;
class PluginUI;
struct PathMap
{
LV2_State_Map_Path map_path {
.handle = this,
.abstract_path = [] (LV2_State_Map_Path_Handle handle, const char *path)
{
PathMap *path_map = (PathMap *) handle;
if (path_map->abstract_path)
return strdup (path_map->abstract_path (path).c_str());
else
{
printerr ("LV2: PathMap: abstract_path (%s) called, but no mapping function defined\n", path);
return strdup (path);
}
},
.absolute_path = [] (LV2_State_Map_Path_Handle handle, const char *path)
{
PathMap *path_map = (PathMap *) handle;
if (path_map->absolute_path)
return strdup (path_map->absolute_path (path).c_str());
else
{
printerr ("LV2: PathMap: absolute_path (%s) called, but no mapping function defined\n", path);
return strdup (path);
}
}
};
LV2_State_Free_Path free_path {
.free_path = [] (LV2_State_Map_Path_Handle handle, char *path) { free (path); }
};
std::function<String(String)> abstract_path;
std::function<String(String)> absolute_path;
};
typedef std::function<void(const Port *)> ControlChangedCallback;
class PluginInstance
{
bool init_ok_ = false;
LilvUIs *uis_ = nullptr;
const LilvUI *ui_ = nullptr;
String ui_type_uri_;
std::unique_ptr<Worker> worker_;
Options options_;
std::array<uint8, 256> last_position_buffer_ {};
std::array<uint8, 256> position_buffer_ {};
std::vector<int> atom_out_ports_;
std::vector<int> atom_in_ports_;
std::vector<int> audio_in_ports_;
std::vector<int> audio_out_ports_;
std::vector<int> control_in_ports_;
std::vector<int> midi_in_ports_;
std::vector<int> position_in_ports_;
int bpm_port_index_ = -1;
const LilvPlugin *plugin_ = nullptr;
LV2_Atom_Forge forge_;
Features features_;
uint sample_rate_ = 0;
bool active_ = false;
uint32_t ui_update_frame_count_ = 0;
ControlEventVector ui2dsp_events_;
ControlEventVector dsp2ui_events_;
ControlEventVector trash_events_;
std::atomic<bool> dsp2ui_notifications_enabled_ { false };
std::unique_ptr<PluginUI> plugin_ui_;
PluginHost& plugin_host_;
LV2_Feature lv2_instance_access_feature_ {};
LV2_Feature lv2_data_access_feature_ {};
LV2_Extension_Data_Feature lv2_ext_data {};
LilvInstance *instance_ = nullptr;
vector<Port> plugin_ports_;
vector<PresetInfo> presets_;
ControlChangedCallback control_in_changed_callback_;
void init_ports();
void free_ports();
void init_presets();
void free_presets();
void send_plugin_events_to_ui();
void send_ui_updates (uint32_t delta_frames);
void restore_state (LilvState *state, PortRestoreHelper *helper, PathMap *path_map = nullptr);
void find_plugin_ui();
static const void* get_port_value_for_save (const char *port_symbol, void *user_data, uint32_t *size, uint32_t *type);
// lilv_state_to_string requires a non-empty URI
static constexpr auto ANKLANG_STATE_URI = "urn:anklang:state";
public:
PluginInstance (PluginHost& plugin_host, uint sample_rate, const LilvPlugin *plugin,
PortRestoreHelper *port_restore_helper, const ControlChangedCallback& callback);
~PluginInstance();
static constexpr double ui_update_fps = 60;
bool init_ok() const { return init_ok_; }
uint n_audio_inputs() const { return audio_in_ports_.size(); }
uint n_audio_outputs() const { return audio_out_ports_.size(); }
uint n_control_inputs() const { return control_in_ports_.size(); }
const LV2_Feature *instance_access_feature() const { return &lv2_instance_access_feature_; }
const LV2_Feature *data_access_feature() const { return &lv2_data_access_feature_; }
const LV2_Feature *options_feature() const { return options_.feature(); }
const Port &control_input_port (size_t index) const { assert (index < control_in_ports_.size()); return plugin_ports_[control_in_ports_[index]]; }
const vector<PresetInfo>& presets() const { return presets_; }
PluginUI *plugin_ui() const { return plugin_ui_.get(); }
bool gui_supported() { return ui_ != nullptr; }
void reset_event_buffers();
void write_midi (uint32_t time, size_t size, const uint8_t *data);
void write_position (const AudioTransport &transport);
void connect_audio_in (uint32_t input_port, const float *buffer);
void connect_audio_out (uint32_t input_port, float *buffer);
void run (uint32_t n_frames);
void activate();
void deactivate();
void set_initial_controls_ui();
void handle_dsp2ui_events (void *ui_instance);
void enable_dsp2ui_notifications (bool enabled);
void clear_dsp2ui_events();
void toggle_ui();
void delete_ui();
void set_control_param (size_t index, double value);
bool restore_string (const String& str, PortRestoreHelper *helper, PathMap *path_map = nullptr);
void restore_preset (int preset, PortRestoreHelper *helper);
String save_string (PathMap *path_map);
static void host_ui_write (void *controller, uint32_t port_index, uint32_t buffer_size, uint32_t protocol, const void *buffer);
static uint32_t host_ui_index (void *controller, const char *symbol);
};
class PluginHost
{
public:
LilvWorld *world = nullptr;
Map urid_map;
void *suil_host = nullptr;
struct URIDs {
LV2_URID param_sampleRate;
LV2_URID atom_Double;
LV2_URID atom_Float;
LV2_URID atom_Int;
LV2_URID atom_Long;
LV2_URID atom_eventTransfer;
LV2_URID bufsz_maxBlockLength;
LV2_URID bufsz_minBlockLength;
LV2_URID midi_MidiEvent;
LV2_URID time_Position;
LV2_URID time_bar;
LV2_URID time_barBeat;
LV2_URID time_beatUnit;
LV2_URID time_beatsPerBar;
LV2_URID time_beatsPerMinute;
LV2_URID time_frame;
LV2_URID time_speed;
URIDs (Map& map) :
param_sampleRate (map.urid_map (LV2_PARAMETERS__sampleRate)),
atom_Double (map.urid_map (LV2_ATOM__Double)),
atom_Float (map.urid_map (LV2_ATOM__Float)),
atom_Int (map.urid_map (LV2_ATOM__Int)),
atom_Long (map.urid_map (LV2_ATOM__Long)),
atom_eventTransfer (map.urid_map (LV2_ATOM__eventTransfer)),
bufsz_maxBlockLength (map.urid_map (LV2_BUF_SIZE__maxBlockLength)),
bufsz_minBlockLength (map.urid_map (LV2_BUF_SIZE__minBlockLength)),
midi_MidiEvent (map.urid_map (LV2_MIDI__MidiEvent)),
time_Position (map.urid_map (LV2_TIME__Position)),
time_bar (map.urid_map (LV2_TIME__bar)),
time_barBeat (map.urid_map (LV2_TIME__barBeat)),
time_beatUnit (map.urid_map (LV2_TIME__beatUnit)),
time_beatsPerBar (map.urid_map (LV2_TIME__beatsPerBar)),
time_beatsPerMinute (map.urid_map (LV2_TIME__beatsPerMinute)),
time_frame (map.urid_map (LV2_TIME__frame)),
time_speed (map.urid_map (LV2_TIME__speed))
{
}
} urids;
struct Nodes {
LilvNode *lv2_audio_class;
LilvNode *lv2_atom_class;
LilvNode *lv2_input_class;
LilvNode *lv2_output_class;
LilvNode *lv2_control_class;
LilvNode *lv2_rsz_minimumSize;
LilvNode *lv2_atom_Chunk;
LilvNode *lv2_atom_Sequence;
LilvNode *lv2_atom_supports;
LilvNode *lv2_midi_MidiEvent;
LilvNode *lv2_time_Position;
LilvNode *lv2_time_beatsPerMinute;
LilvNode *lv2_presets_Preset;
LilvNode *lv2_units_unit;
LilvNode *lv2_units_symbol;
LilvNode *lv2_pprop_logarithmic;
LilvNode *lv2_pprop_notOnGUI;
LilvNode *lv2_integer;
LilvNode *lv2_toggled;
LilvNode *lv2_enumeration;
LilvNode *lv2_ui_external;
LilvNode *lv2_ui_externalkx;
LilvNode *lv2_ui_fixedSize;
LilvNode *lv2_ui_noUserResize;
LilvNode *lv2_ui_x11ui;
LilvNode *lv2_optionalFeature;
LilvNode *lv2_requiredFeature;
LilvNode *lv2_worker_schedule;
LilvNode *lv2_state_loadDefaultState;
LilvNode *rdfs_label;
LilvNode *native_ui_type;
void init (LilvWorld *world)
{
lv2_audio_class = lilv_new_uri (world, LILV_URI_AUDIO_PORT);
lv2_atom_class = lilv_new_uri (world, LILV_URI_ATOM_PORT);
lv2_input_class = lilv_new_uri (world, LILV_URI_INPUT_PORT);
lv2_output_class = lilv_new_uri (world, LILV_URI_OUTPUT_PORT);
lv2_control_class = lilv_new_uri (world, LILV_URI_CONTROL_PORT);
lv2_rsz_minimumSize = lilv_new_uri (world, LV2_RESIZE_PORT__minimumSize);
lv2_atom_Chunk = lilv_new_uri (world, LV2_ATOM__Chunk);
lv2_atom_Sequence = lilv_new_uri (world, LV2_ATOM__Sequence);
lv2_atom_supports = lilv_new_uri (world, LV2_ATOM__supports);
lv2_midi_MidiEvent = lilv_new_uri (world, LV2_MIDI__MidiEvent);
lv2_time_Position = lilv_new_uri (world, LV2_TIME__Position);
lv2_time_beatsPerMinute = lilv_new_uri (world, LV2_TIME__beatsPerMinute);
lv2_units_unit = lilv_new_uri (world, LV2_UNITS__unit);
lv2_units_symbol = lilv_new_uri (world, LV2_UNITS__symbol);
lv2_pprop_logarithmic = lilv_new_uri (world, LV2_PORT_PROPS__logarithmic);
lv2_pprop_notOnGUI = lilv_new_uri (world, LV2_PORT_PROPS__notOnGUI);
lv2_integer = lilv_new_uri (world, LV2_CORE__integer);
lv2_toggled = lilv_new_uri (world, LV2_CORE__toggled);
lv2_enumeration = lilv_new_uri (world, LV2_CORE__enumeration);
lv2_ui_external = lilv_new_uri (world, "http://lv2plug.in/ns/extensions/ui#external");
lv2_ui_externalkx = lilv_new_uri (world, "http://kxstudio.sf.net/ns/lv2ext/external-ui#Widget");
lv2_ui_fixedSize = lilv_new_uri (world, LV2_UI__fixedSize);
lv2_ui_noUserResize = lilv_new_uri (world, LV2_UI__noUserResize);
lv2_ui_x11ui = lilv_new_uri (world, LV2_UI__X11UI);
lv2_optionalFeature = lilv_new_uri (world, LV2_CORE__optionalFeature);
lv2_requiredFeature = lilv_new_uri (world, LV2_CORE__requiredFeature);
lv2_worker_schedule = lilv_new_uri (world, LV2_WORKER__schedule);
lv2_state_loadDefaultState = lilv_new_uri (world, LV2_STATE__loadDefaultState);
lv2_presets_Preset = lilv_new_uri (world, LV2_PRESETS__Preset);
rdfs_label = lilv_new_uri (world, LILV_NS_RDFS "label");
native_ui_type = lilv_new_uri (world, "http://lv2plug.in/ns/extensions/ui#GtkUI");
}
} nodes;
private:
PluginHost() :
urids (urid_map)
{
if (!x11wrapper)
x11wrapper = get_x11wrapper();
if (x11wrapper)
{
suil_host = x11wrapper->create_suil_host (PluginInstance::host_ui_write, PluginInstance::host_ui_index);
// TODO: free suil_host when done
}
world = lilv_world_new();
lilv_world_load_all (world);
nodes.init (world);
}
public:
static PluginHost&
the()
{
static PluginHost host;
return host;
}
PluginInstance *instantiate (const char *plugin_uri, uint sample_rate, PortRestoreHelper *port_restore_helper, const ControlChangedCallback& callback);
private:
DeviceInfoS devs;
map<string, DeviceInfo> lv2_device_info_map;
bool
required_features_supported (const LilvPlugin *plugin, const string& name)
{
bool can_use_plugin = true;
set<String> supported_features {
LV2_WORKER__schedule,
LV2_URID_MAP_URI,
LV2_URID_UNMAP_URI,
LV2_OPTIONS__options,
LV2_BUF_SIZE__boundedBlockLength,
LV2_STATE__loadDefaultState,
};
LilvNodes *req_features = lilv_plugin_get_required_features (plugin);
LILV_FOREACH (nodes, i, req_features)
{
const LilvNode *feature = lilv_nodes_get (req_features, i);
if (!supported_features.contains (lilv_node_as_string (feature)))
{
#ifdef DEBUG_MISSING_FEATURES
printerr ("LV2: unsupported feature %s required for plugin %s\n", lilv_node_as_string (feature), name.c_str());
#endif
can_use_plugin = false;
}
}
lilv_nodes_free (req_features);
return can_use_plugin;
}
bool
required_ui_features_supported (const LilvUI *ui, const string& name)
{
const LilvNode *s = lilv_ui_get_uri (ui);
bool can_use_ui = true;
set<String> supported_features {
LV2_INSTANCE_ACCESS_URI,
LV2_DATA_ACCESS_URI,
LV2_URID_MAP_URI,
LV2_URID_UNMAP_URI,
LV2_OPTIONS__options,
LV2_UI_PREFIX "makeResident", // feature is pointless/deprecated so we simply ignore that some plugins want it
};
if (lilv_ui_is_a (ui, nodes.lv2_ui_x11ui))
{
supported_features.insert (LV2_UI__idleInterface); // SUIL provides this interface for X11 UIs
}
if (lilv_ui_is_a (ui, nodes.lv2_ui_external) || lilv_ui_is_a (ui, nodes.lv2_ui_externalkx))
{
supported_features.insert (lilv_node_as_string (nodes.lv2_ui_externalkx));
}
else
{
supported_features.insert (LV2_UI__parent);
supported_features.insert (LV2_UI__resize); // SUIL provides this interface
}
LilvNodes *req_features = lilv_world_find_nodes (world, s, nodes.lv2_requiredFeature, nullptr);
LILV_FOREACH (nodes, i, req_features)
{
const LilvNode *feature = lilv_nodes_get (req_features, i);
if (!supported_features.contains (lilv_node_as_string (feature)))
{
#ifdef DEBUG_MISSING_FEATURES
printerr ("LV2: unsupported feature %s required for plugin ui %s\n", lilv_node_as_string (feature), name.c_str());
#endif
can_use_ui = false;
}
}
lilv_nodes_free (req_features);
return can_use_ui;
}
public:
DeviceInfo
lv2_device_info (const string& uri)
{
if (devs.empty())
list_plugins();
return lv2_device_info_map[uri];
}
DeviceInfoS
list_plugins()
{
if (!devs.empty())
return devs;
const LilvPlugins* plugins = lilv_world_get_all_plugins (world);
LILV_FOREACH (plugins, i, plugins)
{
const LilvPlugin* p = lilv_plugins_get (plugins, i);
DeviceInfo device_info;
string lv2_uri = lilv_node_as_uri (lilv_plugin_get_uri (p));
device_info.uri = "LV2:" + lv2_uri;
LilvNode* n = lilv_plugin_get_name (p);
device_info.name = lilv_node_as_string (n);
lilv_node_free (n);
auto plugin_class = lilv_plugin_get_class (p);
device_info.category = string_format ("LV2 %s", lilv_node_as_string (lilv_plugin_class_get_label (plugin_class)));
if (required_features_supported (p, device_info.name))
{
devs.push_back (device_info);
lv2_device_info_map[lv2_uri] = device_info;
LilvUIs *uis = lilv_plugin_get_uis (p);
LILV_FOREACH (uis, u, uis)
{
const LilvUI *ui = lilv_uis_get (uis, u);
// just check required features here for debugging missing features for UIs
// don't exclude plugin if UI not supported, since we can instantiate the plugin without custom UI
required_ui_features_supported (ui, device_info.name);
}
lilv_uis_free (uis);
}
}
std::stable_sort (devs.begin(), devs.end(), [] (auto& d1, auto& d2) { return string_casecmp (d1.name, d2.name) < 0; });
return devs;
}
};
class PluginUI
{
PluginHost& plugin_host_;
bool init_ok_ = false;
bool ui_closed_ = false;
bool external_ui_ = false;
LV2_External_UI_Host external_ui_host_ {};
LV2_External_UI_Widget *external_ui_widget_ = nullptr;
void *window_ = nullptr;
uint timer_id_ = 0;
PluginInstance *plugin_instance_ = nullptr;
void *ui_instance_ = nullptr;
bool ui_is_resizable (const LilvUI *ui);
public:
PluginUI (PluginHost &plugin_host, PluginInstance *plugin_instance, const string& plugin_uri, const string& ui_type_uri, const LilvUI *ui);
~PluginUI();
bool init_ok() const { return init_ok_; }
};
PluginUI::PluginUI (PluginHost &plugin_host, PluginInstance *plugin_instance, const string& plugin_uri,
const string& ui_type_uri, const LilvUI *ui) :
plugin_host_ (plugin_host)
{
assert_return (this_thread_is_gtk());
plugin_instance_ = plugin_instance;
external_ui_ = lilv_ui_is_a (ui, plugin_host_.nodes.lv2_ui_external) ||
lilv_ui_is_a (ui, plugin_host_.nodes.lv2_ui_externalkx);
string window_title = PluginHost::the().lv2_device_info (plugin_uri).name;
const char* bundle_uri = lilv_node_as_uri (lilv_ui_get_bundle_uri (ui));
const char* binary_uri = lilv_node_as_uri (lilv_ui_get_binary_uri (ui));
char* bundle_path = lilv_file_uri_parse (bundle_uri, nullptr);
char* binary_path = lilv_file_uri_parse (binary_uri, nullptr);
Features ui_features;
ui_features.add (plugin_instance->instance_access_feature());
ui_features.add (plugin_instance->data_access_feature());
ui_features.add (plugin_instance->options_feature());
ui_features.add (plugin_host_.urid_map.map_feature());
ui_features.add (plugin_host_.urid_map.unmap_feature());
if (external_ui_)
{
external_ui_host_.ui_closed = [] (LV2UI_Controller controller)
{
PluginInstance *plugin_instance = (PluginInstance *) controller;
if (plugin_instance->plugin_ui())
plugin_instance->plugin_ui()->ui_closed_ = true; // will destroy UI on next timer iteration
};
external_ui_host_.plugin_human_id = strdup (window_title.c_str());
ui_features.add (LV2_EXTERNAL_UI__Host, &external_ui_host_);
ui_features.add (LV2_EXTERNAL_UI_DEPRECATED_URI, &external_ui_host_);
}
else
{
window_ = x11wrapper->create_suil_window (window_title, ui_is_resizable (ui),
[this] ()
{
ui_closed_ = true; // will destroy UI on next timer iteration
});
ui_features.add (LV2_UI__parent, window_);
}