DistrhoUIDSSI.cpp (13879B)
1 /* 2 * DISTRHO Plugin Framework (DPF) 3 * Copyright (C) 2012-2024 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 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS 20 # error DSSI UIs do not support direct access! 21 #endif 22 23 #include "../extra/Sleep.hpp" 24 25 #include <lo/lo.h> 26 27 START_NAMESPACE_DISTRHO 28 29 // -------------------------------------------------------------------------------------------------------------------- 30 31 #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT 32 static constexpr const sendNoteFunc sendNoteCallback = nullptr; 33 #endif 34 35 // unused in DSSI, we only use external and standalone UIs 36 static constexpr const setSizeFunc setSizeCallback = nullptr; 37 38 // unsupported in DSSI 39 static constexpr const fileRequestFunc fileRequestCallback = nullptr; 40 41 #ifdef DPF_USING_LD_LINUX_WEBVIEW 42 int dpf_webview_start(int argc, char* argv[]); 43 #endif 44 45 // -------------------------------------------------------------------------------------------------------------------- 46 47 48 struct OscData { 49 lo_address addr; 50 const char* path; 51 lo_server server; 52 53 OscData() 54 : addr(nullptr), 55 path(nullptr), 56 server(nullptr) {} 57 58 void idle() const 59 { 60 if (server == nullptr) 61 return; 62 63 while (lo_server_recv_noblock(server, 0) != 0) {} 64 } 65 66 void send_configure(const char* const key, const char* const value) const 67 { 68 char targetPath[std::strlen(path)+11]; 69 std::strcpy(targetPath, path); 70 std::strcat(targetPath, "/configure"); 71 lo_send(addr, targetPath, "ss", key, value); 72 } 73 74 void send_control(const int32_t index, const float value) const 75 { 76 char targetPath[std::strlen(path)+9]; 77 std::strcpy(targetPath, path); 78 std::strcat(targetPath, "/control"); 79 lo_send(addr, targetPath, "if", index, value); 80 } 81 82 void send_midi(uchar data[4]) const 83 { 84 char targetPath[std::strlen(path)+6]; 85 std::strcpy(targetPath, path); 86 std::strcat(targetPath, "/midi"); 87 lo_send(addr, targetPath, "m", data); 88 } 89 90 void send_update(const char* const url) const 91 { 92 char targetPath[std::strlen(path)+8]; 93 std::strcpy(targetPath, path); 94 std::strcat(targetPath, "/update"); 95 lo_send(addr, targetPath, "s", url); 96 } 97 98 void send_exiting() const 99 { 100 char targetPath[std::strlen(path)+9]; 101 std::strcpy(targetPath, path); 102 std::strcat(targetPath, "/exiting"); 103 lo_send(addr, targetPath, ""); 104 } 105 }; 106 107 // ----------------------------------------------------------------------- 108 109 class UIDssi : public DGL_NAMESPACE::IdleCallback 110 { 111 public: 112 UIDssi(const OscData& oscData, const char* const uiTitle, const double sampleRate) 113 : fUI(this, 0, sampleRate, nullptr, 114 setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, fileRequestCallback), 115 fHostClosed(false), 116 fOscData(oscData) 117 { 118 fUI.setWindowTitle(uiTitle); 119 } 120 121 ~UIDssi() 122 { 123 if (fOscData.server != nullptr && ! fHostClosed) 124 fOscData.send_exiting(); 125 } 126 127 void exec_start() 128 { 129 fUI.exec(this); 130 } 131 132 void idleCallback() override 133 { 134 fOscData.idle(); 135 136 if (fHostClosed) 137 return; 138 139 fUI.exec_idle(); 140 } 141 142 // ------------------------------------------------------------------- 143 144 #if DISTRHO_PLUGIN_WANT_STATE 145 void dssiui_configure(const char* key, const char* value) 146 { 147 fUI.stateChanged(key, value); 148 } 149 #endif 150 151 void dssiui_control(ulong index, float value) 152 { 153 fUI.parameterChanged(index, value); 154 } 155 156 #if DISTRHO_PLUGIN_WANT_PROGRAMS 157 void dssiui_program(ulong bank, ulong program) 158 { 159 fUI.programLoaded(bank * 128 + program); 160 } 161 #endif 162 163 void dssiui_samplerate(const double sampleRate) 164 { 165 fUI.setSampleRate(sampleRate, true); 166 } 167 168 void dssiui_show(const bool focus = false) 169 { 170 fUI.setWindowVisible(true); 171 172 if (focus) 173 fUI.focus(); 174 } 175 176 void dssiui_hide() 177 { 178 fUI.setWindowVisible(false); 179 } 180 181 void dssiui_quit() 182 { 183 fHostClosed = true; 184 fUI.quit(); 185 } 186 187 // ------------------------------------------------------------------- 188 189 protected: 190 void setParameterValue(const uint32_t rindex, const float value) 191 { 192 if (fOscData.server == nullptr) 193 return; 194 195 fOscData.send_control(rindex, value); 196 } 197 198 void setState(const char* const key, const char* const value) 199 { 200 if (fOscData.server == nullptr) 201 return; 202 203 fOscData.send_configure(key, value); 204 } 205 206 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT 207 void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) 208 { 209 if (fOscData.server == nullptr) 210 return; 211 if (channel > 0xF) 212 return; 213 214 uint8_t mdata[4] = { 215 0, 216 static_cast<uint8_t>(channel + (velocity != 0 ? 0x90 : 0x80)), 217 note, 218 velocity 219 }; 220 fOscData.send_midi(mdata); 221 } 222 #endif 223 224 private: 225 UIExporter fUI; 226 bool fHostClosed; 227 228 const OscData& fOscData; 229 230 // ------------------------------------------------------------------- 231 // Callbacks 232 233 #define uiPtr ((UIDssi*)ptr) 234 235 static void setParameterCallback(void* ptr, uint32_t rindex, float value) 236 { 237 uiPtr->setParameterValue(rindex, value); 238 } 239 240 static void setStateCallback(void* ptr, const char* key, const char* value) 241 { 242 uiPtr->setState(key, value); 243 } 244 245 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT 246 static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity) 247 { 248 uiPtr->sendNote(channel, note, velocity); 249 } 250 #endif 251 252 #undef uiPtr 253 }; 254 255 // ----------------------------------------------------------------------- 256 257 static OscData gOscData; 258 static const char* gUiTitle = nullptr; 259 static UIDssi* globalUI = nullptr; 260 static double sampleRate = 0.0; 261 262 static void initUiIfNeeded() 263 { 264 if (globalUI != nullptr) 265 return; 266 267 if (sampleRate == 0.0) 268 sampleRate = 44100.0; 269 270 globalUI = new UIDssi(gOscData, gUiTitle, sampleRate); 271 } 272 273 // ----------------------------------------------------------------------- 274 275 int osc_debug_handler(const char* path, const char*, lo_arg**, int, lo_message, void*) 276 { 277 d_debug("osc_debug_handler(\"%s\")", path); 278 return 0; 279 280 #ifndef DEBUG 281 // unused 282 (void)path; 283 #endif 284 } 285 286 void osc_error_handler(int num, const char* msg, const char* path) 287 { 288 d_stderr("osc_error_handler(%i, \"%s\", \"%s\")", num, msg, path); 289 } 290 291 #if DISTRHO_PLUGIN_WANT_STATE 292 int osc_configure_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*) 293 { 294 const char* const key = &argv[0]->s; 295 const char* const value = &argv[1]->s; 296 d_debug("osc_configure_handler(\"%s\", \"%s\")", key, value); 297 298 initUiIfNeeded(); 299 300 globalUI->dssiui_configure(key, value); 301 302 return 0; 303 } 304 #endif 305 306 int osc_control_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*) 307 { 308 const int32_t rindex = argv[0]->i; 309 const float value = argv[1]->f; 310 d_debug("osc_control_handler(%i, %f)", rindex, value); 311 312 int32_t index = rindex - DISTRHO_PLUGIN_NUM_INPUTS - DISTRHO_PLUGIN_NUM_OUTPUTS; 313 314 // latency 315 #if DISTRHO_PLUGIN_WANT_LATENCY 316 index -= 1; 317 #endif 318 319 if (index < 0) 320 return 0; 321 322 initUiIfNeeded(); 323 324 globalUI->dssiui_control(index, value); 325 326 return 0; 327 } 328 329 #if DISTRHO_PLUGIN_WANT_PROGRAMS 330 int osc_program_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*) 331 { 332 const int32_t bank = argv[0]->i; 333 const int32_t program = argv[1]->f; 334 d_debug("osc_program_handler(%i, %i)", bank, program); 335 336 initUiIfNeeded(); 337 338 globalUI->dssiui_program(bank, program); 339 340 return 0; 341 } 342 #endif 343 344 int osc_sample_rate_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*) 345 { 346 sampleRate = argv[0]->i; 347 d_debug("osc_sample_rate_handler(%f)", sampleRate); 348 349 if (globalUI != nullptr) 350 globalUI->dssiui_samplerate(sampleRate); 351 352 return 0; 353 } 354 355 int osc_show_handler(const char*, const char*, lo_arg**, int, lo_message, void*) 356 { 357 d_debug("osc_show_handler()"); 358 359 initUiIfNeeded(); 360 361 globalUI->dssiui_show(); 362 363 return 0; 364 } 365 366 int osc_hide_handler(const char*, const char*, lo_arg**, int, lo_message, void*) 367 { 368 d_debug("osc_hide_handler()"); 369 370 if (globalUI != nullptr) 371 globalUI->dssiui_hide(); 372 373 return 0; 374 } 375 376 int osc_quit_handler(const char*, const char*, lo_arg**, int, lo_message, void*) 377 { 378 d_debug("osc_quit_handler()"); 379 380 if (globalUI != nullptr) 381 globalUI->dssiui_quit(); 382 383 return 0; 384 } 385 386 END_NAMESPACE_DISTRHO 387 388 // ----------------------------------------------------------------------- 389 390 int main(int argc, char* argv[]) 391 { 392 USE_NAMESPACE_DISTRHO 393 394 #ifdef DPF_USING_LD_LINUX_WEBVIEW 395 if (argc >= 2 && std::strcmp(argv[1], "dpf-ld-linux-webview") == 0) 396 return dpf_webview_start(argc - 1, argv + 1); 397 #endif 398 399 // dummy test mode 400 if (argc == 1) 401 { 402 gUiTitle = "DSSI UI Test"; 403 404 initUiIfNeeded(); 405 globalUI->dssiui_show(true); 406 globalUI->exec_start(); 407 408 delete globalUI; 409 globalUI = nullptr; 410 411 return 0; 412 } 413 414 if (argc != 5) 415 { 416 d_stderr("Usage: %s <osc-url> <plugin-dll> <plugin-label> <instance-name>", argv[0]); 417 return 1; 418 } 419 420 const char* oscUrl = argv[1]; 421 const char* uiTitle = argv[4]; 422 423 char* const oscHost = lo_url_get_hostname(oscUrl); 424 char* const oscPort = lo_url_get_port(oscUrl); 425 char* const oscPath = lo_url_get_path(oscUrl); 426 size_t oscPathSize = strlen(oscPath); 427 lo_address oscAddr = lo_address_new(oscHost, oscPort); 428 lo_server oscServer = lo_server_new_with_proto(nullptr, LO_UDP, osc_error_handler); 429 430 char* const oscServerPath = lo_server_get_url(oscServer); 431 432 char pluginPath[strlen(oscServerPath)+oscPathSize]; 433 strcpy(pluginPath, oscServerPath); 434 strcat(pluginPath, oscPath+1); 435 436 #if DISTRHO_PLUGIN_WANT_STATE 437 char oscPathConfigure[oscPathSize+11]; 438 strcpy(oscPathConfigure, oscPath); 439 strcat(oscPathConfigure, "/configure"); 440 lo_server_add_method(oscServer, oscPathConfigure, "ss", osc_configure_handler, nullptr); 441 #endif 442 443 char oscPathControl[oscPathSize+9]; 444 strcpy(oscPathControl, oscPath); 445 strcat(oscPathControl, "/control"); 446 lo_server_add_method(oscServer, oscPathControl, "if", osc_control_handler, nullptr); 447 448 d_stdout("oscServerPath: \"%s\"", oscServerPath); 449 d_stdout("pluginPath: \"%s\"", pluginPath); 450 d_stdout("oscPathControl: \"%s\"", oscPathControl); 451 452 #if DISTRHO_PLUGIN_WANT_PROGRAMS 453 char oscPathProgram[oscPathSize+9]; 454 strcpy(oscPathProgram, oscPath); 455 strcat(oscPathProgram, "/program"); 456 lo_server_add_method(oscServer, oscPathProgram, "ii", osc_program_handler, nullptr); 457 #endif 458 459 char oscPathSampleRate[oscPathSize+13]; 460 strcpy(oscPathSampleRate, oscPath); 461 strcat(oscPathSampleRate, "/sample-rate"); 462 lo_server_add_method(oscServer, oscPathSampleRate, "i", osc_sample_rate_handler, nullptr); 463 464 char oscPathShow[oscPathSize+6]; 465 strcpy(oscPathShow, oscPath); 466 strcat(oscPathShow, "/show"); 467 lo_server_add_method(oscServer, oscPathShow, "", osc_show_handler, nullptr); 468 469 char oscPathHide[oscPathSize+6]; 470 strcpy(oscPathHide, oscPath); 471 strcat(oscPathHide, "/hide"); 472 lo_server_add_method(oscServer, oscPathHide, "", osc_hide_handler, nullptr); 473 474 char oscPathQuit[oscPathSize+6]; 475 strcpy(oscPathQuit, oscPath); 476 strcat(oscPathQuit, "/quit"); 477 lo_server_add_method(oscServer, oscPathQuit, "", osc_quit_handler, nullptr); 478 479 lo_server_add_method(oscServer, nullptr, nullptr, osc_debug_handler, nullptr); 480 481 gUiTitle = uiTitle; 482 483 gOscData.addr = oscAddr; 484 gOscData.path = oscPath; 485 gOscData.server = oscServer; 486 gOscData.send_update(pluginPath); 487 488 // wait for init 489 for (int i=0; i < 100; ++i) 490 { 491 lo_server_recv(oscServer); 492 493 if (sampleRate != 0.0 || globalUI != nullptr) 494 break; 495 496 d_msleep(50); 497 } 498 499 int ret = 1; 500 501 if (sampleRate != 0.0 || globalUI != nullptr) 502 { 503 initUiIfNeeded(); 504 505 globalUI->exec_start(); 506 507 delete globalUI; 508 globalUI = nullptr; 509 510 ret = 0; 511 } 512 513 #if DISTRHO_PLUGIN_WANT_STATE 514 lo_server_del_method(oscServer, oscPathConfigure, "ss"); 515 #endif 516 lo_server_del_method(oscServer, oscPathControl, "if"); 517 #if DISTRHO_PLUGIN_WANT_PROGRAMS 518 lo_server_del_method(oscServer, oscPathProgram, "ii"); 519 #endif 520 lo_server_del_method(oscServer, oscPathSampleRate, "i"); 521 lo_server_del_method(oscServer, oscPathShow, ""); 522 lo_server_del_method(oscServer, oscPathHide, ""); 523 lo_server_del_method(oscServer, oscPathQuit, ""); 524 lo_server_del_method(oscServer, nullptr, nullptr); 525 526 std::free(oscServerPath); 527 std::free(oscHost); 528 std::free(oscPort); 529 std::free(oscPath); 530 531 lo_address_free(oscAddr); 532 lo_server_free(oscServer); 533 534 return ret; 535 }