comparison DPF-Prymula-audioplugins/dpf/distrho/src/DistrhoPluginCLAP.cpp @ 3:84e66ea83026

DPF-Prymula-audioplugins-0.231015-2
author prymula <prymula76@outlook.com>
date Mon, 16 Oct 2023 21:53:34 +0200
parents
children
comparison
equal deleted inserted replaced
2:cf2cb71d31dd 3:84e66ea83026
1 /*
2 * DISTRHO Plugin Framework (DPF)
3 * Copyright (C) 2012-2023 Filipe Coelho <falktx@falktx.com>
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any purpose with
6 * or without fee is hereby granted, provided that the above copyright notice and this
7 * permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
10 * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
11 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
12 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
13 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 /* TODO items:
18 * CV: write a specification
19 * INFO: define url, manual url, support url and string version
20 * PARAMETERS: test parameter triggers
21 * States: skip DSP/UI only states as appropriate
22 * UI: expose external-only UIs
23 */
24
25 #include "DistrhoPluginInternal.hpp"
26 #include "extra/ScopedPointer.hpp"
27
28 #ifndef DISTRHO_PLUGIN_CLAP_ID
29 # error DISTRHO_PLUGIN_CLAP_ID undefined!
30 #endif
31
32 #if DISTRHO_PLUGIN_HAS_UI && ! defined(HAVE_DGL) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI
33 # undef DISTRHO_PLUGIN_HAS_UI
34 # define DISTRHO_PLUGIN_HAS_UI 0
35 #endif
36
37 #if DISTRHO_PLUGIN_HAS_UI
38 # include "DistrhoUIInternal.hpp"
39 # include "../extra/Mutex.hpp"
40 #endif
41
42 #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
43 # include "../extra/RingBuffer.hpp"
44 #endif
45
46 #include <map>
47 #include <vector>
48
49 #include "clap/entry.h"
50 #include "clap/plugin-factory.h"
51 #include "clap/ext/audio-ports.h"
52 #include "clap/ext/latency.h"
53 #include "clap/ext/gui.h"
54 #include "clap/ext/note-ports.h"
55 #include "clap/ext/params.h"
56 #include "clap/ext/state.h"
57 #include "clap/ext/thread-check.h"
58 #include "clap/ext/timer-support.h"
59
60 #if (defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS)) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI
61 # define DPF_CLAP_USING_HOST_TIMER 0
62 #else
63 # define DPF_CLAP_USING_HOST_TIMER 1
64 #endif
65
66 #ifndef DPF_CLAP_TIMER_INTERVAL
67 # define DPF_CLAP_TIMER_INTERVAL 16 /* ~60 fps */
68 #endif
69
70 START_NAMESPACE_DISTRHO
71
72 // --------------------------------------------------------------------------------------------------------------------
73
74 typedef std::map<const String, String> StringMap;
75
76 struct ClapEventQueue
77 {
78 #if DISTRHO_PLUGIN_HAS_UI
79 enum EventType {
80 kEventGestureBegin,
81 kEventGestureEnd,
82 kEventParamSet
83 };
84
85 struct Event {
86 EventType type;
87 uint32_t index;
88 float value;
89 };
90
91 struct Queue {
92 RecursiveMutex lock;
93 uint allocated;
94 uint used;
95 Event* events;
96
97 Queue()
98 : allocated(0),
99 used(0),
100 events(nullptr) {}
101
102 ~Queue()
103 {
104 delete[] events;
105 }
106
107 void addEventFromUI(const Event& event)
108 {
109 const RecursiveMutexLocker crml(lock);
110
111 if (events == nullptr)
112 {
113 events = static_cast<Event*>(std::malloc(sizeof(Event) * 8));
114 allocated = 8;
115 }
116 else if (used + 1 > allocated)
117 {
118 allocated = used * 2;
119 events = static_cast<Event*>(std::realloc(events, sizeof(Event) * allocated));
120 }
121
122 std::memcpy(&events[used++], &event, sizeof(Event));
123 }
124 } fEventQueue;
125
126 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
127 SmallStackBuffer fNotesBuffer;
128 #endif
129 #endif
130
131 #if DISTRHO_PLUGIN_WANT_PROGRAMS
132 uint32_t fCurrentProgram;
133 #endif
134
135 #if DISTRHO_PLUGIN_WANT_STATE
136 StringMap fStateMap;
137 #if DISTRHO_PLUGIN_HAS_UI
138 virtual void setStateFromUI(const char* key, const char* value) = 0;
139 #endif
140 #endif
141
142 struct CachedParameters {
143 uint numParams;
144 bool* changed;
145 float* values;
146
147 CachedParameters()
148 : numParams(0),
149 changed(nullptr),
150 values(nullptr) {}
151
152 ~CachedParameters()
153 {
154 delete[] changed;
155 delete[] values;
156 }
157
158 void setup(const uint numParameters)
159 {
160 if (numParameters == 0)
161 return;
162
163 numParams = numParameters;
164 changed = new bool[numParameters];
165 values = new float[numParameters];
166
167 std::memset(changed, 0, sizeof(bool)*numParameters);
168 std::memset(values, 0, sizeof(float)*numParameters);
169 }
170 } fCachedParameters;
171
172 ClapEventQueue()
173 #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
174 : fNotesBuffer(StackBuffer_INIT)
175 #endif
176 {
177 #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT && ! defined(DISTRHO_PROPER_CPP11_SUPPORT)
178 std::memset(&fNotesBuffer, 0, sizeof(fNotesBuffer));
179 #endif
180 #if DISTRHO_PLUGIN_WANT_PROGRAMS
181 fCurrentProgram = 0;
182 #endif
183 }
184
185 virtual ~ClapEventQueue() {}
186 };
187
188 // --------------------------------------------------------------------------------------------------------------------
189
190 #if DISTRHO_PLUGIN_HAS_UI
191
192 #if ! DISTRHO_PLUGIN_WANT_STATE
193 static constexpr const setStateFunc setStateCallback = nullptr;
194 #endif
195 #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT
196 static constexpr const sendNoteFunc sendNoteCallback = nullptr;
197 #endif
198
199 /**
200 * CLAP UI class.
201 */
202 class ClapUI : public DGL_NAMESPACE::IdleCallback
203 {
204 public:
205 ClapUI(PluginExporter& plugin,
206 ClapEventQueue* const eventQueue,
207 const clap_host_t* const host,
208 const clap_host_gui_t* const hostGui,
209 #if DPF_CLAP_USING_HOST_TIMER
210 const clap_host_timer_support_t* const hostTimer,
211 #endif
212 const bool isFloating)
213 : fPlugin(plugin),
214 fPluginEventQueue(eventQueue),
215 fEventQueue(eventQueue->fEventQueue),
216 fCachedParameters(eventQueue->fCachedParameters),
217 #if DISTRHO_PLUGIN_WANT_PROGRAMS
218 fCurrentProgram(eventQueue->fCurrentProgram),
219 #endif
220 #if DISTRHO_PLUGIN_WANT_STATE
221 fStateMap(eventQueue->fStateMap),
222 #endif
223 fHost(host),
224 fHostGui(hostGui),
225 #if DPF_CLAP_USING_HOST_TIMER
226 fTimerId(0),
227 fHostTimer(hostTimer),
228 #else
229 fCallbackRegistered(false),
230 #endif
231 fIsFloating(isFloating),
232 fScaleFactor(0.0),
233 fParentWindow(0),
234 fTransientWindow(0)
235 {
236 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
237 fNotesRingBuffer.setRingBuffer(&eventQueue->fNotesBuffer, false);
238 #endif
239 }
240
241 ~ClapUI() override
242 {
243 #if DPF_CLAP_USING_HOST_TIMER
244 if (fTimerId != 0)
245 fHostTimer->unregister_timer(fHost, fTimerId);
246 #else
247 if (fCallbackRegistered && fUI != nullptr)
248 fUI->removeIdleCallbackForNativeIdle(this);
249 #endif
250 }
251
252 #ifndef DISTRHO_OS_MAC
253 bool setScaleFactor(const double scaleFactor)
254 {
255 if (d_isEqual(fScaleFactor, scaleFactor))
256 return true;
257
258 fScaleFactor = scaleFactor;
259
260 if (UIExporter* const ui = fUI.get())
261 ui->notifyScaleFactorChanged(scaleFactor);
262
263 return true;
264 }
265 #endif
266
267 bool getSize(uint32_t* const width, uint32_t* const height) const
268 {
269 if (UIExporter* const ui = fUI.get())
270 {
271 *width = ui->getWidth();
272 *height = ui->getHeight();
273 #ifdef DISTRHO_OS_MAC
274 const double scaleFactor = ui->getScaleFactor();
275 *width /= scaleFactor;
276 *height /= scaleFactor;
277 #endif
278 return true;
279 }
280
281 double scaleFactor = fScaleFactor;
282 #if defined(DISTRHO_UI_DEFAULT_WIDTH) && defined(DISTRHO_UI_DEFAULT_HEIGHT)
283 *width = DISTRHO_UI_DEFAULT_WIDTH;
284 *height = DISTRHO_UI_DEFAULT_HEIGHT;
285 if (d_isZero(scaleFactor))
286 scaleFactor = 1.0;
287 #else
288 UIExporter tmpUI(nullptr, 0, fPlugin.getSampleRate(),
289 nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, d_nextBundlePath,
290 fPlugin.getInstancePointer(), scaleFactor);
291 *width = tmpUI.getWidth();
292 *height = tmpUI.getHeight();
293 scaleFactor = tmpUI.getScaleFactor();
294 tmpUI.quit();
295 #endif
296
297 #ifdef DISTRHO_OS_MAC
298 *width /= scaleFactor;
299 *height /= scaleFactor;
300 #endif
301
302 return true;
303 }
304
305 bool canResize() const noexcept
306 {
307 #if DISTRHO_UI_USER_RESIZABLE
308 if (UIExporter* const ui = fUI.get())
309 return ui->isResizable();
310 #endif
311 return false;
312 }
313
314 bool getResizeHints(clap_gui_resize_hints_t* const hints) const
315 {
316 if (canResize())
317 {
318 uint minimumWidth, minimumHeight;
319 bool keepAspectRatio;
320 fUI->getGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio);
321
322 #ifdef DISTRHO_OS_MAC
323 const double scaleFactor = fUI->getScaleFactor();
324 minimumWidth /= scaleFactor;
325 minimumHeight /= scaleFactor;
326 #endif
327
328 hints->can_resize_horizontally = true;
329 hints->can_resize_vertically = true;
330 hints->preserve_aspect_ratio = keepAspectRatio;
331 hints->aspect_ratio_width = minimumWidth;
332 hints->aspect_ratio_height = minimumHeight;
333
334 return true;
335 }
336
337 hints->can_resize_horizontally = false;
338 hints->can_resize_vertically = false;
339 hints->preserve_aspect_ratio = false;
340 hints->aspect_ratio_width = 0;
341 hints->aspect_ratio_height = 0;
342
343 return false;
344 }
345
346 bool adjustSize(uint32_t* const width, uint32_t* const height) const
347 {
348 if (canResize())
349 {
350 uint minimumWidth, minimumHeight;
351 bool keepAspectRatio;
352 fUI->getGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio);
353
354 #ifdef DISTRHO_OS_MAC
355 const double scaleFactor = fUI->getScaleFactor();
356 minimumWidth /= scaleFactor;
357 minimumHeight /= scaleFactor;
358 #endif
359
360 if (keepAspectRatio)
361 {
362 if (*width < 1)
363 *width = 1;
364 if (*height < 1)
365 *height = 1;
366
367 const double ratio = static_cast<double>(minimumWidth) / static_cast<double>(minimumHeight);
368 const double reqRatio = static_cast<double>(*width) / static_cast<double>(*height);
369
370 if (d_isNotEqual(ratio, reqRatio))
371 {
372 // fix width
373 if (reqRatio > ratio)
374 *width = static_cast<int32_t>(*height * ratio + 0.5);
375 // fix height
376 else
377 *height = static_cast<int32_t>(static_cast<double>(*width) / ratio + 0.5);
378 }
379 }
380
381 if (minimumWidth > *width)
382 *width = minimumWidth;
383 if (minimumHeight > *height)
384 *height = minimumHeight;
385
386 return true;
387 }
388
389 return false;
390 }
391
392 bool setSizeFromHost(uint32_t width, uint32_t height)
393 {
394 if (UIExporter* const ui = fUI.get())
395 {
396 #ifdef DISTRHO_OS_MAC
397 const double scaleFactor = ui->getScaleFactor();
398 width *= scaleFactor;
399 height *= scaleFactor;
400 #endif
401 ui->setWindowSizeFromHost(width, height);
402 return true;
403 }
404
405 return false;
406 }
407
408 bool setParent(const clap_window_t* const window)
409 {
410 if (fIsFloating)
411 return false;
412
413 fParentWindow = window->uptr;
414
415 if (fUI == nullptr)
416 {
417 createUI();
418 fHostGui->resize_hints_changed(fHost);
419 }
420
421 return true;
422 }
423
424 bool setTransient(const clap_window_t* const window)
425 {
426 if (! fIsFloating)
427 return false;
428
429 fTransientWindow = window->uptr;
430
431 if (UIExporter* const ui = fUI.get())
432 ui->setWindowTransientWinId(window->uptr);
433
434 return true;
435 }
436
437 void suggestTitle(const char* const title)
438 {
439 if (! fIsFloating)
440 return;
441
442 fWindowTitle = title;
443
444 if (UIExporter* const ui = fUI.get())
445 ui->setWindowTitle(title);
446 }
447
448 bool show()
449 {
450 if (fUI == nullptr)
451 {
452 createUI();
453 fHostGui->resize_hints_changed(fHost);
454 }
455
456 if (fIsFloating)
457 fUI->setWindowVisible(true);
458
459 #if DPF_CLAP_USING_HOST_TIMER
460 fHostTimer->register_timer(fHost, DPF_CLAP_TIMER_INTERVAL, &fTimerId);
461 #else
462 fCallbackRegistered = true;
463 fUI->addIdleCallbackForNativeIdle(this, DPF_CLAP_TIMER_INTERVAL);
464 #endif
465 return true;
466 }
467
468 bool hide()
469 {
470 if (UIExporter* const ui = fUI.get())
471 {
472 ui->setWindowVisible(false);
473 #if DPF_CLAP_USING_HOST_TIMER
474 fHostTimer->unregister_timer(fHost, fTimerId);
475 fTimerId = 0;
476 #else
477 ui->removeIdleCallbackForNativeIdle(this);
478 fCallbackRegistered = false;
479 #endif
480 }
481
482 return true;
483 }
484
485 // ----------------------------------------------------------------------------------------------------------------
486
487 void idleCallback() override
488 {
489 if (UIExporter* const ui = fUI.get())
490 {
491 #if DPF_CLAP_USING_HOST_TIMER
492 ui->plugin_idle();
493 #else
494 ui->idleFromNativeIdle();
495 #endif
496
497 for (uint i=0; i<fCachedParameters.numParams; ++i)
498 {
499 if (fCachedParameters.changed[i])
500 {
501 fCachedParameters.changed[i] = false;
502 ui->parameterChanged(i, fCachedParameters.values[i]);
503 }
504 }
505 }
506 }
507
508 // ----------------------------------------------------------------------------------------------------------------
509
510 void setParameterValueFromPlugin(const uint index, const float value)
511 {
512 if (UIExporter* const ui = fUI.get())
513 ui->parameterChanged(index, value);
514 }
515
516 #if DISTRHO_PLUGIN_WANT_PROGRAMS
517 void setProgramFromPlugin(const uint index)
518 {
519 if (UIExporter* const ui = fUI.get())
520 ui->programLoaded(index);
521 }
522 #endif
523
524 #if DISTRHO_PLUGIN_WANT_STATE
525 void setStateFromPlugin(const char* const key, const char* const value)
526 {
527 if (UIExporter* const ui = fUI.get())
528 ui->stateChanged(key, value);
529 }
530 #endif
531
532 // ----------------------------------------------------------------------------------------------------------------
533
534 private:
535 // Plugin and UI
536 PluginExporter& fPlugin;
537 ClapEventQueue* const fPluginEventQueue;
538 ClapEventQueue::Queue& fEventQueue;
539 ClapEventQueue::CachedParameters& fCachedParameters;
540 #if DISTRHO_PLUGIN_WANT_PROGRAMS
541 uint32_t& fCurrentProgram;
542 #endif
543 #if DISTRHO_PLUGIN_WANT_STATE
544 StringMap& fStateMap;
545 #endif
546 const clap_host_t* const fHost;
547 const clap_host_gui_t* const fHostGui;
548 #if DPF_CLAP_USING_HOST_TIMER
549 clap_id fTimerId;
550 const clap_host_timer_support_t* const fHostTimer;
551 #else
552 bool fCallbackRegistered;
553 #endif
554 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
555 RingBufferControl<SmallStackBuffer> fNotesRingBuffer;
556 #endif
557 ScopedPointer<UIExporter> fUI;
558
559 const bool fIsFloating;
560
561 // Temporary data
562 double fScaleFactor;
563 uintptr_t fParentWindow;
564 uintptr_t fTransientWindow;
565 String fWindowTitle;
566
567 // ----------------------------------------------------------------------------------------------------------------
568
569 void createUI()
570 {
571 DISTRHO_SAFE_ASSERT_RETURN(fUI == nullptr,);
572
573 fUI = new UIExporter(this,
574 fParentWindow,
575 fPlugin.getSampleRate(),
576 editParameterCallback,
577 setParameterCallback,
578 setStateCallback,
579 sendNoteCallback,
580 setSizeCallback,
581 nullptr, // TODO fileRequestCallback,
582 d_nextBundlePath,
583 fPlugin.getInstancePointer(),
584 fScaleFactor);
585
586 #if DISTRHO_PLUGIN_WANT_PROGRAMS
587 fUI->programLoaded(fCurrentProgram);
588 #endif
589
590 #if DISTRHO_PLUGIN_WANT_FULL_STATE
591 // Update current state from plugin side
592 for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
593 {
594 const String& key = cit->first;
595 fStateMap[key] = fPlugin.getStateValue(key);
596 }
597 #endif
598
599 #if DISTRHO_PLUGIN_WANT_STATE
600 // Set state
601 for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
602 {
603 const String& key = cit->first;
604 const String& value = cit->second;
605
606 // TODO skip DSP only states
607
608 fUI->stateChanged(key, value);
609 }
610 #endif
611
612 for (uint32_t i=0; i<fCachedParameters.numParams; ++i)
613 {
614 const float value = fCachedParameters.values[i] = fPlugin.getParameterValue(i);
615 fCachedParameters.changed[i] = false;
616 fUI->parameterChanged(i, value);
617 }
618
619 if (fIsFloating)
620 {
621 if (fWindowTitle.isNotEmpty())
622 fUI->setWindowTitle(fWindowTitle);
623
624 if (fTransientWindow != 0)
625 fUI->setWindowTransientWinId(fTransientWindow);
626 }
627 }
628
629 // ----------------------------------------------------------------------------------------------------------------
630 // DPF callbacks
631
632 void editParameter(const uint32_t rindex, const bool started) const
633 {
634 const ClapEventQueue::Event ev = {
635 started ? ClapEventQueue::kEventGestureBegin : ClapEventQueue::kEventGestureEnd,
636 rindex, 0.f
637 };
638 fEventQueue.addEventFromUI(ev);
639 }
640
641 static void editParameterCallback(void* const ptr, const uint32_t rindex, const bool started)
642 {
643 static_cast<ClapUI*>(ptr)->editParameter(rindex, started);
644 }
645
646 void setParameterValue(const uint32_t rindex, const float value)
647 {
648 const ClapEventQueue::Event ev = {
649 ClapEventQueue::kEventParamSet,
650 rindex, value
651 };
652 fEventQueue.addEventFromUI(ev);
653 }
654
655 static void setParameterCallback(void* const ptr, const uint32_t rindex, const float value)
656 {
657 static_cast<ClapUI*>(ptr)->setParameterValue(rindex, value);
658 }
659
660 void setSizeFromPlugin(const uint width, const uint height)
661 {
662 DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,);
663
664 #ifdef DISTRHO_OS_MAC
665 const double scaleFactor = fUI->getScaleFactor();
666 const uint hostWidth = width / scaleFactor;
667 const uint hostHeight = height / scaleFactor;
668 #else
669 const uint hostWidth = width;
670 const uint hostHeight = height;
671 #endif
672
673 if (fHostGui->request_resize(fHost, hostWidth, hostHeight))
674 fUI->setWindowSizeFromHost(width, height);
675 }
676
677 static void setSizeCallback(void* const ptr, const uint width, const uint height)
678 {
679 static_cast<ClapUI*>(ptr)->setSizeFromPlugin(width, height);
680 }
681
682 #if DISTRHO_PLUGIN_WANT_STATE
683 void setState(const char* const key, const char* const value)
684 {
685 fPluginEventQueue->setStateFromUI(key, value);
686 }
687
688 static void setStateCallback(void* const ptr, const char* key, const char* value)
689 {
690 static_cast<ClapUI*>(ptr)->setState(key, value);
691 }
692 #endif
693
694 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
695 void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity)
696 {
697 uint8_t midiData[3];
698 midiData[0] = (velocity != 0 ? 0x90 : 0x80) | channel;
699 midiData[1] = note;
700 midiData[2] = velocity;
701 fNotesRingBuffer.writeCustomData(midiData, 3);
702 fNotesRingBuffer.commitWrite();
703 }
704
705 static void sendNoteCallback(void* const ptr, const uint8_t channel, const uint8_t note, const uint8_t velocity)
706 {
707 static_cast<ClapUI*>(ptr)->sendNote(channel, note, velocity);
708 }
709 #endif
710
711 /* TODO
712 bool fileRequest(const char*)
713 {
714 return true;
715 }
716
717 static bool fileRequestCallback(void* const ptr, const char* const key)
718 {
719 return static_cast<ClapUI*>(ptr)->fileRequest(key);
720 }
721 */
722 };
723
724 // --------------------------------------------------------------------------------------------------------------------
725
726 #endif // DISTRHO_PLUGIN_HAS_UI
727
728 #if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
729 static constexpr const writeMidiFunc writeMidiCallback = nullptr;
730 #endif
731 #if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST
732 static constexpr const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr;
733 #endif
734 #if ! DISTRHO_PLUGIN_WANT_STATE
735 static constexpr const updateStateValueFunc updateStateValueCallback = nullptr;
736 #endif
737
738 // --------------------------------------------------------------------------------------------------------------------
739
740 /**
741 * CLAP plugin class.
742 */
743 class PluginCLAP : ClapEventQueue
744 {
745 public:
746 PluginCLAP(const clap_host_t* const host)
747 : fPlugin(this,
748 writeMidiCallback,
749 requestParameterValueChangeCallback,
750 updateStateValueCallback),
751 fHost(host),
752 fOutputEvents(nullptr),
753 #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0
754 fUsingCV(false),
755 #endif
756 #if DISTRHO_PLUGIN_WANT_LATENCY
757 fLatencyChanged(false),
758 fLastKnownLatency(0),
759 #endif
760 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
761 fMidiEventCount(0),
762 #endif
763 fHostExtensions(host)
764 {
765 fCachedParameters.setup(fPlugin.getParameterCount());
766
767 #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
768 fNotesRingBuffer.setRingBuffer(&fNotesBuffer, true);
769 #endif
770
771 #if DISTRHO_PLUGIN_WANT_STATE
772 for (uint32_t i=0, count=fPlugin.getStateCount(); i<count; ++i)
773 {
774 const String& dkey(fPlugin.getStateKey(i));
775 fStateMap[dkey] = fPlugin.getStateDefaultValue(i);
776 }
777 #endif
778
779 #if DISTRHO_PLUGIN_NUM_INPUTS != 0
780 fillInBusInfoDetails<true>();
781 #endif
782 #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
783 fillInBusInfoDetails<false>();
784 #endif
785 #if DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0
786 fillInBusInfoPairs();
787 #endif
788 }
789
790 // ----------------------------------------------------------------------------------------------------------------
791 // core
792
793 template <class T>
794 const T* getHostExtension(const char* const extensionId) const
795 {
796 return static_cast<const T*>(fHost->get_extension(fHost, extensionId));
797 }
798
799 bool init()
800 {
801 if (!clap_version_is_compatible(fHost->clap_version))
802 return false;
803
804 return fHostExtensions.init();
805 }
806
807 void activate(const double sampleRate, const uint32_t maxFramesCount)
808 {
809 fPlugin.setSampleRate(sampleRate, true);
810 fPlugin.setBufferSize(maxFramesCount, true);
811 fPlugin.activate();
812 }
813
814 void deactivate()
815 {
816 fPlugin.deactivate();
817 #if DISTRHO_PLUGIN_WANT_LATENCY
818 checkForLatencyChanges(false, true);
819 reportLatencyChangeIfNeeded();
820 #endif
821 }
822
823 bool process(const clap_process_t* const process)
824 {
825 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
826 fMidiEventCount = 0;
827 #endif
828
829 #if DISTRHO_PLUGIN_HAS_UI
830 if (const clap_output_events_t* const outputEvents = process->out_events)
831 {
832 const RecursiveMutexTryLocker crmtl(fEventQueue.lock);
833
834 if (crmtl.wasLocked())
835 {
836 // reuse the same struct for gesture and parameters, they are compatible up to where it matters
837 clap_event_param_value_t clapEvent = {
838 { 0, 0, 0, 0, CLAP_EVENT_IS_LIVE },
839 0, nullptr, 0, 0, 0, 0, 0.0
840 };
841
842 for (uint32_t i=0; i<fEventQueue.used; ++i)
843 {
844 const Event& event(fEventQueue.events[i]);
845
846 switch (event.type)
847 {
848 case kEventGestureBegin:
849 clapEvent.header.size = sizeof(clap_event_param_gesture_t);
850 clapEvent.header.type = CLAP_EVENT_PARAM_GESTURE_BEGIN;
851 clapEvent.param_id = event.index;
852 break;
853 case kEventGestureEnd:
854 clapEvent.header.size = sizeof(clap_event_param_gesture_t);
855 clapEvent.header.type = CLAP_EVENT_PARAM_GESTURE_END;
856 clapEvent.param_id = event.index;
857 break;
858 case kEventParamSet:
859 clapEvent.header.size = sizeof(clap_event_param_value_t);
860 clapEvent.header.type = CLAP_EVENT_PARAM_VALUE;
861 clapEvent.param_id = event.index;
862 clapEvent.value = event.value;
863 fPlugin.setParameterValue(event.index, event.value);
864 break;
865 default:
866 continue;
867 }
868
869 outputEvents->try_push(outputEvents, &clapEvent.header);
870 }
871
872 fEventQueue.used = 0;
873 }
874 }
875 #endif
876
877 #if DISTRHO_PLUGIN_WANT_TIMEPOS
878 if (const clap_event_transport_t* const transport = process->transport)
879 {
880 fTimePosition.playing = (transport->flags & CLAP_TRANSPORT_IS_PLAYING) != 0 &&
881 (transport->flags & CLAP_TRANSPORT_IS_WITHIN_PRE_ROLL) == 0;
882
883 fTimePosition.frame = process->steady_time >= 0 ? process->steady_time : 0;
884
885 if (transport->flags & CLAP_TRANSPORT_HAS_TEMPO)
886 fTimePosition.bbt.beatsPerMinute = transport->tempo;
887 else
888 fTimePosition.bbt.beatsPerMinute = 120.0;
889
890 // ticksPerBeat is not possible with CLAP
891 fTimePosition.bbt.ticksPerBeat = 1920.0;
892
893 if ((transport->flags & (CLAP_TRANSPORT_HAS_BEATS_TIMELINE|CLAP_TRANSPORT_HAS_TIME_SIGNATURE)) == (CLAP_TRANSPORT_HAS_BEATS_TIMELINE|CLAP_TRANSPORT_HAS_TIME_SIGNATURE))
894 {
895 if (transport->song_pos_beats >= 0)
896 {
897 const int64_t clapPos = std::abs(transport->song_pos_beats);
898 const int64_t clapBeats = clapPos >> 31;
899 const double clapRest = static_cast<double>(clapPos & 0x7fffffff) / CLAP_BEATTIME_FACTOR;
900
901 fTimePosition.bbt.bar = static_cast<int32_t>(clapBeats) / transport->tsig_num + 1;
902 fTimePosition.bbt.beat = static_cast<int32_t>(clapBeats % transport->tsig_num) + 1;
903 fTimePosition.bbt.tick = clapRest * fTimePosition.bbt.ticksPerBeat;
904 }
905 else
906 {
907 fTimePosition.bbt.bar = 1;
908 fTimePosition.bbt.beat = 1;
909 fTimePosition.bbt.tick = 0.0;
910 }
911
912 fTimePosition.bbt.valid = true;
913 fTimePosition.bbt.beatsPerBar = transport->tsig_num;
914 fTimePosition.bbt.beatType = transport->tsig_denom;
915 }
916 else
917 {
918 fTimePosition.bbt.valid = false;
919 fTimePosition.bbt.bar = 1;
920 fTimePosition.bbt.beat = 1;
921 fTimePosition.bbt.tick = 0.0;
922 fTimePosition.bbt.beatsPerBar = 4.0f;
923 fTimePosition.bbt.beatType = 4.0f;
924 }
925
926 fTimePosition.bbt.barStartTick = fTimePosition.bbt.ticksPerBeat*
927 fTimePosition.bbt.beatsPerBar*
928 (fTimePosition.bbt.bar-1);
929 }
930 else
931 {
932 fTimePosition.playing = false;
933 fTimePosition.frame = 0;
934 fTimePosition.bbt.valid = false;
935 fTimePosition.bbt.beatsPerMinute = 120.0;
936 fTimePosition.bbt.bar = 1;
937 fTimePosition.bbt.beat = 1;
938 fTimePosition.bbt.tick = 0.0;
939 fTimePosition.bbt.beatsPerBar = 4.f;
940 fTimePosition.bbt.beatType = 4.f;
941 fTimePosition.bbt.barStartTick = 0;
942 }
943
944 fPlugin.setTimePosition(fTimePosition);
945 #endif
946
947 if (const clap_input_events_t* const inputEvents = process->in_events)
948 {
949 if (const uint32_t len = inputEvents->size(inputEvents))
950 {
951 for (uint32_t i=0; i<len; ++i)
952 {
953 const clap_event_header_t* const event = inputEvents->get(inputEvents, i);
954
955 switch (event->type)
956 {
957 case CLAP_EVENT_NOTE_ON:
958 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
959 // BUG: even though we only report CLAP_NOTE_DIALECT_MIDI as supported, Bitwig sends us this anyway
960 addNoteEvent(static_cast<const clap_event_note_t*>(static_cast<const void*>(event)), true);
961 #endif
962 break;
963 case CLAP_EVENT_NOTE_OFF:
964 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
965 // BUG: even though we only report CLAP_NOTE_DIALECT_MIDI as supported, Bitwig sends us this anyway
966 addNoteEvent(static_cast<const clap_event_note_t*>(static_cast<const void*>(event)), false);
967 #endif
968 break;
969 case CLAP_EVENT_NOTE_CHOKE:
970 case CLAP_EVENT_NOTE_END:
971 case CLAP_EVENT_NOTE_EXPRESSION:
972 break;
973 case CLAP_EVENT_PARAM_VALUE:
974 DISTRHO_SAFE_ASSERT_UINT2_BREAK(event->size == sizeof(clap_event_param_value),
975 event->size, sizeof(clap_event_param_value));
976 setParameterValueFromEvent(static_cast<const clap_event_param_value*>(static_cast<const void*>(event)));
977 break;
978 case CLAP_EVENT_PARAM_MOD:
979 case CLAP_EVENT_PARAM_GESTURE_BEGIN:
980 case CLAP_EVENT_PARAM_GESTURE_END:
981 case CLAP_EVENT_TRANSPORT:
982 break;
983 case CLAP_EVENT_MIDI:
984 DISTRHO_SAFE_ASSERT_UINT2_BREAK(event->size == sizeof(clap_event_midi_t),
985 event->size, sizeof(clap_event_midi_t));
986 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
987 addMidiEvent(static_cast<const clap_event_midi_t*>(static_cast<const void*>(event)));
988 #endif
989 break;
990 case CLAP_EVENT_MIDI_SYSEX:
991 case CLAP_EVENT_MIDI2:
992 break;
993 }
994 }
995 }
996 }
997
998 #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
999 if (fMidiEventCount != kMaxMidiEvents && fNotesRingBuffer.isDataAvailableForReading())
1000 {
1001 uint8_t midiData[3];
1002 const uint32_t frame = fMidiEventCount != 0 ? fMidiEvents[fMidiEventCount-1].frame : 0;
1003
1004 while (fNotesRingBuffer.isDataAvailableForReading())
1005 {
1006 if (! fNotesRingBuffer.readCustomData(midiData, 3))
1007 break;
1008
1009 MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]);
1010 midiEvent.frame = frame;
1011 midiEvent.size = 3;
1012 std::memcpy(midiEvent.data, midiData, 3);
1013
1014 if (fMidiEventCount == kMaxMidiEvents)
1015 break;
1016 }
1017 }
1018 #endif
1019
1020 if (const uint32_t frames = process->frames_count)
1021 {
1022 #if DISTRHO_PLUGIN_NUM_INPUTS != 0
1023 const float** const audioInputs = fAudioInputs;
1024
1025 uint32_t in=0;
1026 for (uint32_t i=0; i<process->audio_inputs_count; ++i)
1027 {
1028 const clap_audio_buffer_t& inputs(process->audio_inputs[i]);
1029 DISTRHO_SAFE_ASSERT_CONTINUE(inputs.channel_count != 0);
1030
1031 for (uint32_t j=0; j<inputs.channel_count; ++j, ++in)
1032 audioInputs[in] = const_cast<const float*>(inputs.data32[j]);
1033 }
1034
1035 if (fUsingCV)
1036 {
1037 for (; in<DISTRHO_PLUGIN_NUM_INPUTS; ++in)
1038 audioInputs[in] = nullptr;
1039 }
1040 else
1041 {
1042 DISTRHO_SAFE_ASSERT_UINT2_RETURN(in == DISTRHO_PLUGIN_NUM_INPUTS,
1043 in, process->audio_inputs_count, false);
1044 }
1045 #else
1046 constexpr const float** const audioInputs = nullptr;
1047 #endif
1048
1049 #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
1050 float** const audioOutputs = fAudioOutputs;
1051
1052 uint32_t out=0;
1053 for (uint32_t i=0; i<process->audio_outputs_count; ++i)
1054 {
1055 const clap_audio_buffer_t& outputs(process->audio_outputs[i]);
1056 DISTRHO_SAFE_ASSERT_CONTINUE(outputs.channel_count != 0);
1057
1058 for (uint32_t j=0; j<outputs.channel_count; ++j, ++out)
1059 audioOutputs[out] = outputs.data32[j];
1060 }
1061
1062 if (fUsingCV)
1063 {
1064 for (; out<DISTRHO_PLUGIN_NUM_OUTPUTS; ++out)
1065 audioOutputs[out] = nullptr;
1066 }
1067 else
1068 {
1069 DISTRHO_SAFE_ASSERT_UINT2_RETURN(out == DISTRHO_PLUGIN_NUM_OUTPUTS,
1070 out, DISTRHO_PLUGIN_NUM_OUTPUTS, false);
1071 }
1072 #else
1073 constexpr float** const audioOutputs = nullptr;
1074 #endif
1075
1076 fOutputEvents = process->out_events;
1077
1078 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
1079 fPlugin.run(audioInputs, audioOutputs, frames, fMidiEvents, fMidiEventCount);
1080 #else
1081 fPlugin.run(audioInputs, audioOutputs, frames);
1082 #endif
1083
1084 flushParameters(nullptr, process->out_events, frames - 1);
1085
1086 fOutputEvents = nullptr;
1087 }
1088
1089 #if DISTRHO_PLUGIN_WANT_LATENCY
1090 checkForLatencyChanges(true, false);
1091 #endif
1092
1093 return true;
1094 }
1095
1096 void onMainThread()
1097 {
1098 #if DISTRHO_PLUGIN_WANT_LATENCY
1099 reportLatencyChangeIfNeeded();
1100 #endif
1101 }
1102
1103 // ----------------------------------------------------------------------------------------------------------------
1104 // parameters
1105
1106 uint32_t getParameterCount() const
1107 {
1108 return fPlugin.getParameterCount();
1109 }
1110
1111 bool getParameterInfo(const uint32_t index, clap_param_info_t* const info) const
1112 {
1113 const ParameterRanges& ranges(fPlugin.getParameterRanges(index));
1114
1115 if (fPlugin.getParameterDesignation(index) == kParameterDesignationBypass)
1116 {
1117 info->flags = CLAP_PARAM_IS_STEPPED|CLAP_PARAM_IS_BYPASS|CLAP_PARAM_IS_AUTOMATABLE;
1118 std::strcpy(info->name, "Bypass");
1119 std::strcpy(info->module, "dpf_bypass");
1120 }
1121 else
1122 {
1123 const uint32_t hints = fPlugin.getParameterHints(index);
1124 const uint32_t groupId = fPlugin.getParameterGroupId(index);
1125
1126 info->flags = 0;
1127 if (hints & kParameterIsOutput)
1128 info->flags |= CLAP_PARAM_IS_READONLY;
1129 else if (hints & kParameterIsAutomatable)
1130 info->flags |= CLAP_PARAM_IS_AUTOMATABLE;
1131
1132 if (hints & (kParameterIsBoolean|kParameterIsInteger))
1133 info->flags |= CLAP_PARAM_IS_STEPPED;
1134
1135 d_strncpy(info->name, fPlugin.getParameterName(index), CLAP_NAME_SIZE);
1136
1137 uint wrtn;
1138 if (groupId != kPortGroupNone)
1139 {
1140 const PortGroupWithId& portGroup(fPlugin.getPortGroupById(groupId));
1141 strncpy(info->module, portGroup.symbol, CLAP_PATH_SIZE / 2);
1142 info->module[CLAP_PATH_SIZE / 2] = '\0';
1143 wrtn = std::strlen(info->module);
1144 info->module[wrtn++] = '/';
1145 }
1146 else
1147 {
1148 wrtn = 0;
1149 }
1150
1151 d_strncpy(info->module + wrtn, fPlugin.getParameterSymbol(index), CLAP_PATH_SIZE - wrtn);
1152 }
1153
1154 info->id = index;
1155 info->cookie = nullptr;
1156 info->min_value = ranges.min;
1157 info->max_value = ranges.max;
1158 info->default_value = ranges.def;
1159 return true;
1160 }
1161
1162 bool getParameterValue(const clap_id param_id, double* const value) const
1163 {
1164 *value = fPlugin.getParameterValue(param_id);
1165 return true;
1166 }
1167
1168 bool getParameterStringForValue(const clap_id param_id, double value, char* const display, const uint32_t size) const
1169 {
1170 const ParameterEnumerationValues& enumValues(fPlugin.getParameterEnumValues(param_id));
1171 const ParameterRanges& ranges(fPlugin.getParameterRanges(param_id));
1172 const uint32_t hints = fPlugin.getParameterHints(param_id);
1173
1174 if (hints & kParameterIsBoolean)
1175 {
1176 const float midRange = ranges.min + (ranges.max - ranges.min) * 0.5f;
1177 value = value > midRange ? ranges.max : ranges.min;
1178 }
1179 else if (hints & kParameterIsInteger)
1180 {
1181 value = std::round(value);
1182 }
1183
1184 for (uint32_t i=0; i < enumValues.count; ++i)
1185 {
1186 if (d_isEqual(static_cast<double>(enumValues.values[i].value), value))
1187 {
1188 d_strncpy(display, enumValues.values[i].label, size);
1189 return true;
1190 }
1191 }
1192
1193 if (hints & kParameterIsInteger)
1194 snprintf_i32(display, value, size);
1195 else
1196 snprintf_f32(display, value, size);
1197
1198 return true;
1199 }
1200
1201 bool getParameterValueForString(const clap_id param_id, const char* const display, double* const value) const
1202 {
1203 const ParameterEnumerationValues& enumValues(fPlugin.getParameterEnumValues(param_id));
1204 const bool isInteger = fPlugin.isParameterInteger(param_id);
1205
1206 for (uint32_t i=0; i < enumValues.count; ++i)
1207 {
1208 if (std::strcmp(display, enumValues.values[i].label) == 0)
1209 {
1210 *value = enumValues.values[i].value;
1211 return true;
1212 }
1213 }
1214
1215 if (isInteger)
1216 *value = std::atoi(display);
1217 else
1218 *value = std::atof(display);
1219
1220 return true;
1221 }
1222
1223 void flushParameters(const clap_input_events_t* const in,
1224 const clap_output_events_t* const out,
1225 const uint32_t frameOffset)
1226 {
1227 if (const uint32_t len = in != nullptr ? in->size(in) : 0)
1228 {
1229 for (uint32_t i=0; i<len; ++i)
1230 {
1231 const clap_event_header_t* const event = in->get(in, i);
1232
1233 if (event->type != CLAP_EVENT_PARAM_VALUE)
1234 continue;
1235
1236 DISTRHO_SAFE_ASSERT_UINT2_BREAK(event->size == sizeof(clap_event_param_value),
1237 event->size, sizeof(clap_event_param_value));
1238
1239 setParameterValueFromEvent(static_cast<const clap_event_param_value*>(static_cast<const void*>(event)));
1240 }
1241 }
1242
1243 if (out != nullptr)
1244 {
1245 clap_event_param_value_t clapEvent = {
1246 { sizeof(clap_event_param_value_t), frameOffset, 0, CLAP_EVENT_PARAM_VALUE, CLAP_EVENT_IS_LIVE },
1247 0, nullptr, 0, 0, 0, 0, 0.0
1248 };
1249
1250 float value;
1251 for (uint i=0; i<fCachedParameters.numParams; ++i)
1252 {
1253 if (fPlugin.isParameterOutputOrTrigger(i))
1254 {
1255 value = fPlugin.getParameterValue(i);
1256
1257 if (d_isEqual(fCachedParameters.values[i], value))
1258 continue;
1259
1260 fCachedParameters.values[i] = value;
1261 fCachedParameters.changed[i] = true;
1262
1263 clapEvent.param_id = i;
1264 clapEvent.value = value;
1265 out->try_push(out, &clapEvent.header);
1266 }
1267 }
1268 }
1269
1270 #if DISTRHO_PLUGIN_WANT_LATENCY
1271 const bool active = fPlugin.isActive();
1272 checkForLatencyChanges(active, !active);
1273 #endif
1274 }
1275
1276 // ----------------------------------------------------------------------------------------------------------------
1277
1278 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
1279 void addNoteEvent(const clap_event_note_t* const event, const bool isOn) noexcept
1280 {
1281 DISTRHO_SAFE_ASSERT_UINT_RETURN(event->port_index == 0, event->port_index,);
1282
1283 if (fMidiEventCount == kMaxMidiEvents)
1284 return;
1285
1286 MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]);
1287 midiEvent.frame = event->header.time;
1288 midiEvent.size = 3;
1289 midiEvent.data[0] = (isOn ? 0x90 : 0x80) | (event->channel & 0x0F);
1290 midiEvent.data[1] = std::max(0, std::min(127, static_cast<int>(event->key)));
1291 midiEvent.data[2] = std::max(0, std::min(127, static_cast<int>(event->velocity * 127 + 0.5)));
1292 }
1293
1294 void addMidiEvent(const clap_event_midi_t* const event) noexcept
1295 {
1296 DISTRHO_SAFE_ASSERT_UINT_RETURN(event->port_index == 0, event->port_index,);
1297
1298 if (fMidiEventCount == kMaxMidiEvents)
1299 return;
1300
1301 MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]);
1302 midiEvent.frame = event->header.time;
1303 midiEvent.size = 3;
1304 std::memcpy(midiEvent.data, event->data, 3);
1305 }
1306 #endif
1307
1308 void setParameterValueFromEvent(const clap_event_param_value* const event)
1309 {
1310 fCachedParameters.values[event->param_id] = event->value;
1311 fCachedParameters.changed[event->param_id] = true;
1312 fPlugin.setParameterValue(event->param_id, event->value);
1313 }
1314
1315 // ----------------------------------------------------------------------------------------------------------------
1316 // audio ports
1317
1318 #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0
1319 template<bool isInput>
1320 uint32_t getAudioPortCount() const noexcept
1321 {
1322 return (isInput ? fAudioInputBuses : fAudioOutputBuses).size();
1323 }
1324
1325 template<bool isInput>
1326 bool getAudioPortInfo(const uint32_t index, clap_audio_port_info_t* const info) const noexcept
1327 {
1328 const std::vector<BusInfo>& busInfos(isInput ? fAudioInputBuses : fAudioOutputBuses);
1329 DISTRHO_SAFE_ASSERT_RETURN(index < busInfos.size(), false);
1330
1331 const BusInfo& busInfo(busInfos[index]);
1332
1333 info->id = busInfo.groupId;
1334 d_strncpy(info->name, busInfo.name, CLAP_NAME_SIZE);
1335
1336 info->flags = busInfo.isMain ? CLAP_AUDIO_PORT_IS_MAIN : 0x0;
1337 info->channel_count = busInfo.numChannels;
1338
1339 switch (busInfo.groupId)
1340 {
1341 case kPortGroupMono:
1342 info->port_type = CLAP_PORT_MONO;
1343 break;
1344 case kPortGroupStereo:
1345 info->port_type = CLAP_PORT_STEREO;
1346 break;
1347 default:
1348 info->port_type = nullptr;
1349 break;
1350 }
1351
1352 info->in_place_pair = busInfo.hasPair ? busInfo.groupId : CLAP_INVALID_ID;
1353 return true;
1354 }
1355 #endif
1356
1357 // ----------------------------------------------------------------------------------------------------------------
1358 // latency
1359
1360 #if DISTRHO_PLUGIN_WANT_LATENCY
1361 uint32_t getLatency() const noexcept
1362 {
1363 return fPlugin.getLatency();
1364 }
1365
1366 void checkForLatencyChanges(const bool isActive, const bool fromMainThread)
1367 {
1368 const uint32_t latency = fPlugin.getLatency();
1369
1370 if (fLastKnownLatency == latency)
1371 return;
1372
1373 fLastKnownLatency = latency;
1374
1375 if (isActive)
1376 {
1377 fLatencyChanged = true;
1378 fHost->request_restart(fHost);
1379 }
1380 else
1381 {
1382 // if this is main-thread we can report latency change directly
1383 if (fromMainThread || (fHostExtensions.threadCheck != nullptr && fHostExtensions.threadCheck->is_main_thread(fHost)))
1384 {
1385 fLatencyChanged = false;
1386 fHostExtensions.latency->changed(fHost);
1387 }
1388 // otherwise we need to request a main-thread callback
1389 else
1390 {
1391 fLatencyChanged = true;
1392 fHost->request_callback(fHost);
1393 }
1394 }
1395 }
1396
1397 // called from main thread
1398 void reportLatencyChangeIfNeeded()
1399 {
1400 if (fLatencyChanged)
1401 {
1402 fLatencyChanged = false;
1403 fHostExtensions.latency->changed(fHost);
1404 }
1405 }
1406 #endif
1407
1408 // ----------------------------------------------------------------------------------------------------------------
1409 // state
1410
1411 bool stateSave(const clap_ostream_t* const stream)
1412 {
1413 const uint32_t paramCount = fPlugin.getParameterCount();
1414 #if DISTRHO_PLUGIN_WANT_STATE
1415 const uint32_t stateCount = fPlugin.getStateCount();
1416 #else
1417 const uint32_t stateCount = 0;
1418 #endif
1419
1420 if (stateCount == 0 && paramCount == 0)
1421 {
1422 char buffer = '\0';
1423 return stream->write(stream, &buffer, 1) == 1;
1424 }
1425
1426 #if DISTRHO_PLUGIN_WANT_FULL_STATE
1427 // Update current state
1428 for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
1429 {
1430 const String& key = cit->first;
1431 fStateMap[key] = fPlugin.getStateValue(key);
1432 }
1433 #endif
1434
1435 String state;
1436
1437 #if DISTRHO_PLUGIN_WANT_PROGRAMS
1438 {
1439 String tmpStr("__dpf_program__\xff");
1440 tmpStr += String(fCurrentProgram);
1441 tmpStr += "\xff";
1442
1443 state += tmpStr;
1444 }
1445 #endif
1446
1447 #if DISTRHO_PLUGIN_WANT_STATE
1448 if (stateCount != 0)
1449 {
1450 state += "__dpf_state_begin__\xff";
1451
1452 for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
1453 {
1454 const String& key = cit->first;
1455 const String& value = cit->second;
1456
1457 // join key and value
1458 String tmpStr;
1459 tmpStr = key;
1460 tmpStr += "\xff";
1461 tmpStr += value;
1462 tmpStr += "\xff";
1463
1464 state += tmpStr;
1465 }
1466
1467 state += "__dpf_state_end__\xff";
1468 }
1469 #endif
1470
1471 if (paramCount != 0)
1472 {
1473 state += "__dpf_parameters_begin__\xff";
1474
1475 for (uint32_t i=0; i<paramCount; ++i)
1476 {
1477 if (fPlugin.isParameterOutputOrTrigger(i))
1478 continue;
1479
1480 // join key and value
1481 String tmpStr;
1482 tmpStr = fPlugin.getParameterSymbol(i);
1483 tmpStr += "\xff";
1484 if (fPlugin.getParameterHints(i) & kParameterIsInteger)
1485 tmpStr += String(static_cast<int>(std::round(fPlugin.getParameterValue(i))));
1486 else
1487 tmpStr += String(fPlugin.getParameterValue(i));
1488 tmpStr += "\xff";
1489
1490 state += tmpStr;
1491 }
1492
1493 state += "__dpf_parameters_end__\xff";
1494 }
1495
1496 // terminator
1497 state += "\xfe";
1498
1499 state.replace('\xff', '\0');
1500
1501 // now saving state, carefully until host written bytes matches full state size
1502 const char* buffer = state.buffer();
1503 const int32_t size = static_cast<int32_t>(state.length())+1;
1504
1505 for (int32_t wrtntotal = 0, wrtn; wrtntotal < size; wrtntotal += wrtn)
1506 {
1507 wrtn = stream->write(stream, buffer, size - wrtntotal);
1508 DISTRHO_SAFE_ASSERT_INT_RETURN(wrtn > 0, wrtn, false);
1509 }
1510
1511 return true;
1512 }
1513
1514 bool stateLoad(const clap_istream_t* const stream)
1515 {
1516 #if DISTRHO_PLUGIN_HAS_UI
1517 ClapUI* const ui = fUI.get();
1518 #endif
1519 String key, value;
1520 bool hasValue = false;
1521 bool fillingKey = true; // if filling key or value
1522 char queryingType = 'i'; // can be 'n', 's' or 'p' (none, states, parameters)
1523
1524 char buffer[512], orig;
1525 buffer[sizeof(buffer)-1] = '\xff';
1526
1527 for (int32_t terminated = 0, read; terminated == 0;)
1528 {
1529 read = stream->read(stream, buffer, sizeof(buffer)-1);
1530 DISTRHO_SAFE_ASSERT_INT_RETURN(read >= 0, read, false);
1531
1532 if (read == 0)
1533 return true;
1534
1535 for (int32_t i = 0; i < read; ++i)
1536 {
1537 // found terminator, stop here
1538 if (buffer[i] == '\xfe')
1539 {
1540 terminated = 1;
1541 break;
1542 }
1543
1544 // store character at read position
1545 orig = buffer[read];
1546
1547 // place null character to create valid string
1548 buffer[read] = '\0';
1549
1550 // append to temporary vars
1551 if (fillingKey)
1552 {
1553 key += buffer + i;
1554 }
1555 else
1556 {
1557 value += buffer + i;
1558 hasValue = true;
1559 }
1560
1561 // increase buffer offset by length of string
1562 i += std::strlen(buffer + i);
1563
1564 // restore read character
1565 buffer[read] = orig;
1566
1567 // if buffer offset points to null, we found the end of a string, lets check
1568 if (buffer[i] == '\0')
1569 {
1570 // special keys
1571 if (key == "__dpf_state_begin__")
1572 {
1573 DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n',
1574 queryingType, false);
1575 queryingType = 's';
1576 key.clear();
1577 value.clear();
1578 hasValue = false;
1579 continue;
1580 }
1581 if (key == "__dpf_state_end__")
1582 {
1583 DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 's', queryingType, false);
1584 queryingType = 'n';
1585 key.clear();
1586 value.clear();
1587 hasValue = false;
1588 continue;
1589 }
1590 if (key == "__dpf_parameters_begin__")
1591 {
1592 DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n',
1593 queryingType, false);
1594 queryingType = 'p';
1595 key.clear();
1596 value.clear();
1597 hasValue = false;
1598 continue;
1599 }
1600 if (key == "__dpf_parameters_end__")
1601 {
1602 DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'p', queryingType, false);
1603 queryingType = 'x';
1604 key.clear();
1605 value.clear();
1606 hasValue = false;
1607 continue;
1608 }
1609
1610 // no special key, swap between reading real key and value
1611 fillingKey = !fillingKey;
1612
1613 // if there is no value yet keep reading until we have one
1614 if (! hasValue)
1615 continue;
1616
1617 if (key == "__dpf_program__")
1618 {
1619 DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i', queryingType, false);
1620 queryingType = 'n';
1621
1622 d_debug("found program '%s'", value.buffer());
1623
1624 #if DISTRHO_PLUGIN_WANT_PROGRAMS
1625 const int program = std::atoi(value.buffer());
1626 DISTRHO_SAFE_ASSERT_CONTINUE(program >= 0);
1627
1628 fCurrentProgram = static_cast<uint32_t>(program);
1629 fPlugin.loadProgram(fCurrentProgram);
1630
1631 #if DISTRHO_PLUGIN_HAS_UI
1632 if (ui != nullptr)
1633 ui->setProgramFromPlugin(fCurrentProgram);
1634 #endif
1635 #endif
1636 }
1637 else if (queryingType == 's')
1638 {
1639 d_debug("found state '%s' '%s'", key.buffer(), value.buffer());
1640
1641 #if DISTRHO_PLUGIN_WANT_STATE
1642 if (fPlugin.wantStateKey(key))
1643 {
1644 fStateMap[key] = value;
1645 fPlugin.setState(key, value);
1646
1647 #if DISTRHO_PLUGIN_HAS_UI
1648 if (ui != nullptr)
1649 ui->setStateFromPlugin(key, value);
1650 #endif
1651 }
1652 #endif
1653 }
1654 else if (queryingType == 'p')
1655 {
1656 d_debug("found parameter '%s' '%s'", key.buffer(), value.buffer());
1657 float fvalue;
1658
1659 // find parameter with this symbol, and set its value
1660 for (uint32_t j=0; j<fCachedParameters.numParams; ++j)
1661 {
1662 if (fPlugin.isParameterOutputOrTrigger(j))
1663 continue;
1664 if (fPlugin.getParameterSymbol(j) != key)
1665 continue;
1666
1667 if (fPlugin.getParameterHints(j) & kParameterIsInteger)
1668 fvalue = std::atoi(value.buffer());
1669 else
1670 fvalue = std::atof(value.buffer());
1671
1672 fCachedParameters.values[j] = fvalue;
1673 #if DISTRHO_PLUGIN_HAS_UI
1674 if (ui != nullptr)
1675 {
1676 // UI parameter updates are handled outside the read loop (after host param restart)
1677 fCachedParameters.changed[j] = true;
1678 }
1679 #endif
1680 fPlugin.setParameterValue(j, fvalue);
1681 break;
1682 }
1683 }
1684
1685 key.clear();
1686 value.clear();
1687 hasValue = false;
1688 }
1689 }
1690 }
1691
1692 if (fHostExtensions.params != nullptr)
1693 fHostExtensions.params->rescan(fHost, CLAP_PARAM_RESCAN_VALUES|CLAP_PARAM_RESCAN_TEXT);
1694
1695 #if DISTRHO_PLUGIN_WANT_LATENCY
1696 checkForLatencyChanges(fPlugin.isActive(), true);
1697 reportLatencyChangeIfNeeded();
1698 #endif
1699
1700 #if DISTRHO_PLUGIN_HAS_UI
1701 if (ui != nullptr)
1702 {
1703 for (uint32_t i=0; i<fCachedParameters.numParams; ++i)
1704 {
1705 if (fPlugin.isParameterOutputOrTrigger(i))
1706 continue;
1707 fCachedParameters.changed[i] = false;
1708 ui->setParameterValueFromPlugin(i, fCachedParameters.values[i]);
1709 }
1710 }
1711 #endif
1712
1713 return true;
1714 }
1715
1716 // ----------------------------------------------------------------------------------------------------------------
1717 // gui
1718
1719 #if DISTRHO_PLUGIN_HAS_UI
1720 bool createUI(const bool isFloating)
1721 {
1722 const clap_host_gui_t* const hostGui = getHostExtension<clap_host_gui_t>(CLAP_EXT_GUI);
1723 DISTRHO_SAFE_ASSERT_RETURN(hostGui != nullptr, false);
1724
1725 #if DPF_CLAP_USING_HOST_TIMER
1726 const clap_host_timer_support_t* const hostTimer = getHostExtension<clap_host_timer_support_t>(CLAP_EXT_TIMER_SUPPORT);
1727 DISTRHO_SAFE_ASSERT_RETURN(hostTimer != nullptr, false);
1728 #endif
1729
1730 fUI = new ClapUI(fPlugin, this, fHost, hostGui,
1731 #if DPF_CLAP_USING_HOST_TIMER
1732 hostTimer,
1733 #endif
1734 isFloating);
1735 return true;
1736 }
1737
1738 void destroyUI()
1739 {
1740 fUI = nullptr;
1741 }
1742
1743 ClapUI* getUI() const noexcept
1744 {
1745 return fUI.get();
1746 }
1747 #endif
1748
1749 #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_STATE
1750 void setStateFromUI(const char* const key, const char* const value) override
1751 {
1752 fPlugin.setState(key, value);
1753
1754 // check if we want to save this key
1755 if (! fPlugin.wantStateKey(key))
1756 return;
1757
1758 // check if key already exists
1759 for (StringMap::iterator it=fStateMap.begin(), ite=fStateMap.end(); it != ite; ++it)
1760 {
1761 const String& dkey(it->first);
1762
1763 if (dkey == key)
1764 {
1765 it->second = value;
1766 return;
1767 }
1768 }
1769
1770 d_stderr("Failed to find plugin state with key \"%s\"", key);
1771 }
1772 #endif
1773
1774 // ----------------------------------------------------------------------------------------------------------------
1775
1776 private:
1777 // Plugin and UI
1778 PluginExporter fPlugin;
1779 #if DISTRHO_PLUGIN_HAS_UI
1780 ScopedPointer<ClapUI> fUI;
1781 #endif
1782
1783 // CLAP stuff
1784 const clap_host_t* const fHost;
1785 const clap_output_events_t* fOutputEvents;
1786
1787 #if DISTRHO_PLUGIN_NUM_INPUTS != 0
1788 const float* fAudioInputs[DISTRHO_PLUGIN_NUM_INPUTS];
1789 #endif
1790 #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0
1791 float* fAudioOutputs[DISTRHO_PLUGIN_NUM_OUTPUTS];
1792 #endif
1793 #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0
1794 bool fUsingCV;
1795 #endif
1796 #if DISTRHO_PLUGIN_WANT_LATENCY
1797 bool fLatencyChanged;
1798 uint32_t fLastKnownLatency;
1799 #endif
1800 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
1801 uint32_t fMidiEventCount;
1802 MidiEvent fMidiEvents[kMaxMidiEvents];
1803 #if DISTRHO_PLUGIN_HAS_UI
1804 RingBufferControl<SmallStackBuffer> fNotesRingBuffer;
1805 #endif
1806 #endif
1807 #if DISTRHO_PLUGIN_WANT_TIMEPOS
1808 TimePosition fTimePosition;
1809 #endif
1810
1811 struct HostExtensions {
1812 const clap_host_t* const host;
1813 const clap_host_params_t* params;
1814 #if DISTRHO_PLUGIN_WANT_LATENCY
1815 const clap_host_latency_t* latency;
1816 const clap_host_thread_check_t* threadCheck;
1817 #endif
1818
1819 HostExtensions(const clap_host_t* const host)
1820 : host(host),
1821 params(nullptr)
1822 #if DISTRHO_PLUGIN_WANT_LATENCY
1823 , latency(nullptr)
1824 , threadCheck(nullptr)
1825 #endif
1826 {}
1827
1828 bool init()
1829 {
1830 params = static_cast<const clap_host_params_t*>(host->get_extension(host, CLAP_EXT_PARAMS));
1831 #if DISTRHO_PLUGIN_WANT_LATENCY
1832 DISTRHO_SAFE_ASSERT_RETURN(host->request_restart != nullptr, false);
1833 DISTRHO_SAFE_ASSERT_RETURN(host->request_callback != nullptr, false);
1834 latency = static_cast<const clap_host_latency_t*>(host->get_extension(host, CLAP_EXT_LATENCY));
1835 threadCheck = static_cast<const clap_host_thread_check_t*>(host->get_extension(host, CLAP_EXT_THREAD_CHECK));
1836 #endif
1837 return true;
1838 }
1839 } fHostExtensions;
1840
1841 // ----------------------------------------------------------------------------------------------------------------
1842 // helper functions for dealing with buses
1843
1844 #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0
1845 struct BusInfo {
1846 char name[CLAP_NAME_SIZE];
1847 uint32_t numChannels;
1848 bool hasPair;
1849 bool isCV;
1850 bool isMain;
1851 uint32_t groupId;
1852 };
1853 std::vector<BusInfo> fAudioInputBuses, fAudioOutputBuses;
1854
1855 template<bool isInput>
1856 void fillInBusInfoDetails()
1857 {
1858 constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS;
1859 std::vector<BusInfo>& busInfos(isInput ? fAudioInputBuses : fAudioOutputBuses);
1860
1861 enum {
1862 kPortTypeNull,
1863 kPortTypeAudio,
1864 kPortTypeSidechain,
1865 kPortTypeCV,
1866 kPortTypeGroup
1867 } lastSeenPortType = kPortTypeNull;
1868 uint32_t lastSeenGroupId = kPortGroupNone;
1869 uint32_t nonGroupAudioId = 0;
1870 uint32_t nonGroupSidechainId = 0x20000000;
1871
1872 for (uint32_t i=0; i<numPorts; ++i)
1873 {
1874 const AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i));
1875
1876 if (port.groupId != kPortGroupNone)
1877 {
1878 if (lastSeenPortType != kPortTypeGroup || lastSeenGroupId != port.groupId)
1879 {
1880 lastSeenPortType = kPortTypeGroup;
1881 lastSeenGroupId = port.groupId;
1882
1883 BusInfo busInfo = {
1884 {}, 1, false, false,
1885 // if this is the first port, set it as main
1886 busInfos.empty(),
1887 // user given group id with extra safety offset
1888 port.groupId + 0x80000000
1889 };
1890
1891 const PortGroupWithId& group(fPlugin.getPortGroupById(port.groupId));
1892
1893 switch (port.groupId)
1894 {
1895 case kPortGroupStereo:
1896 case kPortGroupMono:
1897 if (busInfo.isMain)
1898 {
1899 d_strncpy(busInfo.name, isInput ? "Audio Input" : "Audio Output", CLAP_NAME_SIZE);
1900 break;
1901 }
1902 // fall-through
1903 default:
1904 if (group.name.isNotEmpty())
1905 d_strncpy(busInfo.name, group.name, CLAP_NAME_SIZE);
1906 else
1907 d_strncpy(busInfo.name, port.name, CLAP_NAME_SIZE);
1908 break;
1909 }
1910
1911 busInfos.push_back(busInfo);
1912 }
1913 else
1914 {
1915 ++busInfos.back().numChannels;
1916 }
1917 }
1918 else if (port.hints & kAudioPortIsCV)
1919 {
1920 // TODO
1921 lastSeenPortType = kPortTypeCV;
1922 lastSeenGroupId = kPortGroupNone;
1923 fUsingCV = true;
1924 }
1925 else if (port.hints & kAudioPortIsSidechain)
1926 {
1927 if (lastSeenPortType != kPortTypeSidechain)
1928 {
1929 lastSeenPortType = kPortTypeSidechain;
1930 lastSeenGroupId = kPortGroupNone;
1931
1932 BusInfo busInfo = {
1933 {}, 1, false, false,
1934 // not main
1935 false,
1936 // give unique id
1937 nonGroupSidechainId++
1938 };
1939
1940 d_strncpy(busInfo.name, port.name, CLAP_NAME_SIZE);
1941
1942 busInfos.push_back(busInfo);
1943 }
1944 else
1945 {
1946 ++busInfos.back().numChannels;
1947 }
1948 }
1949 else
1950 {
1951 if (lastSeenPortType != kPortTypeAudio)
1952 {
1953 lastSeenPortType = kPortTypeAudio;
1954 lastSeenGroupId = kPortGroupNone;
1955
1956 BusInfo busInfo = {
1957 {}, 1, false, false,
1958 // if this is the first port, set it as main
1959 busInfos.empty(),
1960 // give unique id
1961 nonGroupAudioId++
1962 };
1963
1964 if (busInfo.isMain)
1965 {
1966 d_strncpy(busInfo.name, isInput ? "Audio Input" : "Audio Output", CLAP_NAME_SIZE);
1967 }
1968 else
1969 {
1970 d_strncpy(busInfo.name, port.name, CLAP_NAME_SIZE);
1971 }
1972
1973 busInfos.push_back(busInfo);
1974 }
1975 else
1976 {
1977 ++busInfos.back().numChannels;
1978 }
1979 }
1980 }
1981 }
1982 #endif
1983
1984 #if DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0
1985 void fillInBusInfoPairs()
1986 {
1987 const size_t numChannels = std::min(fAudioInputBuses.size(), fAudioOutputBuses.size());
1988
1989 for (size_t i=0; i<numChannels; ++i)
1990 {
1991 if (fAudioInputBuses[i].groupId != fAudioOutputBuses[i].groupId)
1992 break;
1993 if (fAudioInputBuses[i].numChannels != fAudioOutputBuses[i].numChannels)
1994 break;
1995 if (fAudioInputBuses[i].isMain != fAudioOutputBuses[i].isMain)
1996 break;
1997
1998 fAudioInputBuses[i].hasPair = fAudioOutputBuses[i].hasPair = true;
1999 }
2000 }
2001 #endif
2002
2003 // ----------------------------------------------------------------------------------------------------------------
2004 // DPF callbacks
2005
2006 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
2007 bool writeMidi(const MidiEvent& midiEvent)
2008 {
2009 DISTRHO_SAFE_ASSERT_RETURN(fOutputEvents != nullptr, false);
2010
2011 if (midiEvent.size > 3)
2012 return true;
2013
2014 const clap_event_midi clapEvent = {
2015 { sizeof(clap_event_midi), midiEvent.frame, 0, CLAP_EVENT_MIDI, CLAP_EVENT_IS_LIVE },
2016 0, { midiEvent.data[0],
2017 static_cast<uint8_t>(midiEvent.size >= 2 ? midiEvent.data[1] : 0),
2018 static_cast<uint8_t>(midiEvent.size >= 3 ? midiEvent.data[2] : 0) }
2019 };
2020 return fOutputEvents->try_push(fOutputEvents, &clapEvent.header);
2021 }
2022
2023 static bool writeMidiCallback(void* const ptr, const MidiEvent& midiEvent)
2024 {
2025 return static_cast<PluginCLAP*>(ptr)->writeMidi(midiEvent);
2026 }
2027 #endif
2028
2029 #if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST
2030 bool requestParameterValueChange(uint32_t, float)
2031 {
2032 return true;
2033 }
2034
2035 static bool requestParameterValueChangeCallback(void* const ptr, const uint32_t index, const float value)
2036 {
2037 return static_cast<PluginCLAP*>(ptr)->requestParameterValueChange(index, value);
2038 }
2039 #endif
2040
2041 #if DISTRHO_PLUGIN_WANT_STATE
2042 bool updateState(const char*, const char*)
2043 {
2044 return true;
2045 }
2046
2047 static bool updateStateValueCallback(void* const ptr, const char* const key, const char* const value)
2048 {
2049 return static_cast<PluginCLAP*>(ptr)->updateState(key, value);
2050 }
2051 #endif
2052 };
2053
2054 // --------------------------------------------------------------------------------------------------------------------
2055
2056 static ScopedPointer<PluginExporter> sPlugin;
2057
2058 // --------------------------------------------------------------------------------------------------------------------
2059 // plugin gui
2060
2061 #if DISTRHO_PLUGIN_HAS_UI
2062
2063 static const char* const kSupportedAPIs[] = {
2064 #if defined(DISTRHO_OS_WINDOWS)
2065 CLAP_WINDOW_API_WIN32,
2066 #elif defined(DISTRHO_OS_MAC)
2067 CLAP_WINDOW_API_COCOA,
2068 #else
2069 CLAP_WINDOW_API_X11,
2070 #endif
2071 };
2072
2073 // TODO DPF external UI
2074 static bool CLAP_ABI clap_gui_is_api_supported(const clap_plugin_t*, const char* const api, bool)
2075 {
2076 for (size_t i=0; i<ARRAY_SIZE(kSupportedAPIs); ++i)
2077 {
2078 if (std::strcmp(kSupportedAPIs[i], api) == 0)
2079 return true;
2080 }
2081
2082 return false;
2083 }
2084
2085 // TODO DPF external UI
2086 static bool CLAP_ABI clap_gui_get_preferred_api(const clap_plugin_t*, const char** const api, bool* const is_floating)
2087 {
2088 *api = kSupportedAPIs[0];
2089 *is_floating = false;
2090 return true;
2091 }
2092
2093 static bool CLAP_ABI clap_gui_create(const clap_plugin_t* const plugin, const char* const api, const bool is_floating)
2094 {
2095 for (size_t i=0; i<ARRAY_SIZE(kSupportedAPIs); ++i)
2096 {
2097 if (std::strcmp(kSupportedAPIs[i], api) == 0)
2098 {
2099 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2100 return instance->createUI(is_floating);
2101 }
2102 }
2103
2104 return false;
2105 }
2106
2107 static void CLAP_ABI clap_gui_destroy(const clap_plugin_t* const plugin)
2108 {
2109 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2110 instance->destroyUI();
2111 }
2112
2113 static bool CLAP_ABI clap_gui_set_scale(const clap_plugin_t* const plugin, const double scale)
2114 {
2115 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2116 ClapUI* const gui = instance->getUI();
2117 DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
2118 #ifndef DISTRHO_OS_MAC
2119 return gui->setScaleFactor(scale);
2120 #else
2121 return true;
2122 // unused
2123 (void)scale;
2124 #endif
2125 }
2126
2127 static bool CLAP_ABI clap_gui_get_size(const clap_plugin_t* const plugin, uint32_t* const width, uint32_t* const height)
2128 {
2129 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2130 ClapUI* const gui = instance->getUI();
2131 DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
2132 return gui->getSize(width, height);
2133 }
2134
2135 static bool CLAP_ABI clap_gui_can_resize(const clap_plugin_t* const plugin)
2136 {
2137 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2138 ClapUI* const gui = instance->getUI();
2139 DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
2140 return gui->canResize();
2141 }
2142
2143 static bool CLAP_ABI clap_gui_get_resize_hints(const clap_plugin_t* const plugin, clap_gui_resize_hints_t* const hints)
2144 {
2145 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2146 ClapUI* const gui = instance->getUI();
2147 DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
2148 return gui->getResizeHints(hints);
2149 }
2150
2151 static bool CLAP_ABI clap_gui_adjust_size(const clap_plugin_t* const plugin, uint32_t* const width, uint32_t* const height)
2152 {
2153 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2154 ClapUI* const gui = instance->getUI();
2155 DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
2156 return gui->adjustSize(width, height);
2157 }
2158
2159 static bool CLAP_ABI clap_gui_set_size(const clap_plugin_t* const plugin, const uint32_t width, const uint32_t height)
2160 {
2161 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2162 ClapUI* const gui = instance->getUI();
2163 DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
2164 return gui->setSizeFromHost(width, height);
2165 }
2166
2167 static bool CLAP_ABI clap_gui_set_parent(const clap_plugin_t* const plugin, const clap_window_t* const window)
2168 {
2169 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2170 ClapUI* const gui = instance->getUI();
2171 DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
2172 return gui->setParent(window);
2173 }
2174
2175 static bool CLAP_ABI clap_gui_set_transient(const clap_plugin_t* const plugin, const clap_window_t* const window)
2176 {
2177 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2178 ClapUI* const gui = instance->getUI();
2179 DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
2180 return gui->setTransient(window);
2181 }
2182
2183 static void CLAP_ABI clap_gui_suggest_title(const clap_plugin_t* const plugin, const char* const title)
2184 {
2185 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2186 ClapUI* const gui = instance->getUI();
2187 DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr,);
2188 return gui->suggestTitle(title);
2189 }
2190
2191 static bool CLAP_ABI clap_gui_show(const clap_plugin_t* const plugin)
2192 {
2193 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2194 ClapUI* const gui = instance->getUI();
2195 DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
2196 return gui->show();
2197 }
2198
2199 static bool CLAP_ABI clap_gui_hide(const clap_plugin_t* const plugin)
2200 {
2201 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2202 ClapUI* const gui = instance->getUI();
2203 DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false);
2204 return gui->hide();
2205 }
2206
2207 static const clap_plugin_gui_t clap_plugin_gui = {
2208 clap_gui_is_api_supported,
2209 clap_gui_get_preferred_api,
2210 clap_gui_create,
2211 clap_gui_destroy,
2212 clap_gui_set_scale,
2213 clap_gui_get_size,
2214 clap_gui_can_resize,
2215 clap_gui_get_resize_hints,
2216 clap_gui_adjust_size,
2217 clap_gui_set_size,
2218 clap_gui_set_parent,
2219 clap_gui_set_transient,
2220 clap_gui_suggest_title,
2221 clap_gui_show,
2222 clap_gui_hide
2223 };
2224
2225 // --------------------------------------------------------------------------------------------------------------------
2226 // plugin timer
2227
2228 #if DPF_CLAP_USING_HOST_TIMER
2229 static void CLAP_ABI clap_plugin_on_timer(const clap_plugin_t* const plugin, clap_id)
2230 {
2231 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2232 ClapUI* const gui = instance->getUI();
2233 DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr,);
2234 return gui->idleCallback();
2235 }
2236
2237 static const clap_plugin_timer_support_t clap_timer = {
2238 clap_plugin_on_timer
2239 };
2240 #endif
2241
2242 #endif // DISTRHO_PLUGIN_HAS_UI
2243
2244 // --------------------------------------------------------------------------------------------------------------------
2245 // plugin audio ports
2246
2247 #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0
2248 static uint32_t CLAP_ABI clap_plugin_audio_ports_count(const clap_plugin_t* const plugin, const bool is_input)
2249 {
2250 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2251 return is_input ? instance->getAudioPortCount<true>()
2252 : instance->getAudioPortCount<false>();
2253 }
2254
2255 static bool CLAP_ABI clap_plugin_audio_ports_get(const clap_plugin_t* const plugin,
2256 const uint32_t index,
2257 const bool is_input,
2258 clap_audio_port_info_t* const info)
2259 {
2260 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2261 return is_input ? instance->getAudioPortInfo<true>(index, info)
2262 : instance->getAudioPortInfo<false>(index, info);
2263 }
2264
2265 static const clap_plugin_audio_ports_t clap_plugin_audio_ports = {
2266 clap_plugin_audio_ports_count,
2267 clap_plugin_audio_ports_get
2268 };
2269 #endif
2270
2271 // --------------------------------------------------------------------------------------------------------------------
2272 // plugin note ports
2273
2274 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT+DISTRHO_PLUGIN_WANT_MIDI_OUTPUT != 0
2275 static uint32_t CLAP_ABI clap_plugin_note_ports_count(const clap_plugin_t*, const bool is_input)
2276 {
2277 return (is_input ? DISTRHO_PLUGIN_WANT_MIDI_INPUT : DISTRHO_PLUGIN_WANT_MIDI_OUTPUT) != 0 ? 1 : 0;
2278 }
2279
2280 static bool CLAP_ABI clap_plugin_note_ports_get(const clap_plugin_t*, uint32_t,
2281 const bool is_input, clap_note_port_info_t* const info)
2282 {
2283 if (is_input)
2284 {
2285 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
2286 info->id = 0;
2287 info->supported_dialects = CLAP_NOTE_DIALECT_MIDI;
2288 info->preferred_dialect = CLAP_NOTE_DIALECT_MIDI;
2289 std::strcpy(info->name, "Event/MIDI Input");
2290 return true;
2291 #endif
2292 }
2293 else
2294 {
2295 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
2296 info->id = 0;
2297 info->supported_dialects = CLAP_NOTE_DIALECT_MIDI;
2298 info->preferred_dialect = CLAP_NOTE_DIALECT_MIDI;
2299 std::strcpy(info->name, "Event/MIDI Output");
2300 return true;
2301 #endif
2302 }
2303
2304 return false;
2305 }
2306
2307 static const clap_plugin_note_ports_t clap_plugin_note_ports = {
2308 clap_plugin_note_ports_count,
2309 clap_plugin_note_ports_get
2310 };
2311 #endif
2312
2313 // --------------------------------------------------------------------------------------------------------------------
2314 // plugin parameters
2315
2316 static uint32_t CLAP_ABI clap_plugin_params_count(const clap_plugin_t* const plugin)
2317 {
2318 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2319 return instance->getParameterCount();
2320 }
2321
2322 static bool CLAP_ABI clap_plugin_params_get_info(const clap_plugin_t* const plugin, const uint32_t index, clap_param_info_t* const info)
2323 {
2324 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2325 return instance->getParameterInfo(index, info);
2326 }
2327
2328 static bool CLAP_ABI clap_plugin_params_get_value(const clap_plugin_t* const plugin, const clap_id param_id, double* const value)
2329 {
2330 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2331 return instance->getParameterValue(param_id, value);
2332 }
2333
2334 static bool CLAP_ABI clap_plugin_params_value_to_text(const clap_plugin_t* plugin, const clap_id param_id, const double value, char* const display, const uint32_t size)
2335 {
2336 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2337 return instance->getParameterStringForValue(param_id, value, display, size);
2338 }
2339
2340 static bool CLAP_ABI clap_plugin_params_text_to_value(const clap_plugin_t* plugin, const clap_id param_id, const char* const display, double* const value)
2341 {
2342 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2343 return instance->getParameterValueForString(param_id, display, value);
2344 }
2345
2346 static void CLAP_ABI clap_plugin_params_flush(const clap_plugin_t* plugin, const clap_input_events_t* in, const clap_output_events_t* out)
2347 {
2348 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2349 return instance->flushParameters(in, out, 0);
2350 }
2351
2352 static const clap_plugin_params_t clap_plugin_params = {
2353 clap_plugin_params_count,
2354 clap_plugin_params_get_info,
2355 clap_plugin_params_get_value,
2356 clap_plugin_params_value_to_text,
2357 clap_plugin_params_text_to_value,
2358 clap_plugin_params_flush
2359 };
2360
2361 #if DISTRHO_PLUGIN_WANT_LATENCY
2362 // --------------------------------------------------------------------------------------------------------------------
2363 // plugin latency
2364
2365 static uint32_t CLAP_ABI clap_plugin_latency_get(const clap_plugin_t* const plugin)
2366 {
2367 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2368 return instance->getLatency();
2369 }
2370
2371 static const clap_plugin_latency_t clap_plugin_latency = {
2372 clap_plugin_latency_get
2373 };
2374 #endif
2375
2376 // --------------------------------------------------------------------------------------------------------------------
2377 // plugin state
2378
2379 static bool CLAP_ABI clap_plugin_state_save(const clap_plugin_t* const plugin, const clap_ostream_t* const stream)
2380 {
2381 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2382 return instance->stateSave(stream);
2383 }
2384
2385 static bool CLAP_ABI clap_plugin_state_load(const clap_plugin_t* const plugin, const clap_istream_t* const stream)
2386 {
2387 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2388 return instance->stateLoad(stream);
2389 }
2390
2391 static const clap_plugin_state_t clap_plugin_state = {
2392 clap_plugin_state_save,
2393 clap_plugin_state_load
2394 };
2395
2396 // --------------------------------------------------------------------------------------------------------------------
2397 // plugin
2398
2399 static bool CLAP_ABI clap_plugin_init(const clap_plugin_t* const plugin)
2400 {
2401 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2402 return instance->init();
2403 }
2404
2405 static void CLAP_ABI clap_plugin_destroy(const clap_plugin_t* const plugin)
2406 {
2407 delete static_cast<PluginCLAP*>(plugin->plugin_data);
2408 std::free(const_cast<clap_plugin_t*>(plugin));
2409 }
2410
2411 static bool CLAP_ABI clap_plugin_activate(const clap_plugin_t* const plugin,
2412 const double sample_rate,
2413 uint32_t,
2414 const uint32_t max_frames_count)
2415 {
2416 d_nextBufferSize = max_frames_count;
2417 d_nextSampleRate = sample_rate;
2418
2419 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2420 instance->activate(sample_rate, max_frames_count);
2421 return true;
2422 }
2423
2424 static void CLAP_ABI clap_plugin_deactivate(const clap_plugin_t* const plugin)
2425 {
2426 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2427 instance->deactivate();
2428 }
2429
2430 static bool CLAP_ABI clap_plugin_start_processing(const clap_plugin_t*)
2431 {
2432 // nothing to do
2433 return true;
2434 }
2435
2436 static void CLAP_ABI clap_plugin_stop_processing(const clap_plugin_t*)
2437 {
2438 // nothing to do
2439 }
2440
2441 static void CLAP_ABI clap_plugin_reset(const clap_plugin_t*)
2442 {
2443 // nothing to do
2444 }
2445
2446 static clap_process_status CLAP_ABI clap_plugin_process(const clap_plugin_t* const plugin, const clap_process_t* const process)
2447 {
2448 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2449 return instance->process(process) ? CLAP_PROCESS_CONTINUE : CLAP_PROCESS_ERROR;
2450 }
2451
2452 static const void* CLAP_ABI clap_plugin_get_extension(const clap_plugin_t*, const char* const id)
2453 {
2454 if (std::strcmp(id, CLAP_EXT_PARAMS) == 0)
2455 return &clap_plugin_params;
2456 if (std::strcmp(id, CLAP_EXT_STATE) == 0)
2457 return &clap_plugin_state;
2458 #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0
2459 if (std::strcmp(id, CLAP_EXT_AUDIO_PORTS) == 0)
2460 return &clap_plugin_audio_ports;
2461 #endif
2462 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT+DISTRHO_PLUGIN_WANT_MIDI_OUTPUT != 0
2463 if (std::strcmp(id, CLAP_EXT_NOTE_PORTS) == 0)
2464 return &clap_plugin_note_ports;
2465 #endif
2466 #if DISTRHO_PLUGIN_WANT_LATENCY
2467 if (std::strcmp(id, CLAP_EXT_LATENCY) == 0)
2468 return &clap_plugin_latency;
2469 #endif
2470 #if DISTRHO_PLUGIN_HAS_UI
2471 if (std::strcmp(id, CLAP_EXT_GUI) == 0)
2472 return &clap_plugin_gui;
2473 #if DPF_CLAP_USING_HOST_TIMER
2474 if (std::strcmp(id, CLAP_EXT_TIMER_SUPPORT) == 0)
2475 return &clap_timer;
2476 #endif
2477 #endif
2478 return nullptr;
2479 }
2480
2481 static void CLAP_ABI clap_plugin_on_main_thread(const clap_plugin_t* const plugin)
2482 {
2483 PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data);
2484 instance->onMainThread();
2485 }
2486
2487 // --------------------------------------------------------------------------------------------------------------------
2488 // plugin factory
2489
2490 static uint32_t CLAP_ABI clap_get_plugin_count(const clap_plugin_factory_t*)
2491 {
2492 return 1;
2493 }
2494
2495 static const clap_plugin_descriptor_t* CLAP_ABI clap_get_plugin_descriptor(const clap_plugin_factory_t*,
2496 const uint32_t index)
2497 {
2498 DISTRHO_SAFE_ASSERT_UINT_RETURN(index == 0, index, nullptr);
2499
2500 static const char* features[] = {
2501 #ifdef DISTRHO_PLUGIN_CLAP_FEATURES
2502 DISTRHO_PLUGIN_CLAP_FEATURES,
2503 #elif DISTRHO_PLUGIN_IS_SYNTH
2504 "instrument",
2505 #endif
2506 nullptr
2507 };
2508
2509 static const clap_plugin_descriptor_t descriptor = {
2510 CLAP_VERSION,
2511 DISTRHO_PLUGIN_CLAP_ID,
2512 sPlugin->getName(),
2513 sPlugin->getMaker(),
2514 // TODO url
2515 "",
2516 // TODO manual url
2517 "",
2518 // TODO support url
2519 "",
2520 // TODO version string
2521 "",
2522 sPlugin->getDescription(),
2523 features
2524 };
2525
2526 return &descriptor;
2527 }
2528
2529 static const clap_plugin_t* CLAP_ABI clap_create_plugin(const clap_plugin_factory_t* const factory,
2530 const clap_host_t* const host,
2531 const char*)
2532 {
2533 clap_plugin_t* const pluginptr = static_cast<clap_plugin_t*>(std::malloc(sizeof(clap_plugin_t)));
2534 DISTRHO_SAFE_ASSERT_RETURN(pluginptr != nullptr, nullptr);
2535
2536 // default early values
2537 if (d_nextBufferSize == 0)
2538 d_nextBufferSize = 1024;
2539 if (d_nextSampleRate <= 0.0)
2540 d_nextSampleRate = 44100.0;
2541
2542 d_nextCanRequestParameterValueChanges = true;
2543
2544 const clap_plugin_t plugin = {
2545 clap_get_plugin_descriptor(factory, 0),
2546 new PluginCLAP(host),
2547 clap_plugin_init,
2548 clap_plugin_destroy,
2549 clap_plugin_activate,
2550 clap_plugin_deactivate,
2551 clap_plugin_start_processing,
2552 clap_plugin_stop_processing,
2553 clap_plugin_reset,
2554 clap_plugin_process,
2555 clap_plugin_get_extension,
2556 clap_plugin_on_main_thread
2557 };
2558
2559 std::memcpy(pluginptr, &plugin, sizeof(clap_plugin_t));
2560 return pluginptr;
2561 }
2562
2563 static const clap_plugin_factory_t clap_plugin_factory = {
2564 clap_get_plugin_count,
2565 clap_get_plugin_descriptor,
2566 clap_create_plugin
2567 };
2568
2569 // --------------------------------------------------------------------------------------------------------------------
2570 // plugin entry
2571
2572 static bool CLAP_ABI clap_plugin_entry_init(const char* const plugin_path)
2573 {
2574 static String bundlePath;
2575 bundlePath = plugin_path;
2576 d_nextBundlePath = bundlePath.buffer();
2577
2578 // init dummy plugin
2579 if (sPlugin == nullptr)
2580 {
2581 // set valid but dummy values
2582 d_nextBufferSize = 512;
2583 d_nextSampleRate = 44100.0;
2584 d_nextPluginIsDummy = true;
2585 d_nextCanRequestParameterValueChanges = true;
2586
2587 // Create dummy plugin to get data from
2588 sPlugin = new PluginExporter(nullptr, nullptr, nullptr, nullptr);
2589
2590 // unset
2591 d_nextBufferSize = 0;
2592 d_nextSampleRate = 0.0;
2593 d_nextPluginIsDummy = false;
2594 d_nextCanRequestParameterValueChanges = false;
2595 }
2596
2597 return true;
2598 }
2599
2600 static void CLAP_ABI clap_plugin_entry_deinit(void)
2601 {
2602 sPlugin = nullptr;
2603 }
2604
2605 static const void* CLAP_ABI clap_plugin_entry_get_factory(const char* const factory_id)
2606 {
2607 if (std::strcmp(factory_id, CLAP_PLUGIN_FACTORY_ID) == 0)
2608 return &clap_plugin_factory;
2609 return nullptr;
2610 }
2611
2612 static const clap_plugin_entry_t clap_plugin_entry = {
2613 CLAP_VERSION,
2614 clap_plugin_entry_init,
2615 clap_plugin_entry_deinit,
2616 clap_plugin_entry_get_factory
2617 };
2618
2619 // --------------------------------------------------------------------------------------------------------------------
2620
2621 END_NAMESPACE_DISTRHO
2622
2623 // --------------------------------------------------------------------------------------------------------------------
2624
2625 DISTRHO_PLUGIN_EXPORT
2626 const clap_plugin_entry_t clap_entry = DISTRHO_NAMESPACE::clap_plugin_entry;
2627
2628 // --------------------------------------------------------------------------------------------------------------------