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