JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_LV2PluginFormat.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26#if JUCE_PLUGINHOST_LV2 && (! (JUCE_ANDROID || JUCE_IOS))
27
28#include "juce_LV2Common.h"
29#include "juce_LV2Resources.h"
30
31#include <juce_gui_extra/native/juce_NSViewFrameWatcher_mac.h>
32
33#include <thread>
34
35namespace juce
36{
37namespace lv2_host
38{
39
40template <typename Struct, typename Value>
41auto with (Struct s, Value Struct::* member, Value value) noexcept
42{
43 s.*member = std::move (value);
44 return s;
45}
46
47/* Converts a void* to an LV2_Atom* if the buffer looks like it holds a well-formed Atom, or
48 returns nullptr otherwise.
49*/
50static const LV2_Atom* convertToAtomPtr (const void* ptr, size_t size)
51{
52 if (size < sizeof (LV2_Atom))
53 {
55 return nullptr;
56 }
57
58 const auto header = readUnaligned<LV2_Atom> (ptr);
59
60 if (size < header.size + sizeof (LV2_Atom))
61 {
63 return nullptr;
64 }
65
66 // This is UB _if_ the ptr doesn't really point to an LV2_Atom.
67 return reinterpret_cast<const LV2_Atom*> (ptr);
68}
69
70// Allows mutable access to the items in a vector, without allowing the vector itself
71// to be modified.
72template <typename T>
73class SimpleSpan
74{
75public:
76 constexpr SimpleSpan (T* beginIn, T* endIn) : b (beginIn), e (endIn) {}
77
78 constexpr auto begin() const { return b; }
79 constexpr auto end() const { return e; }
80
81 JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4814)
82 constexpr auto& operator[] (size_t index) { return b[index]; }
83 JUCE_END_IGNORE_WARNINGS_MSVC
84
85 constexpr auto size() const { return (size_t) (e - b); }
86
87private:
88 T* b;
89 T* e;
90};
91
92template <typename T>
93constexpr auto makeSimpleSpan (T* b, T* e) { return SimpleSpan<T> { b, e }; }
94
95template <typename R>
96constexpr auto makeSimpleSpan (R& r) { return makeSimpleSpan (r.data(), r.data() + r.size()); }
97
99{
100 virtual ~PhysicalResizeListener() = default;
101 virtual void viewRequestedResizeInPhysicalPixels (int width, int height) = 0;
102};
103
105{
106 virtual ~LogicalResizeListener() = default;
107 virtual void viewRequestedResizeInLogicalPixels (int width, int height) = 0;
108};
109
110#if JUCE_WINDOWS
112{
113public:
114 WindowSizeChangeDetector()
115 : hook (SetWindowsHookEx (WH_CALLWNDPROC,
116 callWndProc,
117 (HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(),
118 GetCurrentThreadId()))
119 {}
120
121 ~WindowSizeChangeDetector() noexcept
122 {
123 UnhookWindowsHookEx (hook);
124 }
125
126 static void addListener (HWND hwnd, PhysicalResizeListener& listener)
127 {
128 getActiveEditors().emplace (hwnd, &listener);
129 }
130
131 static void removeListener (HWND hwnd)
132 {
133 getActiveEditors().erase (hwnd);
134 }
135
136private:
137 static std::map<HWND, PhysicalResizeListener*>& getActiveEditors()
138 {
140 return map;
141 }
142
143 static void processMessage (int nCode, const CWPSTRUCT* info)
144 {
145 if (nCode < 0 || info == nullptr)
146 return;
147
148 constexpr UINT events[] { WM_SIZING, WM_SIZE, WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED };
149
150 if (std::find (std::begin (events), std::end (events), info->message) == std::end (events))
151 return;
152
153 auto& map = getActiveEditors();
154 auto iter = map.find (info->hwnd);
155
156 if (iter == map.end())
157 return;
158
159 RECT rect;
160 GetWindowRect (info->hwnd, &rect);
161 iter->second->viewRequestedResizeInPhysicalPixels (rect.right - rect.left, rect.bottom - rect.top);
162 }
163
164 static LRESULT CALLBACK callWndProc (int nCode, WPARAM wParam, LPARAM lParam)
165 {
166 processMessage (nCode, lv2_shared::wordCast<CWPSTRUCT*> (lParam));
167 return CallNextHookEx ({}, nCode, wParam, lParam);
168 }
169
170 HHOOK hook;
171};
172
174{
175public:
176 WindowSizeChangeListener (HWND hwndIn, PhysicalResizeListener& l)
177 : hwnd (hwndIn)
178 {
179 detector->addListener (hwnd, l);
180 }
181
182 ~WindowSizeChangeListener()
183 {
184 detector->removeListener (hwnd);
185 }
186
187private:
188 SharedResourcePointer<WindowSizeChangeDetector> detector;
189 HWND hwnd;
190
191 JUCE_LEAK_DETECTOR (WindowSizeChangeListener)
192};
193#endif
194
195struct FreeLilvNode
196{
197 void operator() (LilvNode* ptr) const noexcept { lilv_node_free (ptr); }
198};
199
201
202template <typename Traits>
204{
205public:
206 template <typename... Ts>
207 TypesafeLilvNode (Ts&&... ts)
208 : node (Traits::construct (std::forward<Ts> (ts)...)) {}
209
210 bool equals (const TypesafeLilvNode& other) const noexcept
211 {
212 return lilv_node_equals (node.get(), other.node.get());
213 }
214
215 const LilvNode* get() const noexcept { return node.get(); }
216
217 auto getTyped() const noexcept -> decltype (Traits::access (nullptr))
218 {
219 return Traits::access (node.get());
220 }
221
222 static TypesafeLilvNode claim (LilvNode* node)
223 {
224 return TypesafeLilvNode { node };
225 }
226
227 static TypesafeLilvNode copy (const LilvNode* node)
228 {
229 return TypesafeLilvNode { lilv_node_duplicate (node) };
230 }
231
232private:
233 explicit TypesafeLilvNode (LilvNode* ptr)
234 : node (ptr)
235 {
236 jassert (ptr == nullptr || Traits::verify (node.get()));
237 }
238
239 OwningNode node;
240
241 JUCE_LEAK_DETECTOR (TypesafeLilvNode)
242};
243
245{
246 static LilvNode* construct (LilvWorld* world, const char* uri) noexcept
247 {
248 return lilv_new_uri (world, uri);
249 }
250
251 static LilvNode* construct (LilvWorld* world, const char* host, const char* path) noexcept
252 {
253 return lilv_new_file_uri (world, host, path);
254 }
255
256 static constexpr auto verify = lilv_node_is_uri;
257 static constexpr auto access = lilv_node_as_uri;
258};
259
260struct StringConstructorTrait { static constexpr auto construct = lilv_new_string;
261 static constexpr auto verify = lilv_node_is_string;
262 static constexpr auto access = lilv_node_as_string; };
263
266
267struct UsefulUris
268{
269 explicit UsefulUris (LilvWorld* worldIn)
270 : world (worldIn) {}
271
272 LilvWorld* const world = nullptr;
273
274 #define X(str) const NodeUri m##str { world, str };
275 X (LV2_ATOM__AtomPort)
276 X (LV2_ATOM__atomTransfer)
277 X (LV2_ATOM__eventTransfer)
278 X (LV2_CORE__AudioPort)
279 X (LV2_CORE__CVPort)
280 X (LV2_CORE__ControlPort)
281 X (LV2_CORE__GeneratorPlugin)
282 X (LV2_CORE__InputPort)
283 X (LV2_CORE__InstrumentPlugin)
284 X (LV2_CORE__OutputPort)
285 X (LV2_CORE__enumeration)
286 X (LV2_CORE__integer)
287 X (LV2_CORE__toggled)
288 X (LV2_RESIZE_PORT__minimumSize)
289 X (LV2_UI__floatProtocol)
290 X (LV2_WORKER__interface)
291 #undef X
292};
293
294template <typename Ptr, typename Free>
295struct OwningPtrTraits
296{
297 using type = std::unique_ptr<Ptr, Free>;
298 static const Ptr* get (const type& t) noexcept { return t.get(); }
299};
300
301template <typename Ptr>
303{
304 using type = const Ptr*;
305 static const Ptr* get (const type& t) noexcept { return t; }
306};
307
309{
310 using Container = const LilvPlugins*;
311 using Iter = LilvIter*;
312 static constexpr auto begin = lilv_plugins_begin;
313 static constexpr auto next = lilv_plugins_next;
314 static constexpr auto isEnd = lilv_plugins_is_end;
315 static constexpr auto get = lilv_plugins_get;
316};
317
318using PluginsIterator = lv2_shared::Iterator<PluginsIteratorTraits>;
319
321{
322 using Container = const LilvPluginClasses*;
323 using Iter = LilvIter*;
324 static constexpr auto begin = lilv_plugin_classes_begin;
325 static constexpr auto next = lilv_plugin_classes_next;
326 static constexpr auto isEnd = lilv_plugin_classes_is_end;
327 static constexpr auto get = lilv_plugin_classes_get;
328};
329
330using PluginClassesIterator = lv2_shared::Iterator<PluginClassesIteratorTraits>;
331
333{
334 using Container = const LilvNodes*;
335 using Iter = LilvIter*;
336 static constexpr auto begin = lilv_nodes_begin;
337 static constexpr auto next = lilv_nodes_next;
338 static constexpr auto isEnd = lilv_nodes_is_end;
339 static constexpr auto get = lilv_nodes_get;
340};
341
342using NodesIterator = lv2_shared::Iterator<NodesIteratorTraits>;
343
345{
346 using Container = const LilvScalePoints*;
347 using Iter = LilvIter*;
348 static constexpr auto begin = lilv_scale_points_begin;
349 static constexpr auto next = lilv_scale_points_next;
350 static constexpr auto isEnd = lilv_scale_points_is_end;
351 static constexpr auto get = lilv_scale_points_get;
352};
353
354using ScalePointsIterator = lv2_shared::Iterator<ScalePointsIteratorTraits>;
355
357{
358 using Container = const LilvUIs*;
359 using Iter = LilvIter*;
360 static constexpr auto begin = lilv_uis_begin;
361 static constexpr auto next = lilv_uis_next;
362 static constexpr auto isEnd = lilv_uis_is_end;
363 static constexpr auto get = lilv_uis_get;
364};
365
366using UisIterator = lv2_shared::Iterator<UisIteratorTraits>;
367
368template <typename PtrTraits>
369class NodesImpl
370{
371public:
372 using type = typename PtrTraits::type;
373
374 template <typename Ptr>
375 explicit NodesImpl (Ptr* ptr)
376 : nodes (type { ptr }) {}
377
378 explicit NodesImpl (type ptr)
379 : nodes (std::move (ptr)) {}
380
381 unsigned size() const noexcept { return lilv_nodes_size (PtrTraits::get (nodes)); }
382
383 NodesIterator begin() const noexcept
384 {
385 return nodes == nullptr ? NodesIterator{}
386 : NodesIterator { PtrTraits::get (nodes) };
387 }
388
389 NodesIterator end() const noexcept { return {}; }
390
391private:
392 type nodes{};
393};
394
395struct NodesFree
396{
397 void operator() (LilvNodes* ptr) const noexcept { lilv_nodes_free (ptr); }
398};
399
402
403class ScalePoints
404{
405public:
406 explicit ScalePoints (const LilvScalePoints* pt)
407 : points (pt) {}
408
409 ScalePointsIterator begin() const noexcept
410 {
411 return points == nullptr ? ScalePointsIterator{}
412 : ScalePointsIterator { points };
413 }
414
415 ScalePointsIterator end() const noexcept { return {}; }
416
417private:
418 const LilvScalePoints* points = nullptr;
419};
420
421class ScalePoint
422{
423public:
424 explicit ScalePoint (const LilvScalePoint* pt)
425 : point (pt) {}
426
427 const LilvNode* getLabel() const noexcept { return lilv_scale_point_get_label (point); }
428 const LilvNode* getValue() const noexcept { return lilv_scale_point_get_value (point); }
429
430private:
431 const LilvScalePoint* point = nullptr;
432};
433
434struct PortRange
435{
436 float defaultValue, min, max;
437};
438
439class Port
440{
441public:
442 enum class Kind
443 {
444 control,
445 audio,
446 cv,
447 atom,
448 unknown,
449 };
450
451 enum class Direction
452 {
453 input,
454 output,
455 unknown,
456 };
457
458 Port (const LilvPlugin* pluginIn, const LilvPort* portIn)
459 : plugin (pluginIn), port (portIn) {}
460
461 Direction getDirection (const UsefulUris& uris) const noexcept
462 {
463 if (isA (uris.mLV2_CORE__InputPort))
464 return Direction::input;
465
466 if (isA (uris.mLV2_CORE__OutputPort))
467 return Direction::output;
468
469 return Direction::unknown;
470 }
471
472 Kind getKind (const UsefulUris& uris) const noexcept
473 {
474 if (isA (uris.mLV2_CORE__ControlPort))
475 return Kind::control;
476
477 if (isA (uris.mLV2_CORE__AudioPort))
478 return Kind::audio;
479
480 if (isA (uris.mLV2_CORE__CVPort))
481 return Kind::cv;
482
483 if (isA (uris.mLV2_ATOM__AtomPort))
484 return Kind::atom;
485
486 return Kind::unknown;
487 }
488
489 OwningNode get (const LilvNode* predicate) const noexcept
490 {
491 return OwningNode { lilv_port_get (plugin, port, predicate) };
492 }
493
494 NonOwningNodes getClasses() const noexcept
495 {
496 return NonOwningNodes { lilv_port_get_classes (plugin, port) };
497 }
498
499 NodeString getName() const noexcept
500 {
501 return NodeString::claim (lilv_port_get_name (plugin, port));
502 }
503
504 NodeString getSymbol() const noexcept
505 {
506 return NodeString::copy (lilv_port_get_symbol (plugin, port));
507 }
508
509 OwningNodes getProperties() const noexcept
510 {
511 return OwningNodes { lilv_port_get_properties (plugin, port) };
512 }
513
514 ScalePoints getScalePoints() const noexcept
515 {
516 return ScalePoints { lilv_port_get_scale_points (plugin, port) };
517 }
518
519 bool hasProperty (const NodeUri& uri) const noexcept
520 {
521 return lilv_port_has_property (plugin, port, uri.get());
522 }
523
524 uint32_t getIndex() const noexcept { return lilv_port_get_index (plugin, port); }
525
526 static float getFloatValue (const LilvNode* node, float fallback)
527 {
528 if (lilv_node_is_float (node) || lilv_node_is_int (node))
529 return lilv_node_as_float (node);
530
531 return fallback;
532 }
533
534 bool supportsEvent (const LilvNode* node) const noexcept
535 {
536 return lilv_port_supports_event (plugin, port, node);
537 }
538
539 PortRange getRange() const noexcept
540 {
541 LilvNode* def = nullptr;
542 LilvNode* min = nullptr;
543 LilvNode* max = nullptr;
544
545 lilv_port_get_range (plugin, port, &def, &min, &max);
546
547 const OwningNode defOwner { def };
548 const OwningNode minOwner { min };
549 const OwningNode maxOwner { max };
550
551 return { getFloatValue (def, 0.0f),
552 getFloatValue (min, 0.0f),
553 getFloatValue (max, 1.0f) };
554 }
555
556 bool isValid() const noexcept { return port != nullptr; }
557
558private:
559 bool isA (const NodeUri& uri) const noexcept
560 {
561 return lilv_port_is_a (plugin, port, uri.get());
562 }
563
564 const LilvPlugin* plugin = nullptr;
565 const LilvPort* port = nullptr;
566
567 JUCE_LEAK_DETECTOR (Port)
568};
569
570class Plugin
571{
572public:
573 explicit Plugin (const LilvPlugin* p) : plugin (p) {}
574
575 bool verify() const noexcept { return lilv_plugin_verify (plugin); }
576 NodeUri getUri() const noexcept { return NodeUri::copy (lilv_plugin_get_uri (plugin)); }
577 NodeUri getBundleUri() const noexcept { return NodeUri::copy (lilv_plugin_get_bundle_uri (plugin)); }
578 NodeUri getLibraryUri() const noexcept { return NodeUri::copy (lilv_plugin_get_library_uri (plugin)); }
579 NodeString getName() const noexcept { return NodeString::claim (lilv_plugin_get_name (plugin)); }
580 NodeString getAuthorName() const noexcept { return NodeString::claim (lilv_plugin_get_author_name (plugin)); }
581 uint32_t getNumPorts() const noexcept { return lilv_plugin_get_num_ports (plugin); }
582 const LilvPluginClass* getClass() const noexcept { return lilv_plugin_get_class (plugin); }
583 OwningNodes getValue (const LilvNode* predicate) const noexcept { return OwningNodes { lilv_plugin_get_value (plugin, predicate) }; }
584
585 Port getPortByIndex (uint32_t index) const noexcept
586 {
587 return Port { plugin, lilv_plugin_get_port_by_index (plugin, index) };
588 }
589
590 Port getPortByDesignation (const LilvNode* portClass, const LilvNode* designation) const noexcept
591 {
592 return Port { plugin, lilv_plugin_get_port_by_designation (plugin, portClass, designation) };
593 }
594
595 OwningNodes getRequiredFeatures() const noexcept
596 {
597 return OwningNodes { lilv_plugin_get_required_features (plugin) };
598 }
599
600 OwningNodes getOptionalFeatures() const noexcept
601 {
602 return OwningNodes { lilv_plugin_get_optional_features (plugin) };
603 }
604
605 bool hasExtensionData (const NodeUri& uri) const noexcept
606 {
607 return lilv_plugin_has_extension_data (plugin, uri.get());
608 }
609
610 bool hasFeature (const NodeUri& uri) const noexcept
611 {
612 return lilv_plugin_has_feature (plugin, uri.get());
613 }
614
615 template <typename... Classes>
616 uint32_t getNumPortsOfClass (const Classes&... classes) const noexcept
617 {
618 return lilv_plugin_get_num_ports_of_class (plugin, classes.get()..., 0);
619 }
620
621 const LilvPlugin* get() const noexcept { return plugin; }
622
623 bool hasLatency() const noexcept { return lilv_plugin_has_latency (plugin); }
624 uint32_t getLatencyPortIndex() const noexcept { return lilv_plugin_get_latency_port_index (plugin); }
625
626private:
627 const LilvPlugin* plugin = nullptr;
628
629 JUCE_LEAK_DETECTOR (Plugin)
630};
631
632/*
633 This is very similar to the symap implementation in jalv.
634*/
635class SymbolMap
636{
637public:
638 SymbolMap() = default;
639
641 {
642 for (const auto* str : uris)
643 map (str);
644 }
645
646 LV2_URID map (const char* uri)
647 {
648 const auto comparator = [this] (size_t index, const String& str)
649 {
650 return strings[index] < str;
651 };
652
653 const auto uriString = String::fromUTF8 (uri);
654 const auto it = std::lower_bound (indices.cbegin(), indices.cend(), uriString, comparator);
655
656 if (it != indices.cend() && strings[*it] == uriString)
657 return static_cast<LV2_URID> (*it + 1);
658
659 const auto index = strings.size();
660 indices.insert (it, index);
661 strings.push_back (uriString);
662 return static_cast<LV2_URID> (index + 1);
663 }
664
665 const char* unmap (LV2_URID urid) const
666 {
667 const auto index = urid - 1;
668 return index < strings.size() ? strings[index].toRawUTF8()
669 : nullptr;
670 }
671
672 static LV2_URID mapUri (LV2_URID_Map_Handle handle, const char* uri)
673 {
674 return static_cast<SymbolMap*> (handle)->map (uri);
675 }
676
677 static const char* unmapUri (LV2_URID_Unmap_Handle handle, LV2_URID urid)
678 {
679 return static_cast<SymbolMap*> (handle)->unmap (urid);
680 }
681
682 LV2_URID_Map getMapFeature() { return { this, mapUri }; }
683 LV2_URID_Unmap getUnmapFeature() { return { this, unmapUri }; }
684
685private:
686 std::vector<String> strings;
687 std::vector<size_t> indices;
688
689 JUCE_LEAK_DETECTOR (SymbolMap)
690};
691
692struct UsefulUrids
693{
694 explicit UsefulUrids (SymbolMap& m) : symap (m) {}
695
696 SymbolMap& symap;
697
698 #define X(token) const LV2_URID m##token = symap.map (token);
699 X (LV2_ATOM__Bool)
700 X (LV2_ATOM__Double)
701 X (LV2_ATOM__Float)
702 X (LV2_ATOM__Int)
703 X (LV2_ATOM__Long)
704 X (LV2_ATOM__Object)
705 X (LV2_ATOM__Sequence)
706 X (LV2_ATOM__atomTransfer)
707 X (LV2_ATOM__beatTime)
708 X (LV2_ATOM__eventTransfer)
709 X (LV2_ATOM__frameTime)
710 X (LV2_LOG__Error)
711 X (LV2_LOG__Note)
712 X (LV2_LOG__Trace)
713 X (LV2_LOG__Warning)
714 X (LV2_MIDI__MidiEvent)
715 X (LV2_PATCH__Set)
716 X (LV2_PATCH__property)
717 X (LV2_PATCH__value)
718 X (LV2_STATE__StateChanged)
719 X (LV2_TIME__Position)
720 X (LV2_TIME__barBeat)
721 X (LV2_TIME__beat)
722 X (LV2_TIME__beatUnit)
723 X (LV2_TIME__beatsPerBar)
724 X (LV2_TIME__beatsPerMinute)
725 X (LV2_TIME__frame)
726 X (LV2_TIME__speed)
727 X (LV2_TIME__bar)
728 X (LV2_UI__floatProtocol)
729 X (LV2_UNITS__beat)
730 X (LV2_UNITS__frame)
731 #undef X
732};
733
734class Log
735{
736public:
737 explicit Log (const UsefulUrids* u) : urids (u) {}
738
739 LV2_Log_Log* getLogFeature() { return &logFeature; }
740
741private:
742 int vprintfCallback ([[maybe_unused]] LV2_URID type, const char* fmt, va_list ap) const
743 {
744 // If this is hit, the plugin has encountered some kind of error
745 ignoreUnused (urids);
746 jassert (type != urids->mLV2_LOG__Error && type != urids->mLV2_LOG__Warning);
747 return std::vfprintf (stderr, fmt, ap);
748 }
749
750 static int vprintfCallback (LV2_Log_Handle handle,
751 LV2_URID type,
752 const char* fmt,
753 va_list ap)
754 {
755 return static_cast<const Log*> (handle)->vprintfCallback (type, fmt, ap);
756 }
757
758 static int printfCallback (LV2_Log_Handle handle, LV2_URID type, const char* fmt, ...)
759 {
760 va_list list;
761 va_start (list, fmt);
762 auto result = vprintfCallback (handle, type, fmt, list);
763 va_end (list);
764 return result;
765 }
766
767 const UsefulUrids* urids = nullptr;
768 LV2_Log_Log logFeature { this, printfCallback, vprintfCallback };
769
771};
772
773struct Features
774{
775 explicit Features (std::vector<LV2_Feature>&& f)
776 : features (std::move (f)) {}
777
778 static std::vector<String> getUris (const std::vector<LV2_Feature>& features)
779 {
780 std::vector<String> result;
781 result.reserve (features.size());
782
783 for (const auto& feature : features)
784 result.push_back (String::fromUTF8 (feature.URI));
785
786 return result;
787 }
788
790 std::vector<const LV2_Feature*> pointers = makeNullTerminatedArray();
791
792private:
793 std::vector<const LV2_Feature*> makeNullTerminatedArray()
794 {
796 result.reserve (features.size() + 1);
797
798 for (const auto& feature : features)
799 result.push_back (&feature);
800
801 result.push_back (nullptr);
802
803 return result;
804 }
805
806 JUCE_LEAK_DETECTOR (Features)
807};
808
809template <typename Extension>
811{
812 OptionalExtension() = default;
813
814 explicit OptionalExtension (Extension extensionIn) : extension (extensionIn), valid (true) {}
815
816 Extension extension;
817 bool valid = false;
818};
819
820class Instance
821{
822 struct Free
823 {
824 void operator() (LilvInstance* ptr) const noexcept { lilv_instance_free (ptr); }
825 };
826
827public:
829 using GetExtensionData = const void* (*) (const char*);
830
831 Instance (const Plugin& pluginIn, double sampleRate, const LV2_Feature* const* features)
832 : plugin (pluginIn),
833 instance (lilv_plugin_instantiate (plugin.get(), sampleRate, features)) {}
834
835 void activate() { lilv_instance_activate (instance.get()); }
836 void run (uint32_t sampleCount) { lilv_instance_run (instance.get(), sampleCount); }
837 void deactivate() { lilv_instance_deactivate (instance.get()); }
838
839 const char* getUri() const noexcept { return lilv_instance_get_uri (instance.get()); }
840
841 LV2_Handle getHandle() const noexcept { return lilv_instance_get_handle (instance.get()); }
842
843 LilvInstance* get() const noexcept { return instance.get(); }
844
845 void connectPort (uint32_t index, void* data)
846 {
847 lilv_instance_connect_port (instance.get(), index, data);
848 }
849
850 template <typename Extension>
851 OptionalExtension<Extension> getExtensionData (const NodeUri& uri) const noexcept
852 {
853 if (plugin.get() == nullptr || ! plugin.hasExtensionData (uri) || instance.get() == nullptr)
854 return {};
855
856 return OptionalExtension<Extension> { readUnaligned<Extension> (lilv_instance_get_extension_data (instance.get(), uri.getTyped())) };
857 }
858
859 GetExtensionData getExtensionDataCallback() const noexcept
860 {
861 return instance->lv2_descriptor->extension_data;
862 }
863
864 bool operator== (std::nullptr_t) const noexcept { return instance == nullptr; }
865 bool operator!= (std::nullptr_t) const noexcept { return ! (*this == nullptr); }
866
867private:
868 Plugin plugin;
869 Ptr instance;
870
871 JUCE_LEAK_DETECTOR (Instance)
872};
873
874enum class Realtime { no, yes };
875
876// Must be trivial!
877struct WorkResponder
878{
879 static WorkResponder getDefault() { return { nullptr, nullptr }; }
880
881 LV2_Worker_Status processResponse (uint32_t size, const void* data) const
882 {
883 return worker->work_response (handle, size, data);
884 }
885
886 bool isValid() const { return handle != nullptr && worker != nullptr; }
887
888 LV2_Handle handle;
889 const LV2_Worker_Interface* worker;
890};
891
893{
894 virtual ~WorkerResponseListener() = default;
895 virtual LV2_Worker_Status responseGenerated (WorkResponder, uint32_t, const void*) = 0;
896};
897
898struct RespondHandle
899{
900 LV2_Worker_Status respond (uint32_t size, const void* data) const
901 {
902 if (realtime == Realtime::yes)
903 return listener.responseGenerated (responder, size, data);
904
905 return responder.processResponse (size, data);
906 }
907
908 static LV2_Worker_Status respond (LV2_Worker_Respond_Handle handle,
909 uint32_t size,
910 const void* data)
911 {
912 return static_cast<const RespondHandle*> (handle)->respond (size, data);
913 }
914
915 WorkResponder responder;
916 WorkerResponseListener& listener;
917 Realtime realtime;
918};
919
920// Must be trivial!
921struct WorkSubmitter
922{
923 static WorkSubmitter getDefault() { return { nullptr, nullptr, nullptr, nullptr }; }
924
925 LV2_Worker_Status doWork (Realtime realtime, uint32_t size, const void* data) const
926 {
927 // The Worker spec says that the host "MUST NOT make concurrent calls to [work] from
928 // several threads".
929 // Taking the work mutex here ensures that only one piece of work is done at a time.
930 // If we didn't take the work mutex, there would be a danger of work happening
931 // simultaneously on the worker thread and the render thread when switching between
932 // realtime/offline modes (in realtime mode, work happens on the worker thread; in
933 // offline mode, work happens immediately on the render/audio thread).
934 const ScopedLock lock (*workMutex);
935
936 RespondHandle respondHandle { WorkResponder { handle, worker }, *listener, realtime };
937 return worker->work (handle, RespondHandle::respond, &respondHandle, size, data);
938 }
939
940 bool isValid() const { return handle != nullptr && worker != nullptr && listener != nullptr && workMutex != nullptr; }
941
942 LV2_Handle handle;
943 const LV2_Worker_Interface* worker;
944 WorkerResponseListener* listener;
945 CriticalSection* workMutex;
946};
947
948template <typename Trivial>
949static auto toChars (Trivial value)
950{
951 static_assert (std::is_trivial_v<Trivial>);
952 std::array<char, sizeof (Trivial)> result;
953 writeUnaligned (result.data(), value);
954 return result;
955}
956
957template <typename Context>
958class WorkQueue
959{
960public:
961 static_assert (std::is_trivial_v<Context>, "Context must be copyable as bytes");
962
963 explicit WorkQueue (int size)
964 : fifo (size), data (static_cast<size_t> (size)) {}
965
966 LV2_Worker_Status push (Context context, size_t size, const void* contents)
967 {
968 const auto* bytes = static_cast<const char*> (contents);
969 const auto numToWrite = sizeof (Header) + size;
970
971 if (static_cast<size_t> (fifo.getFreeSpace()) < numToWrite)
972 return LV2_WORKER_ERR_NO_SPACE;
973
974 Header header { size, context };
975 const auto headerBuffer = toChars (header);
976
977 const auto scope = fifo.write (static_cast<int> (numToWrite));
978 jassert (scope.blockSize1 + scope.blockSize2 == static_cast<int> (numToWrite));
979
980 size_t index = 0;
981 scope.forEach ([&] (int i)
982 {
983 data[static_cast<size_t> (i)] = index < headerBuffer.size() ? headerBuffer[index]
984 : bytes[index - headerBuffer.size()];
985 ++index;
986 });
987
988 return LV2_WORKER_SUCCESS;
989 }
990
991 Context pop (std::vector<char>& dest)
992 {
993 // If the vector is too small we'll have to resize it on the audio thread
994 jassert (dest.capacity() >= data.size());
995 dest.clear();
996
997 const auto numReady = fifo.getNumReady();
998
999 if (static_cast<size_t> (numReady) < sizeof (Header))
1000 {
1001 jassert (numReady == 0);
1002 return Context::getDefault();
1003 }
1004
1005 std::array<char, sizeof (Header)> headerBuffer;
1006
1007 {
1008 size_t index = 0;
1009 fifo.read (sizeof (Header)).forEach ([&] (int i)
1010 {
1011 headerBuffer[index++] = data[static_cast<size_t> (i)];
1012 });
1013 }
1014
1015 const auto header = readUnaligned<Header> (headerBuffer.data());
1016
1017 jassert (static_cast<size_t> (fifo.getNumReady()) >= header.size);
1018
1019 dest.resize (header.size);
1020
1021 {
1022 size_t index = 0;
1023 fifo.read (static_cast<int> (header.size)).forEach ([&] (int i)
1024 {
1025 dest[index++] = data[static_cast<size_t> (i)];
1026 });
1027 }
1028
1029 return header.context;
1030 }
1031
1032private:
1033 struct Header
1034 {
1035 size_t size;
1036 Context context;
1037 };
1038
1039 AbstractFifo fifo;
1041
1042 JUCE_LEAK_DETECTOR (WorkQueue)
1043};
1044
1045/*
1046 Keeps track of active plugin instances, so that we can avoid sending work
1047 messages to dead plugins.
1048*/
1049class HandleRegistry
1050{
1051public:
1052 void insert (LV2_Handle handle)
1053 {
1054 const SpinLock::ScopedLockType lock (mutex);
1055 handles.insert (handle);
1056 }
1057
1058 void erase (LV2_Handle handle)
1059 {
1060 const SpinLock::ScopedLockType lock (mutex);
1061 handles.erase (handle);
1062 }
1063
1064 template <typename Fn>
1065 LV2_Worker_Status ifContains (LV2_Handle handle, Fn&& callback)
1066 {
1067 const SpinLock::ScopedLockType lock (mutex);
1068
1069 if (handles.find (handle) != handles.cend())
1070 return callback();
1071
1072 return LV2_WORKER_ERR_UNKNOWN;
1073 }
1074
1075private:
1076 std::set<LV2_Handle> handles;
1077 SpinLock mutex;
1078
1079 JUCE_LEAK_DETECTOR (HandleRegistry)
1080};
1081
1082/*
1083 Implements an LV2 Worker, allowing work to be scheduled in realtime
1084 by the plugin instance.
1085
1086 IMPORTANT this will die pretty hard if `getExtensionData (LV2_WORKER__interface)`
1087 returns garbage, so make sure to check that the plugin `hasExtensionData` before
1088 constructing one of these!
1089*/
1091{
1092public:
1093 ~SharedThreadedWorker() noexcept override
1094 {
1095 shouldExit = true;
1096 thread.join();
1097 }
1098
1099 LV2_Worker_Status schedule (WorkSubmitter submitter,
1100 uint32_t size,
1101 const void* data)
1102 {
1103 return registry.ifContains (submitter.handle, [&]
1104 {
1105 return incoming.push (submitter, size, data);
1106 });
1107 }
1108
1109 LV2_Worker_Status responseGenerated (WorkResponder responder,
1110 uint32_t size,
1111 const void* data) override
1112 {
1113 return registry.ifContains (responder.handle, [&]
1114 {
1115 return outgoing.push (responder, size, data);
1116 });
1117 }
1118
1119 void processResponses()
1120 {
1121 for (;;)
1122 {
1123 auto workerResponder = outgoing.pop (message);
1124
1125 if (! message.empty() && workerResponder.isValid())
1126 workerResponder.processResponse (static_cast<uint32_t> (message.size()), message.data());
1127 else
1128 break;
1129 }
1130 }
1131
1132 void registerHandle (LV2_Handle handle) { registry.insert (handle); }
1133 void deregisterHandle (LV2_Handle handle) { registry.erase (handle); }
1134
1135private:
1136 static constexpr auto queueSize = 8192;
1137 std::atomic<bool> shouldExit { false };
1138 WorkQueue<WorkSubmitter> incoming { queueSize };
1139 WorkQueue<WorkResponder> outgoing { queueSize };
1140 std::vector<char> message = std::vector<char> (queueSize);
1141 std::thread thread { [this]
1142 {
1143 std::vector<char> buffer (queueSize);
1144
1145 while (! shouldExit)
1146 {
1147 const auto submitter = incoming.pop (buffer);
1148
1149 if (! buffer.empty() && submitter.isValid())
1150 submitter.doWork (Realtime::yes, (uint32_t) buffer.size(), buffer.data());
1151 else
1153 }
1154 } };
1155 HandleRegistry registry;
1156
1157 JUCE_LEAK_DETECTOR (SharedThreadedWorker)
1158};
1159
1160struct HandleHolder
1161{
1162 virtual ~HandleHolder() = default;
1163 virtual LV2_Handle getHandle() const = 0;
1164 virtual const LV2_Worker_Interface* getWorkerInterface() const = 0;
1165};
1166
1167class WorkScheduler
1168{
1169public:
1170 explicit WorkScheduler (HandleHolder& handleHolderIn)
1171 : handleHolder (handleHolderIn) {}
1172
1173 void processResponses() { workerThread->processResponses(); }
1174
1175 LV2_Worker_Schedule& getWorkerSchedule() { return schedule; }
1176
1177 void setNonRealtime (bool nonRealtime) { realtime = ! nonRealtime; }
1178
1179 void registerHandle (LV2_Handle handle) { workerThread->registerHandle (handle); }
1180 void deregisterHandle (LV2_Handle handle) { workerThread->deregisterHandle (handle); }
1181
1182private:
1183 LV2_Worker_Status scheduleWork (uint32_t size, const void* data)
1184 {
1185 WorkSubmitter submitter { handleHolder.getHandle(),
1186 handleHolder.getWorkerInterface(),
1187 workerThread,
1188 &workMutex };
1189
1190 // If we're in realtime mode, the work should go onto a background thread,
1191 // and we'll process it later.
1192 // If we're offline, we can just do the work immediately, without worrying about
1193 // drop-outs
1194 return realtime ? workerThread->schedule (submitter, size, data)
1195 : submitter.doWork (Realtime::no, size, data);
1196 }
1197
1198 static LV2_Worker_Status scheduleWork (LV2_Worker_Schedule_Handle handle,
1199 uint32_t size,
1200 const void* data)
1201 {
1202 return static_cast<WorkScheduler*> (handle)->scheduleWork (size, data);
1203 }
1204
1205 SharedResourcePointer<SharedThreadedWorker> workerThread;
1206 HandleHolder& handleHolder;
1207 LV2_Worker_Schedule schedule { this, scheduleWork };
1208 CriticalSection workMutex;
1209 bool realtime = true;
1210
1211 JUCE_LEAK_DETECTOR (WorkScheduler)
1212};
1213
1215{
1216 virtual ~FeaturesDataListener() = default;
1217 virtual LV2_Resize_Port_Status resizeCallback (uint32_t index, size_t size) = 0;
1218};
1219
1220class Resize
1221{
1222public:
1223 explicit Resize (FeaturesDataListener& l)
1224 : listener (l) {}
1225
1226 LV2_Resize_Port_Resize& getFeature() { return resize; }
1227
1228private:
1229 LV2_Resize_Port_Status resizeCallback (uint32_t index, size_t size)
1230 {
1231 return listener.resizeCallback (index, size);
1232 }
1233
1234 static LV2_Resize_Port_Status resizeCallback (LV2_Resize_Port_Feature_Data data, uint32_t index, size_t size)
1235 {
1236 return static_cast<Resize*> (data)->resizeCallback (index, size);
1237 }
1238
1239 FeaturesDataListener& listener;
1240 LV2_Resize_Port_Resize resize { this, resizeCallback };
1241};
1242
1243class FeaturesData
1244{
1245public:
1246 FeaturesData (HandleHolder& handleHolder,
1247 FeaturesDataListener& l,
1248 int32_t maxBlockSizeIn,
1249 int32_t sequenceSizeIn,
1250 const UsefulUrids* u)
1251 : urids (u),
1252 resize (l),
1253 maxBlockSize (maxBlockSizeIn),
1254 sequenceSize (sequenceSizeIn),
1255 workScheduler (handleHolder)
1256 {}
1257
1258 LV2_Options_Option* getOptions() noexcept { return options.data(); }
1259
1260 int32_t getMaxBlockSize() const noexcept { return maxBlockSize; }
1261
1262 void setNonRealtime (bool newValue) { realtime = ! newValue; }
1263
1264 const LV2_Feature* const* getFeatureArray() const noexcept { return features.pointers.data(); }
1265
1266 static std::vector<String> getFeatureUris()
1267 {
1268 return Features::getUris (makeFeatures ({}, {}, {}, {}, {}, {}));
1269 }
1270
1271 void processResponses() { workScheduler.processResponses(); }
1272
1273 void registerHandle (LV2_Handle handle) { workScheduler.registerHandle (handle); }
1274 void deregisterHandle (LV2_Handle handle) { workScheduler.deregisterHandle (handle); }
1275
1276private:
1277 static std::vector<LV2_Feature> makeFeatures (LV2_URID_Map* map,
1278 LV2_URID_Unmap* unmap,
1279 LV2_Options_Option* options,
1280 LV2_Worker_Schedule* schedule,
1281 LV2_Resize_Port_Resize* resize,
1282 [[maybe_unused]] LV2_Log_Log* log)
1283 {
1284 return { LV2_Feature { LV2_STATE__loadDefaultState, nullptr },
1285 LV2_Feature { LV2_BUF_SIZE__boundedBlockLength, nullptr },
1286 LV2_Feature { LV2_URID__map, map },
1287 LV2_Feature { LV2_URID__unmap, unmap },
1288 LV2_Feature { LV2_OPTIONS__options, options },
1289 LV2_Feature { LV2_WORKER__schedule, schedule },
1290 LV2_Feature { LV2_STATE__threadSafeRestore, nullptr },
1291 #if JUCE_DEBUG
1292 LV2_Feature { LV2_LOG__log, log },
1293 #endif
1294 LV2_Feature { LV2_RESIZE_PORT__resize, resize } };
1295 }
1296
1297 LV2_Options_Option makeOption (const char* uid, const int32_t* ptr)
1298 {
1299 return { LV2_OPTIONS_INSTANCE,
1300 0, // INSTANCE kinds must have a subject of 0
1301 urids->symap.map (uid),
1302 sizeof (int32_t),
1303 urids->symap.map (LV2_ATOM__Int),
1304 ptr };
1305 }
1306
1307 const UsefulUrids* urids;
1308 Resize resize;
1309 Log log { urids };
1310
1311 const int32_t minBlockSize = 0, maxBlockSize = 0, sequenceSize = 0;
1312
1314 {
1315 makeOption (LV2_BUF_SIZE__minBlockLength, &minBlockSize),
1316 makeOption (LV2_BUF_SIZE__maxBlockLength, &maxBlockSize),
1317 makeOption (LV2_BUF_SIZE__sequenceSize, &sequenceSize),
1318 { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, nullptr }, // The final entry must be nulled out
1319 };
1320
1321 WorkScheduler workScheduler;
1322
1323 LV2_URID_Map map = urids->symap.getMapFeature();
1324 LV2_URID_Unmap unmap = urids->symap.getUnmapFeature();
1325 Features features { makeFeatures (&map,
1326 &unmap,
1327 options.data(),
1328 &workScheduler.getWorkerSchedule(),
1329 &resize.getFeature(),
1330 log.getLogFeature()) };
1331
1332 bool realtime = true;
1333
1334 JUCE_LEAK_DETECTOR (FeaturesData)
1335};
1336
1337//==============================================================================
1338struct TryLockAndCall
1339{
1340 template <typename Fn>
1341 void operator() (SpinLock& mutex, Fn&& fn)
1342 {
1343 const SpinLock::ScopedTryLockType lock (mutex);
1344
1345 if (lock.isLocked())
1346 fn();
1347 }
1348};
1349
1350struct LockAndCall
1351{
1352 template <typename Fn>
1353 void operator() (SpinLock& mutex, Fn&& fn)
1354 {
1355 const SpinLock::ScopedLockType lock (mutex);
1356 fn();
1357 }
1358};
1359
1360struct RealtimeReadTrait
1361{
1362 using Read = TryLockAndCall;
1363 using Write = LockAndCall;
1364};
1365
1366struct RealtimeWriteTrait
1367{
1368 using Read = LockAndCall;
1369 using Write = TryLockAndCall;
1370};
1371
1372struct MessageHeader
1373{
1374 uint32_t portIndex;
1375 uint32_t protocol;
1376};
1377
1378template <typename Header>
1380{
1381 virtual ~MessageBufferInterface() = default;
1382 virtual void pushMessage (Header header, uint32_t size, const void* buffer) = 0;
1383};
1384
1385template <typename Header, typename LockTraits>
1386class Messages final : public MessageBufferInterface<Header>
1387{
1388 using Read = typename LockTraits::Read;
1389 using Write = typename LockTraits::Write;
1390
1391 struct FullHeader
1392 {
1393 Header header;
1394 uint32_t size;
1395 };
1396
1397public:
1398 Messages() { data.reserve (initialBufferSize); }
1399
1400 void pushMessage (Header header, uint32_t size, const void* buffer) override
1401 {
1402 Write{} (mutex, [&]
1403 {
1404 const auto chars = toChars (FullHeader { header, size });
1405 const auto bufferAsChars = static_cast<const char*> (buffer);
1406 data.insert (data.end(), chars.begin(), chars.end());
1407 data.insert (data.end(), bufferAsChars, bufferAsChars + size);
1408 });
1409 }
1410
1411 template <typename Callback>
1412 void readAllAndClear (Callback&& callback)
1413 {
1414 Read{} (mutex, [&]
1415 {
1416 if (data.empty())
1417 return;
1418
1419 const auto end = data.data() + data.size();
1420
1421 for (auto ptr = data.data(); ptr < end;)
1422 {
1423 const auto header = readUnaligned<FullHeader> (ptr);
1424 callback (header.header, header.size, ptr + sizeof (header));
1425 ptr += sizeof (header) + header.size;
1426 }
1427
1428 data.clear();
1429 });
1430 }
1431
1432private:
1433 static constexpr auto initialBufferSize = 8192;
1434 SpinLock mutex;
1436
1437 JUCE_LEAK_DETECTOR (Messages)
1438};
1439
1440//==============================================================================
1441struct UiEventListener : public MessageBufferInterface<MessageHeader>
1442{
1443 virtual int idle() = 0;
1444};
1445
1446struct UiMessageHeader
1447{
1448 UiEventListener* listener;
1449 MessageHeader header;
1450};
1451
1452class ProcessorToUi final : public MessageBufferInterface<UiMessageHeader>
1453{
1454public:
1455 ProcessorToUi() { timer.startTimerHz (60); }
1456
1457 void addUi (UiEventListener& l) { JUCE_ASSERT_MESSAGE_THREAD; activeUis.insert (&l); }
1458 void removeUi (UiEventListener& l) { JUCE_ASSERT_MESSAGE_THREAD; activeUis.erase (&l); }
1459
1460 void pushMessage (UiMessageHeader header, uint32_t size, const void* buffer) override
1461 {
1462 processorToUi.pushMessage (header, size, buffer);
1463 }
1464
1465private:
1466 Messages<UiMessageHeader, RealtimeWriteTrait> processorToUi;
1468 TimedCallback timer { [this]
1469 {
1470 for (auto* l : activeUis)
1471 if (l->idle() != 0)
1472 return;
1473
1474 processorToUi.readAllAndClear ([&] (const UiMessageHeader& header, uint32_t size, const char* data)
1475 {
1476 if (activeUis.find (header.listener) != activeUis.cend())
1477 header.listener->pushMessage (header.header, size, data);
1478 });
1479 } };
1480};
1481
1482/* These type identifiers may be used to check the type of the incoming data. */
1483struct StatefulPortUrids
1484{
1485 explicit StatefulPortUrids (SymbolMap& map)
1486 : Float (map.map (LV2_ATOM__Float)),
1487 Double (map.map (LV2_ATOM__Double)),
1488 Int (map.map (LV2_ATOM__Int)),
1489 Long (map.map (LV2_ATOM__Long))
1490 {}
1491
1492 const LV2_URID Float, Double, Int, Long;
1493};
1494
1495/*
1496 A bit like SortedSet, but only requires `operator<` and not `operator==`, so
1497 it behaves a bit more like a std::set.
1498*/
1499template <typename Value>
1500class SafeSortedSet
1501{
1502public:
1503 using iterator = typename std::vector<Value>:: iterator;
1504 using const_iterator = typename std::vector<Value>::const_iterator;
1505
1506 template <typename Other>
1507 const_iterator find (const Other& other) const noexcept
1508 {
1509 const auto it = std::lower_bound (storage.cbegin(), storage.cend(), other);
1510
1511 if (it != storage.cend() && ! (other < *it))
1512 return it;
1513
1514 return storage.cend();
1515 }
1516
1517 void insert (Value&& value) { insertImpl (std::move (value)); }
1518 void insert (const Value& value) { insertImpl (value); }
1519
1520 size_t size() const noexcept { return storage.size(); }
1521 bool empty() const noexcept { return storage.empty(); }
1522
1523 iterator begin() noexcept { return storage. begin(); }
1524 const_iterator begin() const noexcept { return storage. begin(); }
1525 const_iterator cbegin() const noexcept { return storage.cbegin(); }
1526
1527 iterator end() noexcept { return storage. end(); }
1528 const_iterator end() const noexcept { return storage. end(); }
1529 const_iterator cend() const noexcept { return storage.cend(); }
1530
1531 auto& operator[] (size_t index) const { return storage[index]; }
1532
1533private:
1534 template <typename Arg>
1535 void insertImpl (Arg&& value)
1536 {
1537 const auto it = std::lower_bound (storage.cbegin(), storage.cend(), value);
1538
1539 if (it == storage.cend() || value < *it)
1540 storage.insert (it, std::forward<Arg> (value));
1541 }
1542
1543 std::vector<Value> storage;
1544};
1545
1546struct StoredScalePoint
1547{
1548 String label;
1549 float value;
1550
1551 bool operator< (const StoredScalePoint& other) const noexcept { return value < other.value; }
1552};
1553
1554inline bool operator< (const StoredScalePoint& a, float b) noexcept { return a.value < b; }
1555inline bool operator< (float a, const StoredScalePoint& b) noexcept { return a < b.value; }
1556
1557struct ParameterInfo
1558{
1559 ParameterInfo() = default;
1560
1561 ParameterInfo (SafeSortedSet<StoredScalePoint> scalePointsIn,
1562 String identifierIn,
1563 float defaultValueIn,
1564 float minIn,
1565 float maxIn,
1566 bool isToggleIn,
1567 bool isIntegerIn,
1568 bool isEnumIn)
1569 : scalePoints (std::move (scalePointsIn)),
1570 identifier (std::move (identifierIn)),
1571 defaultValue (defaultValueIn),
1572 min (minIn),
1573 max (maxIn),
1574 isToggle (isToggleIn),
1575 isInteger (isIntegerIn),
1576 isEnum (isEnumIn)
1577 {}
1578
1579 static SafeSortedSet<StoredScalePoint> getScalePoints (const Port& port)
1580 {
1581 SafeSortedSet<StoredScalePoint> scalePoints;
1582
1583 for (const LilvScalePoint* p : port.getScalePoints())
1584 {
1585 const ScalePoint wrapper { p };
1586 const auto value = wrapper.getValue();
1587 const auto label = wrapper.getLabel();
1588
1589 if (lilv_node_is_float (value) || lilv_node_is_int (value))
1590 scalePoints.insert ({ lilv_node_as_string (label), lilv_node_as_float (value) });
1591 }
1592
1593 return scalePoints;
1594 }
1595
1596 static ParameterInfo getInfoForPort (const UsefulUris& uris, const Port& port)
1597 {
1598 const auto range = port.getRange();
1599
1600 return { getScalePoints (port),
1601 "sym:" + String::fromUTF8 (port.getSymbol().getTyped()),
1602 range.defaultValue,
1603 range.min,
1604 range.max,
1605 port.hasProperty (uris.mLV2_CORE__toggled),
1606 port.hasProperty (uris.mLV2_CORE__integer),
1607 port.hasProperty (uris.mLV2_CORE__enumeration) };
1608 }
1609
1610 SafeSortedSet<StoredScalePoint> scalePoints;
1611
1612 /* This is the 'symbol' of a port, or the 'designation' of a parameter without a symbol. */
1613 String identifier;
1614
1615 float defaultValue = 0.0f, min = 0.0f, max = 1.0f;
1616 bool isToggle = false, isInteger = false, isEnum = false;
1617
1618 JUCE_LEAK_DETECTOR (ParameterInfo)
1619};
1620
1621struct PortHeader
1622{
1623 String name;
1624 String symbol;
1625 uint32_t index;
1626 Port::Direction direction;
1627};
1628
1629struct ControlPort
1630{
1631 ControlPort (const PortHeader& headerIn, const ParameterInfo& infoIn)
1632 : header (headerIn), info (infoIn) {}
1633
1634 PortHeader header;
1635 ParameterInfo info;
1636 float currentValue = info.defaultValue;
1637};
1638
1639struct CVPort
1640{
1641 PortHeader header;
1642};
1643
1644struct AudioPort
1645{
1646 PortHeader header;
1647};
1648
1649template <size_t Alignment>
1651{
1652public:
1653 SingleSizeAlignedStorage() = default;
1654
1655 explicit SingleSizeAlignedStorage (size_t sizeInBytes)
1656 : storage (new char[sizeInBytes + Alignment]),
1657 alignedPointer (storage.get()),
1658 space (sizeInBytes + Alignment)
1659 {
1660 alignedPointer = std::align (Alignment, sizeInBytes, alignedPointer, space);
1661 }
1662
1663 void* data() const { return alignedPointer; }
1664 size_t size() const { return space; }
1665
1666private:
1668 void* alignedPointer = nullptr;
1669 size_t space = 0;
1670};
1671
1672template <size_t Alignment>
1674{
1675 if (size <= storage.size())
1676 return storage;
1677
1678 SingleSizeAlignedStorage<Alignment> newStorage { jmax (size, (storage.size() * 3) / 2) };
1679 std::memcpy (newStorage.data(), storage.data(), storage.size());
1680 return newStorage;
1681}
1682
1683enum class SupportsTime { no, yes };
1684
1685class AtomPort
1686{
1687public:
1688 AtomPort (PortHeader h, size_t bytes, SymbolMap& map, SupportsTime supportsTime)
1689 : header (h), contents (bytes), forge (map.getMapFeature()), time (supportsTime) {}
1690
1691 PortHeader header;
1692
1693 void replaceWithChunk()
1694 {
1695 forge.setBuffer (data(), size());
1696 forge.writeChunk ((uint32_t) (size() - sizeof (LV2_Atom)));
1697 }
1698
1699 void replaceBufferWithAtom (const LV2_Atom* atom)
1700 {
1701 const auto totalSize = atom->size + sizeof (LV2_Atom);
1702
1703 if (totalSize <= size())
1704 std::memcpy (data(), atom, totalSize);
1705 else
1706 replaceWithChunk();
1707 }
1708
1709 void beginSequence()
1710 {
1711 forge.setBuffer (data(), size());
1712 lv2_atom_forge_sequence_head (forge.get(), &frame, 0);
1713 }
1714
1715 void endSequence()
1716 {
1717 lv2_atom_forge_pop (forge.get(), &frame);
1718 }
1719
1720 /* For this to work, the 'atom' pointer must be well-formed.
1721
1722 It must be followed by an atom header, then at least 'size' bytes of body.
1723 */
1724 void addAtomToSequence (int64_t timestamp, const LV2_Atom* atom)
1725 {
1726 // This reinterpret_cast is not UB, casting to a char* is acceptable.
1727 // Doing arithmetic on this pointer is dubious, but I can't think of a better alternative
1728 // given that we don't have any way of knowing the concrete type of the atom.
1729 addEventToSequence (timestamp,
1730 atom->type,
1731 atom->size,
1732 reinterpret_cast<const char*> (atom) + sizeof (LV2_Atom));
1733 }
1734
1735 void addEventToSequence (int64_t timestamp, uint32_t type, uint32_t size, const void* content)
1736 {
1737 lv2_atom_forge_frame_time (forge.get(), timestamp);
1738 lv2_atom_forge_atom (forge.get(), size, type);
1739 lv2_atom_forge_write (forge.get(), content, size);
1740 }
1741
1742 void ensureSizeInBytes (size_t size)
1743 {
1744 contents = grow (std::move (contents), size);
1745 }
1746
1747 char* data() noexcept { return data (*this); }
1748 const char* data() const noexcept { return data (*this); }
1749
1750 size_t size() const noexcept { return contents.size(); }
1751
1752 lv2_shared::AtomForge& getForge() { return forge; }
1753 const lv2_shared::AtomForge& getForge() const { return forge; }
1754
1755 bool getSupportsTime() const { return time == SupportsTime::yes; }
1756
1757private:
1758 template <typename This>
1759 static auto data (This& t) -> decltype (t.data())
1760 {
1761 return unalignedPointerCast<decltype (t.data())> (t.contents.data());
1762 }
1763
1764 // Atoms are required to be 64-bit aligned
1765 SingleSizeAlignedStorage<8> contents;
1766 lv2_shared::AtomForge forge;
1767 LV2_Atom_Forge_Frame frame;
1768 SupportsTime time = SupportsTime::no;
1769};
1770
1771struct FreeString { void operator() (void* ptr) const noexcept { lilv_free (ptr); } };
1772
1773static File bundlePathFromUri (const char* uri)
1774{
1775 return File { std::unique_ptr<char, FreeString> { lilv_file_uri_parse (uri, nullptr) }.get() };
1776}
1777
1778class Plugins
1779{
1780public:
1781 explicit Plugins (const LilvPlugins* list) noexcept : plugins (list) {}
1782
1783 unsigned size() const noexcept { return lilv_plugins_size (plugins); }
1784
1785 PluginsIterator begin() const noexcept { return PluginsIterator { plugins }; }
1786 PluginsIterator end() const noexcept { return PluginsIterator{}; }
1787
1788 const LilvPlugin* getByUri (const NodeUri& uri) const
1789 {
1790 return lilv_plugins_get_by_uri (plugins, uri.get());
1791 }
1792
1793 const LilvPlugin* getByFile (const File& file) const
1794 {
1795 for (const auto* plugin : *this)
1796 {
1797 if (bundlePathFromUri (lilv_node_as_uri (lilv_plugin_get_bundle_uri (plugin))) == file)
1798 return plugin;
1799 }
1800
1801 return nullptr;
1802 }
1803
1804private:
1805 const LilvPlugins* plugins = nullptr;
1806};
1807
1808template <typename PtrTraits>
1810{
1811public:
1812 using type = typename PtrTraits::type;
1813
1814 explicit PluginClassesImpl (type ptr)
1815 : classes (std::move (ptr)) {}
1816
1817 unsigned size() const noexcept { return lilv_plugin_classes_size (PtrTraits::get (classes)); }
1818
1819 PluginClassesIterator begin() const noexcept { return PluginClassesIterator { PtrTraits::get (classes) }; }
1820 PluginClassesIterator end() const noexcept { return PluginClassesIterator{}; }
1821
1822 const LilvPluginClass* getByUri (const NodeUri& uri) const noexcept
1823 {
1824 return lilv_plugin_classes_get_by_uri (PtrTraits::get (classes), uri.get());
1825 }
1826
1827private:
1828 type classes{};
1829};
1830
1831struct PluginClassesFree
1832{
1833 void operator() (LilvPluginClasses* ptr) const noexcept { lilv_plugin_classes_free (ptr); }
1834};
1835
1838
1839class World
1840{
1841public:
1842 World() : world (lilv_world_new()) {}
1843
1844 void loadAllFromPaths (const NodeString& paths)
1845 {
1846 lilv_world_set_option (world.get(), LILV_OPTION_LV2_PATH, paths.get());
1847 lilv_world_load_all (world.get());
1848 }
1849
1850 void loadBundle (const NodeUri& uri) { lilv_world_load_bundle (world.get(), uri.get()); }
1851 void unloadBundle (const NodeUri& uri) { lilv_world_unload_bundle (world.get(), uri.get()); }
1852
1853 void loadResource (const NodeUri& uri) { lilv_world_load_resource (world.get(), uri.get()); }
1854 void unloadResource (const NodeUri& uri) { lilv_world_unload_resource (world.get(), uri.get()); }
1855
1856 void loadSpecifications() { lilv_world_load_specifications (world.get()); }
1857 void loadPluginClasses() { lilv_world_load_plugin_classes (world.get()); }
1858
1859 Plugins getAllPlugins() const { return Plugins { lilv_world_get_all_plugins (world.get()) }; }
1860 NonOwningPluginClasses getPluginClasses() const { return NonOwningPluginClasses { lilv_world_get_plugin_classes (world.get()) }; }
1861
1862 NodeUri newUri (const char* uri) { return NodeUri { world.get(), uri }; }
1863 NodeUri newFileUri (const char* host, const char* path) { return NodeUri { world.get(), host, path }; }
1864 NodeString newString (const char* str) { return NodeString { world.get(), str }; }
1865
1866 bool ask (const LilvNode* subject, const LilvNode* predicate, const LilvNode* object) const
1867 {
1868 return lilv_world_ask (world.get(), subject, predicate, object);
1869 }
1870
1871 OwningNode get (const LilvNode* subject, const LilvNode* predicate, const LilvNode* object) const
1872 {
1873 return OwningNode { lilv_world_get (world.get(), subject, predicate, object) };
1874 }
1875
1876 OwningNodes findNodes (const LilvNode* subject, const LilvNode* predicate, const LilvNode* object) const
1877 {
1878 return OwningNodes { lilv_world_find_nodes (world.get(), subject, predicate, object) };
1879 }
1880
1881 LilvWorld* get() const { return world.get(); }
1882
1883private:
1884 struct Free
1885 {
1886 void operator() (LilvWorld* ptr) const noexcept { lilv_world_free (ptr); }
1887 };
1888
1890};
1891
1892class Ports
1893{
1894public:
1895 static constexpr auto sequenceSize = 8192;
1896
1897 template <typename Callback>
1898 void forEachPort (Callback&& callback) const
1899 {
1900 for (const auto& port : controlPorts)
1901 callback (port.header);
1902
1903 for (const auto& port : cvPorts)
1904 callback (port.header);
1905
1906 for (const auto& port : audioPorts)
1907 callback (port.header);
1908
1909 for (const auto& port : atomPorts)
1910 callback (port.header);
1911 }
1912
1913 auto getControlPorts() { return makeSimpleSpan (controlPorts); }
1914 auto getControlPorts() const { return makeSimpleSpan (controlPorts); }
1915 auto getCvPorts() { return makeSimpleSpan (cvPorts); }
1916 auto getCvPorts() const { return makeSimpleSpan (cvPorts); }
1917 auto getAudioPorts() { return makeSimpleSpan (audioPorts); }
1918 auto getAudioPorts() const { return makeSimpleSpan (audioPorts); }
1919 auto getAtomPorts() { return makeSimpleSpan (atomPorts); }
1920 auto getAtomPorts() const { return makeSimpleSpan (atomPorts); }
1921
1922 static Optional<Ports> getPorts (World& world, const UsefulUris& uris, const Plugin& plugin, SymbolMap& symap)
1923 {
1924 Ports value;
1925 bool successful = true;
1926
1927 const auto numPorts = plugin.getNumPorts();
1928 const auto timeNode = world.newUri (LV2_TIME__Position);
1929
1930 for (uint32_t i = 0; i != numPorts; ++i)
1931 {
1932 const auto port = plugin.getPortByIndex (i);
1933
1934 const PortHeader header { String::fromUTF8 (port.getName().getTyped()),
1935 String::fromUTF8 (port.getSymbol().getTyped()),
1936 i,
1937 port.getDirection (uris) };
1938
1939 switch (port.getKind (uris))
1940 {
1941 case Port::Kind::control:
1942 {
1943 value.controlPorts.push_back ({ header, ParameterInfo::getInfoForPort (uris, port) });
1944 break;
1945 }
1946
1947 case Port::Kind::cv:
1948 value.cvPorts.push_back ({ header });
1949 break;
1950
1951 case Port::Kind::audio:
1952 {
1953 value.audioPorts.push_back ({ header });
1954 break;
1955 }
1956
1957 case Port::Kind::atom:
1958 {
1959 const auto supportsTime = port.supportsEvent (timeNode.get());
1960 value.atomPorts.push_back ({ header,
1961 (size_t) Ports::sequenceSize,
1962 symap,
1963 supportsTime ? SupportsTime::yes : SupportsTime::no });
1964 break;
1965 }
1966
1967 case Port::Kind::unknown:
1968 successful = false;
1969 break;
1970 }
1971 }
1972
1973 for (auto& atomPort : value.atomPorts)
1974 {
1975 const auto port = plugin.getPortByIndex (atomPort.header.index);
1976 const auto minSize = port.get (uris.mLV2_RESIZE_PORT__minimumSize.get());
1977
1978 if (minSize != nullptr)
1979 atomPort.ensureSizeInBytes ((size_t) lilv_node_as_int (minSize.get()));
1980 }
1981
1982 return successful ? makeOptional (std::move (value)) : nullopt;
1983 }
1984
1985private:
1986 std::vector<ControlPort> controlPorts;
1987 std::vector<CVPort> cvPorts;
1988 std::vector<AudioPort> audioPorts;
1989 std::vector<AtomPort> atomPorts;
1990};
1991
1992class InstanceWithSupports final : private FeaturesDataListener,
1993 private HandleHolder
1994{
1995public:
1996 InstanceWithSupports (World& world,
1998 const Plugin& plugin,
1999 Ports portsIn,
2000 int32_t initialBufferSize,
2001 double sampleRate)
2002 : symap (std::move (symapIn)),
2003 ports (std::move (portsIn)),
2004 features (*this, *this, initialBufferSize, lv2_host::Ports::sequenceSize, &urids),
2005 instance (plugin, sampleRate, features.getFeatureArray()),
2006 workerInterface (instance.getExtensionData<LV2_Worker_Interface> (world.newUri (LV2_WORKER__interface)))
2007 {
2008 if (instance == nullptr)
2009 return;
2010
2011 for (auto& port : ports.getControlPorts())
2012 instance.connectPort (port.header.index, &port.currentValue);
2013
2014 for (auto& port : ports.getAtomPorts())
2015 instance.connectPort (port.header.index, port.data());
2016
2017 for (auto& port : ports.getCvPorts())
2018 instance.connectPort (port.header.index, nullptr);
2019
2020 for (auto& port : ports.getAudioPorts())
2021 instance.connectPort (port.header.index, nullptr);
2022
2023 features.registerHandle (instance.getHandle());
2024 }
2025
2026 ~InstanceWithSupports() override
2027 {
2028 if (instance != nullptr)
2029 features.deregisterHandle (instance.getHandle());
2030 }
2031
2033 const UsefulUrids urids { *symap };
2034 Ports ports;
2035 FeaturesData features;
2036 Instance instance;
2037 Messages<MessageHeader, RealtimeReadTrait> uiToProcessor;
2038 SharedResourcePointer<ProcessorToUi> processorToUi;
2039
2040private:
2041 LV2_Handle handle = instance == nullptr ? nullptr : instance.getHandle();
2042 OptionalExtension<LV2_Worker_Interface> workerInterface;
2043
2044 LV2_Handle getHandle() const override { return handle; }
2045 const LV2_Worker_Interface* getWorkerInterface() const override { return workerInterface.valid ? &workerInterface.extension : nullptr; }
2046
2047 LV2_Resize_Port_Status resizeCallback (uint32_t index, size_t size) override
2048 {
2049 if (ports.getAtomPorts().size() <= index)
2050 return LV2_RESIZE_PORT_ERR_UNKNOWN;
2051
2052 auto& port = ports.getAtomPorts()[index];
2053
2054 if (port.header.direction != Port::Direction::output)
2055 return LV2_RESIZE_PORT_ERR_UNKNOWN;
2056
2057 port.ensureSizeInBytes (size);
2058 instance.connectPort (port.header.index, port.data());
2059
2060 return LV2_RESIZE_PORT_SUCCESS;
2061 }
2062
2063 JUCE_DECLARE_NON_COPYABLE (InstanceWithSupports)
2064 JUCE_DECLARE_NON_MOVEABLE (InstanceWithSupports)
2065 JUCE_LEAK_DETECTOR (InstanceWithSupports)
2066};
2067
2068struct PortState
2069{
2070 const void* data;
2071 uint32_t size;
2072 uint32_t kind;
2073};
2074
2075class PortMap
2076{
2077public:
2078 explicit PortMap (Ports& ports)
2079 {
2080 for (auto& port : ports.getControlPorts())
2081 symbolToControlPortMap.emplace (port.header.symbol, &port);
2082 }
2083
2084 PortState getState (const String& symbol, const StatefulPortUrids& urids)
2085 {
2086 if (auto* port = getControlPortForSymbol (symbol))
2087 return { &port->currentValue, sizeof (float), urids.Float };
2088
2089 // At time of writing, lilv_state_new_from_instance did not attempt to store
2090 // the state of non-control ports. Perhaps that has changed?
2092 return { nullptr, 0, 0 };
2093 }
2094
2095 void restoreState (const String& symbol, const StatefulPortUrids& urids, PortState ps)
2096 {
2097 if (auto* port = getControlPortForSymbol (symbol))
2098 {
2099 port->currentValue = [&]() -> float
2100 {
2101 if (ps.kind == urids.Float)
2102 return getValueFrom<float> (ps.data, ps.size);
2103
2104 if (ps.kind == urids.Double)
2105 return getValueFrom<double> (ps.data, ps.size);
2106
2107 if (ps.kind == urids.Int)
2108 return getValueFrom<int32_t> (ps.data, ps.size);
2109
2110 if (ps.kind == urids.Long)
2111 return getValueFrom<int64_t> (ps.data, ps.size);
2112
2114 return {};
2115 }();
2116 }
2117 else
2118 jassertfalse; // Restoring state for non-control ports is not currently supported.
2119 }
2120
2121private:
2122 template <typename Value>
2123 static float getValueFrom (const void* data, [[maybe_unused]] uint32_t size)
2124 {
2125 jassert (size == sizeof (Value));
2126 return (float) readUnaligned<Value> (data);
2127 }
2128
2129 ControlPort* getControlPortForSymbol (const String& symbol) const
2130 {
2131 const auto iter = symbolToControlPortMap.find (symbol);
2132 return iter != symbolToControlPortMap.cend() ? iter->second : nullptr;
2133 }
2134
2135 std::map<String, ControlPort*> symbolToControlPortMap;
2136 JUCE_LEAK_DETECTOR (PortMap)
2137};
2138
2139class PluginState
2140{
2141public:
2142 PluginState() = default;
2143
2144 explicit PluginState (LilvState* ptr)
2145 : state (ptr) {}
2146
2147 const LilvState* get() const noexcept { return state.get(); }
2148
2149 void restore (InstanceWithSupports& instance, PortMap& portMap) const
2150 {
2151 if (state != nullptr)
2152 SaveRestoreHandle { instance, portMap }.restore (state.get());
2153 }
2154
2155 std::string toString (LilvWorld* world, LV2_URID_Map* map, LV2_URID_Unmap* unmap, const char* uri) const
2156 {
2157 std::unique_ptr<char, FreeString> result { lilv_state_to_string (world,
2158 map,
2159 unmap,
2160 state.get(),
2161 uri,
2162 nullptr) };
2163 return std::string { result.get() };
2164 }
2165
2166 String getLabel() const
2167 {
2168 return String::fromUTF8 (lilv_state_get_label (state.get()));
2169 }
2170
2171 void setLabel (const String& label)
2172 {
2173 lilv_state_set_label (state.get(), label.toRawUTF8());
2174 }
2175
2176 class SaveRestoreHandle
2177 {
2178 public:
2179 explicit SaveRestoreHandle (InstanceWithSupports& instanceIn, PortMap& portMap)
2180 : instance (instanceIn.instance.get()),
2181 features (instanceIn.features.getFeatureArray()),
2182 urids (*instanceIn.symap),
2183 map (portMap)
2184 {}
2185
2186 PluginState save (const LilvPlugin* plugin, LV2_URID_Map* mapFeature)
2187 {
2188 return PluginState { lilv_state_new_from_instance (plugin,
2189 instance,
2190 mapFeature,
2191 nullptr,
2192 nullptr,
2193 nullptr,
2194 nullptr,
2195 getPortValue,
2196 this,
2197 LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE,
2198 features ) };
2199 }
2200
2201 void restore (const LilvState* stateIn)
2202 {
2203 lilv_state_restore (stateIn,
2204 instance,
2205 setPortValue,
2206 this,
2207 0,
2208 features);
2209 }
2210
2211 private:
2212 static const void* getPortValue (const char* portSymbol,
2213 void* userData,
2214 uint32_t* size,
2215 uint32_t* type)
2216 {
2217 auto& handle = *static_cast<SaveRestoreHandle*> (userData);
2218
2219 const auto state = handle.map.getState (portSymbol, handle.urids);
2220 *size = state.size;
2221 *type = state.kind;
2222 return state.data;
2223 }
2224
2225 static void setPortValue (const char* portSymbol,
2226 void* userData,
2227 const void* value,
2228 uint32_t size,
2229 uint32_t type)
2230 {
2231 const auto& handle = *static_cast<const SaveRestoreHandle*> (userData);
2232 handle.map.restoreState (portSymbol, handle.urids, { static_cast<const char*> (value), size, type });
2233 }
2234
2235 LilvInstance* instance = nullptr;
2236 const LV2_Feature* const* features = nullptr;
2237 const StatefulPortUrids urids;
2238 PortMap& map;
2239 };
2240
2241private:
2242 struct Free
2243 {
2244 void operator() (LilvState* ptr) const noexcept { lilv_state_free (ptr); }
2245 };
2246
2248
2249 JUCE_LEAK_DETECTOR (PluginState)
2250};
2251
2252/*
2253 Wraps an LV2 UI bundle, providing access to the descriptor (if available).
2254*/
2256{
2257 using GetDescriptor = LV2UI_Descriptor* (*) (uint32_t);
2258
2259 UiDescriptorLibrary() = default;
2260
2261 explicit UiDescriptorLibrary (const String& libraryPath)
2262 : library (std::make_unique<DynamicLibrary> (libraryPath)),
2263 getDescriptor (lv2_shared::wordCast<GetDescriptor> (library->getFunction ("lv2ui_descriptor"))) {}
2264
2266 GetDescriptor getDescriptor = nullptr;
2267};
2268
2269class UiDescriptorArgs
2270{
2271public:
2272 String libraryPath;
2273 String uiUri;
2274
2275 auto withLibraryPath (String v) const noexcept { return with (&UiDescriptorArgs::libraryPath, v); }
2276 auto withUiUri (String v) const noexcept { return with (&UiDescriptorArgs::uiUri, v); }
2277
2278private:
2279 UiDescriptorArgs with (String UiDescriptorArgs::* member, String value) const noexcept
2280 {
2281 return juce::lv2_host::with (*this, member, std::move (value));
2282 }
2283};
2284
2285/*
2286 Stores a pointer to the descriptor for a specific UI bundle and UI URI.
2287*/
2288class UiDescriptor
2289{
2290public:
2291 UiDescriptor() = default;
2292
2293 explicit UiDescriptor (const UiDescriptorArgs& args)
2294 : library (args.libraryPath),
2295 descriptor (extractUiDescriptor (library, args.uiUri.toRawUTF8()))
2296 {}
2297
2298 void portEvent (LV2UI_Handle ui,
2299 uint32_t portIndex,
2300 uint32_t bufferSize,
2301 uint32_t format,
2302 const void* buffer) const
2303 {
2305
2306 if (auto* lv2Descriptor = get())
2307 if (auto* callback = lv2Descriptor->port_event)
2308 callback (ui, portIndex, bufferSize, format, buffer);
2309 }
2310
2311 bool hasExtensionData (World& world, const char* uid) const
2312 {
2313 return world.ask (world.newUri (descriptor->URI).get(),
2314 world.newUri (LV2_CORE__extensionData).get(),
2315 world.newUri (uid).get());
2316 }
2317
2318 template <typename Extension>
2319 OptionalExtension<Extension> getExtensionData (World& world, const char* uid) const
2320 {
2321 if (! hasExtensionData (world, uid))
2322 return {};
2323
2324 if (auto* lv2Descriptor = get())
2325 if (auto* extension = lv2Descriptor->extension_data)
2326 return OptionalExtension<Extension> (readUnaligned<Extension> (extension (uid)));
2327
2328 return {};
2329 }
2330
2331 const LV2UI_Descriptor* get() const noexcept { return descriptor; }
2332
2333private:
2334 static const LV2UI_Descriptor* extractUiDescriptor (const UiDescriptorLibrary& lib, const char* uiUri)
2335 {
2336 if (lib.getDescriptor == nullptr)
2337 return nullptr;
2338
2339 for (uint32_t i = 0;; ++i)
2340 {
2341 const auto* descriptor = lib.getDescriptor (i);
2342
2343 if (descriptor == nullptr)
2344 return nullptr;
2345
2346 if (strcmp (uiUri, descriptor->URI) == 0)
2347 return descriptor;
2348 }
2349 }
2350
2351 UiDescriptorLibrary library;
2352 const LV2UI_Descriptor* descriptor = nullptr;
2353
2354 JUCE_LEAK_DETECTOR (UiDescriptor)
2355};
2356
2357enum class Update { no, yes };
2358
2359/* A bit like the FlaggedFloatCache used by the VST3 host/client.
2360
2361 While the FlaggedFloatCache always clears all set flags during the ifSet() call,
2362 this class stores the "value changed" flags for the processor and UI separately,
2363 so that they can be read at different rates.
2364*/
2366{
2367public:
2368 ParameterValuesAndFlags() = default;
2369
2370 explicit ParameterValuesAndFlags (size_t sizeIn)
2371 : values (sizeIn),
2372 needsUiUpdate (sizeIn),
2373 needsProcessorUpdate (sizeIn)
2374 {
2375 std::fill (values.begin(), values.end(), 0.0f);
2376 }
2377
2378 size_t size() const noexcept { return values.size(); }
2379
2380 void set (size_t index, float value, Update update)
2381 {
2382 jassert (index < size());
2383 values[index].store (value, std::memory_order_relaxed);
2384 needsUiUpdate .set (index, update == Update::yes ? 1 : 0);
2385 needsProcessorUpdate.set (index, update == Update::yes ? 1 : 0);
2386 }
2387
2388 float get (size_t index) const noexcept
2389 {
2390 jassert (index < size());
2391 return values[index].load (std::memory_order_relaxed);
2392 }
2393
2394 template <typename Callback>
2395 void ifProcessorValuesChanged (Callback&& callback)
2396 {
2397 ifChanged (needsProcessorUpdate, std::forward<Callback> (callback));
2398 }
2399
2400 template <typename Callback>
2401 void ifUiValuesChanged (Callback&& callback)
2402 {
2403 ifChanged (needsUiUpdate, std::forward<Callback> (callback));
2404 }
2405
2406 void clearUiFlags() { needsUiUpdate.clear(); }
2407
2408private:
2409 template <typename Callback>
2410 void ifChanged (FlagCache<1>& flags, Callback&& callback)
2411 {
2412 flags.ifSet ([this, &callback] (size_t groupIndex, uint32_t)
2413 {
2414 callback (groupIndex, values[groupIndex].load (std::memory_order_relaxed));
2415 });
2416 }
2417
2419 FlagCache<1> needsUiUpdate;
2420 FlagCache<1> needsProcessorUpdate;
2421
2422 JUCE_LEAK_DETECTOR (ParameterValuesAndFlags)
2423};
2424
2425class LV2Parameter : public AudioPluginInstance::HostedParameter
2426{
2427public:
2428 LV2Parameter (const String& nameIn,
2429 const ParameterInfo& infoIn,
2430 ParameterValuesAndFlags& floatCache)
2431 : cache (floatCache),
2432 info (infoIn),
2433 range (info.min, info.max),
2434 name (nameIn),
2435 normalisedDefault (range.convertTo0to1 (infoIn.defaultValue))
2436 {}
2437
2438 float getValue() const noexcept override
2439 {
2440 return range.convertTo0to1 (getDenormalisedValue());
2441 }
2442
2443 void setValue (float f) override
2444 {
2445 cache.set ((size_t) getParameterIndex(), range.convertFrom0to1 (f), Update::yes);
2446 }
2447
2448 void setDenormalisedValue (float denormalised)
2449 {
2450 cache.set ((size_t) getParameterIndex(), denormalised, Update::yes);
2451 sendValueChangedMessageToListeners (range.convertTo0to1 (denormalised));
2452 }
2453
2454 void setDenormalisedValueWithoutTriggeringUpdate (float denormalised)
2455 {
2456 cache.set ((size_t) getParameterIndex(), denormalised, Update::no);
2457 sendValueChangedMessageToListeners (range.convertTo0to1 (denormalised));
2458 }
2459
2460 float getDenormalisedValue() const noexcept
2461 {
2462 return cache.get ((size_t) getParameterIndex());
2463 }
2464
2465 float getDefaultValue() const override { return normalisedDefault; }
2466 float getDenormalisedDefaultValue() const { return info.defaultValue; }
2467
2468 float getValueForText (const String& text) const override
2469 {
2470 if (! info.isEnum)
2471 return range.convertTo0to1 (text.getFloatValue());
2472
2473 const auto it = std::find_if (info.scalePoints.begin(),
2474 info.scalePoints.end(),
2475 [&] (const StoredScalePoint& stored) { return stored.label == text; });
2476 return it != info.scalePoints.end() ? range.convertTo0to1 (it->value) : normalisedDefault;
2477 }
2478
2479 int getNumSteps() const override
2480 {
2481 if (info.isToggle)
2482 return 2;
2483
2484 if (info.isEnum)
2485 return static_cast<int> (info.scalePoints.size());
2486
2487 if (info.isInteger)
2488 return static_cast<int> (range.getRange().getLength()) + 1;
2489
2490 return AudioProcessorParameter::getNumSteps();
2491 }
2492
2493 bool isDiscrete() const override { return info.isEnum || info.isInteger || info.isToggle; }
2494 bool isBoolean() const override { return info.isToggle; }
2495
2496 StringArray getAllValueStrings() const override
2497 {
2498 if (! info.isEnum)
2499 return {};
2500
2501 return AudioProcessorParameter::getAllValueStrings();
2502 }
2503
2504 String getText (float normalisedValue, int) const override
2505 {
2506 const auto denormalised = range.convertFrom0to1 (normalisedValue);
2507
2508 if (info.isEnum && ! info.scalePoints.empty())
2509 {
2510 // The normalised value might not correspond to the exact value of a scale point.
2511 // In this case, we find the closest label by searching the midpoints of the scale
2512 // point values.
2513 const auto index = std::distance (midPoints.begin(),
2514 std::lower_bound (midPoints.begin(), midPoints.end(), denormalised));
2515 jassert (isPositiveAndBelow (index, info.scalePoints.size()));
2516 return info.scalePoints[(size_t) index].label;
2517 }
2518
2519 return getFallbackParameterString (denormalised);
2520 }
2521
2522 String getParameterID() const override
2523 {
2524 return info.identifier;
2525 }
2526
2527 String getName (int maxLength) const override
2528 {
2529 return name.substring (0, maxLength);
2530 }
2531
2532 String getLabel() const override
2533 {
2534 // TODO
2535 return {};
2536 }
2537
2538private:
2539 String getFallbackParameterString (float denormalised) const
2540 {
2541 if (info.isToggle)
2542 return denormalised > 0.0f ? "On" : "Off";
2543
2544 if (info.isInteger)
2545 return String { static_cast<int> (denormalised) };
2546
2547 return String { denormalised };
2548 }
2549
2550 static std::vector<float> findScalePointMidPoints (const SafeSortedSet<StoredScalePoint>& set)
2551 {
2552 if (set.size() < 2)
2553 return {};
2554
2555 std::vector<float> result;
2556 result.reserve (set.size() - 1);
2557
2558 for (auto it = std::next (set.begin()); it != set.end(); ++it)
2559 result.push_back ((std::prev (it)->value + it->value) * 0.5f);
2560
2561 jassert (std::is_sorted (result.begin(), result.end()));
2562 jassert (result.size() + 1 == set.size());
2563 return result;
2564 }
2565
2566 ParameterValuesAndFlags& cache;
2567 const ParameterInfo info;
2568 const std::vector<float> midPoints = findScalePointMidPoints (info.scalePoints);
2569 const NormalisableRange<float> range;
2570 const String name;
2571 const float normalisedDefault;
2572
2573 JUCE_LEAK_DETECTOR (LV2Parameter)
2574};
2575
2576class UiInstanceArgs
2577{
2578public:
2579 File bundlePath;
2580 URL pluginUri;
2581
2582 auto withBundlePath (File v) const noexcept { return withMember (*this, &UiInstanceArgs::bundlePath, std::move (v)); }
2583 auto withPluginUri (URL v) const noexcept { return withMember (*this, &UiInstanceArgs::pluginUri, std::move (v)); }
2584};
2585
2586/*
2587 Creates and holds a UI instance for a plugin with a specific URI, using the provided descriptor.
2588*/
2589class UiInstance
2590{
2591public:
2592 UiInstance (World& world,
2593 const UiDescriptor* descriptorIn,
2594 const UiInstanceArgs& args,
2595 const LV2_Feature* const* features,
2596 MessageBufferInterface<MessageHeader>& messagesIn,
2597 SymbolMap& map,
2598 PhysicalResizeListener& rl)
2599 : descriptor (descriptorIn),
2600 resizeListener (rl),
2601 uiToProcessor (messagesIn),
2602 mLV2_UI__floatProtocol (map.map (LV2_UI__floatProtocol)),
2603 mLV2_ATOM__atomTransfer (map.map (LV2_ATOM__atomTransfer)),
2604 mLV2_ATOM__eventTransfer (map.map (LV2_ATOM__eventTransfer)),
2605 instance (makeInstance (args, features)),
2606 idleCallback (getExtensionData<LV2UI_Idle_Interface> (world, LV2_UI__idleInterface))
2607 {
2608 jassert (descriptor != nullptr);
2609 jassert (widget != nullptr);
2610
2611 ignoreUnused (resizeListener);
2612 }
2613
2614 LV2UI_Handle getHandle() const noexcept { return instance.get(); }
2615
2616 void pushMessage (MessageHeader header, uint32_t size, const void* buffer)
2617 {
2618 descriptor->portEvent (getHandle(), header.portIndex, size, header.protocol, buffer);
2619 }
2620
2621 int idle()
2622 {
2623 if (idleCallback.valid && idleCallback.extension.idle != nullptr)
2624 return idleCallback.extension.idle (getHandle());
2625
2626 return 0;
2627 }
2628
2629 template <typename Extension>
2630 OptionalExtension<Extension> getExtensionData (World& world, const char* uid) const
2631 {
2632 return descriptor->getExtensionData<Extension> (world, uid);
2633 }
2634
2635 Rectangle<int> getDetectedViewBounds() const
2636 {
2637 #if JUCE_MAC
2638 const auto frame = [(NSView*) widget frame];
2639 return { (int) frame.size.width, (int) frame.size.height };
2640 #elif JUCE_LINUX || JUCE_BSD
2641 Window root = 0;
2642 int wx = 0, wy = 0;
2643 unsigned int ww = 0, wh = 0, bw = 0, bitDepth = 0;
2644
2645 XWindowSystemUtilities::ScopedXLock xLock;
2646 auto* display = XWindowSystem::getInstance()->getDisplay();
2647 X11Symbols::getInstance()->xGetGeometry (display,
2648 (::Drawable) widget,
2649 &root,
2650 &wx,
2651 &wy,
2652 &ww,
2653 &wh,
2654 &bw,
2655 &bitDepth);
2656
2657 return { (int) ww, (int) wh };
2658 #elif JUCE_WINDOWS
2659 RECT rect;
2660 GetWindowRect ((HWND) widget, &rect);
2661 return { rect.right - rect.left, rect.bottom - rect.top };
2662 #else
2663 return {};
2664 #endif
2665 }
2666
2667 const UiDescriptor* descriptor = nullptr;
2668
2669private:
2670 using Instance = std::unique_ptr<void, void (*) (LV2UI_Handle)>;
2671 using Idle = int (*) (LV2UI_Handle);
2672
2673 Instance makeInstance (const UiInstanceArgs& args, const LV2_Feature* const* features)
2674 {
2675 if (descriptor->get() == nullptr)
2676 return { nullptr, [] (LV2UI_Handle) {} };
2677
2678 return Instance { descriptor->get()->instantiate (descriptor->get(),
2679 args.pluginUri.toString (true).toRawUTF8(),
2680 File::addTrailingSeparator (args.bundlePath.getFullPathName()).toRawUTF8(),
2681 writeFunction,
2682 this,
2683 &widget,
2684 features),
2685 descriptor->get()->cleanup };
2686 }
2687
2688 void write (uint32_t portIndex, uint32_t bufferSize, uint32_t protocol, const void* buffer)
2689 {
2690 const LV2_URID protocols[] { 0, mLV2_UI__floatProtocol, mLV2_ATOM__atomTransfer, mLV2_ATOM__eventTransfer };
2691 const auto it = std::find (std::begin (protocols), std::end (protocols), protocol);
2692
2693 if (it != std::end (protocols))
2694 {
2695 uiToProcessor.pushMessage ({ portIndex, protocol }, bufferSize, buffer);
2696 }
2697 }
2698
2699 static void writeFunction (LV2UI_Controller controller,
2700 uint32_t portIndex,
2701 uint32_t bufferSize,
2702 uint32_t portProtocol,
2703 const void* buffer)
2704 {
2705 jassert (controller != nullptr);
2706 static_cast<UiInstance*> (controller)->write (portIndex, bufferSize, portProtocol, buffer);
2707 }
2708
2709 PhysicalResizeListener& resizeListener;
2710 MessageBufferInterface<MessageHeader>& uiToProcessor;
2711 LV2UI_Widget widget = nullptr;
2712 const LV2_URID mLV2_UI__floatProtocol;
2713 const LV2_URID mLV2_ATOM__atomTransfer;
2714 const LV2_URID mLV2_ATOM__eventTransfer;
2715 Instance instance;
2716 OptionalExtension<LV2UI_Idle_Interface> idleCallback;
2717
2718 #if JUCE_MAC
2719 NSViewFrameWatcher frameWatcher { (NSView*) widget, [this]
2720 {
2721 const auto bounds = getDetectedViewBounds();
2722 resizeListener.viewRequestedResizeInPhysicalPixels (bounds.getWidth(), bounds.getHeight());
2723 } };
2724 #elif JUCE_WINDOWS
2725 WindowSizeChangeListener frameWatcher { (HWND) widget, resizeListener };
2726 #endif
2727
2728 JUCE_LEAK_DETECTOR (UiInstance)
2729};
2730
2731struct TouchListener
2732{
2733 virtual ~TouchListener() = default;
2734 virtual void controlGrabbed (uint32_t port, bool grabbed) = 0;
2735};
2736
2737class AsyncFn final : public AsyncUpdater
2738{
2739public:
2740 explicit AsyncFn (std::function<void()> callbackIn)
2741 : callback (std::move (callbackIn)) {}
2742
2743 ~AsyncFn() override { cancelPendingUpdate(); }
2744
2745 void handleAsyncUpdate() override { callback(); }
2746
2747private:
2748 std::function<void()> callback;
2749};
2750
2752{
2753public:
2754 float initialScaleFactor = 0.0f, sampleRate = 0.0f;
2755
2756 auto withInitialScaleFactor (float v) const { return with (&UiFeaturesDataOptions::initialScaleFactor, v); }
2757 auto withSampleRate (float v) const { return with (&UiFeaturesDataOptions::sampleRate, v); }
2758
2759private:
2760 UiFeaturesDataOptions with (float UiFeaturesDataOptions::* member, float value) const
2761 {
2762 return juce::lv2_host::with (*this, member, value);
2763 }
2764};
2765
2766class UiFeaturesData
2767{
2768public:
2769 UiFeaturesData (PhysicalResizeListener& rl,
2770 TouchListener& tl,
2771 LV2_Handle instanceIn,
2772 LV2UI_Widget parentIn,
2773 Instance::GetExtensionData getExtensionData,
2774 const Ports& ports,
2775 SymbolMap& symapIn,
2776 const UiFeaturesDataOptions& optIn)
2777 : opts (optIn),
2778 resizeListener (rl),
2779 touchListener (tl),
2780 instance (instanceIn),
2781 parent (parentIn),
2782 symap (symapIn),
2783 dataAccess { getExtensionData },
2784 portIndices (makePortIndices (ports))
2785 {
2786 }
2787
2788 const LV2_Feature* const* getFeatureArray() const noexcept { return features.pointers.data(); }
2789
2790 static std::vector<String> getFeatureUris()
2791 {
2792 return Features::getUris (makeFeatures ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}));
2793 }
2794
2795 Rectangle<int> getLastRequestedBounds() const { return { lastRequestedWidth, lastRequestedHeight }; }
2796
2797private:
2798 static std::vector<LV2_Feature> makeFeatures (LV2UI_Resize* resize,
2799 LV2UI_Widget parent,
2800 LV2_Handle handle,
2801 LV2_Extension_Data_Feature* data,
2802 LV2_URID_Map* map,
2803 LV2_URID_Unmap* unmap,
2804 LV2UI_Port_Map* portMap,
2805 LV2UI_Touch* touch,
2806 LV2_Options_Option* options,
2807 LV2_Log_Log* log)
2808 {
2809 return { LV2_Feature { LV2_UI__resize, resize },
2810 LV2_Feature { LV2_UI__parent, parent },
2811 LV2_Feature { LV2_UI__idleInterface, nullptr },
2812 LV2_Feature { LV2_INSTANCE_ACCESS_URI, handle },
2813 LV2_Feature { LV2_DATA_ACCESS_URI, data },
2814 LV2_Feature { LV2_URID__map, map },
2815 LV2_Feature { LV2_URID__unmap, unmap},
2816 LV2_Feature { LV2_UI__portMap, portMap },
2817 LV2_Feature { LV2_UI__touch, touch },
2818 LV2_Feature { LV2_OPTIONS__options, options },
2819 LV2_Feature { LV2_LOG__log, log } };
2820 }
2821
2822 int resizeCallback (int width, int height)
2823 {
2824 lastRequestedWidth = width;
2825 lastRequestedHeight = height;
2826 resizeListener.viewRequestedResizeInPhysicalPixels (width, height);
2827 return 0;
2828 }
2829
2830 static int resizeCallback (LV2UI_Feature_Handle handle, int width, int height)
2831 {
2832 return static_cast<UiFeaturesData*> (handle)->resizeCallback (width, height);
2833 }
2834
2835 uint32_t portIndexCallback (const char* symbol) const
2836 {
2837 const auto it = portIndices.find (symbol);
2838 return it != portIndices.cend() ? it->second : LV2UI_INVALID_PORT_INDEX;
2839 }
2840
2841 static uint32_t portIndexCallback (LV2UI_Feature_Handle handle, const char* symbol)
2842 {
2843 return static_cast<const UiFeaturesData*> (handle)->portIndexCallback (symbol);
2844 }
2845
2846 void touchCallback (uint32_t portIndex, bool grabbed) const
2847 {
2848 touchListener.controlGrabbed (portIndex, grabbed);
2849 }
2850
2851 static void touchCallback (LV2UI_Feature_Handle handle, uint32_t index, bool b)
2852 {
2853 return static_cast<const UiFeaturesData*> (handle)->touchCallback (index, b);
2854 }
2855
2856 static std::map<String, uint32_t> makePortIndices (const Ports& ports)
2857 {
2859
2860 ports.forEachPort ([&] (const PortHeader& header)
2861 {
2862 [[maybe_unused]] const auto emplaced = result.emplace (header.symbol, header.index);
2863
2864 // This will complain if there are duplicate port symbols.
2865 jassert (emplaced.second);
2866 });
2867
2868 return result;
2869 }
2870
2871 const UiFeaturesDataOptions opts;
2872 PhysicalResizeListener& resizeListener;
2873 TouchListener& touchListener;
2874 LV2_Handle instance{};
2875 LV2UI_Widget parent{};
2876 SymbolMap& symap;
2877 const UsefulUrids urids { symap };
2878 Log log { &urids };
2879 int lastRequestedWidth = 0, lastRequestedHeight = 0;
2880 std::vector<LV2_Options_Option> options { { LV2_OPTIONS_INSTANCE,
2881 0,
2882 symap.map (LV2_UI__scaleFactor),
2883 sizeof (float),
2884 symap.map (LV2_ATOM__Float),
2885 &opts.initialScaleFactor },
2886 { LV2_OPTIONS_INSTANCE,
2887 0,
2888 symap.map (LV2_PARAMETERS__sampleRate),
2889 sizeof (float),
2890 symap.map (LV2_ATOM__Float),
2891 &opts.sampleRate },
2892 { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, nullptr } }; // The final entry must be nulled out
2893 LV2UI_Resize resize { this, resizeCallback };
2894 LV2_URID_Map map = symap.getMapFeature();
2895 LV2_URID_Unmap unmap = symap.getUnmapFeature();
2896 LV2UI_Port_Map portMap { this, portIndexCallback };
2897 LV2UI_Touch touch { this, touchCallback };
2898 LV2_Extension_Data_Feature dataAccess;
2899 std::map<String, uint32_t> portIndices;
2900 Features features { makeFeatures (&resize,
2901 parent,
2902 instance,
2903 &dataAccess,
2904 &map,
2905 &unmap,
2906 &portMap,
2907 &touch,
2908 options.data(),
2909 log.getLogFeature()) };
2910
2911 JUCE_LEAK_DETECTOR (UiFeaturesData)
2912};
2913
2915{
2916public:
2917 UiInstanceWithSupports (World& world,
2918 PhysicalResizeListener& resizeListener,
2919 TouchListener& touchListener,
2920 const UiDescriptor* descriptor,
2921 const UiInstanceArgs& args,
2922 LV2UI_Widget parent,
2923 InstanceWithSupports& engineInstance,
2924 const UiFeaturesDataOptions& opts)
2925 : features (resizeListener,
2926 touchListener,
2927 engineInstance.instance.getHandle(),
2928 parent,
2929 engineInstance.instance.getExtensionDataCallback(),
2930 engineInstance.ports,
2931 *engineInstance.symap,
2932 opts),
2933 instance (world,
2934 descriptor,
2935 args,
2936 features.getFeatureArray(),
2937 engineInstance.uiToProcessor,
2938 *engineInstance.symap,
2939 resizeListener)
2940 {}
2941
2942 UiFeaturesData features;
2943 UiInstance instance;
2944
2945 JUCE_LEAK_DETECTOR (UiInstanceWithSupports)
2946};
2947
2948struct RequiredFeatures
2949{
2950 explicit RequiredFeatures (OwningNodes nodes)
2951 : values (std::move (nodes)) {}
2952
2953 OwningNodes values;
2954};
2955
2956struct OptionalFeatures
2957{
2958 explicit OptionalFeatures (OwningNodes nodes)
2959 : values (std::move (nodes)) {}
2960
2961 OwningNodes values;
2962};
2963
2964template <typename Range, typename Predicate>
2965static bool noneOf (Range&& range, Predicate&& pred)
2966{
2967 // Not a mistake, this is for ADL
2968 using std::begin;
2969 using std::end;
2970 return std::none_of (begin (range), end (range), std::forward<Predicate> (pred));
2971}
2972
2973class PeerChangedListener final : private ComponentMovementWatcher
2974{
2975public:
2976 PeerChangedListener (Component& c, std::function<void()> peerChangedIn)
2977 : ComponentMovementWatcher (&c), peerChanged (std::move (peerChangedIn))
2978 {
2979 }
2980
2981 void componentMovedOrResized (bool, bool) override {}
2982 void componentPeerChanged() override { NullCheckedInvocation::invoke (peerChanged); }
2983 void componentVisibilityChanged() override {}
2984
2985 using ComponentMovementWatcher::componentVisibilityChanged;
2986 using ComponentMovementWatcher::componentMovedOrResized;
2987
2988private:
2989 std::function<void()> peerChanged;
2990};
2991
2992struct ViewSizeListener final : private ComponentMovementWatcher
2993{
2994 ViewSizeListener (Component& c, PhysicalResizeListener& l)
2995 : ComponentMovementWatcher (&c), listener (l)
2996 {
2997 }
2998
2999 void componentMovedOrResized (bool, bool wasResized) override
3000 {
3001 if (wasResized)
3002 {
3003 const auto physicalSize = Desktop::getInstance().getDisplays()
3004 .logicalToPhysical (getComponent()->localAreaToGlobal (getComponent()->getLocalBounds()));
3005 const auto width = physicalSize.getWidth();
3006 const auto height = physicalSize.getHeight();
3007
3008 if (width > 10 && height > 10)
3009 listener.viewRequestedResizeInPhysicalPixels (width, height);
3010 }
3011 }
3012
3013 void componentPeerChanged() override {}
3014 void componentVisibilityChanged() override {}
3015
3016 using ComponentMovementWatcher::componentVisibilityChanged;
3017 using ComponentMovementWatcher::componentMovedOrResized;
3018
3019 PhysicalResizeListener& listener;
3020};
3021
3022class ConfiguredEditorComponent final : public Component,
3024{
3025public:
3026 ConfiguredEditorComponent (World& world,
3027 InstanceWithSupports& instance,
3028 UiDescriptor& uiDescriptor,
3029 LogicalResizeListener& resizeListenerIn,
3030 TouchListener& touchListener,
3031 const String& uiBundleUri,
3032 const UiFeaturesDataOptions& opts)
3033 : resizeListener (resizeListenerIn),
3034 floatUrid (instance.symap->map (LV2_ATOM__Float)),
3035 scaleFactorUrid (instance.symap->map (LV2_UI__scaleFactor)),
3036 uiInstance (new UiInstanceWithSupports (world,
3037 *this,
3038 touchListener,
3039 &uiDescriptor,
3040 UiInstanceArgs{}.withBundlePath (bundlePathFromUri (uiBundleUri.toRawUTF8()))
3041 .withPluginUri (URL (instance.instance.getUri())),
3042 viewComponent.getWidget(),
3043 instance,
3044 opts)),
3045 resizeClient (uiInstance->instance.getExtensionData<LV2UI_Resize> (world, LV2_UI__resize)),
3046 optionsInterface (uiInstance->instance.getExtensionData<LV2_Options_Interface> (world, LV2_OPTIONS__interface))
3047 {
3048 jassert (uiInstance != nullptr);
3049
3050 setOpaque (true);
3051 addAndMakeVisible (viewComponent);
3052
3053 const auto boundsToUse = [&]
3054 {
3055 const auto requested = uiInstance->features.getLastRequestedBounds();
3056
3057 if (requested.getWidth() > 10 && requested.getHeight() > 10)
3058 return requested;
3059
3060 return uiInstance->instance.getDetectedViewBounds();
3061 }();
3062
3063 const auto scaled = lv2ToComponentRect (boundsToUse);
3064 lastWidth = scaled.getWidth();
3065 lastHeight = scaled.getHeight();
3066 setSize (lastWidth, lastHeight);
3067 }
3068
3069 ~ConfiguredEditorComponent() override
3070 {
3071 viewComponent.prepareForDestruction();
3072 }
3073
3074 void paint (Graphics& g) override
3075 {
3076 g.fillAll (Colours::black);
3077 }
3078
3079 void resized() override
3080 {
3081 viewComponent.setBounds (getLocalBounds());
3082 }
3083
3084 void updateViewBounds()
3085 {
3086 // If the editor changed size as a result of a request from the client,
3087 // we shouldn't send a notification back to the client.
3088 if (uiInstance != nullptr)
3089 {
3090 if (resizeClient.valid && resizeClient.extension.ui_resize != nullptr)
3091 {
3092 const auto physicalSize = componentToLv2Rect (getLocalBounds());
3093
3094 resizeClient.extension.ui_resize (uiInstance->instance.getHandle(),
3095 physicalSize.getWidth(),
3096 physicalSize.getHeight());
3097 }
3098 }
3099 }
3100
3101 void pushMessage (MessageHeader header, uint32_t size, const void* buffer)
3102 {
3103 if (uiInstance != nullptr)
3104 uiInstance->instance.pushMessage (header, size, buffer);
3105 }
3106
3107 int idle()
3108 {
3109 if (uiInstance != nullptr)
3110 return uiInstance->instance.idle();
3111
3112 return 0;
3113 }
3114
3115 void childBoundsChanged (Component* c) override
3116 {
3117 if (c == nullptr)
3118 resizeToFitView();
3119 }
3120
3121 void setUserScaleFactor (float userScale) { userScaleFactor = userScale; }
3122
3123 void sendScaleFactorToPlugin()
3124 {
3125 const auto factor = getEffectiveScale();
3126
3127 const LV2_Options_Option options[]
3128 {
3129 { LV2_OPTIONS_INSTANCE, 0, scaleFactorUrid, sizeof (float), floatUrid, &factor },
3130 { {}, {}, {}, {}, {}, {} }
3131 };
3132
3133 if (optionsInterface.valid)
3134 optionsInterface.extension.set (uiInstance->instance.getHandle(), options);
3135
3136 applyLastRequestedPhysicalSize();
3137 }
3138
3139private:
3140 void viewRequestedResizeInPhysicalPixels (int width, int height) override
3141 {
3142 lastWidth = width;
3143 lastHeight = height;
3144 const auto logical = lv2ToComponentRect ({ width, height });
3145 resizeListener.viewRequestedResizeInLogicalPixels (logical.getWidth(), logical.getHeight());
3146 }
3147
3148 void resizeToFitView()
3149 {
3150 viewComponent.fitToView();
3151 resizeListener.viewRequestedResizeInLogicalPixels (viewComponent.getWidth(), viewComponent.getHeight());
3152 }
3153
3154 void applyLastRequestedPhysicalSize()
3155 {
3156 viewRequestedResizeInPhysicalPixels (lastWidth, lastHeight);
3157 viewComponent.forceViewToSize();
3158 }
3159
3160 /* Convert from the component's coordinate system to the hosted LV2's coordinate system. */
3161 Rectangle<int> componentToLv2Rect (Rectangle<int> r) const
3162 {
3163 return localAreaToGlobal (r) * nativeScaleFactor * getDesktopScaleFactor();
3164 }
3165
3166 /* Convert from the hosted LV2's coordinate system to the component's coordinate system. */
3167 Rectangle<int> lv2ToComponentRect (Rectangle<int> vr) const
3168 {
3169 return getLocalArea (nullptr, vr / (nativeScaleFactor * getDesktopScaleFactor()));
3170 }
3171
3172 float getEffectiveScale() const { return nativeScaleFactor * userScaleFactor; }
3173
3174 // If possible, try to keep platform-specific handing restricted to the implementation of
3175 // ViewComponent. Keep the interface of ViewComponent consistent on all platforms.
3176 #if JUCE_LINUX || JUCE_BSD
3177 struct InnerHolder
3178 {
3179 struct Inner final : public XEmbedComponent
3180 {
3181 Inner() : XEmbedComponent (true, true)
3182 {
3183 setOpaque (true);
3184 addToDesktop (0);
3185 }
3186 };
3187
3188 Inner inner;
3189 };
3190
3191 struct ViewComponent final : public InnerHolder,
3192 public XEmbedComponent
3193 {
3194 explicit ViewComponent (PhysicalResizeListener& l)
3195 : XEmbedComponent ((unsigned long) inner.getPeer()->getNativeHandle(), true, false),
3196 listener (inner, l)
3197 {
3198 setOpaque (true);
3199 }
3200
3201 ~ViewComponent()
3202 {
3203 removeClient();
3204 }
3205
3206 void prepareForDestruction()
3207 {
3208 inner.removeClient();
3209 }
3210
3211 LV2UI_Widget getWidget() { return lv2_shared::wordCast<LV2UI_Widget> (inner.getHostWindowID()); }
3212 void forceViewToSize() {}
3213 void fitToView() {}
3214
3215 ViewSizeListener listener;
3216 };
3217 #elif JUCE_MAC
3218 struct ViewComponent final : public NSViewComponentWithParent
3219 {
3220 explicit ViewComponent (PhysicalResizeListener&)
3221 : NSViewComponentWithParent (WantsNudge::no) {}
3222 LV2UI_Widget getWidget() { return getView(); }
3223 void forceViewToSize() {}
3224 void fitToView() { resizeToFitView(); }
3225 void prepareForDestruction() {}
3226 };
3227 #elif JUCE_WINDOWS
3228 struct ViewComponent final : public HWNDComponent
3229 {
3230 explicit ViewComponent (PhysicalResizeListener&)
3231 {
3232 setOpaque (true);
3233 inner.addToDesktop (0);
3234
3235 if (auto* peer = inner.getPeer())
3236 setHWND (peer->getNativeHandle());
3237 }
3238
3239 void paint (Graphics& g) override { g.fillAll (Colours::black); }
3240
3241 LV2UI_Widget getWidget() { return getHWND(); }
3242
3243 void forceViewToSize() { updateHWNDBounds(); }
3244 void fitToView() { resizeToFit(); }
3245
3246 void prepareForDestruction() {}
3247
3248 private:
3249 struct Inner final : public Component
3250 {
3251 Inner() { setOpaque (true); }
3252 void paint (Graphics& g) override { g.fillAll (Colours::black); }
3253 };
3254
3255 Inner inner;
3256 };
3257 #else
3258 struct ViewComponent final : public Component
3259 {
3260 explicit ViewComponent (PhysicalResizeListener&) {}
3261 void* getWidget() { return nullptr; }
3262 void forceViewToSize() {}
3263 void fitToView() {}
3264 void prepareForDestruction() {}
3265 };
3266 #endif
3267
3268 struct ScaleNotifierCallback
3269 {
3270 ConfiguredEditorComponent& window;
3271
3272 void operator() (float platformScale) const
3273 {
3274 MessageManager::callAsync ([ref = Component::SafePointer<ConfiguredEditorComponent> (&window), platformScale]
3275 {
3276 if (auto* r = ref.getComponent())
3277 {
3278 if (approximatelyEqual (std::exchange (r->nativeScaleFactor, platformScale), platformScale))
3279 return;
3280
3281 r->nativeScaleFactor = platformScale;
3282 r->sendScaleFactorToPlugin();
3283 }
3284 });
3285 }
3286 };
3287
3288 LogicalResizeListener& resizeListener;
3289 int lastWidth = 0, lastHeight = 0;
3290 float nativeScaleFactor = 1.0f, userScaleFactor = 1.0f;
3291 NativeScaleFactorNotifier scaleNotifier { this, ScaleNotifierCallback { *this } };
3292 ViewComponent viewComponent { *this };
3293 LV2_URID floatUrid, scaleFactorUrid;
3295 OptionalExtension<LV2UI_Resize> resizeClient;
3296 OptionalExtension<LV2_Options_Interface> optionsInterface;
3297 PeerChangedListener peerListener { *this, [this]
3298 {
3299 applyLastRequestedPhysicalSize();
3300 } };
3301
3302 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConfiguredEditorComponent)
3303};
3304
3305//==============================================================================
3306/* Interface to receive notifications when the Editor changes. */
3307struct EditorListener
3308{
3309 virtual ~EditorListener() = default;
3310
3311 /* The editor needs to be recreated in a few different scenarios, such as:
3312 - When the scale factor of the window changes, because we can only provide the
3313 scale factor to the view during construction
3314 - When the sample rate changes, because the processor also needs to be destroyed
3315 and recreated in this case
3316
3317 This function will be called whenever the editor has been recreated, in order to
3318 allow the processor (or other listeners) to respond, e.g. by sending all of the
3319 current port/parameter values to the view.
3320 */
3321 virtual void viewCreated (UiEventListener* newListener) = 0;
3322
3323 virtual void notifyEditorBeingDeleted() = 0;
3324};
3325
3326/* We can't pass the InstanceWithSupports directly to the editor, because
3327 it might be destroyed and reconstructed if the sample rate changes.
3328*/
3329struct InstanceProvider
3330{
3331 virtual ~InstanceProvider() noexcept = default;
3332
3333 virtual InstanceWithSupports* getInstanceWithSupports() const = 0;
3334};
3335
3336class Editor final : public AudioProcessorEditor,
3337 public UiEventListener,
3338 private LogicalResizeListener
3339{
3340public:
3341 Editor (World& worldIn,
3342 AudioPluginInstance& p,
3343 InstanceProvider& instanceProviderIn,
3344 UiDescriptor& uiDescriptorIn,
3345 TouchListener& touchListenerIn,
3346 EditorListener& listenerIn,
3347 const String& uiBundleUriIn,
3348 RequiredFeatures requiredIn,
3349 OptionalFeatures optionalIn)
3350 : AudioProcessorEditor (p),
3351 world (worldIn),
3352 instanceProvider (&instanceProviderIn),
3353 uiDescriptor (&uiDescriptorIn),
3354 touchListener (&touchListenerIn),
3355 listener (&listenerIn),
3356 uiBundleUri (uiBundleUriIn),
3357 required (std::move (requiredIn)),
3358 optional (std::move (optionalIn))
3359 {
3360 setResizable (isResizable (required, optional), false);
3361 setSize (10, 10);
3362 setOpaque (true);
3363
3364 createView();
3365
3366 instanceProvider->getInstanceWithSupports()->processorToUi->addUi (*this);
3367 }
3368
3369 ~Editor() noexcept override
3370 {
3371 instanceProvider->getInstanceWithSupports()->processorToUi->removeUi (*this);
3372
3373 listener->notifyEditorBeingDeleted();
3374 }
3375
3376 void createView()
3377 {
3378 const auto initialScale = userScaleFactor * (float) [&]
3379 {
3380 if (auto* p = getPeer())
3381 return p->getPlatformScaleFactor();
3382
3383 return 1.0;
3384 }();
3385
3386 const auto opts = UiFeaturesDataOptions{}.withInitialScaleFactor (initialScale)
3387 .withSampleRate ((float) processor.getSampleRate());
3388 configuredEditor = nullptr;
3389 configuredEditor = rawToUniquePtr (new ConfiguredEditorComponent (world,
3390 *instanceProvider->getInstanceWithSupports(),
3391 *uiDescriptor,
3392 *this,
3393 *touchListener,
3394 uiBundleUri,
3395 opts));
3396 parentHierarchyChanged();
3397 const auto initialSize = configuredEditor->getBounds();
3398 setSize (initialSize.getWidth(), initialSize.getHeight());
3399
3400 listener->viewCreated (this);
3401 }
3402
3403 void destroyView()
3404 {
3405 configuredEditor = nullptr;
3406 }
3407
3408 void paint (Graphics& g) override
3409 {
3410 g.fillAll (Colours::black);
3411 }
3412
3413 void resized() override
3414 {
3415 const ScopedValueSetter<bool> scope (resizeFromHost, true);
3416
3417 if (auto* inner = configuredEditor.get())
3418 {
3419 inner->setBounds (getLocalBounds());
3420 inner->updateViewBounds();
3421 }
3422 }
3423
3424 void parentHierarchyChanged() override
3425 {
3426 if (auto* comp = configuredEditor.get())
3427 {
3428 if (isShowing())
3429 addAndMakeVisible (comp);
3430 else
3431 removeChildComponent (comp);
3432 }
3433 }
3434
3435 void pushMessage (MessageHeader header, uint32_t size, const void* buffer) override
3436 {
3437 if (auto* comp = configuredEditor.get())
3438 comp->pushMessage (header, size, buffer);
3439 }
3440
3441 int idle() override
3442 {
3443 if (auto* comp = configuredEditor.get())
3444 return comp->idle();
3445
3446 return 0;
3447 }
3448
3449 void setScaleFactor (float newScale) override
3450 {
3451 userScaleFactor = newScale;
3452
3453 if (configuredEditor != nullptr)
3454 {
3455 configuredEditor->setUserScaleFactor (userScaleFactor);
3456 configuredEditor->sendScaleFactorToPlugin();
3457 }
3458 }
3459
3460private:
3461 bool isResizable (const RequiredFeatures& requiredFeatures,
3462 const OptionalFeatures& optionalFeatures) const
3463 {
3464 const auto uriMatches = [] (const LilvNode* node)
3465 {
3466 const auto* uri = lilv_node_as_uri (node);
3467 return std::strcmp (uri, LV2_UI__noUserResize) == 0;
3468 };
3469
3470 return uiDescriptor->hasExtensionData (world, LV2_UI__resize)
3471 && ! uiDescriptor->hasExtensionData (world, LV2_UI__noUserResize)
3472 && noneOf (requiredFeatures.values, uriMatches)
3473 && noneOf (optionalFeatures.values, uriMatches);
3474 }
3475
3476 bool isScalable() const
3477 {
3478 return uiDescriptor->hasExtensionData (world, LV2_OPTIONS__interface);
3479 }
3480
3481 void viewRequestedResizeInLogicalPixels (int width, int height) override
3482 {
3483 if (! resizeFromHost)
3484 setSize (width, height);
3485 }
3486
3487 World& world;
3488 InstanceProvider* instanceProvider;
3489 UiDescriptor* uiDescriptor;
3490 TouchListener* touchListener;
3491 EditorListener* listener;
3492 String uiBundleUri;
3493 const RequiredFeatures required;
3494 const OptionalFeatures optional;
3496 float userScaleFactor = 1.0f;
3497 bool resizeFromHost = false;
3498
3500};
3501
3502class Uis
3503{
3504public:
3505 explicit Uis (const LilvPlugin* plugin) noexcept : uis (lilv_plugin_get_uis (plugin)) {}
3506
3507 unsigned size() const noexcept { return lilv_uis_size (uis.get()); }
3508
3509 UisIterator begin() const noexcept { return UisIterator { uis.get() }; }
3510 UisIterator end() const noexcept { return UisIterator{}; }
3511
3512 const LilvUI* getByUri (const NodeUri& uri) const
3513 {
3514 return lilv_uis_get_by_uri (uis.get(), uri.get());
3515 }
3516
3517private:
3518 struct Free
3519 {
3520 void operator() (LilvUIs* ptr) const noexcept { lilv_uis_free (ptr); }
3521 };
3522
3524};
3525
3526//==============================================================================
3527class PluginClass
3528{
3529public:
3530 explicit PluginClass (const LilvPluginClass* c) : pluginClass (c) {}
3531
3532 NodeUri getParentUri() const noexcept { return NodeUri::copy (lilv_plugin_class_get_parent_uri (pluginClass)); }
3533 NodeUri getUri() const noexcept { return NodeUri::copy (lilv_plugin_class_get_uri (pluginClass)); }
3534 NodeString getLabel() const noexcept { return NodeString::copy (lilv_plugin_class_get_label (pluginClass)); }
3535 OwningPluginClasses getChildren() const noexcept
3536 {
3537 return OwningPluginClasses { OwningPluginClasses::type { lilv_plugin_class_get_children (pluginClass) } };
3538 }
3539
3540private:
3541 const LilvPluginClass* pluginClass = nullptr;
3542};
3543
3544using FloatWriter = void (*) (LV2_Atom_Forge*, float);
3545
3546struct ParameterWriterUrids
3547{
3548 LV2_URID mLV2_PATCH__Set;
3549 LV2_URID mLV2_PATCH__property;
3550 LV2_URID mLV2_PATCH__value;
3551 LV2_URID mLV2_ATOM__eventTransfer;
3552};
3553
3554struct MessageHeaderAndSize
3555{
3556 MessageHeader header;
3557 uint32_t size;
3558};
3559
3560class ParameterWriter
3561{
3562public:
3563 ParameterWriter (ControlPort* p)
3564 : data (PortBacking { p }), kind (Kind::port) {}
3565
3566 ParameterWriter (FloatWriter write, LV2_URID urid, uint32_t controlPortIndex)
3567 : data (PatchBacking { write, urid, controlPortIndex }), kind (Kind::patch) {}
3568
3569 void writeToProcessor (const ParameterWriterUrids urids, LV2_Atom_Forge* forge, float value) const
3570 {
3571 switch (kind)
3572 {
3573 case Kind::patch:
3574 {
3575 if (forge != nullptr)
3576 {
3577 lv2_atom_forge_frame_time (forge, 0);
3578 writeSetToForge (urids, *forge, value);
3579 }
3580
3581 break;
3582 }
3583
3584 case Kind::port:
3585 data.port.port->currentValue = value;
3586 break;
3587 }
3588 }
3589
3590 MessageHeaderAndSize writeToUi (const ParameterWriterUrids urids, LV2_Atom_Forge& forge, float value) const
3591 {
3592 const auto getWrittenBytes = [&]() -> uint32_t
3593 {
3594 if (const auto* atom = convertToAtomPtr (forge.buf, forge.size))
3595 return (uint32_t) (atom->size + sizeof (LV2_Atom));
3596
3598 return 0;
3599 };
3600
3601 switch (kind)
3602 {
3603 case Kind::patch:
3604 writeSetToForge (urids, forge, value);
3605 return { { data.patch.controlPortIndex, urids.mLV2_ATOM__eventTransfer }, getWrittenBytes() };
3606
3607 case Kind::port:
3608 lv2_atom_forge_raw (&forge, &value, sizeof (value));
3609 return { { data.port.port->header.index, 0 }, sizeof (value) };
3610 }
3611
3612 return { { 0, 0 }, 0 };
3613 }
3614
3615 const LV2_URID* getUrid() const
3616 {
3617 return kind == Kind::patch ? &data.patch.urid : nullptr;
3618 }
3619
3620 const uint32_t* getPortIndex() const
3621 {
3622 return kind == Kind::port ? &data.port.port->header.index : nullptr;
3623 }
3624
3625private:
3626 void writeSetToForge (const ParameterWriterUrids urids, LV2_Atom_Forge& forge, float value) const
3627 {
3628 lv2_shared::ObjectFrame object { &forge, (uint32_t) 0, urids.mLV2_PATCH__Set };
3629
3630 lv2_atom_forge_key (&forge, urids.mLV2_PATCH__property);
3631 lv2_atom_forge_urid (&forge, data.patch.urid);
3632
3633 lv2_atom_forge_key (&forge, urids.mLV2_PATCH__value);
3634 data.patch.write (&forge, value);
3635 }
3636
3637 struct PortBacking
3638 {
3639 ControlPort* port;
3640 };
3641
3642 struct PatchBacking
3643 {
3644 FloatWriter write;
3645 LV2_URID urid;
3646 uint32_t controlPortIndex;
3647 };
3648
3649 union Data
3650 {
3651 static_assert (std::is_trivial_v<PortBacking>, "PortBacking must be trivial");
3652 static_assert (std::is_trivial_v<PatchBacking>, "PatchBacking must be trivial");
3653
3654 explicit Data (PortBacking p) : port (p) {}
3655 explicit Data (PatchBacking p) : patch (p) {}
3656
3657 PortBacking port;
3658 PatchBacking patch;
3659 };
3660
3661 enum class Kind { port, patch };
3662
3663 Data data;
3664 Kind kind;
3665
3666 JUCE_LEAK_DETECTOR (ParameterWriter)
3667};
3668
3669static String lilvNodeToUriString (const LilvNode* node)
3670{
3671 return node != nullptr ? String::fromUTF8 (lilv_node_as_uri (node)) : String{};
3672}
3673
3674static String lilvNodeToString (const LilvNode* node)
3675{
3676 return node != nullptr ? String::fromUTF8 (lilv_node_as_string (node)) : String{};
3677}
3678
3679/* This holds all of the discovered groups in the plugin's manifest, and allows us to
3680 add parameters to these groups as we discover them.
3681
3682 Once all the parameters have been added with addParameter(), you can call
3683 getTree() to convert this class' contents (which are optimised for fast lookup
3684 and modification) into a plain old AudioProcessorParameterGroup.
3685*/
3686class IntermediateParameterTree
3687{
3688public:
3689 explicit IntermediateParameterTree (World& worldIn)
3690 : world (worldIn)
3691 {
3692 const auto groups = getGroups (world);
3693 const auto symbolNode = world.newUri (LV2_CORE__symbol);
3694 const auto nameNode = world.newUri (LV2_CORE__name);
3695
3696 for (const auto& group : groups)
3697 {
3698 const auto symbol = lilvNodeToString (world.get (group.get(), symbolNode.get(), nullptr).get());
3699 const auto name = lilvNodeToString (world.get (group.get(), nameNode .get(), nullptr).get());
3700 owning.emplace (lilvNodeToUriString (group.get()),
3701 std::make_unique<AudioProcessorParameterGroup> (symbol, name, "|"));
3702 }
3703 }
3704
3705 void addParameter (StringRef group, std::unique_ptr<LV2Parameter> param)
3706 {
3707 if (param == nullptr)
3708 return;
3709
3710 const auto it = owning.find (group);
3711 (it != owning.cend() ? *it->second : topLevel).addChild (std::move (param));
3712 }
3713
3714 static AudioProcessorParameterGroup getTree (IntermediateParameterTree tree)
3715 {
3717
3718 for (const auto& pair : tree.owning)
3719 nonowning.emplace (pair.first, pair.second.get());
3720
3721 const auto groups = getGroups (tree.world);
3722 const auto subgroupNode = tree.world.newUri (LV2_PORT_GROUPS__subGroupOf);
3723
3724 for (const auto& group : groups)
3725 {
3726 const auto innerIt = tree.owning.find (lilvNodeToUriString (group.get()));
3727
3728 if (innerIt == tree.owning.cend())
3729 continue;
3730
3731 const auto outer = lilvNodeToUriString (tree.world.get (group.get(), subgroupNode.get(), nullptr).get());
3732 const auto outerIt = nonowning.find (outer);
3733
3734 if (outerIt != nonowning.cend() && containsParameters (outerIt->second))
3735 outerIt->second->addChild (std::move (innerIt->second));
3736 }
3737
3738 for (auto& subgroup : tree.owning)
3739 if (containsParameters (subgroup.second.get()))
3740 tree.topLevel.addChild (std::move (subgroup.second));
3741
3742 return std::move (tree.topLevel);
3743 }
3744
3745private:
3746 static std::vector<OwningNode> getGroups (World& world)
3747 {
3749
3750 for (auto* uri : { LV2_PORT_GROUPS__Group, LV2_PORT_GROUPS__InputGroup, LV2_PORT_GROUPS__OutputGroup })
3751 for (const auto* group : world.findNodes (nullptr, world.newUri (LILV_NS_RDF "type").get(), world.newUri (uri).get()))
3752 names.push_back (OwningNode { lilv_node_duplicate (group) });
3753
3754 return names;
3755 }
3756
3757 static bool containsParameters (const AudioProcessorParameterGroup* g)
3758 {
3759 if (g == nullptr)
3760 return false;
3761
3762 for (auto* node : *g)
3763 {
3764 if (node->getParameter() != nullptr)
3765 return true;
3766
3767 if (auto* group = node->getGroup())
3768 if (containsParameters (group))
3769 return true;
3770 }
3771
3772 return false;
3773 }
3774
3775 World& world;
3776 AudioProcessorParameterGroup topLevel;
3778
3779 JUCE_LEAK_DETECTOR (IntermediateParameterTree)
3780};
3781
3782struct BypassParameter final : public LV2Parameter
3783{
3784 BypassParameter (const ParameterInfo& parameterInfo, ParameterValuesAndFlags& cacheIn)
3785 : LV2Parameter ("Bypass", parameterInfo, cacheIn) {}
3786
3787 float getValue() const noexcept override
3788 {
3789 return LV2Parameter::getValue() > 0.0f ? 0.0f : 1.0f;
3790 }
3791
3792 void setValue (float newValue) override
3793 {
3794 LV2Parameter::setValue (newValue > 0.0f ? 0.0f : 1.0f);
3795 }
3796
3797 float getDefaultValue() const override { return 0.0f; }
3798 bool isAutomatable() const override { return true; }
3799 bool isDiscrete() const override { return true; }
3800 bool isBoolean() const override { return true; }
3801 int getNumSteps() const override { return 2; }
3802 StringArray getAllValueStrings() const override { return { TRANS ("Off"), TRANS ("On") }; }
3803};
3804
3805struct ParameterData
3806{
3807 ParameterInfo info;
3808 ParameterWriter writer;
3809 String group;
3810 String name;
3811};
3812
3813template <typename T>
3814static auto getPortPointers (SimpleSpan<T> range)
3815{
3816 using std::begin;
3817 std::vector<decltype (&(*begin (range)))> result;
3818
3819 for (auto& port : range)
3820 {
3821 result.resize (std::max ((size_t) (port.header.index + 1), result.size()), nullptr);
3822 result[port.header.index] = &port;
3823 }
3824
3825 return result;
3826}
3827
3828static std::unique_ptr<LV2Parameter> makeParameter (const uint32_t* enabledPortIndex,
3829 const ParameterData& data,
3830 ParameterValuesAndFlags& cache)
3831{
3832 // The bypass parameter is a bit special, in that JUCE expects the parameter to be a bypass
3833 // (where 0 is active, 1 is inactive), but the LV2 version is called "enabled" and has
3834 // different semantics (0 is inactive, 1 is active).
3835 // To work around this, we wrap the LV2 parameter in a special inverting JUCE parameter.
3836
3837 if (enabledPortIndex != nullptr)
3838 if (auto* index = data.writer.getPortIndex())
3839 if (*index == *enabledPortIndex)
3840 return std::make_unique<BypassParameter> (data.info, cache);
3841
3842 return std::make_unique<LV2Parameter> (data.name, data.info, cache);
3843}
3844
3845class ControlPortAccelerationStructure
3846{
3847public:
3848 ControlPortAccelerationStructure (SimpleSpan<ControlPort> controlPorts)
3849 : indexedControlPorts (getPortPointers (controlPorts))
3850 {
3851 for (const auto& port : controlPorts)
3852 if (port.header.direction == Port::Direction::output)
3853 outputPorts.push_back (&port);
3854 }
3855
3856 const std::vector<ControlPort*>& getIndexedControlPorts() { return indexedControlPorts; }
3857
3858 ControlPort* getControlPortByIndex (uint32_t index) const
3859 {
3860 if (isPositiveAndBelow (index, indexedControlPorts.size()))
3861 return indexedControlPorts[index];
3862
3863 return nullptr;
3864 }
3865
3866 void writeOutputPorts (UiEventListener* target, MessageBufferInterface<UiMessageHeader>& uiMessages) const
3867 {
3868 if (target == nullptr)
3869 return;
3870
3871 for (const auto* port : outputPorts)
3872 {
3873 const auto chars = toChars (port->currentValue);
3874 uiMessages.pushMessage ({ target, { port->header.index, 0 } }, (uint32_t) chars.size(), chars.data());
3875 }
3876 }
3877
3878private:
3879 std::vector<ControlPort*> indexedControlPorts;
3881};
3882
3883class ParameterValueCache
3884{
3885public:
3886 /* This takes some information about all the parameters that this plugin wants to expose,
3887 then builds and installs the actual parameters.
3888 */
3889 ParameterValueCache (AudioPluginInstance& processor,
3890 World& world,
3891 LV2_URID_Map mapFeature,
3892 const std::vector<ParameterData>& data,
3893 ControlPort* enabledPort)
3894 : uiForge (mapFeature),
3895 cache (data.size())
3896 {
3897 // Parameter indices are unknown until we add the parameters to the processor.
3898 // This map lets us keep track of which ParameterWriter corresponds to each parameter.
3899 // After the parameters have been added to the processor, we'll convert this
3900 // to a simple vector that stores each ParameterWriter at the same index
3901 // as the corresponding parameter.
3903
3904 IntermediateParameterTree tree { world };
3905
3906 const auto* enabledPortIndex = enabledPort != nullptr ? &enabledPort->header.index
3907 : nullptr;
3908
3909 for (const auto& item : data)
3910 {
3911 auto param = makeParameter (enabledPortIndex, item, cache);
3912
3913 if (auto* urid = item.writer.getUrid())
3914 urids.emplace (*urid, param.get());
3915
3916 if (auto* index = item.writer.getPortIndex())
3917 portIndices.emplace (*index, param.get());
3918
3919 writerForParameter.emplace (param.get(), item.writer);
3920
3921 tree.addParameter (item.group, std::move (param));
3922 }
3923
3924 processor.setHostedParameterTree (IntermediateParameterTree::getTree (std::move (tree)));
3925
3926 // Build the vector of writers
3927 writers.reserve (data.size());
3928
3929 for (auto* param : processor.getParameters())
3930 {
3931 const auto it = writerForParameter.find (param);
3932 jassert (it != writerForParameter.end());
3933 writers.push_back (it->second); // The writer must exist at the same index as the parameter!
3934 }
3935
3936 // Duplicate port indices or urids?
3937 jassert (processor.getParameters().size() == (int) (urids.size() + portIndices.size()));
3938
3939 // Set parameters to default values
3940 const auto setToDefault = [] (auto& container)
3941 {
3942 for (auto& item : container)
3943 item.second->setDenormalisedValueWithoutTriggeringUpdate (item.second->getDenormalisedDefaultValue());
3944 };
3945
3946 setToDefault (urids);
3947 setToDefault (portIndices);
3948 }
3949
3950 void postChangedParametersToProcessor (const ParameterWriterUrids helperUrids,
3951 LV2_Atom_Forge* forge)
3952 {
3953 cache.ifProcessorValuesChanged ([&] (size_t index, float value)
3954 {
3955 writers[index].writeToProcessor (helperUrids, forge, value);
3956 });
3957 }
3958
3959 void postChangedParametersToUi (UiEventListener* target,
3960 const ParameterWriterUrids helperUrids,
3961 MessageBufferInterface<UiMessageHeader>& uiMessages)
3962 {
3963 if (target == nullptr)
3964 return;
3965
3966 cache.ifUiValuesChanged ([&] (size_t index, float value)
3967 {
3968 writeParameterToUi (target, writers[index], value, helperUrids, uiMessages);
3969 });
3970 }
3971
3972 void postAllParametersToUi (UiEventListener* target,
3973 const ParameterWriterUrids helperUrids,
3974 MessageBufferInterface<UiMessageHeader>& uiMessages)
3975 {
3976 if (target == nullptr)
3977 return;
3978
3979 const auto numWriters = writers.size();
3980
3981 for (size_t i = 0; i < numWriters; ++i)
3982 writeParameterToUi (target, writers[i], cache.get (i), helperUrids, uiMessages);
3983
3984 cache.clearUiFlags();
3985 }
3986
3987 LV2Parameter* getParamByUrid (LV2_URID urid) const
3988 {
3989 const auto it = urids.find (urid);
3990 return it != urids.end() ? it->second : nullptr;
3991 }
3992
3993 LV2Parameter* getParamByPortIndex (uint32_t portIndex) const
3994 {
3995 const auto it = portIndices.find (portIndex);
3996 return it != portIndices.end() ? it->second : nullptr;
3997 }
3998
3999 void updateFromControlPorts (const ControlPortAccelerationStructure& ports) const
4000 {
4001 for (const auto& pair : portIndices)
4002 if (auto* port = ports.getControlPortByIndex (pair.first))
4003 if (auto* param = pair.second)
4004 param->setDenormalisedValueWithoutTriggeringUpdate (port->currentValue);
4005 }
4006
4007private:
4008 void writeParameterToUi (UiEventListener* target,
4009 const ParameterWriter& writer,
4010 float value,
4011 const ParameterWriterUrids helperUrids,
4012 MessageBufferInterface<UiMessageHeader>& uiMessages)
4013 {
4015
4016 uiForge.setBuffer (forgeStorage.data(), forgeStorage.size());
4017 const auto messageHeader = writer.writeToUi (helperUrids, *uiForge.get(), value);
4018 uiMessages.pushMessage ({ target, messageHeader.header }, messageHeader.size, forgeStorage.data());
4019 }
4020
4021 SingleSizeAlignedStorage<8> forgeStorage { 256 };
4022 lv2_shared::AtomForge uiForge;
4023
4024 ParameterValuesAndFlags cache;
4028
4029 JUCE_LEAK_DETECTOR (ParameterValueCache)
4030};
4031
4032struct PatchSetCallback
4033{
4034 explicit PatchSetCallback (ParameterValueCache& x) : cache (x) {}
4035
4036 // If we receive a patch set from the processor, we can assume that the UI will
4037 // put itself into the correct state when it receives the message.
4038 void setParameter (LV2_URID property, float value) const noexcept
4039 {
4040 if (auto* param = cache.getParamByUrid (property))
4041 param->setDenormalisedValueWithoutTriggeringUpdate (value);
4042 }
4043
4044 // TODO gesture support will probably go here, once it's part of the LV2 spec
4045
4046 ParameterValueCache& cache;
4047};
4048
4049struct SupportedParameter
4050{
4051 ParameterInfo info;
4052 bool supported;
4053 LV2_URID type;
4054};
4055
4056static SupportedParameter getInfoForPatchParameter (World& worldIn,
4057 const UsefulUrids& urids,
4058 const NodeUri& property)
4059{
4060 const auto rangeUri = worldIn.newUri (LILV_NS_RDFS "range");
4061 const auto type = worldIn.get (property.get(), rangeUri.get(), nullptr);
4062
4063 if (type == nullptr)
4064 return { {}, false, {} };
4065
4066 const auto typeUrid = urids.symap.map (lilv_node_as_uri (type.get()));
4067
4068 const LV2_URID types[] { urids.mLV2_ATOM__Int,
4069 urids.mLV2_ATOM__Long,
4070 urids.mLV2_ATOM__Float,
4071 urids.mLV2_ATOM__Double,
4072 urids.mLV2_ATOM__Bool };
4073
4074 if (std::find (std::begin (types), std::end (types), typeUrid) == std::end (types))
4075 return { {}, false, {} };
4076
4077 const auto getValue = [&] (const char* uri, float fallback)
4078 {
4079 return Port::getFloatValue (worldIn.get (property.get(), worldIn.newUri (uri).get(), nullptr).get(), fallback);
4080 };
4081
4082 const auto hasPortProperty = [&] (const char* uri)
4083 {
4084 return worldIn.ask (property.get(),
4085 worldIn.newUri (LV2_CORE__portProperty).get(),
4086 worldIn.newUri (uri).get());
4087 };
4088
4089 const auto metadataScalePoints = worldIn.findNodes (property.get(),
4090 worldIn.newUri (LV2_CORE__scalePoint).get(),
4091 nullptr);
4092 SafeSortedSet<StoredScalePoint> parsedScalePoints;
4093
4094 for (const auto* scalePoint : metadataScalePoints)
4095 {
4096 const auto label = worldIn.get (scalePoint, worldIn.newUri (LILV_NS_RDFS "label").get(), nullptr);
4097 const auto value = worldIn.get (scalePoint, worldIn.newUri (LILV_NS_RDF "value").get(), nullptr);
4098
4099 if (label != nullptr && value != nullptr)
4100 parsedScalePoints.insert ({ lilv_node_as_string (label.get()), lilv_node_as_float (value.get()) });
4101 else
4102 jassertfalse; // A ScalePoint must have both a rdfs:label and a rdf:value
4103 }
4104
4105 const auto minimum = getValue (LV2_CORE__minimum, 0.0f);
4106 const auto maximum = getValue (LV2_CORE__maximum, 1.0f);
4107
4108 return { { std::move (parsedScalePoints),
4109 "des:" + String::fromUTF8 (property.getTyped()),
4110 getValue (LV2_CORE__default, (minimum + maximum) * 0.5f),
4111 minimum,
4112 maximum,
4113 typeUrid == urids.mLV2_ATOM__Bool || hasPortProperty (LV2_CORE__toggled),
4114 typeUrid == urids.mLV2_ATOM__Int || typeUrid == urids.mLV2_ATOM__Long,
4115 hasPortProperty (LV2_CORE__enumeration) },
4116 true,
4117 typeUrid };
4118}
4119
4120static std::vector<ParameterData> getPortBasedParameters (World& world,
4121 const Plugin& plugin,
4123 SimpleSpan<ControlPort> controlPorts)
4124{
4126
4127 const auto groupNode = world.newUri (LV2_PORT_GROUPS__group);
4128
4129 for (auto& port : controlPorts)
4130 {
4131 if (port.header.direction != Port::Direction::input)
4132 continue;
4133
4134 if (std::find (std::begin (hiddenPorts), std::end (hiddenPorts), &port) != std::end (hiddenPorts))
4135 continue;
4136
4137 const auto lilvPort = plugin.getPortByIndex (port.header.index);
4138 const auto group = lilvNodeToUriString (lilvPort.get (groupNode.get()).get());
4139
4140 result.push_back ({ port.info, ParameterWriter { &port }, group, port.header.name });
4141 }
4142
4143 return result;
4144}
4145
4146static void writeFloatToForge (LV2_Atom_Forge* forge, float value) { lv2_atom_forge_float (forge, value); }
4147static void writeDoubleToForge (LV2_Atom_Forge* forge, float value) { lv2_atom_forge_double (forge, (double) value); }
4148static void writeIntToForge (LV2_Atom_Forge* forge, float value) { lv2_atom_forge_int (forge, (int32_t) value); }
4149static void writeLongToForge (LV2_Atom_Forge* forge, float value) { lv2_atom_forge_long (forge, (int64_t) value); }
4150static void writeBoolToForge (LV2_Atom_Forge* forge, float value) { lv2_atom_forge_bool (forge, value > 0.5f); }
4151
4152static std::vector<ParameterData> getPatchBasedParameters (World& world,
4153 const Plugin& plugin,
4154 const UsefulUrids& urids,
4155 uint32_t controlPortIndex)
4156{
4157 // This returns our writable parameters in an indeterminate order.
4158 // We want our parameters to be in a consistent order between runs, so
4159 // we'll create all the parameters in one pass, sort them, and then
4160 // add them in a separate pass.
4161 const auto writableControls = world.findNodes (plugin.getUri().get(),
4162 world.newUri (LV2_PATCH__writable).get(),
4163 nullptr);
4164
4165 struct DataAndUri
4166 {
4167 ParameterData data;
4168 String uri;
4169 };
4170
4171 std::vector<DataAndUri> resultWithUris;
4172
4173 const auto groupNode = world.newUri (LV2_PORT_GROUPS__group);
4174
4175 for (auto* ctrl : writableControls)
4176 {
4177 const auto labelString = [&]
4178 {
4179 if (auto label = world.get (ctrl, world.newUri (LILV_NS_RDFS "label").get(), nullptr))
4180 return String::fromUTF8 (lilv_node_as_string (label.get()));
4181
4182 return String();
4183 }();
4184
4185 const auto uri = String::fromUTF8 (lilv_node_as_uri (ctrl));
4186 const auto info = getInfoForPatchParameter (world, urids, world.newUri (uri.toRawUTF8()));
4187
4188 if (! info.supported)
4189 continue;
4190
4191 const auto write = [&]
4192 {
4193 if (info.type == urids.mLV2_ATOM__Int)
4194 return writeIntToForge;
4195
4196 if (info.type == urids.mLV2_ATOM__Long)
4197 return writeLongToForge;
4198
4199 if (info.type == urids.mLV2_ATOM__Double)
4200 return writeDoubleToForge;
4201
4202 if (info.type == urids.mLV2_ATOM__Bool)
4203 return writeBoolToForge;
4204
4205 return writeFloatToForge;
4206 }();
4207
4208 const auto group = lilvNodeToUriString (world.get (ctrl, groupNode.get(), nullptr).get());
4209 resultWithUris.push_back ({ { info.info,
4210 ParameterWriter { write, urids.symap.map (uri.toRawUTF8()), controlPortIndex },
4211 group,
4212 labelString },
4213 uri });
4214 }
4215
4216 const auto compareUris = [] (const DataAndUri& a, const DataAndUri& b) { return a.uri < b.uri; };
4217 std::sort (resultWithUris.begin(), resultWithUris.end(), compareUris);
4218
4220
4221 for (const auto& item : resultWithUris)
4222 result.push_back (item.data);
4223
4224 return result;
4225}
4226
4227static std::vector<ParameterData> getJuceParameterInfo (World& world,
4228 const Plugin& plugin,
4229 const UsefulUrids& urids,
4231 SimpleSpan<ControlPort> controlPorts,
4232 uint32_t controlPortIndex)
4233{
4234 auto port = getPortBasedParameters (world, plugin, hiddenPorts, controlPorts);
4235 auto patch = getPatchBasedParameters (world, plugin, urids, controlPortIndex);
4236
4237 port.insert (port.end(), patch.begin(), patch.end());
4238 return port;
4239}
4240
4241// Rather than sprinkle #ifdef everywhere, risking the wrath of the entire C++
4242// standards committee, we put all of our conditionally-compiled stuff into a
4243// specialised template that compiles away to nothing when editor support is
4244// not available.
4245#if JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD
4246 constexpr auto editorFunctionalityEnabled = true;
4247#else
4248 constexpr auto editorFunctionalityEnabled = false;
4249#endif
4250
4251template <bool editorEnabled = editorFunctionalityEnabled> class OptionalEditor;
4252
4253template <>
4254class OptionalEditor<true>
4255{
4256public:
4257 OptionalEditor (String uiBundleUriIn, UiDescriptor uiDescriptorIn, std::function<void()> timerCallback)
4258 : uiBundleUri (std::move (uiBundleUriIn)),
4259 uiDescriptor (std::move (uiDescriptorIn)),
4260 changedParameterFlusher (std::move (timerCallback)) {}
4261
4262 void createView()
4263 {
4264 if (auto* editor = editorPointer.getComponent())
4265 editor->createView();
4266 }
4267
4268 void destroyView()
4269 {
4270 if (auto* editor = editorPointer.getComponent())
4271 editor->destroyView();
4272 }
4273
4274 std::unique_ptr<AudioProcessorEditor> createEditor (World& world,
4275 AudioPluginInstance& p,
4276 InstanceProvider& instanceProviderIn,
4277 TouchListener& touchListenerIn,
4278 EditorListener& listenerIn)
4279 {
4280 if (! hasEditor())
4281 return nullptr;
4282
4283 const auto queryFeatures = [this, &world] (const char* kind)
4284 {
4285 return world.findNodes (world.newUri (uiDescriptor.get()->URI).get(),
4286 world.newUri (kind).get(),
4287 nullptr);
4288 };
4289
4290 auto newEditor = std::make_unique<Editor> (world,
4291 p,
4292 instanceProviderIn,
4293 uiDescriptor,
4294 touchListenerIn,
4295 listenerIn,
4296 uiBundleUri,
4297 RequiredFeatures { queryFeatures (LV2_CORE__requiredFeature) },
4298 OptionalFeatures { queryFeatures (LV2_CORE__optionalFeature) });
4299
4300 editorPointer = newEditor.get();
4301
4302 changedParameterFlusher.startTimerHz (60);
4303
4304 return newEditor;
4305 }
4306
4307 bool hasEditor() const
4308 {
4309 return uiDescriptor.get() != nullptr;
4310 }
4311
4312 void prepareToDestroyEditor()
4313 {
4314 changedParameterFlusher.stopTimer();
4315 }
4316
4317private:
4318 Component::SafePointer<Editor> editorPointer = nullptr;
4319 String uiBundleUri;
4320 UiDescriptor uiDescriptor;
4321 TimedCallback changedParameterFlusher;
4322};
4323
4324template <>
4325class OptionalEditor<false>
4326{
4327public:
4328 OptionalEditor (String, UiDescriptor, std::function<void()>) {}
4329
4330 void createView() {}
4331 void destroyView() {}
4332
4333 std::unique_ptr<AudioProcessorEditor> createEditor (World&,
4334 AudioPluginInstance&,
4335 InstanceProvider&,
4336 TouchListener&,
4337 EditorListener&)
4338 {
4339 return nullptr;
4340 }
4341
4342 bool hasEditor() const { return false; }
4343 void prepareToDestroyEditor() {}
4344};
4345
4346//==============================================================================
4347class LV2AudioPluginInstance final : public AudioPluginInstance,
4348 private TouchListener,
4349 private EditorListener,
4350 private InstanceProvider
4351{
4352public:
4353 LV2AudioPluginInstance (std::shared_ptr<World> worldIn,
4354 const Plugin& pluginIn,
4355 const UsefulUris& uris,
4357 PluginDescription&& desc,
4358 std::vector<String> knownPresetUris,
4359 PluginState stateToApply,
4360 String uiBundleUriIn,
4361 UiDescriptor uiDescriptorIn)
4362 : LV2AudioPluginInstance (worldIn,
4363 pluginIn,
4364 std::move (in),
4365 std::move (desc),
4366 std::move (knownPresetUris),
4367 std::move (stateToApply),
4368 std::move (uiBundleUriIn),
4369 std::move (uiDescriptorIn),
4370 getParsedBuses (*worldIn, pluginIn, uris)) {}
4371
4372 void fillInPluginDescription (PluginDescription& d) const override { d = description; }
4373
4374 const String getName() const override { return description.name; }
4375
4376 void prepareToPlay (double sampleRate, int numSamples) override
4377 {
4378 // In REAPER, changing the sample rate will deactivate the plugin,
4379 // save its state, destroy it, create a new instance, restore the
4380 // state, and then activate the new instance.
4381 // We'll do the same, because there's no way to retroactively change the
4382 // plugin sample rate.
4383 // This is a bit expensive, so try to avoid changing the sample rate too
4384 // frequently.
4385
4386 // In addition to the above, we also need to destroy the custom view,
4387 // and recreate it after creating the new plugin instance.
4388 // Ideally this should all happen in the same Component.
4389
4390 deactivate();
4391 destroyView();
4392
4393 MemoryBlock mb;
4394 getStateInformation (mb);
4395
4396 instance = std::make_unique<InstanceWithSupports> (*world,
4397 std::move (instance->symap),
4398 plugin,
4399 std::move (instance->ports),
4400 numSamples,
4401 sampleRate);
4402
4403 // prepareToPlay is *guaranteed* not to be called concurrently with processBlock
4404 setStateInformationImpl (mb.getData(), (int) mb.getSize(), ConcurrentWithAudioCallback::no);
4405
4406 jassert (numSamples == instance->features.getMaxBlockSize());
4407
4408 optionalEditor.createView();
4409 activate();
4410 }
4411
4412 void releaseResources() override { deactivate(); }
4413
4414 using AudioPluginInstance::processBlock;
4415 using AudioPluginInstance::processBlockBypassed;
4416
4417 void processBlock (AudioBuffer<float>& audio, MidiBuffer& midi) override
4418 {
4419 processBlockImpl (audio, midi);
4420 }
4421
4422 void processBlockBypassed (AudioBuffer<float>& audio, MidiBuffer& midi) override
4423 {
4424 if (bypassParam != nullptr)
4425 processBlockImpl (audio, midi);
4426 else
4427 AudioPluginInstance::processBlockBypassed (audio, midi);
4428 }
4429
4430 double getTailLengthSeconds() const override { return {}; } // TODO
4431
4432 bool acceptsMidi() const override
4433 {
4434 if (instance == nullptr)
4435 return false;
4436
4437 auto ports = instance->ports.getAtomPorts();
4438
4439 return std::any_of (ports.begin(), ports.end(), [&] (const AtomPort& a)
4440 {
4441 if (a.header.direction != Port::Direction::input)
4442 return false;
4443
4444 return portAtIndexSupportsMidi (a.header.index);
4445 });
4446 }
4447
4448 bool producesMidi() const override
4449 {
4450 if (instance == nullptr)
4451 return false;
4452
4453 auto ports = instance->ports.getAtomPorts();
4454
4455 return std::any_of (ports.begin(), ports.end(), [&] (const AtomPort& a)
4456 {
4457 if (a.header.direction != Port::Direction::output)
4458 return false;
4459
4460 return portAtIndexSupportsMidi (a.header.index);
4461 });
4462 }
4463
4464 AudioProcessorEditor* createEditor() override
4465 {
4466 return optionalEditor.createEditor (*world, *this, *this, *this, *this).release();
4467 }
4468
4469 bool hasEditor() const override
4470 {
4471 return optionalEditor.hasEditor();
4472 }
4473
4474 int getNumPrograms() override { return (int) presetUris.size(); }
4475
4476 int getCurrentProgram() override
4477 {
4478 return lastAppliedPreset;
4479 }
4480
4481 void setCurrentProgram (int newProgram) override
4482 {
4484
4485 if (! isPositiveAndBelow (newProgram, presetUris.size()))
4486 return;
4487
4488 lastAppliedPreset = newProgram;
4489 applyStateWithAppropriateLocking (loadStateWithUri (presetUris[(size_t) newProgram]),
4490 ConcurrentWithAudioCallback::yes);
4491 }
4492
4493 const String getProgramName (int program) override
4494 {
4496
4497 if (isPositiveAndBelow (program, presetUris.size()))
4498 return loadStateWithUri (presetUris[(size_t) program]).getLabel();
4499
4500 return {};
4501 }
4502
4503 void changeProgramName (int program, const String& label) override
4504 {
4506
4507 if (isPositiveAndBelow (program, presetUris.size()))
4508 loadStateWithUri (presetUris[(size_t) program]).setLabel (label);
4509 }
4510
4511 void getStateInformation (MemoryBlock& block) override
4512 {
4514
4515 // TODO where should the state URI come from?
4516 PortMap portStateManager (instance->ports);
4517 const auto stateUri = String::fromUTF8 (instance->instance.getUri()) + "/savedState";
4518 auto mapFeature = instance->symap->getMapFeature();
4519 auto unmapFeature = instance->symap->getUnmapFeature();
4520 const auto state = PluginState::SaveRestoreHandle (*instance, portStateManager).save (plugin.get(), &mapFeature);
4521 const auto string = state.toString (world->get(), &mapFeature, &unmapFeature, stateUri.toRawUTF8());
4522 block.replaceAll (string.data(), string.size());
4523 }
4524
4525 void setStateInformation (const void* data, int size) override
4526 {
4527 setStateInformationImpl (data, size, ConcurrentWithAudioCallback::yes);
4528 }
4529
4530 void setNonRealtime (bool newValue) noexcept override
4531 {
4533
4534 AudioPluginInstance::setNonRealtime (newValue);
4535 instance->features.setNonRealtime (newValue);
4536 }
4537
4538 bool isBusesLayoutSupported (const BusesLayout& layout) const override
4539 {
4540 for (const auto& pair : { std::make_tuple (&layout.inputBuses, &declaredBusLayout.inputs),
4541 std::make_tuple (&layout.outputBuses, &declaredBusLayout.outputs) })
4542 {
4543 const auto& requested = *std::get<0> (pair);
4544 const auto& allowed = *std::get<1> (pair);
4545
4546 if ((size_t) requested.size() != allowed.size())
4547 return false;
4548
4549 for (size_t busIndex = 0; busIndex < allowed.size(); ++busIndex)
4550 {
4551 const auto& requestedBus = requested[(int) busIndex];
4552 const auto& allowedBus = allowed[busIndex];
4553
4554 if (! allowedBus.isCompatible (requestedBus))
4555 return false;
4556 }
4557 }
4558
4559 return true;
4560 }
4561
4562 void processorLayoutsChanged() override { ioMap = lv2_shared::PortToAudioBufferMap { getBusesLayout(), declaredBusLayout }; }
4563
4564 AudioProcessorParameter* getBypassParameter() const override { return bypassParam; }
4565
4566private:
4567 enum class ConcurrentWithAudioCallback { no, yes };
4568
4569 LV2AudioPluginInstance (std::shared_ptr<World> worldIn,
4570 const Plugin& pluginIn,
4572 PluginDescription&& desc,
4573 std::vector<String> knownPresetUris,
4574 PluginState stateToApply,
4575 String uiBundleUriIn,
4576 UiDescriptor uiDescriptorIn,
4577 const lv2_shared::ParsedBuses& parsedBuses)
4578 : AudioPluginInstance (getBusesProperties (parsedBuses, *worldIn)),
4579 declaredBusLayout (parsedBuses),
4580 world (std::move (worldIn)),
4581 plugin (pluginIn),
4582 description (std::move (desc)),
4583 presetUris (std::move (knownPresetUris)),
4584 instance (std::move (in)),
4585 optionalEditor (std::move (uiBundleUriIn),
4586 std::move (uiDescriptorIn),
4587 [this] { postChangedParametersToUi(); })
4588 {
4589 applyStateWithAppropriateLocking (std::move (stateToApply), ConcurrentWithAudioCallback::no);
4590 }
4591
4592 void setStateInformationImpl (const void* data, int size, ConcurrentWithAudioCallback concurrent)
4593 {
4595
4596 if (data == nullptr || size == 0)
4597 return;
4598
4599 auto begin = static_cast<const char*> (data);
4600 std::vector<char> copy (begin, begin + size);
4601 copy.push_back (0);
4602 auto mapFeature = instance->symap->getMapFeature();
4603 applyStateWithAppropriateLocking (PluginState { lilv_state_new_from_string (world->get(), &mapFeature, copy.data()) },
4604 concurrent);
4605 }
4606
4607 // This does *not* destroy the editor component.
4608 // If we destroy the processor, the view must also be destroyed to avoid dangling pointers.
4609 // However, JUCE clients expect their editors to remain valid for the duration of the
4610 // AudioProcessor's lifetime.
4611 // As a compromise, this will create a new LV2 view into an existing editor component.
4612 void destroyView()
4613 {
4614 optionalEditor.destroyView();
4615 }
4616
4617 void activate()
4618 {
4619 if (! active)
4620 instance->instance.activate();
4621
4622 active = true;
4623 }
4624
4625 void deactivate()
4626 {
4627 if (active)
4628 instance->instance.deactivate();
4629
4630 active = false;
4631 }
4632
4633 void processBlockImpl (AudioBuffer<float>& audio, MidiBuffer& midi)
4634 {
4635 preparePortsForRun (audio, midi);
4636
4637 instance->instance.run (static_cast<uint32_t> (audio.getNumSamples()));
4638 instance->features.processResponses();
4639
4640 processPortsAfterRun (midi);
4641 }
4642
4643 bool portAtIndexSupportsMidi (uint32_t index) const noexcept
4644 {
4645 const auto port = plugin.getPortByIndex (index);
4646
4647 if (! port.isValid())
4648 return false;
4649
4650 return port.supportsEvent (world->newUri (LV2_MIDI__MidiEvent).get());
4651 }
4652
4653 void controlGrabbed (uint32_t port, bool grabbed) override
4654 {
4655 if (auto* param = parameterValues.getParamByPortIndex (port))
4656 {
4657 if (grabbed)
4658 param->beginChangeGesture();
4659 else
4660 param->endChangeGesture();
4661 }
4662 }
4663
4664 void viewCreated (UiEventListener* newListener) override
4665 {
4666 uiEventListener = newListener;
4667 postAllParametersToUi();
4668 }
4669
4670 ParameterWriterUrids getParameterWriterUrids() const
4671 {
4672 return { instance->urids.mLV2_PATCH__Set,
4673 instance->urids.mLV2_PATCH__property,
4674 instance->urids.mLV2_PATCH__value,
4675 instance->urids.mLV2_ATOM__eventTransfer };
4676 }
4677
4678 void postAllParametersToUi()
4679 {
4680 parameterValues.postAllParametersToUi (uiEventListener, getParameterWriterUrids(), *instance->processorToUi);
4681 controlPortStructure.writeOutputPorts (uiEventListener, *instance->processorToUi);
4682 }
4683
4684 void postChangedParametersToUi()
4685 {
4686 parameterValues.postChangedParametersToUi (uiEventListener, getParameterWriterUrids(), *instance->processorToUi);
4687 controlPortStructure.writeOutputPorts (uiEventListener, *instance->processorToUi);
4688 }
4689
4690 void notifyEditorBeingDeleted() override
4691 {
4692 optionalEditor.prepareToDestroyEditor();
4693 uiEventListener = nullptr;
4694 editorBeingDeleted (getActiveEditor());
4695 }
4696
4697 InstanceWithSupports* getInstanceWithSupports() const override
4698 {
4699 return instance.get();
4700 }
4701
4702 void applyStateWithAppropriateLocking (PluginState&& state, ConcurrentWithAudioCallback concurrent)
4703 {
4704 PortMap portStateManager (instance->ports);
4705
4706 // If a plugin supports threadSafeRestore, its restore method is thread-safe
4707 // and may be called concurrently with audio class functions.
4708 if (hasThreadSafeRestore || concurrent == ConcurrentWithAudioCallback::no)
4709 {
4710 state.restore (*instance, portStateManager);
4711 }
4712 else
4713 {
4714 const ScopedLock lock (getCallbackLock());
4715 state.restore (*instance, portStateManager);
4716 }
4717
4718 parameterValues.updateFromControlPorts (controlPortStructure);
4719 asyncFullUiParameterUpdate.triggerAsyncUpdate();
4720 }
4721
4722 PluginState loadStateWithUri (const String& str)
4723 {
4724 auto mapFeature = instance->symap->getMapFeature();
4725 const auto presetUri = world->newUri (str.toRawUTF8());
4726 lilv_world_load_resource (world->get(), presetUri.get());
4727 return PluginState { lilv_state_new_from_world (world->get(), &mapFeature, presetUri.get()) };
4728 }
4729
4730 void connectPorts (AudioBuffer<float>& audio)
4731 {
4732 // Plugins that cannot process in-place will require the feature "inPlaceBroken".
4733 // We don't support that feature, so if we made it to this point we can assume that
4734 // in-place processing works.
4735 for (const auto& port : instance->ports.getAudioPorts())
4736 {
4737 const auto channel = ioMap.getChannelForPort (port.header.index);
4738 auto* ptr = isPositiveAndBelow (channel, audio.getNumChannels()) ? audio.getWritePointer (channel)
4739 : nullptr;
4740 instance->instance.connectPort (port.header.index, ptr);
4741 }
4742
4743 for (const auto& port : instance->ports.getCvPorts())
4744 instance->instance.connectPort (port.header.index, nullptr);
4745
4746 for (auto& port : instance->ports.getAtomPorts())
4747 instance->instance.connectPort (port.header.index, port.data());
4748 }
4749
4750 void writeTimeInfoToPort (AtomPort& port)
4751 {
4752 if (port.header.direction != Port::Direction::input || ! port.getSupportsTime())
4753 return;
4754
4755 auto* forge = port.getForge().get();
4756 auto* playhead = getPlayHead();
4757
4758 if (playhead == nullptr)
4759 return;
4760
4761 // Write timing info to the control port
4762 const auto info = playhead->getPosition();
4763
4764 if (! info.hasValue())
4765 return;
4766
4767 const auto& urids = instance->urids;
4768
4769 lv2_atom_forge_frame_time (forge, 0);
4770
4771 lv2_shared::ObjectFrame object { forge, (uint32_t) 0, urids.mLV2_TIME__Position };
4772
4773 lv2_atom_forge_key (forge, urids.mLV2_TIME__speed);
4774 lv2_atom_forge_float (forge, info->getIsPlaying() ? 1.0f : 0.0f);
4775
4776 if (const auto samples = info->getTimeInSamples())
4777 {
4778 lv2_atom_forge_key (forge, urids.mLV2_TIME__frame);
4779 lv2_atom_forge_long (forge, *samples);
4780 }
4781
4782 if (const auto bar = info->getBarCount())
4783 {
4784 lv2_atom_forge_key (forge, urids.mLV2_TIME__bar);
4785 lv2_atom_forge_long (forge, *bar);
4786 }
4787
4788 if (const auto beat = info->getPpqPosition())
4789 {
4790 if (const auto barStart = info->getPpqPositionOfLastBarStart())
4791 {
4792 lv2_atom_forge_key (forge, urids.mLV2_TIME__barBeat);
4793 lv2_atom_forge_float (forge, (float) (*beat - *barStart));
4794 }
4795
4796 lv2_atom_forge_key (forge, urids.mLV2_TIME__beat);
4797 lv2_atom_forge_double (forge, *beat);
4798 }
4799
4800 if (const auto sig = info->getTimeSignature())
4801 {
4802 lv2_atom_forge_key (forge, urids.mLV2_TIME__beatUnit);
4803 lv2_atom_forge_int (forge, sig->denominator);
4804
4805 lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerBar);
4806 lv2_atom_forge_float (forge, (float) sig->numerator);
4807 }
4808
4809 if (const auto bpm = info->getBpm())
4810 {
4811 lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerMinute);
4812 lv2_atom_forge_float (forge, (float) *bpm);
4813 }
4814 }
4815
4816 void preparePortsForRun (AudioBuffer<float>& audio, MidiBuffer& midiBuffer)
4817 {
4818 connectPorts (audio);
4819
4820 for (auto& port : instance->ports.getAtomPorts())
4821 {
4822 switch (port.header.direction)
4823 {
4824 case Port::Direction::input:
4825 port.beginSequence();
4826 break;
4827
4828 case Port::Direction::output:
4829 port.replaceWithChunk();
4830 break;
4831
4832 case Port::Direction::unknown:
4834 break;
4835 }
4836 }
4837
4838 for (auto& port : instance->ports.getAtomPorts())
4839 writeTimeInfoToPort (port);
4840
4841 const auto controlPortForge = controlPort != nullptr ? controlPort->getForge().get()
4842 : nullptr;
4843
4844 parameterValues.postChangedParametersToProcessor (getParameterWriterUrids(), controlPortForge);
4845
4846 instance->uiToProcessor.readAllAndClear ([this] (MessageHeader header, uint32_t size, const void* buffer)
4847 {
4848 pushMessage (header, size, buffer);
4849 });
4850
4851 for (auto& port : instance->ports.getAtomPorts())
4852 {
4853 if (port.header.direction == Port::Direction::input)
4854 {
4855 for (const auto meta : midiBuffer)
4856 {
4857 port.addEventToSequence (meta.samplePosition,
4858 instance->urids.mLV2_MIDI__MidiEvent,
4859 static_cast<uint32_t> (meta.numBytes),
4860 meta.data);
4861 }
4862
4863 port.endSequence();
4864 }
4865 }
4866
4867 if (freeWheelingPort != nullptr)
4868 freeWheelingPort->currentValue = isNonRealtime() ? freeWheelingPort->info.max
4869 : freeWheelingPort->info.min;
4870 }
4871
4872 void pushMessage (MessageHeader header, [[maybe_unused]] uint32_t size, const void* data)
4873 {
4874 if (header.protocol == 0 || header.protocol == instance->urids.mLV2_UI__floatProtocol)
4875 {
4876 const auto value = readUnaligned<float> (data);
4877
4878 if (auto* param = parameterValues.getParamByPortIndex (header.portIndex))
4879 {
4880 param->setDenormalisedValue (value);
4881 }
4882 else if (auto* port = controlPortStructure.getControlPortByIndex (header.portIndex))
4883 {
4884 // No parameter corresponds to this port, write to the port directly
4885 port->currentValue = value;
4886 }
4887 }
4888 else if (auto* atomPort = header.portIndex < atomPorts.size() ? atomPorts[header.portIndex] : nullptr)
4889 {
4890 if (header.protocol == instance->urids.mLV2_ATOM__eventTransfer)
4891 {
4892 if (const auto* atom = convertToAtomPtr (data, (size_t) size))
4893 {
4894 atomPort->addAtomToSequence (0, atom);
4895
4896 // Not UB; LV2_Atom_Object has LV2_Atom as its first member
4897 if (atom->type == instance->urids.mLV2_ATOM__Object)
4898 patchSetHelper.processPatchSet (reinterpret_cast<const LV2_Atom_Object*> (data), PatchSetCallback { parameterValues });
4899 }
4900 }
4901 else if (header.protocol == instance->urids.mLV2_ATOM__atomTransfer)
4902 {
4903 if (const auto* atom = convertToAtomPtr (data, (size_t) size))
4904 atomPort->replaceBufferWithAtom (atom);
4905 }
4906 }
4907 }
4908
4909 void processPortsAfterRun (MidiBuffer& midi)
4910 {
4911 midi.clear();
4912
4913 for (auto& port : instance->ports.getAtomPorts())
4914 processAtomPort (port, midi);
4915
4916 if (latencyPort != nullptr)
4917 setLatencySamples ((int) latencyPort->currentValue);
4918 }
4919
4920 void processAtomPort (const AtomPort& port, MidiBuffer& midi)
4921 {
4922 if (port.header.direction != Port::Direction::output)
4923 return;
4924
4925 // The port holds an Atom, by definition
4926 const auto* atom = reinterpret_cast<const LV2_Atom*> (port.data());
4927
4928 if (atom->type != instance->urids.mLV2_ATOM__Sequence)
4929 return;
4930
4931 // The Atom said that it was of sequence type, so this isn't UB
4932 const auto* sequence = reinterpret_cast<const LV2_Atom_Sequence*> (port.data());
4933
4934 // http://lv2plug.in/ns/ext/atom#Sequence - run() stamps are always audio frames
4935 jassert (sequence->body.unit == 0 || sequence->body.unit == instance->urids.mLV2_UNITS__frame);
4936
4937 for (const auto* event : lv2_shared::SequenceIterator { lv2_shared::SequenceWithSize { sequence } })
4938 {
4939 // At the moment, we forward all outgoing events to the UI.
4940 instance->processorToUi->pushMessage ({ uiEventListener, { port.header.index, instance->urids.mLV2_ATOM__eventTransfer } },
4941 (uint32_t) (event->body.size + sizeof (LV2_Atom)),
4942 &event->body);
4943
4944 if (event->body.type == instance->urids.mLV2_MIDI__MidiEvent)
4945 midi.addEvent (event + 1, static_cast<int> (event->body.size), static_cast<int> (event->time.frames));
4946
4947 if (lv2_atom_forge_is_object_type (port.getForge().get(), event->body.type))
4948 if (reinterpret_cast<const LV2_Atom_Object_Body*> (event + 1)->otype == instance->urids.mLV2_STATE__StateChanged)
4949 updateHostDisplay (ChangeDetails{}.withNonParameterStateChanged (true));
4950
4951 patchSetHelper.processPatchSet (event, PatchSetCallback { parameterValues });
4952 }
4953 }
4954
4955 // Check for duplicate channel designations, and convert the set to a discrete channel layout
4956 // if any designations are duplicated.
4957 static std::set<lv2_shared::SinglePortInfo> validateAndRedesignatePorts (std::set<lv2_shared::SinglePortInfo> info)
4958 {
4959 const auto channelSet = lv2_shared::ParsedGroup::getEquivalentSet (info);
4960
4961 if ((int) info.size() == channelSet.size())
4962 return info;
4963
4965 auto designation = (int) AudioChannelSet::discreteChannel0;
4966
4967 for (auto& item : info)
4968 {
4969 auto copy = item;
4970 copy.designation = (AudioChannelSet::ChannelType) designation++;
4971 result.insert (copy);
4972 }
4973
4974 return result;
4975 }
4976
4977 static AudioChannelSet::ChannelType getPortDesignation (World& world, const Port& port, size_t indexInGroup)
4978 {
4979 const auto defaultResult = (AudioChannelSet::ChannelType) (AudioChannelSet::discreteChannel0 + indexInGroup);
4980 const auto node = port.get (world.newUri (LV2_CORE__designation).get());
4981
4982 if (node == nullptr)
4983 return defaultResult;
4984
4985 const auto it = lv2_shared::channelDesignationMap.find (lilvNodeToUriString (node.get()));
4986
4987 if (it == lv2_shared::channelDesignationMap.end())
4988 return defaultResult;
4989
4990 return it->second;
4991 }
4992
4993 static lv2_shared::ParsedBuses getParsedBuses (World& world, const Plugin& p, const UsefulUris& uris)
4994 {
4995 const auto groupPropertyUri = world.newUri (LV2_PORT_GROUPS__group);
4996 const auto optionalUri = world.newUri (LV2_CORE__connectionOptional);
4997
4999 std::set<lv2_shared::SinglePortInfo> ungroupedInputs, ungroupedOutputs;
5000
5001 for (uint32_t i = 0, numPorts = p.getNumPorts(); i < numPorts; ++i)
5002 {
5003 const auto port = p.getPortByIndex (i);
5004
5005 if (port.getKind (uris) != Port::Kind::audio)
5006 continue;
5007
5008 const auto groupUri = lilvNodeToUriString (port.get (groupPropertyUri.get()).get());
5009
5010 auto& set = [&]() -> auto&
5011 {
5012 if (groupUri.isEmpty())
5013 return port.getDirection (uris) == Port::Direction::input ? ungroupedInputs : ungroupedOutputs;
5014
5015 auto& group = port.getDirection (uris) == Port::Direction::input ? inputGroups : outputGroups;
5016 return group[groupUri];
5017 }();
5018
5019 set.insert ({ port.getIndex(), getPortDesignation (world, port, set.size()), port.hasProperty (optionalUri) });
5020 }
5021
5022 for (auto* groups : { &inputGroups, &outputGroups })
5023 for (auto& pair : *groups)
5024 pair.second = validateAndRedesignatePorts (std::move (pair.second));
5025
5026 JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4702)
5027 const auto getMainGroupName = [&] (const char* propertyName)
5028 {
5029 for (const auto* item : p.getValue (world.newUri (propertyName).get()))
5030 return lilvNodeToUriString (item);
5031
5032 return String{};
5033 };
5034 JUCE_END_IGNORE_WARNINGS_MSVC
5035
5036 return { findStableBusOrder (getMainGroupName (LV2_PORT_GROUPS__mainInput), inputGroups, ungroupedInputs),
5037 findStableBusOrder (getMainGroupName (LV2_PORT_GROUPS__mainOutput), outputGroups, ungroupedOutputs) };
5038 }
5039
5040 static String getNameForUri (World& world, StringRef uri)
5041 {
5042 if (uri.isEmpty())
5043 return String();
5044
5045 const auto node = world.get (world.newUri (uri).get(),
5046 world.newUri (LV2_CORE__name).get(),
5047 nullptr);
5048
5049 if (node == nullptr)
5050 return String();
5051
5052 return String::fromUTF8 (lilv_node_as_string (node.get()));
5053 }
5054
5055 static BusesProperties getBusesProperties (const lv2_shared::ParsedBuses& parsedBuses, World& world)
5056 {
5057 BusesProperties result;
5058
5059 for (const auto& pair : { std::make_tuple (&parsedBuses.inputs, &result.inputLayouts),
5060 std::make_tuple (&parsedBuses.outputs, &result.outputLayouts) })
5061 {
5062 const auto& buses = *std::get<0> (pair);
5063 auto& layout = *std::get<1> (pair);
5064
5065 for (const auto& bus : buses)
5066 {
5067 layout.add (AudioProcessor::BusProperties { getNameForUri (world, bus.uid),
5068 bus.getEquivalentSet(),
5069 bus.isRequired() });
5070 }
5071 }
5072
5073 return result;
5074 }
5075
5076 LV2_URID map (const char* str) const
5077 {
5078 return instance != nullptr ? instance->symap->map (str)
5079 : LV2_URID();
5080 }
5081
5082 ControlPort* findControlPortWithIndex (uint32_t index) const
5083 {
5084 auto ports = instance->ports.getControlPorts();
5085 const auto indexMatches = [&] (const ControlPort& p) { return p.header.index == index; };
5086 const auto it = std::find_if (ports.begin(), ports.end(), indexMatches);
5087
5088 return it != ports.end() ? &(*it) : nullptr;
5089 }
5090
5091 const lv2_shared::ParsedBuses declaredBusLayout;
5092 lv2_shared::PortToAudioBufferMap ioMap { getBusesLayout(), declaredBusLayout };
5094 Plugin plugin;
5095 PluginDescription description;
5096 std::vector<String> presetUris;
5098 AsyncFn asyncFullUiParameterUpdate { [this] { postAllParametersToUi(); } };
5099
5100 std::vector<AtomPort*> atomPorts = getPortPointers (instance->ports.getAtomPorts());
5101
5102 AtomPort* const controlPort = [&]() -> AtomPort*
5103 {
5104 const auto port = plugin.getPortByDesignation (world->newUri (LV2_CORE__InputPort).get(),
5105 world->newUri (LV2_CORE__control).get());
5106
5107 if (! port.isValid())
5108 return nullptr;
5109
5110 const auto index = port.getIndex();
5111
5112 if (! isPositiveAndBelow (index, atomPorts.size()))
5113 return nullptr;
5114
5115 return atomPorts[index];
5116 }();
5117
5118 ControlPort* const latencyPort = [&]() -> ControlPort*
5119 {
5120 if (! plugin.hasLatency())
5121 return nullptr;
5122
5123 return findControlPortWithIndex (plugin.getLatencyPortIndex());
5124 }();
5125
5126 ControlPort* const freeWheelingPort = [&]() -> ControlPort*
5127 {
5128 const auto port = plugin.getPortByDesignation (world->newUri (LV2_CORE__InputPort).get(),
5129 world->newUri (LV2_CORE__freeWheeling).get());
5130
5131 if (! port.isValid())
5132 return nullptr;
5133
5134 return findControlPortWithIndex (port.getIndex());
5135 }();
5136
5137 ControlPort* const enabledPort = [&]() -> ControlPort*
5138 {
5139 const auto port = plugin.getPortByDesignation (world->newUri (LV2_CORE__InputPort).get(),
5140 world->newUri (LV2_CORE_PREFIX "enabled").get());
5141
5142 if (! port.isValid())
5143 return nullptr;
5144
5145 return findControlPortWithIndex (port.getIndex());
5146 }();
5147
5148 lv2_shared::PatchSetHelper patchSetHelper { instance->symap->getMapFeature(), plugin.getUri().getTyped() };
5149 ControlPortAccelerationStructure controlPortStructure { instance->ports.getControlPorts() };
5150 ParameterValueCache parameterValues { *this,
5151 *world,
5152 instance->symap->getMapFeature(),
5153 getJuceParameterInfo (*world,
5154 plugin,
5155 instance->urids,
5156 { latencyPort, freeWheelingPort },
5157 instance->ports.getControlPorts(),
5158 controlPort != nullptr ? controlPort->header.index : 0),
5159 enabledPort };
5160 LV2Parameter* bypassParam = enabledPort != nullptr ? parameterValues.getParamByPortIndex (enabledPort->header.index)
5161 : nullptr;
5162
5163 std::atomic<UiEventListener*> uiEventListener { nullptr };
5164 OptionalEditor<> optionalEditor;
5165 int lastAppliedPreset = 0;
5166 bool hasThreadSafeRestore = plugin.hasExtensionData (world->newUri (LV2_STATE__threadSafeRestore));
5167 bool active { false };
5168
5169 JUCE_LEAK_DETECTOR (LV2AudioPluginInstance)
5170};
5171
5172} // namespace lv2_host
5173
5174//==============================================================================
5175class LV2PluginFormat::Pimpl
5176{
5177public:
5178 Pimpl()
5179 {
5180 loadAllPluginsFromPaths (getDefaultLocationsToSearch());
5181
5182 const auto tempFile = lv2ResourceFolder.getFile();
5183
5184 if (tempFile.createDirectory())
5185 {
5186 for (const auto& bundle : lv2::Bundle::getAllBundles())
5187 {
5188 const auto pathToBundle = tempFile.getChildFile (bundle.name + String (".lv2"));
5189
5190 if (! pathToBundle.createDirectory())
5191 continue;
5192
5193 for (const auto& resource : bundle.contents)
5194 pathToBundle.getChildFile (resource.name).replaceWithText (resource.contents);
5195
5196 const auto pathString = File::addTrailingSeparator (pathToBundle.getFullPathName());
5197 world->loadBundle (world->newFileUri (nullptr, pathString.toRawUTF8()));
5198 }
5199 }
5200 }
5201
5202 ~Pimpl()
5203 {
5204 lv2ResourceFolder.getFile().deleteRecursively();
5205 }
5206
5207 void findAllTypesForFile (OwnedArray<PluginDescription>& result,
5208 const String& identifier)
5209 {
5210 if (File::isAbsolutePath (identifier))
5211 world->loadBundle (world->newFileUri (nullptr, File::addTrailingSeparator (identifier).toRawUTF8()));
5212
5213 for (const auto& plugin : { findPluginByUri (identifier), findPluginByFile (identifier) })
5214 {
5215 if (auto desc = getDescription (plugin); desc.fileOrIdentifier.isNotEmpty())
5216 {
5217 result.add (std::make_unique<PluginDescription> (desc));
5218 break;
5219 }
5220 }
5221 }
5222
5223 bool fileMightContainThisPluginType (const String& file) const
5224 {
5225 // If the string looks like a URI, then it could be a valid LV2 identifier
5226 const auto* data = file.toRawUTF8();
5227 const auto numBytes = file.getNumBytesAsUTF8();
5228 std::vector<uint8_t> vec (numBytes + 1, 0);
5229 std::copy (data, data + numBytes, vec.begin());
5230 return serd_uri_string_has_scheme (vec.data()) || file.endsWith (".lv2");
5231 }
5232
5233 String getNameOfPluginFromIdentifier (const String& identifier)
5234 {
5235 // We would have to actually load the bundle to get its name,
5236 // and the bundle may contain multiple plugins
5237 return identifier;
5238 }
5239
5240 bool pluginNeedsRescanning (const PluginDescription&)
5241 {
5242 return true;
5243 }
5244
5245 bool doesPluginStillExist (const PluginDescription& description)
5246 {
5247 return findPluginByUri (description.fileOrIdentifier) != nullptr;
5248 }
5249
5250 StringArray searchPathsForPlugins (const FileSearchPath& paths, bool, bool)
5251 {
5252 loadAllPluginsFromPaths (paths);
5253
5254 StringArray result;
5255
5256 for (const auto* plugin : world->getAllPlugins())
5257 result.add (lv2_host::Plugin { plugin }.getUri().getTyped());
5258
5259 return result;
5260 }
5261
5262 FileSearchPath getDefaultLocationsToSearch()
5263 {
5264 #if JUCE_MAC
5265 return { "~/Library/Audio/Plug-Ins/LV2;"
5266 "~/.lv2;"
5267 "/usr/local/lib/lv2;"
5268 "/usr/lib/lv2;"
5269 "/Library/Audio/Plug-Ins/LV2;" };
5270 #elif JUCE_WINDOWS
5271 return { "%APPDATA%\\LV2;"
5272 "%COMMONPROGRAMFILES%\\LV2" };
5273 #else
5274 #if JUCE_64BIT
5275 if (File ("/usr/lib64/lv2").exists() || File ("/usr/local/lib64/lv2").exists())
5276 return { "~/.lv2;"
5277 "/usr/lib64/lv2;"
5278 "/usr/local/lib64/lv2" };
5279 #endif
5280
5281 return { "~/.lv2;"
5282 "/usr/lib/lv2;"
5283 "/usr/local/lib/lv2" };
5284 #endif
5285 }
5286
5287 const LilvUI* findEmbeddableUi (const lv2_host::Uis* pluginUis, std::true_type)
5288 {
5289 if (pluginUis == nullptr)
5290 return nullptr;
5291
5292 const std::vector<const LilvUI*> allUis (pluginUis->begin(), pluginUis->end());
5293
5294 if (allUis.empty())
5295 return nullptr;
5296
5297 constexpr const char* rawUri =
5298 #if JUCE_MAC
5299 LV2_UI__CocoaUI;
5300 #elif JUCE_WINDOWS
5301 LV2_UI__WindowsUI;
5302 #elif JUCE_LINUX || JUCE_BSD
5303 LV2_UI__X11UI;
5304 #else
5305 nullptr;
5306 #endif
5307
5308 jassert (rawUri != nullptr);
5309 const auto nativeUiUri = world->newUri (rawUri);
5310
5311 struct UiWithSuitability
5312 {
5313 const LilvUI* ui;
5314 unsigned suitability;
5315
5316 bool operator< (const UiWithSuitability& other) const noexcept
5317 {
5318 return suitability < other.suitability;
5319 }
5320
5321 static unsigned uiIsSupported (const char* hostUri, const char* pluginUri)
5322 {
5323 if (strcmp (hostUri, pluginUri) == 0)
5324 return 1;
5325
5326 return 0;
5327 }
5328 };
5329
5330 std::vector<UiWithSuitability> uisWithSuitability;
5331 uisWithSuitability.reserve (allUis.size());
5332
5333 std::transform (allUis.cbegin(), allUis.cend(), std::back_inserter (uisWithSuitability), [&] (const LilvUI* ui)
5334 {
5335 const LilvNode* type = nullptr;
5336 return UiWithSuitability { ui, lilv_ui_is_supported (ui, UiWithSuitability::uiIsSupported, nativeUiUri.get(), &type) };
5337 });
5338
5339 std::sort (uisWithSuitability.begin(), uisWithSuitability.end());
5340
5341 if (uisWithSuitability.back().suitability != 0)
5342 return uisWithSuitability.back().ui;
5343
5344 return nullptr;
5345 }
5346
5347 const LilvUI* findEmbeddableUi (const lv2_host::Uis*, std::false_type)
5348 {
5349 return nullptr;
5350 }
5351
5352 const LilvUI* findEmbeddableUi (const lv2_host::Uis* pluginUis)
5353 {
5354 return findEmbeddableUi (pluginUis, std::integral_constant<bool, lv2_host::editorFunctionalityEnabled>{});
5355 }
5356
5357 static lv2_host::UiDescriptor getUiDescriptor (const LilvUI* ui)
5358 {
5359 if (ui == nullptr)
5360 return {};
5361
5362 const auto libraryFile = StringPtr { lilv_file_uri_parse (lilv_node_as_uri (lilv_ui_get_binary_uri (ui)), nullptr) };
5363
5364 return lv2_host::UiDescriptor { lv2_host::UiDescriptorArgs{}.withLibraryPath (libraryFile.get())
5365 .withUiUri (lilv_node_as_uri (lilv_ui_get_uri (ui))) };
5366 }
5367
5368 // Returns the name of a missing feature, if any.
5369 template <typename RequiredFeatures, typename AvailableFeatures>
5370 static std::vector<String> findMissingFeatures (RequiredFeatures&& required,
5371 AvailableFeatures&& available)
5372 {
5373 std::vector<String> result;
5374
5375 for (const auto* node : required)
5376 {
5377 const auto nodeString = String::fromUTF8 (lilv_node_as_uri (node));
5378
5379 if (std::find (std::begin (available), std::end (available), nodeString) == std::end (available))
5380 result.push_back (nodeString);
5381 }
5382
5383 return result;
5384 }
5385
5386 void createPluginInstance (const PluginDescription& desc,
5387 double initialSampleRate,
5388 int initialBufferSize,
5389 PluginCreationCallback callback)
5390 {
5391 const auto* pluginPtr = findPluginByUri (desc.fileOrIdentifier);
5392
5393 if (pluginPtr == nullptr)
5394 return callback (nullptr, "Unable to locate plugin with the requested URI");
5395
5396 const lv2_host::Plugin plugin { pluginPtr };
5397
5398 auto symap = std::make_unique<lv2_host::SymbolMap>();
5399
5400 const auto missingFeatures = findMissingFeatures (plugin.getRequiredFeatures(),
5401 lv2_host::FeaturesData::getFeatureUris());
5402
5403 if (! missingFeatures.empty())
5404 {
5405 const auto missingFeaturesString = StringArray (missingFeatures.data(), (int) missingFeatures.size()).joinIntoString (", ");
5406
5407 return callback (nullptr, "plugin requires missing features: " + missingFeaturesString);
5408 }
5409
5410 auto stateToApply = [&]
5411 {
5412 if (! plugin.hasFeature (world->newUri (LV2_STATE__loadDefaultState)))
5413 return lv2_host::PluginState{};
5414
5415 auto map = symap->getMapFeature();
5416 return lv2_host::PluginState { lilv_state_new_from_world (world->get(), &map, plugin.getUri().get()) };
5417 }();
5418
5419 auto ports = lv2_host::Ports::getPorts (*world, uris, plugin, *symap);
5420
5421 if (! ports.hasValue())
5422 return callback (nullptr, "Plugin has ports of an unsupported type");
5423
5424 auto instance = std::make_unique<lv2_host::InstanceWithSupports> (*world,
5425 std::move (symap),
5426 plugin,
5427 std::move (*ports),
5428 (int32_t) initialBufferSize,
5429 initialSampleRate);
5430
5431 if (instance->instance == nullptr)
5432 return callback (nullptr, "Plugin was located, but could not be opened");
5433
5434 auto potentialPresets = world->findNodes (nullptr,
5435 world->newUri (LV2_CORE__appliesTo).get(),
5436 plugin.getUri().get());
5437
5438 const lv2_host::Uis pluginUis { plugin.get() };
5439
5440 const auto uiToUse = [&]() -> const LilvUI*
5441 {
5442 const auto bestMatch = findEmbeddableUi (&pluginUis);
5443
5444 if (bestMatch == nullptr)
5445 return bestMatch;
5446
5447 const auto uiUri = lilv_ui_get_uri (bestMatch);
5448 lilv_world_load_resource (world->get(), uiUri);
5449
5450 const auto queryUi = [&] (const char* featureUri)
5451 {
5452 const auto featureUriNode = world->newUri (featureUri);
5453 return world->findNodes (uiUri, featureUriNode.get(), nullptr);
5454 };
5455
5456 const auto missingUiFeatures = findMissingFeatures (queryUi (LV2_CORE__requiredFeature),
5457 lv2_host::UiFeaturesData::getFeatureUris());
5458
5459 return missingUiFeatures.empty() ? bestMatch : nullptr;
5460 }();
5461
5462 auto uiBundleUri = uiToUse != nullptr ? String::fromUTF8 (lilv_node_as_uri (lilv_ui_get_bundle_uri (uiToUse)))
5463 : String();
5464
5465 auto wrapped = std::make_unique<lv2_host::LV2AudioPluginInstance> (world,
5466 plugin,
5467 uris,
5468 std::move (instance),
5469 getDescription (pluginPtr),
5470 findPresetUrisForPlugin (plugin.get()),
5471 std::move (stateToApply),
5472 std::move (uiBundleUri),
5473 getUiDescriptor (uiToUse));
5474 callback (std::move (wrapped), {});
5475 }
5476
5477private:
5478 void loadAllPluginsFromPaths (const FileSearchPath& path)
5479 {
5480 const auto joined = path.toStringWithSeparator (LILV_PATH_SEP);
5481 world->loadAllFromPaths (world->newString (joined.toRawUTF8()));
5482 }
5483
5484 struct Free { void operator() (char* ptr) const noexcept { free (ptr); } };
5485 using StringPtr = std::unique_ptr<char, Free>;
5486
5487 const LilvPlugin* findPluginByUri (const String& s)
5488 {
5489 return world->getAllPlugins().getByUri (world->newUri (s.toRawUTF8()));
5490 }
5491
5492 const LilvPlugin* findPluginByFile (const File& f)
5493 {
5494 return world->getAllPlugins().getByFile (f);
5495 }
5496
5497 template <typename Fn>
5498 void visitParentClasses (const LilvPluginClass* c, Fn&& fn) const
5499 {
5500 if (c == nullptr)
5501 return;
5502
5503 const lv2_host::PluginClass wrapped { c };
5504 fn (wrapped);
5505
5506 const auto parentUri = wrapped.getParentUri();
5507
5508 if (parentUri.get() != nullptr)
5509 visitParentClasses (world->getPluginClasses().getByUri (parentUri), fn);
5510 }
5511
5512 std::vector<lv2_host::NodeUri> collectPluginClassUris (const LilvPluginClass* c) const
5513 {
5515
5516 visitParentClasses (c, [&results] (const lv2_host::PluginClass& wrapped)
5517 {
5518 results.emplace_back (wrapped.getUri());
5519 });
5520
5521 return results;
5522 }
5523
5524 PluginDescription getDescription (const LilvPlugin* plugin)
5525 {
5526 if (plugin == nullptr)
5527 return {};
5528
5529 const auto wrapped = lv2_host::Plugin { plugin };
5530 const auto bundle = wrapped.getBundleUri().getTyped();
5531 const auto bundleFile = File { StringPtr { lilv_file_uri_parse (bundle, nullptr) }.get() };
5532
5533 const auto numInputs = wrapped.getNumPortsOfClass (uris.mLV2_CORE__AudioPort, uris.mLV2_CORE__InputPort);
5534 const auto numOutputs = wrapped.getNumPortsOfClass (uris.mLV2_CORE__AudioPort, uris.mLV2_CORE__OutputPort);
5535
5536 PluginDescription result;
5537 result.name = wrapped.getName().getTyped();
5538 result.descriptiveName = wrapped.getName().getTyped();
5539 result.lastFileModTime = bundleFile.getLastModificationTime();
5540 result.lastInfoUpdateTime = Time::getCurrentTime();
5541 result.manufacturerName = wrapped.getAuthorName().getTyped();
5542 result.pluginFormatName = LV2PluginFormat::getFormatName();
5543 result.numInputChannels = static_cast<int> (numInputs);
5544 result.numOutputChannels = static_cast<int> (numOutputs);
5545
5546 const auto classPtr = wrapped.getClass();
5547 const auto classes = collectPluginClassUris (classPtr);
5548 const auto isInstrument = std::any_of (classes.cbegin(),
5549 classes.cend(),
5550 [this] (const lv2_host::NodeUri& uri)
5551 {
5552 return uri.equals (uris.mLV2_CORE__GeneratorPlugin);
5553 });
5554
5555 result.category = lv2_host::PluginClass { classPtr }.getLabel().getTyped();
5556 result.isInstrument = isInstrument;
5557
5558 // The plugin URI is required to be globally unique, so a hash of it should be too
5559 result.fileOrIdentifier = wrapped.getUri().getTyped();
5560
5561 const auto uid = DefaultHashFunctions::generateHash (result.fileOrIdentifier, std::numeric_limits<int>::max());;
5562 result.deprecatedUid = result.uniqueId = uid;
5563 return result;
5564 }
5565
5566 std::vector<String> findPresetUrisForPlugin (const LilvPlugin* plugin)
5567 {
5568 std::vector<String> presetUris;
5569
5570 lv2_host::OwningNodes potentialPresets { lilv_plugin_get_related (plugin, world->newUri (LV2_PRESETS__Preset).get()) };
5571
5572 for (const auto* potentialPreset : potentialPresets)
5573 presetUris.push_back (lilv_node_as_string (potentialPreset));
5574
5575 return presetUris;
5576 }
5577
5578 TemporaryFile lv2ResourceFolder;
5579 std::shared_ptr<lv2_host::World> world = std::make_shared<lv2_host::World>();
5580 lv2_host::UsefulUris uris { world->get() };
5581};
5582
5583//==============================================================================
5584LV2PluginFormat::LV2PluginFormat()
5585 : pimpl (std::make_unique<Pimpl>()) {}
5586
5587LV2PluginFormat::~LV2PluginFormat() = default;
5588
5589void LV2PluginFormat::findAllTypesForFile (OwnedArray<PluginDescription>& results,
5590 const String& fileOrIdentifier)
5591{
5592 pimpl->findAllTypesForFile (results, fileOrIdentifier);
5593}
5594
5595bool LV2PluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier)
5596{
5597 return pimpl->fileMightContainThisPluginType (fileOrIdentifier);
5598}
5599
5600String LV2PluginFormat::getNameOfPluginFromIdentifier (const String& fileOrIdentifier)
5601{
5602 return pimpl->getNameOfPluginFromIdentifier (fileOrIdentifier);
5603}
5604
5605bool LV2PluginFormat::pluginNeedsRescanning (const PluginDescription& desc)
5606{
5607 return pimpl->pluginNeedsRescanning (desc);
5608}
5609
5610bool LV2PluginFormat::doesPluginStillExist (const PluginDescription& desc)
5611{
5612 return pimpl->doesPluginStillExist (desc);
5613}
5614
5615bool LV2PluginFormat::canScanForPlugins() const { return true; }
5616bool LV2PluginFormat::isTrivialToScan() const { return true; }
5617
5618StringArray LV2PluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch,
5619 bool recursive,
5620 bool allowAsync)
5621{
5622 return pimpl->searchPathsForPlugins (directoriesToSearch, recursive, allowAsync);
5623}
5624
5625FileSearchPath LV2PluginFormat::getDefaultLocationsToSearch()
5626{
5627 return pimpl->getDefaultLocationsToSearch();
5628}
5629
5630bool LV2PluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const
5631{
5632 return false;
5633}
5634
5635void LV2PluginFormat::createPluginInstance (const PluginDescription& desc,
5636 double sampleRate,
5637 int bufferSize,
5638 PluginCreationCallback callback)
5639{
5640 pimpl->createPluginInstance (desc, sampleRate, bufferSize, std::move (callback));
5641}
5642
5643} // namespace juce
5644
5645#endif
access
T align(T... args)
T none_of(T... args)
T back(T... args)
T back_inserter(T... args)
T begin(T... args)
T capacity(T... args)
T clear(T... args)
T copy(T... args)
T data(T... args)
T distance(T... args)
T emplace_back(T... args)
T emplace(T... args)
T empty(T... args)
T end(T... args)
T fill(T... args)
T find(T... args)
T forward(T... args)
free
T get(T... args)
T insert(T... args)
T is_sorted(T... args)
#define JUCE_LEAK_DETECTOR(OwnerClass)
This macro lets you embed a leak-detecting object inside a class.
#define TRANS(stringLiteral)
Uses the LocalisedStrings class to translate the given string literal.
#define JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
This macro is used to catch unsafe use of functions which expect to only be called on the message thr...
#define JUCE_ASSERT_MESSAGE_THREAD
This macro is used to catch unsafe use of functions which expect to only be called on the message thr...
#define jassert(expression)
Platform-independent assertion macro.
#define JUCE_DECLARE_NON_MOVEABLE(className)
This is a shorthand macro for deleting a class's move constructor and move assignment operator.
#define JUCE_DECLARE_NON_COPYABLE(className)
This is a shorthand macro for deleting a class's copy constructor and copy assignment operator.
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
#define jassertfalse
This will always cause an assertion failure.
auto & get(ProcessorChain< Processors... > &chain) noexcept
Non-member equivalent of ProcessorChain::get which avoids awkward member template syntax.
typedef char
T lock(T... args)
log
T lower_bound(T... args)
T make_tuple(T... args)
T make_unique(T... args)
typedef float
T max(T... args)
T memcpy(T... args)
T min(T... args)
T move(T... args)
@ copy
The command ID that should be used to send a "Copy to clipboard" command.
JUCE Namespace.
CriticalSection::ScopedLockType ScopedLock
Automatically locks and unlocks a CriticalSection object.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.
void ignoreUnused(Types &&...) noexcept
Handy function for avoiding unused variables warning.
Object withMember(Object copy, Member OtherObject::*member, Other &&value)
Copies an object, sets one of the copy's members to the specified value, and then returns the copy.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
Returns true if a value is at least zero, and also below a specified upper limit.
void writeUnaligned(void *dstPtr, Type value) noexcept
A handy function to write un-aligned memory without a performance penalty or bus-error.
Definition juce_Memory.h:74
RangedDirectoryIterator begin(const RangedDirectoryIterator &it)
Returns the iterator that was passed in.
std::unique_ptr< T > rawToUniquePtr(T *ptr)
Converts an owning raw pointer into a unique_ptr, deriving the type of the unique_ptr automatically.
T next(T... args)
T prev(T... args)
T push_back(T... args)
write
T ref(T... args)
T reserve(T... args)
T resize(T... args)
T size(T... args)
T sleep_for(T... args)
T sort(T... args)
typedef uint32_t
T strcmp(T... args)
std::u16string toString(NumberT value)
convert an number to an UTF-16 string
typedef size_t
time
T transform(T... args)
va_start
T vfprintf(T... args)