1 /* 2 * DISTRHO Plugin Framework (DPF) 3 * Copyright (C) 2012-2016 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/ui.h" 29 #include "lv2/urid.h" 30 #include "lv2/lv2_kxstudio_properties.h" 31 #include "lv2/lv2_programs.h" 32 33 #ifndef DISTRHO_PLUGIN_LV2_STATE_PREFIX 34 # define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:" 35 #endif 36 37 START_NAMESPACE_DISTRHO 38 39 typedef struct _LV2_Atom_MidiEvent { 40 LV2_Atom atom; /**< Atom header. */ 41 uint8_t data[3]; /**< MIDI data (body). */ 42 } LV2_Atom_MidiEvent; 43 44 #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT 45 static const sendNoteFunc sendNoteCallback = nullptr; 46 #endif 47 48 // ----------------------------------------------------------------------- 49 50 class UiLv2 51 { 52 public: 53 UiLv2(const char* const bundlePath, const intptr_t winId, 54 const LV2_Options_Option* options, const LV2_URID_Map* const uridMap, const LV2UI_Resize* const uiResz, const LV2UI_Touch* uiTouch, 55 const LV2UI_Controller controller, const LV2UI_Write_Function writeFunc, 56 const float scaleFactor, LV2UI_Widget* const widget, void* const dspPtr) 57 : fUI(this, winId, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, scaleFactor, dspPtr, bundlePath), 58 fUridMap(uridMap), 59 fUiResize(uiResz), 60 fUiTouch(uiTouch), 61 fController(controller), 62 fWriteFunction(writeFunc), 63 fEventTransferURID(uridMap->map(uridMap->handle, LV2_ATOM__eventTransfer)), 64 fMidiEventURID(uridMap->map(uridMap->handle, LV2_MIDI__MidiEvent)), 65 fKeyValueURID(uridMap->map(uridMap->handle, DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState")), 66 fWinIdWasNull(winId == 0) 67 { 68 if (fUiResize != nullptr && winId != 0) 69 fUiResize->ui_resize(fUiResize->handle, fUI.getWidth(), fUI.getHeight()); 70 71 if (widget != nullptr) 72 *widget = (LV2UI_Widget)fUI.getWindowId(); 73 74 #if DISTRHO_PLUGIN_WANT_STATE 75 // tell the DSP we're ready to receive msgs 76 setState("__dpf_ui_data__", ""); 77 #endif 78 79 if (winId != 0) 80 return; 81 82 // if winId == 0 then options must not be null 83 DISTRHO_SAFE_ASSERT_RETURN(options != nullptr,); 84 85 const LV2_URID uridWindowTitle(uridMap->map(uridMap->handle, LV2_UI__windowTitle)); 86 const LV2_URID uridTransientWinId(uridMap->map(uridMap->handle, LV2_KXSTUDIO_PROPERTIES__TransientWindowId)); 87 88 bool hasTitle = false; 89 90 for (int i=0; options[i].key != 0; ++i) log_pre_callback(void)91 { 92 if (options[i].key == uridTransientWinId) 93 { 94 if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__Long)) 95 { 96 if (const int64_t transientWinId = *(const int64_t*)options[i].value) 97 fUI.setWindowTransientWinId(static_cast<intptr_t>(transientWinId)); 98 } 99 else 100 d_stderr("Host provides transientWinId but has wrong value type"); 101 } 102 else if (options[i].key == uridWindowTitle) 103 { 104 if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__String)) 105 { 106 if (const char* const windowTitle = (const char*)options[i].value) 107 { 108 hasTitle = true; 109 fUI.setWindowTitle(windowTitle); 110 } 111 } 112 else 113 d_stderr("Host provides windowTitle but has wrong value type"); 114 } 115 } 116 117 if (! hasTitle) main(int argc,char * argv[])118 fUI.setWindowTitle(DISTRHO_PLUGIN_NAME); 119 } 120 121 // ------------------------------------------------------------------- 122 123 void lv2ui_port_event(const uint32_t rindex, const uint32_t bufferSize, const uint32_t format, const void* const buffer) 124 { 125 if (format == 0) 126 { 127 const uint32_t parameterOffset(fUI.getParameterOffset()); 128 129 if (rindex < parameterOffset) 130 return; 131 132 DISTRHO_SAFE_ASSERT_RETURN(bufferSize == sizeof(float),) 133 134 const float value(*(const float*)buffer); 135 fUI.parameterChanged(rindex-parameterOffset, value); 136 } 137 #if DISTRHO_PLUGIN_WANT_STATE 138 else if (format == fEventTransferURID) 139 { 140 const LV2_Atom* const atom((const LV2_Atom*)buffer); 141 142 DISTRHO_SAFE_ASSERT_RETURN(atom->type == fKeyValueURID,); 143 144 const char* const key = (const char*)LV2_ATOM_BODY_CONST(atom); 145 const char* const value = key+(std::strlen(key)+1); 146 147 fUI.stateChanged(key, value); 148 } 149 #endif 150 } 151 152 // ------------------------------------------------------------------- 153 154 int lv2ui_idle() 155 { 156 if (fWinIdWasNull) 157 return (fUI.idle() && fUI.isVisible()) ? 0 : 1; 158 159 return fUI.idle() ? 0 : 1; 160 } 161 162 int lv2ui_show() 163 { 164 return fUI.setWindowVisible(true) ? 0 : 1; 165 } 166 167 int lv2ui_hide() 168 { 169 return fUI.setWindowVisible(false) ? 0 : 1; 170 } 171 172 int lv2ui_resize(uint width, uint height) 173 { 174 fUI.setWindowSize(width, height, true); 175 return 0; 176 } 177 178 // ------------------------------------------------------------------- 179 180 uint32_t lv2_get_options(LV2_Options_Option* const /*options*/) 181 { 182 // currently unused 183 return LV2_OPTIONS_ERR_UNKNOWN; 184 } 185 186 uint32_t lv2_set_options(const LV2_Options_Option* const options) 187 { 188 for (int i=0; options[i].key != 0; ++i) 189 { 190 if (options[i].key == fUridMap->map(fUridMap->handle, LV2_PARAMETERS__sampleRate)) 191 { 192 if (options[i].type == fUridMap->map(fUridMap->handle, LV2_ATOM__Float)) 193 { 194 const float sampleRate(*(const float*)options[i].value); 195 fUI.setSampleRate(sampleRate); 196 continue; 197 } 198 else 199 { 200 d_stderr("Host changed UI sample-rate but with wrong value type"); 201 continue; 202 } 203 } 204 } 205 206 return LV2_OPTIONS_SUCCESS; 207 } 208 209 // ------------------------------------------------------------------- 210 211 #if DISTRHO_PLUGIN_WANT_PROGRAMS 212 void lv2ui_select_program(const uint32_t bank, const uint32_t program) 213 { 214 const uint32_t realProgram(bank * 128 + program); 215 216 fUI.programLoaded(realProgram); 217 } 218 #endif 219 220 // ------------------------------------------------------------------- 221 222 protected: 223 void editParameterValue(const uint32_t rindex, const bool started) 224 { 225 if (fUiTouch != nullptr && fUiTouch->touch != nullptr) 226 fUiTouch->touch(fUiTouch->handle, rindex, started); 227 } 228 229 void setParameterValue(const uint32_t rindex, const float value) 230 { 231 DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,); 232 233 fWriteFunction(fController, rindex, sizeof(float), 0, &value); 234 } 235 236 void setState(const char* const key, const char* const value) 237 { 238 DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,); 239 240 const uint32_t eventInPortIndex(DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS); 241 242 // join key and value 243 String tmpStr; 244 tmpStr += key; 245 tmpStr += "\xff"; 246 tmpStr += value; 247 248 tmpStr[std::strlen(key)] = '\0'; 249 250 // set msg size (key + separator + value + null terminator) 251 const size_t msgSize(tmpStr.length()+1); 252 253 // reserve atom space 254 const size_t atomSize(sizeof(LV2_Atom) + msgSize); 255 char atomBuf[atomSize]; 256 std::memset(atomBuf, 0, atomSize); 257 258 // set atom info 259 LV2_Atom* const atom((LV2_Atom*)atomBuf); 260 atom->size = msgSize; 261 atom->type = fKeyValueURID; 262 263 // set atom data 264 std::memcpy(atomBuf + sizeof(LV2_Atom), tmpStr.buffer(), msgSize); 265 266 // send to DSP side 267 fWriteFunction(fController, eventInPortIndex, atomSize, fEventTransferURID, atom); 268 } 269 270 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT 271 void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) 272 { 273 DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,); 274 275 if (channel > 0xF) 276 return; 277 278 const uint32_t eventInPortIndex(DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS); 279 280 LV2_Atom_MidiEvent atomMidiEvent; 281 atomMidiEvent.atom.size = 3; 282 atomMidiEvent.atom.type = fMidiEventURID; 283 284 atomMidiEvent.data[0] = channel + (velocity != 0 ? 0x90 : 0x80); 285 atomMidiEvent.data[1] = note; 286 atomMidiEvent.data[2] = velocity; 287 288 // send to DSP side 289 fWriteFunction(fController, eventInPortIndex, lv2_atom_total_size(&atomMidiEvent.atom), fEventTransferURID, &atomMidiEvent); 290 } 291 #endif 292 293 void setSize(const uint width, const uint height) 294 { 295 fUI.setWindowSize(width, height); 296 297 if (fUiResize != nullptr && ! fWinIdWasNull) 298 fUiResize->ui_resize(fUiResize->handle, width, height); 299 } 300 301 private: 302 UIExporter fUI; 303 304 // LV2 features 305 const LV2_URID_Map* const fUridMap; 306 const LV2UI_Resize* const fUiResize; 307 const LV2UI_Touch* const fUiTouch; 308 309 // LV2 UI stuff 310 const LV2UI_Controller fController; 311 const LV2UI_Write_Function fWriteFunction; 312 313 // Need to save this 314 const LV2_URID fEventTransferURID; 315 const LV2_URID fMidiEventURID; 316 const LV2_URID fKeyValueURID; 317 318 // using ui:showInterface if true 319 bool fWinIdWasNull; 320 321 // ------------------------------------------------------------------- 322 // Callbacks 323 324 #define uiPtr ((UiLv2*)ptr) 325 326 static void editParameterCallback(void* ptr, uint32_t rindex, bool started) 327 { 328 uiPtr->editParameterValue(rindex, started); 329 } 330 331 static void setParameterCallback(void* ptr, uint32_t rindex, float value) 332 { 333 uiPtr->setParameterValue(rindex, value); 334 } 335 336 static void setStateCallback(void* ptr, const char* key, const char* value) 337 { 338 uiPtr->setState(key, value); 339 } 340 341 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT 342 static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity) 343 { 344 uiPtr->sendNote(channel, note, velocity); 345 } 346 #endif 347 348 static void setSizeCallback(void* ptr, uint width, uint height) 349 { 350 uiPtr->setSize(width, height); 351 } 352 353 #undef uiPtr 354 }; 355 356 // ----------------------------------------------------------------------- 357 358 static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, const char* uri, const char* bundlePath, 359 LV2UI_Write_Function writeFunction, LV2UI_Controller controller, LV2UI_Widget* widget, const LV2_Feature* const* features) 360 { 361 if (uri == nullptr || std::strcmp(uri, DISTRHO_PLUGIN_URI) != 0) 362 { 363 d_stderr("Invalid plugin URI"); 364 return nullptr; 365 } 366 367 const LV2_Options_Option* options = nullptr; 368 const LV2_URID_Map* uridMap = nullptr; 369 const LV2UI_Resize* uiResize = nullptr; 370 const LV2UI_Touch* uiTouch = nullptr; 371 void* parentId = nullptr; 372 void* instance = nullptr; 373 374 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS 375 struct LV2_DirectAccess_Interface { 376 void* (*get_instance_pointer)(LV2_Handle handle); 377 }; 378 const LV2_Extension_Data_Feature* extData = nullptr; 379 #endif 380 381 for (int i=0; features[i] != nullptr; ++i) 382 { 383 if (std::strcmp(features[i]->URI, LV2_OPTIONS__options) == 0) 384 options = (const LV2_Options_Option*)features[i]->data; 385 else if (std::strcmp(features[i]->URI, LV2_URID__map) == 0) 386 uridMap = (const LV2_URID_Map*)features[i]->data; 387 else if (std::strcmp(features[i]->URI, LV2_UI__resize) == 0) 388 uiResize = (const LV2UI_Resize*)features[i]->data; 389 else if (std::strcmp(features[i]->URI, LV2_UI__parent) == 0) 390 parentId = features[i]->data; 391 else if (std::strcmp(features[i]->URI, LV2_UI__touch) == 0) 392 uiTouch = (const LV2UI_Touch*)features[i]->data; 393 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS 394 else if (std::strcmp(features[i]->URI, LV2_DATA_ACCESS_URI) == 0) 395 extData = (const LV2_Extension_Data_Feature*)features[i]->data; 396 else if (std::strcmp(features[i]->URI, LV2_INSTANCE_ACCESS_URI) == 0) 397 instance = features[i]->data; 398 #endif 399 } 400 401 if (options == nullptr && parentId == nullptr) 402 { 403 d_stderr("Options feature missing (needed for show-interface), cannot continue!"); 404 return nullptr; 405 } 406 407 if (uridMap == nullptr) 408 { 409 d_stderr("URID Map feature missing, cannot continue!"); 410 return nullptr; 411 } 412 413 if (parentId == nullptr) 414 { 415 d_stdout("Parent Window Id missing, host should be using ui:showInterface..."); 416 } 417 418 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS 419 if (extData == nullptr || instance == nullptr) 420 { 421 d_stderr("Data or instance access missing, cannot continue!"); 422 return nullptr; 423 } 424 425 if (const LV2_DirectAccess_Interface* const directAccess = (const LV2_DirectAccess_Interface*)extData->data_access(DISTRHO_PLUGIN_LV2_STATE_PREFIX "direct-access")) 426 instance = directAccess->get_instance_pointer(instance); 427 else 428 instance = nullptr; 429 430 if (instance == nullptr) 431 { 432 d_stderr("Failed to get direct access, cannot continue!"); 433 return nullptr; 434 } 435 #endif 436 437 float scaleFactor = 1.0f; 438 const intptr_t winId((intptr_t)parentId); 439 440 if (options != nullptr) 441 { 442 const LV2_URID uridAtomFloat(uridMap->map(uridMap->handle, LV2_ATOM__Float)); 443 const LV2_URID uridSampleRate(uridMap->map(uridMap->handle, LV2_PARAMETERS__sampleRate)); 444 const LV2_URID uridScaleFactor(uridMap->map(uridMap->handle, LV2_UI__scaleFactor)); 445 446 for (int i=0; options[i].key != 0; ++i) 447 { 448 /**/ if (options[i].key == uridSampleRate) 449 { 450 if (options[i].type == uridAtomFloat) 451 d_lastUiSampleRate = *(const float*)options[i].value; 452 else 453 d_stderr("Host provides UI sample-rate but has wrong value type"); 454 } 455 else if (options[i].key == uridScaleFactor) 456 { 457 if (options[i].type == uridAtomFloat) 458 scaleFactor = *(const float*)options[i].value; parse_psql_options(int argc,char * argv[],struct adhoc_opts * options)459 else 460 d_stderr("Host provides UI scale factor but has wrong value type"); 461 } 462 } 463 } 464 465 if (d_lastUiSampleRate < 1.0) 466 { 467 d_stdout("WARNING: this host does not send sample-rate information for LV2 UIs, using 44100 as fallback (this could be wrong)"); 468 d_lastUiSampleRate = 44100.0; 469 } 470 471 return new UiLv2(bundlePath, winId, options, uridMap, uiResize, uiTouch, controller, writeFunction, scaleFactor, widget, instance); 472 } 473 474 #define uiPtr ((UiLv2*)ui) 475 476 static void lv2ui_cleanup(LV2UI_Handle ui) 477 { 478 delete uiPtr; 479 } 480 481 static void lv2ui_port_event(LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer) 482 { 483 uiPtr->lv2ui_port_event(portIndex, bufferSize, format, buffer); 484 } 485 486 // ----------------------------------------------------------------------- 487 488 static int lv2ui_idle(LV2UI_Handle ui) 489 { 490 return uiPtr->lv2ui_idle(); 491 } 492 493 static int lv2ui_show(LV2UI_Handle ui) 494 { 495 return uiPtr->lv2ui_show(); 496 } 497 498 static int lv2ui_hide(LV2UI_Handle ui) 499 { 500 return uiPtr->lv2ui_hide(); 501 } 502 503 static int lv2ui_resize(LV2UI_Handle ui, int width, int height) 504 { 505 DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, 1); 506 DISTRHO_SAFE_ASSERT_RETURN(width > 0, 1); 507 DISTRHO_SAFE_ASSERT_RETURN(height > 0, 1); 508 509 return 1; // This needs more testing 510 //return uiPtr->lv2ui_resize(width, height); 511 } 512 513 // ----------------------------------------------------------------------- 514 515 static uint32_t lv2_get_options(LV2UI_Handle ui, LV2_Options_Option* options) 516 { 517 return uiPtr->lv2_get_options(options); 518 } 519 520 static uint32_t lv2_set_options(LV2UI_Handle ui, const LV2_Options_Option* options) 521 { 522 return uiPtr->lv2_set_options(options); 523 } 524 525 // ----------------------------------------------------------------------- 526 527 #if DISTRHO_PLUGIN_WANT_PROGRAMS 528 static void lv2ui_select_program(LV2UI_Handle ui, uint32_t bank, uint32_t program) 529 { 530 uiPtr->lv2ui_select_program(bank, program); 531 } 532 #endif 533 534 // ----------------------------------------------------------------------- 535 536 static const void* lv2ui_extension_data(const char* uri) 537 { 538 static const LV2_Options_Interface options = { lv2_get_options, lv2_set_options }; 539 static const LV2UI_Idle_Interface uiIdle = { lv2ui_idle }; 540 static const LV2UI_Show_Interface uiShow = { lv2ui_show, lv2ui_hide }; 541 static const LV2UI_Resize uiResz = { nullptr, lv2ui_resize }; 542 543 if (std::strcmp(uri, LV2_OPTIONS__interface) == 0) 544 return &options; 545 if (std::strcmp(uri, LV2_UI__idleInterface) == 0) 546 return &uiIdle; 547 if (std::strcmp(uri, LV2_UI__showInterface) == 0) 548 return &uiShow; 549 if (std::strcmp(uri, LV2_UI__resize) == 0) 550 return &uiResz; 551 552 #if DISTRHO_PLUGIN_WANT_PROGRAMS 553 static const LV2_Programs_UI_Interface uiPrograms = { lv2ui_select_program }; 554 555 if (std::strcmp(uri, LV2_PROGRAMS__UIInterface) == 0) 556 return &uiPrograms; 557 #endif 558 559 return nullptr; 560 } 561 562 #undef instancePtr 563 564 // ----------------------------------------------------------------------- 565 566 static const LV2UI_Descriptor sLv2UiDescriptor = { 567 DISTRHO_UI_URI, 568 lv2ui_instantiate, 569 lv2ui_cleanup, 570 lv2ui_port_event, 571 lv2ui_extension_data 572 }; 573 574 // ----------------------------------------------------------------------- 575 576 END_NAMESPACE_DISTRHO 577 578 DISTRHO_PLUGIN_EXPORT 579 const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) 580 { 581 USE_NAMESPACE_DISTRHO 582 return (index == 0) ? &sLv2UiDescriptor : nullptr; 583 } 584 585 // ----------------------------------------------------------------------- 586