DistrhoUILV2.cpp (32138B)
1 /* 2 * DISTRHO Plugin Framework (DPF) 3 * Copyright (C) 2012-2021 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 #include "DistrhoUIInternal.hpp" 18 19 #include "../extra/String.hpp" 20 21 #include "lv2/atom.h" 22 #include "lv2/atom-util.h" 23 #include "lv2/data-access.h" 24 #include "lv2/instance-access.h" 25 #include "lv2/midi.h" 26 #include "lv2/options.h" 27 #include "lv2/parameters.h" 28 #include "lv2/patch.h" 29 #include "lv2/ui.h" 30 #include "lv2/urid.h" 31 #include "lv2/lv2_kxstudio_properties.h" 32 #include "lv2/lv2_programs.h" 33 34 #ifndef DISTRHO_PLUGIN_LV2_STATE_PREFIX 35 # define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:" 36 #endif 37 38 START_NAMESPACE_DISTRHO 39 40 typedef struct _LV2_Atom_MidiEvent { 41 LV2_Atom atom; /**< Atom header. */ 42 uint8_t data[3]; /**< MIDI data (body). */ 43 } LV2_Atom_MidiEvent; 44 45 #if ! DISTRHO_PLUGIN_WANT_STATE 46 static constexpr const setStateFunc setStateCallback = nullptr; 47 #endif 48 #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT 49 static constexpr const sendNoteFunc sendNoteCallback = nullptr; 50 #endif 51 52 // unwanted in LV2, resize extension is deprecated and hosts can do it without extensions 53 static constexpr const setSizeFunc setSizeCallback = nullptr; 54 55 // ----------------------------------------------------------------------- 56 57 template <class LV2F> 58 static const LV2F* getLv2Feature(const LV2_Feature* const* features, const char* const uri) 59 { 60 for (int i=0; features[i] != nullptr; ++i) 61 { 62 if (std::strcmp(features[i]->URI, uri) == 0) 63 return (const LV2F*)features[i]->data; 64 } 65 66 return nullptr; 67 } 68 69 class UiLv2 70 { 71 public: 72 UiLv2(const char* const bundlePath, 73 const intptr_t winId, 74 const LV2_Options_Option* options, 75 const LV2_URID_Map* const uridMap, 76 const LV2_Feature* const* const features, 77 const LV2UI_Controller controller, 78 const LV2UI_Write_Function writeFunc, 79 LV2UI_Widget* const widget, 80 void* const dspPtr, 81 const float sampleRate, 82 const float scaleFactor, 83 const uint32_t bgColor, 84 const uint32_t fgColor, 85 const char* const appClassName) 86 : fUridMap(uridMap), 87 fUridUnmap(getLv2Feature<LV2_URID_Unmap>(features, LV2_URID__unmap)), 88 fUiPortMap(getLv2Feature<LV2UI_Port_Map>(features, LV2_UI__portMap)), 89 fUiRequestValue(getLv2Feature<LV2UI_Request_Value>(features, LV2_UI__requestValue)), 90 fUiTouch(getLv2Feature<LV2UI_Touch>(features, LV2_UI__touch)), 91 fController(controller), 92 fWriteFunction(writeFunc), 93 fURIDs(uridMap), 94 fBypassParameterIndex(fUiPortMap != nullptr ? fUiPortMap->port_index(fUiPortMap->handle, ParameterDesignationSymbols::bypass_lv2) 95 : LV2UI_INVALID_PORT_INDEX), 96 fWinIdWasNull(winId == 0), 97 fUI(this, winId, sampleRate, 98 editParameterCallback, 99 setParameterCallback, 100 setStateCallback, 101 sendNoteCallback, 102 setSizeCallback, 103 fileRequestCallback, 104 bundlePath, dspPtr, scaleFactor, bgColor, fgColor, appClassName) 105 { 106 if (widget != nullptr) 107 *widget = (LV2UI_Widget)fUI.getNativeWindowHandle(); 108 109 #if DISTRHO_PLUGIN_WANT_STATE 110 // tell the DSP we're ready to receive msgs 111 setState("__dpf_ui_data__", ""); 112 #endif 113 114 if (winId != 0) 115 return; 116 117 // if winId == 0 then options must not be null 118 DISTRHO_SAFE_ASSERT_RETURN(options != nullptr,); 119 120 #ifndef __EMSCRIPTEN__ 121 const LV2_URID uridWindowTitle = uridMap->map(uridMap->handle, LV2_UI__windowTitle); 122 const LV2_URID uridTransientWinId = uridMap->map(uridMap->handle, LV2_KXSTUDIO_PROPERTIES__TransientWindowId); 123 124 const char* windowTitle = nullptr; 125 126 for (int i=0; options[i].key != 0; ++i) 127 { 128 if (options[i].key == uridTransientWinId) 129 { 130 if (options[i].type == fURIDs.atomLong) 131 { 132 if (const int64_t transientWinId = *(const int64_t*)options[i].value) 133 fUI.setWindowTransientWinId(static_cast<intptr_t>(transientWinId)); 134 } 135 else 136 d_stderr("Host provides transientWinId but has wrong value type"); 137 } 138 else if (options[i].key == uridWindowTitle) 139 { 140 if (options[i].type == fURIDs.atomString) 141 { 142 windowTitle = (const char*)options[i].value; 143 } 144 else 145 d_stderr("Host provides windowTitle but has wrong value type"); 146 } 147 } 148 149 if (windowTitle == nullptr) 150 windowTitle = DISTRHO_PLUGIN_NAME; 151 152 fUI.setWindowTitle(windowTitle); 153 #endif 154 } 155 156 // ------------------------------------------------------------------- 157 158 void lv2ui_port_event(const uint32_t rindex, const uint32_t bufferSize, const uint32_t format, const void* const buffer) 159 { 160 if (format == 0) 161 { 162 const uint32_t parameterOffset = fUI.getParameterOffset(); 163 164 if (rindex < parameterOffset) 165 return; 166 167 DISTRHO_SAFE_ASSERT_RETURN(bufferSize == sizeof(float),) 168 169 float value = *(const float*)buffer; 170 171 if (rindex == fBypassParameterIndex) 172 value = 1.0f - value; 173 174 fUI.parameterChanged(rindex-parameterOffset, value); 175 } 176 #if DISTRHO_PLUGIN_WANT_STATE 177 else if (format == fURIDs.atomEventTransfer) 178 { 179 const LV2_Atom* const atom = (const LV2_Atom*)buffer; 180 181 if (atom->type == fURIDs.dpfKeyValue) 182 { 183 const char* const key = (const char*)LV2_ATOM_BODY_CONST(atom); 184 const char* const value = key+(std::strlen(key)+1); 185 186 fUI.stateChanged(key, value); 187 } 188 else if (atom->type == fURIDs.atomObject && fUridUnmap != nullptr) 189 { 190 const LV2_Atom_Object* const obj = (const LV2_Atom_Object*)atom; 191 192 const LV2_Atom* property = nullptr; 193 const LV2_Atom* atomvalue = nullptr; 194 lv2_atom_object_get(obj, fURIDs.patchProperty, &property, fURIDs.patchValue, &atomvalue, 0); 195 196 DISTRHO_SAFE_ASSERT_RETURN(property != nullptr,); 197 DISTRHO_SAFE_ASSERT_RETURN(atomvalue != nullptr,); 198 199 DISTRHO_SAFE_ASSERT_RETURN(property->type == fURIDs.atomURID,); 200 DISTRHO_SAFE_ASSERT_RETURN(atomvalue->type == fURIDs.atomPath || atomvalue->type == fURIDs.atomString,); 201 202 if (property != nullptr && property->type == fURIDs.atomURID && 203 atomvalue != nullptr && (atomvalue->type == fURIDs.atomPath || atomvalue->type == fURIDs.atomString)) 204 { 205 const LV2_URID dpf_lv2_urid = ((const LV2_Atom_URID*)property)->body; 206 DISTRHO_SAFE_ASSERT_RETURN(dpf_lv2_urid != 0,); 207 208 const char* const dpf_lv2_key = fUridUnmap->unmap(fUridUnmap->handle, dpf_lv2_urid); 209 DISTRHO_SAFE_ASSERT_RETURN(dpf_lv2_key != nullptr,); 210 211 /*constexpr*/ const size_t reqLen = std::strlen(DISTRHO_PLUGIN_URI "#"); 212 DISTRHO_SAFE_ASSERT_RETURN(std::strlen(dpf_lv2_key) > reqLen,); 213 214 const char* const key = dpf_lv2_key + reqLen; 215 const char* const value = (const char*)LV2_ATOM_BODY_CONST(atomvalue); 216 217 fUI.stateChanged(key, value); 218 } 219 } 220 else if (atom->type == fURIDs.midiEvent) 221 { 222 // ignore 223 } 224 else 225 { 226 d_stdout("DPF :: received atom not handled :: %s", 227 fUridUnmap != nullptr ? fUridUnmap->unmap(fUridUnmap->handle, atom->type) : "(null)"); 228 } 229 } 230 #endif 231 } 232 233 // ------------------------------------------------------------------- 234 235 int lv2ui_idle() 236 { 237 if (fWinIdWasNull) 238 return (fUI.plugin_idle() && fUI.isVisible()) ? 0 : 1; 239 240 return fUI.plugin_idle() ? 0 : 1; 241 } 242 243 int lv2ui_show() 244 { 245 return fUI.setWindowVisible(true) ? 0 : 1; 246 } 247 248 int lv2ui_hide() 249 { 250 return fUI.setWindowVisible(false) ? 0 : 1; 251 } 252 253 // ------------------------------------------------------------------- 254 255 uint32_t lv2_get_options(LV2_Options_Option* const /*options*/) 256 { 257 // currently unused 258 return LV2_OPTIONS_ERR_UNKNOWN; 259 } 260 261 uint32_t lv2_set_options(const LV2_Options_Option* const options) 262 { 263 for (int i=0; options[i].key != 0; ++i) 264 { 265 if (options[i].key == fURIDs.paramSampleRate) 266 { 267 if (options[i].type == fURIDs.atomFloat) 268 { 269 const float sampleRate = *(const float*)options[i].value; 270 fUI.setSampleRate(sampleRate, true); 271 continue; 272 } 273 else 274 { 275 d_stderr("Host changed UI sample-rate but with wrong value type"); 276 continue; 277 } 278 } 279 } 280 281 return LV2_OPTIONS_SUCCESS; 282 } 283 284 // ------------------------------------------------------------------- 285 286 #if DISTRHO_PLUGIN_WANT_PROGRAMS 287 void lv2ui_select_program(const uint32_t bank, const uint32_t program) 288 { 289 const uint32_t realProgram = bank * 128 + program; 290 291 fUI.programLoaded(realProgram); 292 } 293 #endif 294 295 // ------------------------------------------------------------------- 296 297 private: 298 // LV2 features 299 const LV2_URID_Map* const fUridMap; 300 const LV2_URID_Unmap* const fUridUnmap; 301 const LV2UI_Port_Map* const fUiPortMap; 302 const LV2UI_Request_Value* const fUiRequestValue; 303 const LV2UI_Touch* const fUiTouch; 304 305 // LV2 UI stuff 306 const LV2UI_Controller fController; 307 const LV2UI_Write_Function fWriteFunction; 308 309 // LV2 URIDs 310 const struct URIDs { 311 const LV2_URID_Map* _uridMap; 312 const LV2_URID dpfKeyValue; 313 const LV2_URID atomEventTransfer; 314 const LV2_URID atomFloat; 315 const LV2_URID atomLong; 316 const LV2_URID atomObject; 317 const LV2_URID atomPath; 318 const LV2_URID atomString; 319 const LV2_URID atomURID; 320 const LV2_URID midiEvent; 321 const LV2_URID paramSampleRate; 322 const LV2_URID patchProperty; 323 const LV2_URID patchSet; 324 const LV2_URID patchValue; 325 326 URIDs(const LV2_URID_Map* const uridMap) 327 : _uridMap(uridMap), 328 dpfKeyValue(map(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState")), 329 atomEventTransfer(map(LV2_ATOM__eventTransfer)), 330 atomFloat(map(LV2_ATOM__Float)), 331 atomLong(map(LV2_ATOM__Long)), 332 atomObject(map(LV2_ATOM__Object)), 333 atomPath(map(LV2_ATOM__Path)), 334 atomString(map(LV2_ATOM__String)), 335 atomURID(map(LV2_ATOM__URID)), 336 midiEvent(map(LV2_MIDI__MidiEvent)), 337 paramSampleRate(map(LV2_PARAMETERS__sampleRate)), 338 patchProperty(map(LV2_PATCH__property)), 339 patchSet(map(LV2_PATCH__Set)), 340 patchValue(map(LV2_PATCH__value)) {} 341 342 inline LV2_URID map(const char* const uri) const 343 { 344 return _uridMap->map(_uridMap->handle, uri); 345 } 346 } fURIDs; 347 348 // index of bypass parameter, if present 349 const uint32_t fBypassParameterIndex; 350 351 // using ui:showInterface if true 352 const bool fWinIdWasNull; 353 354 // Plugin UI (after LV2 stuff so the UI can call into us during its constructor) 355 UIExporter fUI; 356 357 // ---------------------------------------------------------------------------------------------------------------- 358 // DPF callbacks 359 360 void editParameterValue(const uint32_t rindex, const bool started) 361 { 362 if (fUiTouch != nullptr && fUiTouch->touch != nullptr) 363 fUiTouch->touch(fUiTouch->handle, rindex, started); 364 } 365 366 static void editParameterCallback(void* const ptr, const uint32_t rindex, const bool started) 367 { 368 static_cast<UiLv2*>(ptr)->editParameterValue(rindex, started); 369 } 370 371 void setParameterValue(const uint32_t rindex, float value) 372 { 373 DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,); 374 375 if (rindex == fBypassParameterIndex) 376 value = 1.0f - value; 377 378 fWriteFunction(fController, rindex, sizeof(float), 0, &value); 379 } 380 381 static void setParameterCallback(void* const ptr, const uint32_t rindex, const float value) 382 { 383 static_cast<UiLv2*>(ptr)->setParameterValue(rindex, value); 384 } 385 386 #if DISTRHO_PLUGIN_WANT_STATE 387 void setState(const char* const key, const char* const value) 388 { 389 DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,); 390 391 const uint32_t eventInPortIndex = DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS; 392 393 // join key and value 394 String tmpStr; 395 tmpStr += key; 396 tmpStr += "\xff"; 397 tmpStr += value; 398 399 tmpStr[std::strlen(key)] = '\0'; 400 401 // set msg size (key + separator + value + null terminator) 402 const uint32_t msgSize = static_cast<uint32_t>(tmpStr.length()) + 1U; 403 404 // reserve atom space 405 const uint32_t atomSize = sizeof(LV2_Atom) + msgSize; 406 char* const atomBuf = (char*)malloc(atomSize); 407 DISTRHO_SAFE_ASSERT_RETURN(atomBuf != nullptr,); 408 409 std::memset(atomBuf, 0, atomSize); 410 411 // set atom info 412 LV2_Atom* const atom = (LV2_Atom*)atomBuf; 413 atom->size = msgSize; 414 atom->type = fURIDs.dpfKeyValue; 415 416 // set atom data 417 std::memcpy(atomBuf + sizeof(LV2_Atom), tmpStr.buffer(), msgSize); 418 419 // send to DSP side 420 fWriteFunction(fController, eventInPortIndex, atomSize, fURIDs.atomEventTransfer, atom); 421 422 // free atom space 423 free(atomBuf); 424 } 425 426 static void setStateCallback(void* const ptr, const char* const key, const char* const value) 427 { 428 static_cast<UiLv2*>(ptr)->setState(key, value); 429 } 430 #endif 431 432 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT 433 void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) 434 { 435 DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,); 436 437 if (channel > 0xF) 438 return; 439 440 const uint32_t eventInPortIndex = DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS; 441 442 LV2_Atom_MidiEvent atomMidiEvent; 443 atomMidiEvent.atom.size = 3; 444 atomMidiEvent.atom.type = fURIDs.midiEvent; 445 446 atomMidiEvent.data[0] = channel + (velocity != 0 ? 0x90 : 0x80); 447 atomMidiEvent.data[1] = note; 448 atomMidiEvent.data[2] = velocity; 449 450 // send to DSP side 451 fWriteFunction(fController, eventInPortIndex, lv2_atom_total_size(&atomMidiEvent.atom), 452 fURIDs.atomEventTransfer, &atomMidiEvent); 453 } 454 455 static void sendNoteCallback(void* const ptr, const uint8_t channel, const uint8_t note, const uint8_t velocity) 456 { 457 static_cast<UiLv2*>(ptr)->sendNote(channel, note, velocity); 458 } 459 #endif 460 461 bool fileRequest(const char* const key) 462 { 463 d_stdout("UI file request %s %p", key, fUiRequestValue); 464 465 if (fUiRequestValue == nullptr) 466 return false; 467 468 String dpf_lv2_key(DISTRHO_PLUGIN_URI "#"); 469 dpf_lv2_key += key; 470 471 const int r = fUiRequestValue->request(fUiRequestValue->handle, 472 fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()), 473 fURIDs.atomPath, 474 nullptr); 475 476 d_stdout("UI file request %s %p => %s %i", key, fUiRequestValue, dpf_lv2_key.buffer(), r); 477 return r == LV2UI_REQUEST_VALUE_SUCCESS; 478 } 479 480 static bool fileRequestCallback(void* ptr, const char* key) 481 { 482 return static_cast<UiLv2*>(ptr)->fileRequest(key); 483 } 484 }; 485 486 // ----------------------------------------------------------------------- 487 488 static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, 489 const char* const uri, 490 const char* const bundlePath, 491 const LV2UI_Write_Function writeFunction, 492 const LV2UI_Controller controller, 493 LV2UI_Widget* const widget, 494 const LV2_Feature* const* const features) 495 { 496 if (uri == nullptr || std::strcmp(uri, DISTRHO_PLUGIN_URI) != 0) 497 { 498 d_stderr("Invalid plugin URI"); 499 return nullptr; 500 } 501 502 const LV2_Options_Option* options = nullptr; 503 const LV2_URID_Map* uridMap = nullptr; 504 void* parentId = nullptr; 505 void* instance = nullptr; 506 507 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS 508 struct LV2_DirectAccess_Interface { 509 void* (*get_instance_pointer)(LV2_Handle handle); 510 }; 511 const LV2_Extension_Data_Feature* extData = nullptr; 512 #endif 513 514 for (int i=0; features[i] != nullptr; ++i) 515 { 516 /**/ if (std::strcmp(features[i]->URI, LV2_OPTIONS__options) == 0) 517 options = (const LV2_Options_Option*)features[i]->data; 518 else if (std::strcmp(features[i]->URI, LV2_URID__map) == 0) 519 uridMap = (const LV2_URID_Map*)features[i]->data; 520 else if (std::strcmp(features[i]->URI, LV2_UI__parent) == 0) 521 parentId = features[i]->data; 522 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS 523 else if (std::strcmp(features[i]->URI, LV2_DATA_ACCESS_URI) == 0) 524 extData = (const LV2_Extension_Data_Feature*)features[i]->data; 525 else if (std::strcmp(features[i]->URI, LV2_INSTANCE_ACCESS_URI) == 0) 526 instance = features[i]->data; 527 #endif 528 } 529 530 if (options == nullptr && parentId == nullptr) 531 { 532 d_stderr("Options feature missing (needed for show-interface), cannot continue!"); 533 return nullptr; 534 } 535 536 if (uridMap == nullptr) 537 { 538 d_stderr("URID Map feature missing, cannot continue!"); 539 return nullptr; 540 } 541 542 if (parentId == nullptr) 543 { 544 d_stdout("Parent Window Id missing, host should be using ui:showInterface..."); 545 } 546 547 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS 548 if (extData == nullptr || instance == nullptr) 549 { 550 d_stderr("Data or instance access missing, cannot continue!"); 551 return nullptr; 552 } 553 554 if (const LV2_DirectAccess_Interface* const directAccess = (const LV2_DirectAccess_Interface*)extData->data_access(DISTRHO_PLUGIN_LV2_STATE_PREFIX "direct-access")) 555 instance = directAccess->get_instance_pointer(instance); 556 else 557 instance = nullptr; 558 559 if (instance == nullptr) 560 { 561 d_stderr("Failed to get direct access, cannot continue!"); 562 return nullptr; 563 } 564 #endif 565 566 const intptr_t winId = (intptr_t)parentId; 567 float sampleRate = 0.0f; 568 float scaleFactor = 0.0f; 569 uint32_t bgColor = 0; 570 uint32_t fgColor = 0xffffffff; 571 const char* appClassName = nullptr; 572 573 if (options != nullptr) 574 { 575 const LV2_URID uridAtomInt = uridMap->map(uridMap->handle, LV2_ATOM__Int); 576 const LV2_URID uridAtomFloat = uridMap->map(uridMap->handle, LV2_ATOM__Float); 577 const LV2_URID uridAtomString = uridMap->map(uridMap->handle, LV2_ATOM__String); 578 const LV2_URID uridSampleRate = uridMap->map(uridMap->handle, LV2_PARAMETERS__sampleRate); 579 const LV2_URID uridBgColor = uridMap->map(uridMap->handle, LV2_UI__backgroundColor); 580 const LV2_URID uridFgColor = uridMap->map(uridMap->handle, LV2_UI__foregroundColor); 581 #ifndef DISTRHO_OS_MAC 582 const LV2_URID uridScaleFactor = uridMap->map(uridMap->handle, LV2_UI__scaleFactor); 583 #endif 584 const LV2_URID uridClassName = uridMap->map(uridMap->handle, "urn:distrho:className"); 585 586 for (int i=0; options[i].key != 0; ++i) 587 { 588 /**/ if (options[i].key == uridSampleRate) 589 { 590 if (options[i].type == uridAtomFloat) 591 sampleRate = *(const float*)options[i].value; 592 else 593 d_stderr("Host provides UI sample-rate but has wrong value type"); 594 } 595 else if (options[i].key == uridBgColor) 596 { 597 if (options[i].type == uridAtomInt) 598 bgColor = (uint32_t)*(const int32_t*)options[i].value; 599 else 600 d_stderr("Host provides UI background color but has wrong value type"); 601 } 602 else if (options[i].key == uridFgColor) 603 { 604 if (options[i].type == uridAtomInt) 605 fgColor = (uint32_t)*(const int32_t*)options[i].value; 606 else 607 d_stderr("Host provides UI foreground color but has wrong value type"); 608 } 609 #ifndef DISTRHO_OS_MAC 610 else if (options[i].key == uridScaleFactor) 611 { 612 if (options[i].type == uridAtomFloat) 613 scaleFactor = *(const float*)options[i].value; 614 else 615 d_stderr("Host provides UI scale factor but has wrong value type"); 616 } 617 #endif 618 else if (options[i].key == uridClassName) 619 { 620 if (options[i].type == uridAtomString) 621 appClassName = (const char*)options[i].value; 622 else 623 d_stderr("Host provides UI scale factor but has wrong value type"); 624 } 625 } 626 } 627 628 if (sampleRate < 1.0) 629 { 630 d_stdout("WARNING: this host does not send sample-rate information for LV2 UIs, using 44100 as fallback (this could be wrong)"); 631 sampleRate = 44100.0; 632 } 633 634 return new UiLv2(bundlePath, winId, options, uridMap, features, 635 controller, writeFunction, widget, instance, 636 sampleRate, scaleFactor, bgColor, fgColor, appClassName); 637 } 638 639 #define uiPtr ((UiLv2*)ui) 640 641 static void lv2ui_cleanup(LV2UI_Handle ui) 642 { 643 delete uiPtr; 644 } 645 646 static void lv2ui_port_event(LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer) 647 { 648 uiPtr->lv2ui_port_event(portIndex, bufferSize, format, buffer); 649 } 650 651 // ----------------------------------------------------------------------- 652 653 static int lv2ui_idle(LV2UI_Handle ui) 654 { 655 return uiPtr->lv2ui_idle(); 656 } 657 658 static int lv2ui_show(LV2UI_Handle ui) 659 { 660 return uiPtr->lv2ui_show(); 661 } 662 663 static int lv2ui_hide(LV2UI_Handle ui) 664 { 665 return uiPtr->lv2ui_hide(); 666 } 667 668 // ----------------------------------------------------------------------- 669 670 static uint32_t lv2_get_options(LV2UI_Handle ui, LV2_Options_Option* options) 671 { 672 return uiPtr->lv2_get_options(options); 673 } 674 675 static uint32_t lv2_set_options(LV2UI_Handle ui, const LV2_Options_Option* options) 676 { 677 return uiPtr->lv2_set_options(options); 678 } 679 680 // ----------------------------------------------------------------------- 681 682 #if DISTRHO_PLUGIN_WANT_PROGRAMS 683 static void lv2ui_select_program(LV2UI_Handle ui, uint32_t bank, uint32_t program) 684 { 685 uiPtr->lv2ui_select_program(bank, program); 686 } 687 #endif 688 689 // ----------------------------------------------------------------------- 690 691 static const void* lv2ui_extension_data(const char* uri) 692 { 693 static const LV2_Options_Interface options = { lv2_get_options, lv2_set_options }; 694 static const LV2UI_Idle_Interface uiIdle = { lv2ui_idle }; 695 static const LV2UI_Show_Interface uiShow = { lv2ui_show, lv2ui_hide }; 696 697 if (std::strcmp(uri, LV2_OPTIONS__interface) == 0) 698 return &options; 699 if (std::strcmp(uri, LV2_UI__idleInterface) == 0) 700 return &uiIdle; 701 if (std::strcmp(uri, LV2_UI__showInterface) == 0) 702 return &uiShow; 703 704 #if DISTRHO_PLUGIN_WANT_PROGRAMS 705 static const LV2_Programs_UI_Interface uiPrograms = { lv2ui_select_program }; 706 707 if (std::strcmp(uri, LV2_PROGRAMS__UIInterface) == 0) 708 return &uiPrograms; 709 #endif 710 711 return nullptr; 712 } 713 714 #undef instancePtr 715 716 // ----------------------------------------------------------------------- 717 718 static const LV2UI_Descriptor sLv2UiDescriptor = { 719 DISTRHO_UI_URI, 720 lv2ui_instantiate, 721 lv2ui_cleanup, 722 lv2ui_port_event, 723 lv2ui_extension_data 724 }; 725 726 // ----------------------------------------------------------------------- 727 728 END_NAMESPACE_DISTRHO 729 730 DISTRHO_PLUGIN_EXPORT 731 const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) 732 { 733 USE_NAMESPACE_DISTRHO 734 return (index == 0) ? &sLv2UiDescriptor : nullptr; 735 } 736 737 #if defined(__MOD_DEVICES__) && defined(__EMSCRIPTEN__) 738 #include <emscripten/html5.h> 739 #include <string> 740 741 typedef void (*_custom_param_set)(uint32_t port_index, float value); 742 typedef void (*_custom_patch_set)(const char* uri, const char* value); 743 744 struct ModguiHandle { 745 LV2UI_Handle handle; 746 long loop_id; 747 _custom_param_set param_set; 748 _custom_patch_set patch_set; 749 }; 750 751 enum URIs { 752 kUriNull, 753 kUriAtomEventTransfer, 754 kUriDpfKeyValue, 755 }; 756 757 static std::vector<std::string> kURIs; 758 759 static LV2_URID lv2_urid_map(LV2_URID_Map_Handle, const char* const uri) 760 { 761 for (size_t i=0, size=kURIs.size(); i<size; ++i) 762 { 763 if (kURIs[i] == uri) 764 return i; 765 } 766 767 kURIs.push_back(uri); 768 return kURIs.size() - 1u; 769 } 770 771 static const char* lv2_urid_unmap(LV2_URID_Map_Handle, const LV2_URID urid) 772 { 773 return kURIs[urid].c_str(); 774 } 775 776 static void lv2ui_write_function(LV2UI_Controller controller, 777 uint32_t port_index, 778 uint32_t buffer_size, 779 uint32_t port_protocol, 780 const void* buffer) 781 { 782 DISTRHO_SAFE_ASSERT_RETURN(buffer_size >= 1,); 783 784 // d_stdout("lv2ui_write_function %p %u %u %u %p", controller, port_index, buffer_size, port_protocol, buffer); 785 ModguiHandle* const mhandle = static_cast<ModguiHandle*>(controller); 786 787 switch (port_protocol) 788 { 789 case kUriNull: 790 mhandle->param_set(port_index, *static_cast<const float*>(buffer)); 791 break; 792 case kUriAtomEventTransfer: 793 if (const LV2_Atom* const atom = static_cast<const LV2_Atom*>(buffer)) 794 { 795 // d_stdout("lv2ui_write_function %u %u:%s", atom->size, atom->type, kURIs[atom->type].c_str()); 796 797 // if (kURIs[atom->type] == "urn:distrho:KeyValueState") 798 { 799 const char* const key = (const char*)(atom + 1); 800 const char* const value = key + (std::strlen(key) + 1U); 801 // d_stdout("lv2ui_write_function %s %s", key, value); 802 803 String urikey; 804 urikey = DISTRHO_PLUGIN_URI "#"; 805 urikey += key; 806 807 mhandle->patch_set(urikey, value); 808 } 809 } 810 break; 811 } 812 } 813 814 static void app_idle(void* const handle) 815 { 816 static_cast<UiLv2*>(handle)->lv2ui_idle(); 817 } 818 819 DISTRHO_PLUGIN_EXPORT 820 LV2UI_Handle modgui_init(const char* const className, _custom_param_set param_set, _custom_patch_set patch_set) 821 { 822 d_stdout("init \"%s\"", className); 823 DISTRHO_SAFE_ASSERT_RETURN(className != nullptr, nullptr); 824 825 static LV2_URID_Map uridMap = { nullptr, lv2_urid_map }; 826 static LV2_URID_Unmap uridUnmap = { nullptr, lv2_urid_unmap }; 827 828 // known first URIDs, matching URIs 829 if (kURIs.empty()) 830 { 831 kURIs.push_back(""); 832 kURIs.push_back("http://lv2plug.in/ns/ext/atom#eventTransfer"); 833 kURIs.push_back(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState"); 834 } 835 836 static float sampleRateValue = 48000.f; 837 static LV2_Options_Option options[3] = { 838 { 839 LV2_OPTIONS_INSTANCE, 840 0, 841 uridMap.map(uridMap.handle, LV2_PARAMETERS__sampleRate), 842 sizeof(float), 843 uridMap.map(uridMap.handle, LV2_ATOM__Float), 844 &sampleRateValue 845 }, 846 { 847 LV2_OPTIONS_INSTANCE, 848 0, 849 uridMap.map(uridMap.handle, "urn:distrho:className"), 850 std::strlen(className) + 1, 851 uridMap.map(uridMap.handle, LV2_ATOM__String), 852 className 853 }, 854 {} 855 }; 856 857 static const LV2_Feature optionsFt = { LV2_OPTIONS__options, static_cast<void*>(options) }; 858 static const LV2_Feature uridMapFt = { LV2_URID__map, static_cast<void*>(&uridMap) }; 859 static const LV2_Feature uridUnmapFt = { LV2_URID__unmap, static_cast<void*>(&uridUnmap) }; 860 861 static const LV2_Feature* features[] = { 862 &optionsFt, 863 &uridMapFt, 864 &uridUnmapFt, 865 nullptr 866 }; 867 868 ModguiHandle* const mhandle = new ModguiHandle; 869 mhandle->handle = nullptr; 870 mhandle->loop_id = 0; 871 mhandle->param_set = param_set; 872 mhandle->patch_set = patch_set; 873 874 LV2UI_Widget widget; 875 const LV2UI_Handle handle = lv2ui_instantiate(&sLv2UiDescriptor, 876 DISTRHO_PLUGIN_URI, 877 "", // bundlePath 878 lv2ui_write_function, 879 mhandle, 880 &widget, 881 features); 882 mhandle->handle = handle; 883 884 static_cast<UiLv2*>(handle)->lv2ui_show(); 885 mhandle->loop_id = emscripten_set_interval(app_idle, 1000.0/60, handle); 886 887 return mhandle; 888 } 889 890 DISTRHO_PLUGIN_EXPORT 891 void modgui_param_set(const LV2UI_Handle handle, const uint32_t index, const float value) 892 { 893 lv2ui_port_event(static_cast<ModguiHandle*>(handle)->handle, index, sizeof(float), kUriNull, &value); 894 } 895 896 DISTRHO_PLUGIN_EXPORT 897 void modgui_patch_set(const LV2UI_Handle handle, const char* const uri, const char* const value) 898 { 899 static const constexpr uint32_t URI_PREFIX_LEN = sizeof(DISTRHO_PLUGIN_URI); 900 DISTRHO_SAFE_ASSERT_RETURN(std::strncmp(uri, DISTRHO_PLUGIN_URI "#", URI_PREFIX_LEN) == 0,); 901 902 const uint32_t keySize = std::strlen(uri + URI_PREFIX_LEN) + 1; 903 const uint32_t valueSize = std::strlen(value) + 1; 904 const uint32_t atomSize = sizeof(LV2_Atom) + keySize + valueSize; 905 906 LV2_Atom* const atom = static_cast<LV2_Atom*>(std::malloc(atomSize)); 907 atom->size = atomSize; 908 atom->type = kUriDpfKeyValue; 909 910 std::memcpy(static_cast<uint8_t*>(static_cast<void*>(atom + 1)), uri + URI_PREFIX_LEN, keySize); 911 std::memcpy(static_cast<uint8_t*>(static_cast<void*>(atom + 1)) + keySize, value, valueSize); 912 913 lv2ui_port_event(static_cast<ModguiHandle*>(handle)->handle, 914 DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS, // events input port 915 atomSize, kUriAtomEventTransfer, atom); 916 917 std::free(atom); 918 } 919 920 DISTRHO_PLUGIN_EXPORT 921 void modgui_cleanup(const LV2UI_Handle handle) 922 { 923 d_stdout("cleanup"); 924 ModguiHandle* const mhandle = static_cast<ModguiHandle*>(handle); 925 if (mhandle->loop_id != 0) 926 emscripten_clear_interval(mhandle->loop_id); 927 lv2ui_cleanup(mhandle->handle); 928 delete mhandle; 929 } 930 #endif 931 932 // -----------------------------------------------------------------------