DistrhoPluginJACK.cpp (37499B)
1 /* 2 * DISTRHO Plugin Framework (DPF) 3 * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> 4 * 5 * This program is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Lesser General Public 7 * License as published by the Free Software Foundation. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU Lesser General Public License for more details. 13 * 14 * For a full copy of the license see the LGPL.txt file 15 */ 16 17 #include "DistrhoPluginInternal.hpp" 18 19 #ifndef STATIC_BUILD 20 # include "../DistrhoPluginUtils.hpp" 21 #endif 22 23 #if DISTRHO_PLUGIN_HAS_UI 24 # include "DistrhoUIInternal.hpp" 25 # include "../extra/RingBuffer.hpp" 26 #else 27 # include "../extra/Sleep.hpp" 28 #endif 29 30 #ifdef DPF_RUNTIME_TESTING 31 # include "../extra/Thread.hpp" 32 #endif 33 34 #if defined(HAVE_JACK) && defined(STATIC_BUILD) && !defined(DISTRHO_OS_WASM) 35 # define JACKBRIDGE_DIRECT 36 #endif 37 38 #include "jackbridge/JackBridge.cpp" 39 #include "lv2/lv2.h" 40 41 #ifdef DISTRHO_OS_MAC 42 # define Point CocoaPoint 43 # include <CoreFoundation/CoreFoundation.h> 44 # undef Point 45 #endif 46 47 #ifndef DISTRHO_OS_WINDOWS 48 # include <signal.h> 49 # include <unistd.h> 50 #endif 51 52 #ifdef __SSE2_MATH__ 53 # include <xmmintrin.h> 54 #endif 55 56 #ifndef JACK_METADATA_ORDER 57 # define JACK_METADATA_ORDER "http://jackaudio.org/metadata/order" 58 #endif 59 60 #ifndef JACK_METADATA_PRETTY_NAME 61 # define JACK_METADATA_PRETTY_NAME "http://jackaudio.org/metadata/pretty-name" 62 #endif 63 64 #ifndef JACK_METADATA_PORT_GROUP 65 # define JACK_METADATA_PORT_GROUP "http://jackaudio.org/metadata/port-group" 66 #endif 67 68 #ifndef JACK_METADATA_SIGNAL_TYPE 69 # define JACK_METADATA_SIGNAL_TYPE "http://jackaudio.org/metadata/signal-type" 70 #endif 71 72 // ----------------------------------------------------------------------- 73 74 START_NAMESPACE_DISTRHO 75 76 #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_MIDI_INPUT 77 static const sendNoteFunc sendNoteCallback = nullptr; 78 #endif 79 #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_STATE 80 static const setStateFunc setStateCallback = nullptr; 81 #endif 82 #if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 83 static const writeMidiFunc writeMidiCallback = nullptr; 84 #endif 85 #if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST 86 static const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; 87 #endif 88 89 #ifdef DPF_USING_LD_LINUX_WEBVIEW 90 int dpf_webview_start(int argc, char* argv[]); 91 #endif 92 93 // ----------------------------------------------------------------------- 94 95 static volatile bool gCloseSignalReceived = false; 96 97 #ifdef DISTRHO_OS_WINDOWS 98 static BOOL WINAPI winSignalHandler(DWORD dwCtrlType) noexcept 99 { 100 if (dwCtrlType == CTRL_C_EVENT) 101 { 102 gCloseSignalReceived = true; 103 return TRUE; 104 } 105 return FALSE; 106 } 107 108 static void initSignalHandler() 109 { 110 SetConsoleCtrlHandler(winSignalHandler, TRUE); 111 } 112 #else 113 static void closeSignalHandler(int) noexcept 114 { 115 gCloseSignalReceived = true; 116 } 117 118 static void initSignalHandler() 119 { 120 struct sigaction sig; 121 memset(&sig, 0, sizeof(sig)); 122 123 sig.sa_handler = closeSignalHandler; 124 sig.sa_flags = SA_RESTART; 125 sigemptyset(&sig.sa_mask); 126 sigaction(SIGINT, &sig, nullptr); 127 sigaction(SIGTERM, &sig, nullptr); 128 } 129 #endif 130 131 // ----------------------------------------------------------------------- 132 133 #if DISTRHO_PLUGIN_HAS_UI 134 class PluginJack : public DGL_NAMESPACE::IdleCallback 135 #else 136 class PluginJack 137 #endif 138 { 139 public: 140 PluginJack(jack_client_t* const client, const uintptr_t winId) 141 : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, nullptr), 142 #if DISTRHO_PLUGIN_HAS_UI 143 fUI(this, 144 winId, 145 d_nextSampleRate, 146 nullptr, // edit param 147 setParameterValueCallback, 148 setStateCallback, 149 sendNoteCallback, 150 nullptr, // window size 151 nullptr, // file request 152 nullptr, // bundle 153 fPlugin.getInstancePointer(), 154 0.0), 155 #endif 156 fClient(client) 157 { 158 #if DISTRHO_PLUGIN_NUM_INPUTS > 0 || DISTRHO_PLUGIN_NUM_OUTPUTS > 0 159 # if DISTRHO_PLUGIN_NUM_INPUTS > 0 160 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i) 161 { 162 const AudioPort& port(fPlugin.getAudioPort(true, i)); 163 ulong hints = JackPortIsInput; 164 if (port.hints & kAudioPortIsCV) 165 hints |= JackPortIsControlVoltage; 166 fPortAudioIns[i] = jackbridge_port_register(fClient, port.symbol, JACK_DEFAULT_AUDIO_TYPE, hints, 0); 167 setAudioPortMetadata(port, fPortAudioIns[i], i); 168 } 169 # endif 170 # if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 171 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) 172 { 173 const AudioPort& port(fPlugin.getAudioPort(false, i)); 174 ulong hints = JackPortIsOutput; 175 if (port.hints & kAudioPortIsCV) 176 hints |= JackPortIsControlVoltage; 177 fPortAudioOuts[i] = jackbridge_port_register(fClient, port.symbol, JACK_DEFAULT_AUDIO_TYPE, hints, 0); 178 setAudioPortMetadata(port, fPortAudioOuts[i], DISTRHO_PLUGIN_NUM_INPUTS+i); 179 } 180 # endif 181 #endif 182 183 fPortEventsIn = jackbridge_port_register(fClient, "events-in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); 184 185 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 186 fPortMidiOut = jackbridge_port_register(fClient, "midi-out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); 187 fPortMidiOutBuffer = nullptr; 188 #endif 189 190 #if DISTRHO_PLUGIN_WANT_PROGRAMS 191 if (fPlugin.getProgramCount() > 0) 192 { 193 fPlugin.loadProgram(0); 194 # if DISTRHO_PLUGIN_HAS_UI 195 fUI.programLoaded(0); 196 # endif 197 } 198 # if DISTRHO_PLUGIN_HAS_UI 199 fProgramChanged = -1; 200 # endif 201 #endif 202 203 if (const uint32_t count = fPlugin.getParameterCount()) 204 { 205 fLastOutputValues = new float[count]; 206 std::memset(fLastOutputValues, 0, sizeof(float)*count); 207 208 #if DISTRHO_PLUGIN_HAS_UI 209 fParametersChanged = new bool[count]; 210 std::memset(fParametersChanged, 0, sizeof(bool)*count); 211 #endif 212 213 for (uint32_t i=0; i < count; ++i) 214 { 215 #if DISTRHO_PLUGIN_HAS_UI 216 if (! fPlugin.isParameterOutput(i)) 217 fUI.parameterChanged(i, fPlugin.getParameterValue(i)); 218 #endif 219 } 220 } 221 else 222 { 223 fLastOutputValues = nullptr; 224 #if DISTRHO_PLUGIN_HAS_UI 225 fParametersChanged = nullptr; 226 #endif 227 } 228 229 jackbridge_set_thread_init_callback(fClient, jackThreadInitCallback, this); 230 jackbridge_set_buffer_size_callback(fClient, jackBufferSizeCallback, this); 231 jackbridge_set_sample_rate_callback(fClient, jackSampleRateCallback, this); 232 jackbridge_set_process_callback(fClient, jackProcessCallback, this); 233 jackbridge_on_shutdown(fClient, jackShutdownCallback, this); 234 235 fPlugin.activate(); 236 237 jackbridge_activate(fClient); 238 239 std::fflush(stdout); 240 241 #if DISTRHO_PLUGIN_HAS_UI 242 String title(fPlugin.getMaker()); 243 244 if (title.isNotEmpty()) 245 title += ": "; 246 247 if (const char* const name = jackbridge_get_client_name(fClient)) 248 title += name; 249 else 250 title += fPlugin.getName(); 251 252 fUI.setWindowTitle(title); 253 fUI.exec(this); 254 #else 255 while (! gCloseSignalReceived) 256 d_sleep(1); 257 258 // unused 259 (void)winId; 260 #endif 261 } 262 263 ~PluginJack() 264 { 265 if (fClient != nullptr) 266 jackbridge_deactivate(fClient); 267 268 if (fLastOutputValues != nullptr) 269 { 270 delete[] fLastOutputValues; 271 fLastOutputValues = nullptr; 272 } 273 274 #if DISTRHO_PLUGIN_HAS_UI 275 if (fParametersChanged != nullptr) 276 { 277 delete[] fParametersChanged; 278 fParametersChanged = nullptr; 279 } 280 #endif 281 282 fPlugin.deactivate(); 283 284 if (fClient == nullptr) 285 return; 286 287 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 288 jackbridge_port_unregister(fClient, fPortMidiOut); 289 fPortMidiOut = nullptr; 290 #endif 291 292 jackbridge_port_unregister(fClient, fPortEventsIn); 293 fPortEventsIn = nullptr; 294 295 #if DISTRHO_PLUGIN_NUM_INPUTS > 0 296 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i) 297 { 298 jackbridge_port_unregister(fClient, fPortAudioIns[i]); 299 fPortAudioIns[i] = nullptr; 300 } 301 #endif 302 303 #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 304 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) 305 { 306 jackbridge_port_unregister(fClient, fPortAudioOuts[i]); 307 fPortAudioOuts[i] = nullptr; 308 } 309 #endif 310 311 jackbridge_client_close(fClient); 312 } 313 314 // ------------------------------------------------------------------- 315 316 protected: 317 #if DISTRHO_PLUGIN_HAS_UI 318 void idleCallback() override 319 { 320 if (gCloseSignalReceived) 321 return fUI.quit(); 322 323 # if DISTRHO_PLUGIN_WANT_PROGRAMS 324 if (fProgramChanged >= 0) 325 { 326 fUI.programLoaded(fProgramChanged); 327 fProgramChanged = -1; 328 } 329 # endif 330 331 for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i) 332 { 333 if (fPlugin.isParameterOutput(i)) 334 { 335 const float value = fPlugin.getParameterValue(i); 336 337 if (d_isEqual(fLastOutputValues[i], value)) 338 continue; 339 340 fLastOutputValues[i] = value; 341 fUI.parameterChanged(i, value); 342 } 343 else if (fParametersChanged[i]) 344 { 345 fParametersChanged[i] = false; 346 fUI.parameterChanged(i, fPlugin.getParameterValue(i)); 347 } 348 } 349 350 fUI.exec_idle(); 351 } 352 #endif 353 354 void jackBufferSize(const jack_nframes_t nframes) 355 { 356 fPlugin.setBufferSize(nframes, true); 357 } 358 359 void jackSampleRate(const jack_nframes_t nframes) 360 { 361 fPlugin.setSampleRate(nframes, true); 362 } 363 364 void jackProcess(const jack_nframes_t nframes) 365 { 366 #if DISTRHO_PLUGIN_NUM_INPUTS > 0 367 const float* audioIns[DISTRHO_PLUGIN_NUM_INPUTS]; 368 369 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i) 370 audioIns[i] = (const float*)jackbridge_port_get_buffer(fPortAudioIns[i], nframes); 371 #else 372 static const float** audioIns = nullptr; 373 #endif 374 375 #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 376 float* audioOuts[DISTRHO_PLUGIN_NUM_OUTPUTS]; 377 378 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) 379 audioOuts[i] = (float*)jackbridge_port_get_buffer(fPortAudioOuts[i], nframes); 380 #else 381 static float** audioOuts = nullptr; 382 #endif 383 384 #if DISTRHO_PLUGIN_WANT_TIMEPOS 385 jack_position_t pos; 386 fTimePosition.playing = (jackbridge_transport_query(fClient, &pos) == JackTransportRolling); 387 388 if (pos.unique_1 == pos.unique_2) 389 { 390 fTimePosition.frame = pos.frame; 391 392 if (pos.valid & JackPositionBBT) 393 { 394 fTimePosition.bbt.valid = true; 395 396 fTimePosition.bbt.bar = pos.bar; 397 fTimePosition.bbt.beat = pos.beat; 398 fTimePosition.bbt.tick = pos.tick; 399 #ifdef JACK_TICK_DOUBLE 400 if (pos.valid & JackTickDouble) 401 fTimePosition.bbt.tick = pos.tick_double; 402 else 403 #endif 404 fTimePosition.bbt.tick = pos.tick; 405 fTimePosition.bbt.barStartTick = pos.bar_start_tick; 406 407 fTimePosition.bbt.beatsPerBar = pos.beats_per_bar; 408 fTimePosition.bbt.beatType = pos.beat_type; 409 410 fTimePosition.bbt.ticksPerBeat = pos.ticks_per_beat; 411 fTimePosition.bbt.beatsPerMinute = pos.beats_per_minute; 412 } 413 else 414 fTimePosition.bbt.valid = false; 415 } 416 else 417 { 418 fTimePosition.bbt.valid = false; 419 fTimePosition.frame = 0; 420 } 421 422 fPlugin.setTimePosition(fTimePosition); 423 #endif 424 425 updateParameterTriggers(); 426 427 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 428 fPortMidiOutBuffer = jackbridge_port_get_buffer(fPortMidiOut, nframes); 429 jackbridge_midi_clear_buffer(fPortMidiOutBuffer); 430 #endif 431 432 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT 433 uint32_t midiEventCount = 0; 434 MidiEvent midiEvents[512]; 435 436 # if DISTRHO_PLUGIN_HAS_UI 437 while (fNotesRingBuffer.isDataAvailableForReading()) 438 { 439 uint8_t midiData[3]; 440 if (! fNotesRingBuffer.readCustomData(midiData, 3)) 441 break; 442 443 MidiEvent& midiEvent(midiEvents[midiEventCount++]); 444 midiEvent.frame = 0; 445 midiEvent.size = 3; 446 std::memcpy(midiEvent.data, midiData, 3); 447 448 if (midiEventCount == 512) 449 break; 450 } 451 # endif 452 #else 453 static const uint32_t midiEventCount = 0; 454 #endif 455 456 void* const midiInBuf = jackbridge_port_get_buffer(fPortEventsIn, nframes); 457 458 if (const uint32_t eventCount = std::min(512u - midiEventCount, jackbridge_midi_get_event_count(midiInBuf))) 459 { 460 jack_midi_event_t jevent; 461 462 for (uint32_t i=0; i < eventCount; ++i) 463 { 464 if (! jackbridge_midi_event_get(&jevent, midiInBuf, i)) 465 break; 466 467 // Check if message is control change on channel 1 468 if (jevent.buffer[0] == 0xB0 && jevent.size == 3) 469 { 470 const uint8_t control = jevent.buffer[1]; 471 const uint8_t value = jevent.buffer[2]; 472 473 /* NOTE: This is not optimal, we're iterating all parameters on every CC message. 474 Since the JACK standalone is more of a test tool, this will do for now. */ 475 for (uint32_t j=0, paramCount=fPlugin.getParameterCount(); j < paramCount; ++j) 476 { 477 if (fPlugin.isParameterOutput(j)) 478 continue; 479 if (fPlugin.getParameterMidiCC(j) != control) 480 continue; 481 482 const float scaled = static_cast<float>(value)/127.0f; 483 const float fvalue = fPlugin.getParameterRanges(j).getUnnormalizedValue(scaled); 484 fPlugin.setParameterValue(j, fvalue); 485 #if DISTRHO_PLUGIN_HAS_UI 486 fParametersChanged[j] = true; 487 #endif 488 break; 489 } 490 } 491 #if DISTRHO_PLUGIN_WANT_PROGRAMS 492 // Check if message is program change on channel 1 493 else if (jevent.buffer[0] == 0xC0 && jevent.size == 2) 494 { 495 const uint8_t program = jevent.buffer[1]; 496 497 if (program < fPlugin.getProgramCount()) 498 { 499 fPlugin.loadProgram(program); 500 # if DISTRHO_PLUGIN_HAS_UI 501 fProgramChanged = program; 502 # endif 503 } 504 } 505 #endif 506 507 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT 508 MidiEvent& midiEvent(midiEvents[midiEventCount++]); 509 510 midiEvent.frame = jevent.time; 511 midiEvent.size = static_cast<uint32_t>(jevent.size); 512 513 if (midiEvent.size > MidiEvent::kDataSize) 514 midiEvent.dataExt = jevent.buffer; 515 else 516 std::memcpy(midiEvent.data, jevent.buffer, midiEvent.size); 517 #endif 518 } 519 } 520 521 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT 522 fPlugin.run(audioIns, audioOuts, nframes, midiEvents, midiEventCount); 523 #else 524 fPlugin.run(audioIns, audioOuts, nframes); 525 #endif 526 527 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 528 fPortMidiOutBuffer = nullptr; 529 #endif 530 } 531 532 void jackShutdown() 533 { 534 d_stderr("jack has shutdown, quitting now..."); 535 fClient = nullptr; 536 #if DISTRHO_PLUGIN_HAS_UI 537 fUI.quit(); 538 #endif 539 } 540 541 // ------------------------------------------------------------------- 542 543 #if DISTRHO_PLUGIN_HAS_UI 544 void setParameterValue(const uint32_t index, const float value) 545 { 546 fPlugin.setParameterValue(index, value); 547 } 548 549 # if DISTRHO_PLUGIN_WANT_MIDI_INPUT 550 void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) 551 { 552 uint8_t midiData[3]; 553 midiData[0] = (velocity != 0 ? 0x90 : 0x80) | channel; 554 midiData[1] = note; 555 midiData[2] = velocity; 556 fNotesRingBuffer.writeCustomData(midiData, 3); 557 fNotesRingBuffer.commitWrite(); 558 } 559 # endif 560 561 # if DISTRHO_PLUGIN_WANT_STATE 562 void setState(const char* const key, const char* const value) 563 { 564 fPlugin.setState(key, value); 565 } 566 # endif 567 #endif // DISTRHO_PLUGIN_HAS_UI 568 569 // NOTE: no trigger support for JACK, simulate it here 570 void updateParameterTriggers() 571 { 572 float defValue; 573 574 for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i) 575 { 576 if ((fPlugin.getParameterHints(i) & kParameterIsTrigger) != kParameterIsTrigger) 577 continue; 578 579 defValue = fPlugin.getParameterRanges(i).def; 580 581 if (d_isNotEqual(defValue, fPlugin.getParameterValue(i))) 582 fPlugin.setParameterValue(i, defValue); 583 } 584 } 585 586 // ------------------------------------------------------------------- 587 588 private: 589 PluginExporter fPlugin; 590 #if DISTRHO_PLUGIN_HAS_UI 591 UIExporter fUI; 592 #endif 593 594 jack_client_t* fClient; 595 596 #if DISTRHO_PLUGIN_NUM_INPUTS > 0 597 jack_port_t* fPortAudioIns[DISTRHO_PLUGIN_NUM_INPUTS]; 598 #endif 599 #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 600 jack_port_t* fPortAudioOuts[DISTRHO_PLUGIN_NUM_OUTPUTS]; 601 #endif 602 jack_port_t* fPortEventsIn; 603 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 604 jack_port_t* fPortMidiOut; 605 void* fPortMidiOutBuffer; 606 #endif 607 #if DISTRHO_PLUGIN_WANT_TIMEPOS 608 TimePosition fTimePosition; 609 #endif 610 611 // Temporary data 612 float* fLastOutputValues; 613 614 #if DISTRHO_PLUGIN_HAS_UI 615 // Store DSP changes to send to UI 616 bool* fParametersChanged; 617 # if DISTRHO_PLUGIN_WANT_PROGRAMS 618 int fProgramChanged; 619 # endif 620 # if DISTRHO_PLUGIN_WANT_MIDI_INPUT 621 SmallStackRingBuffer fNotesRingBuffer; 622 # endif 623 #endif 624 625 void setAudioPortMetadata(const AudioPort& port, jack_port_t* const jackport, const uint32_t index) 626 { 627 DISTRHO_SAFE_ASSERT_RETURN(jackport != nullptr,); 628 629 const jack_uuid_t uuid = jackbridge_port_uuid(jackport); 630 631 if (uuid == JACK_UUID_EMPTY_INITIALIZER) 632 return; 633 634 jackbridge_set_property(fClient, uuid, JACK_METADATA_PRETTY_NAME, port.name, "text/plain"); 635 636 { 637 char strBuf[0xff]; 638 snprintf(strBuf, 0xff - 2, "%u", index); 639 strBuf[0xff - 1] = '\0'; 640 jackbridge_set_property(fClient, uuid, JACK_METADATA_ORDER, strBuf, "http://www.w3.org/2001/XMLSchema#integer"); 641 } 642 643 if (port.groupId != kPortGroupNone) 644 { 645 const PortGroupWithId& portGroup(fPlugin.getPortGroupById(port.groupId)); 646 jackbridge_set_property(fClient, uuid, JACK_METADATA_PORT_GROUP, portGroup.name, "text/plain"); 647 } 648 649 if (port.hints & kAudioPortIsCV) 650 { 651 jackbridge_set_property(fClient, uuid, JACK_METADATA_SIGNAL_TYPE, "CV", "text/plain"); 652 } 653 else 654 { 655 jackbridge_set_property(fClient, uuid, JACK_METADATA_SIGNAL_TYPE, "AUDIO", "text/plain"); 656 return; 657 } 658 659 // set cv ranges 660 const bool cvPortScaled = port.hints & kCVPortHasScaledRange; 661 662 if (port.hints & kCVPortHasBipolarRange) 663 { 664 if (cvPortScaled) 665 { 666 jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "-5", "http://www.w3.org/2001/XMLSchema#integer"); 667 jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "5", "http://www.w3.org/2001/XMLSchema#integer"); 668 } 669 else 670 { 671 jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "-1", "http://www.w3.org/2001/XMLSchema#integer"); 672 jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "1", "http://www.w3.org/2001/XMLSchema#integer"); 673 } 674 } 675 else if (port.hints & kCVPortHasNegativeUnipolarRange) 676 { 677 if (cvPortScaled) 678 { 679 jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "-10", "http://www.w3.org/2001/XMLSchema#integer"); 680 jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "0", "http://www.w3.org/2001/XMLSchema#integer"); 681 } 682 else 683 { 684 jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "-1", "http://www.w3.org/2001/XMLSchema#integer"); 685 jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "0", "http://www.w3.org/2001/XMLSchema#integer"); 686 } 687 } 688 else if (port.hints & kCVPortHasPositiveUnipolarRange) 689 { 690 if (cvPortScaled) 691 { 692 jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "0", "http://www.w3.org/2001/XMLSchema#integer"); 693 jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "10", "http://www.w3.org/2001/XMLSchema#integer"); 694 } 695 else 696 { 697 jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "0", "http://www.w3.org/2001/XMLSchema#integer"); 698 jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "1", "http://www.w3.org/2001/XMLSchema#integer"); 699 } 700 } 701 } 702 703 // ------------------------------------------------------------------- 704 // Callbacks 705 706 #define thisPtr ((PluginJack*)ptr) 707 708 static void jackThreadInitCallback(void*) 709 { 710 #if defined(__SSE2_MATH__) 711 _mm_setcsr(_mm_getcsr() | 0x8040); 712 #elif defined(__aarch64__) 713 uint64_t c; 714 __asm__ __volatile__("mrs %0, fpcr \n" 715 "orr %0, %0, #0x1000000\n" 716 "msr fpcr, %0 \n" 717 "isb \n" 718 : "=r"(c) :: "memory"); 719 #elif defined(__arm__) && !defined(__SOFTFP__) 720 uint32_t c; 721 __asm__ __volatile__("vmrs %0, fpscr \n" 722 "orr %0, %0, #0x1000000\n" 723 "vmsr fpscr, %0 \n" 724 : "=r"(c) :: "memory"); 725 #endif 726 } 727 728 static int jackBufferSizeCallback(jack_nframes_t nframes, void* ptr) 729 { 730 thisPtr->jackBufferSize(nframes); 731 return 0; 732 } 733 734 static int jackSampleRateCallback(jack_nframes_t nframes, void* ptr) 735 { 736 thisPtr->jackSampleRate(nframes); 737 return 0; 738 } 739 740 static int jackProcessCallback(jack_nframes_t nframes, void* ptr) 741 { 742 thisPtr->jackProcess(nframes); 743 return 0; 744 } 745 746 static void jackShutdownCallback(void* ptr) 747 { 748 thisPtr->jackShutdown(); 749 } 750 751 #if DISTRHO_PLUGIN_HAS_UI 752 static void setParameterValueCallback(void* ptr, uint32_t index, float value) 753 { 754 thisPtr->setParameterValue(index, value); 755 } 756 757 # if DISTRHO_PLUGIN_WANT_MIDI_INPUT 758 static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity) 759 { 760 thisPtr->sendNote(channel, note, velocity); 761 } 762 # endif 763 764 # if DISTRHO_PLUGIN_WANT_STATE 765 static void setStateCallback(void* ptr, const char* key, const char* value) 766 { 767 thisPtr->setState(key, value); 768 } 769 # endif 770 #endif // DISTRHO_PLUGIN_HAS_UI 771 772 #if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST 773 bool requestParameterValueChange(const uint32_t index, const float value) 774 { 775 DISTRHO_SAFE_ASSERT_RETURN(index < fPlugin.getParameterCount(), false); 776 777 fPlugin.setParameterValue(index, value); 778 # if DISTRHO_PLUGIN_HAS_UI 779 fParametersChanged[index] = true; 780 # endif 781 return true; 782 } 783 784 static bool requestParameterValueChangeCallback(void* ptr, const uint32_t index, const float value) 785 { 786 return thisPtr->requestParameterValueChange(index, value); 787 } 788 #endif 789 790 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 791 bool writeMidi(const MidiEvent& midiEvent) 792 { 793 DISTRHO_SAFE_ASSERT_RETURN(fPortMidiOutBuffer != nullptr, false); 794 795 return jackbridge_midi_event_write(fPortMidiOutBuffer, 796 midiEvent.frame, 797 midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data, 798 midiEvent.size); 799 } 800 801 static bool writeMidiCallback(void* ptr, const MidiEvent& midiEvent) 802 { 803 return thisPtr->writeMidi(midiEvent); 804 } 805 #endif 806 807 #undef thisPtr 808 }; 809 810 // ----------------------------------------------------------------------- 811 812 #ifdef DPF_RUNTIME_TESTING 813 class PluginProcessTestingThread : public Thread 814 { 815 PluginExporter& plugin; 816 817 public: 818 PluginProcessTestingThread(PluginExporter& p) : plugin(p) {} 819 820 protected: 821 void run() override 822 { 823 plugin.setBufferSize(256, true); 824 plugin.activate(); 825 826 float buffer[256]; 827 const float* inputs[DISTRHO_PLUGIN_NUM_INPUTS > 0 ? DISTRHO_PLUGIN_NUM_INPUTS : 1]; 828 float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS > 0 ? DISTRHO_PLUGIN_NUM_OUTPUTS : 1]; 829 for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i) 830 inputs[i] = buffer; 831 for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) 832 outputs[i] = buffer; 833 834 while (! shouldThreadExit()) 835 { 836 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT 837 plugin.run(inputs, outputs, 128, nullptr, 0); 838 #else 839 plugin.run(inputs, outputs, 128); 840 #endif 841 d_msleep(100); 842 } 843 844 plugin.deactivate(); 845 } 846 }; 847 848 bool runSelfTests() 849 { 850 // simple plugin creation first 851 { 852 d_nextBufferSize = 512; 853 d_nextSampleRate = 44100.0; 854 PluginExporter plugin(nullptr, nullptr, nullptr, nullptr); 855 d_nextBufferSize = 0; 856 d_nextSampleRate = 0.0; 857 } 858 859 // keep values for all tests now 860 d_nextBufferSize = 512; 861 d_nextSampleRate = 44100.0; 862 863 // simple processing 864 { 865 d_nextPluginIsSelfTest = true; 866 PluginExporter plugin(nullptr, nullptr, nullptr, nullptr); 867 d_nextPluginIsSelfTest = false; 868 869 #if DISTRHO_PLUGIN_HAS_UI 870 UIExporter ui(nullptr, 0, plugin.getSampleRate(), 871 nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 872 plugin.getInstancePointer(), 0.0); 873 ui.showAndFocus(); 874 #endif 875 876 plugin.activate(); 877 plugin.deactivate(); 878 plugin.setBufferSize(128, true); 879 plugin.setSampleRate(48000, true); 880 plugin.activate(); 881 882 float buffer[128] = {}; 883 const float* inputs[DISTRHO_PLUGIN_NUM_INPUTS > 0 ? DISTRHO_PLUGIN_NUM_INPUTS : 1]; 884 float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS > 0 ? DISTRHO_PLUGIN_NUM_OUTPUTS : 1]; 885 for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i) 886 inputs[i] = buffer; 887 for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) 888 outputs[i] = buffer; 889 890 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT 891 plugin.run(inputs, outputs, 128, nullptr, 0); 892 #else 893 plugin.run(inputs, outputs, 128); 894 #endif 895 896 plugin.deactivate(); 897 898 #if DISTRHO_PLUGIN_HAS_UI 899 ui.plugin_idle(); 900 #endif 901 } 902 903 return true; 904 905 // multi-threaded processing with UI 906 { 907 PluginExporter pluginA(nullptr, nullptr, nullptr, nullptr); 908 PluginExporter pluginB(nullptr, nullptr, nullptr, nullptr); 909 PluginExporter pluginC(nullptr, nullptr, nullptr, nullptr); 910 PluginProcessTestingThread procTestA(pluginA); 911 PluginProcessTestingThread procTestB(pluginB); 912 PluginProcessTestingThread procTestC(pluginC); 913 procTestA.startThread(); 914 procTestB.startThread(); 915 procTestC.startThread(); 916 917 // wait 2s 918 d_sleep(2); 919 920 // stop the 2nd instance now 921 procTestB.stopThread(5000); 922 923 #if DISTRHO_PLUGIN_HAS_UI 924 // start UI in the middle of this 925 { 926 UIExporter uiA(nullptr, 0, pluginA.getSampleRate(), 927 nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 928 pluginA.getInstancePointer(), 0.0); 929 UIExporter uiB(nullptr, 0, pluginA.getSampleRate(), 930 nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 931 pluginB.getInstancePointer(), 0.0); 932 UIExporter uiC(nullptr, 0, pluginA.getSampleRate(), 933 nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 934 pluginC.getInstancePointer(), 0.0); 935 936 // show UIs 937 uiB.showAndFocus(); 938 uiA.showAndFocus(); 939 uiC.showAndFocus(); 940 941 // idle for 3s 942 for (int i=0; i<30; i++) 943 { 944 uiC.plugin_idle(); 945 uiB.plugin_idle(); 946 uiA.plugin_idle(); 947 d_msleep(100); 948 } 949 } 950 #endif 951 952 procTestA.stopThread(5000); 953 procTestC.stopThread(5000); 954 } 955 956 return true; 957 } 958 #endif // DPF_RUNTIME_TESTING 959 960 END_NAMESPACE_DISTRHO 961 962 // ----------------------------------------------------------------------- 963 964 int main(int argc, char* argv[]) 965 { 966 USE_NAMESPACE_DISTRHO; 967 968 initSignalHandler(); 969 970 #ifndef STATIC_BUILD 971 // find plugin bundle 972 static String bundlePath; 973 if (bundlePath.isEmpty()) 974 { 975 String tmpPath(getBinaryFilename()); 976 tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP)); 977 #if defined(DISTRHO_OS_MAC) 978 if (tmpPath.endsWith("/MacOS")) 979 { 980 tmpPath.truncate(tmpPath.rfind('/')); 981 if (tmpPath.endsWith("/Contents")) 982 { 983 tmpPath.truncate(tmpPath.rfind('/')); 984 bundlePath = tmpPath; 985 d_nextBundlePath = bundlePath.buffer(); 986 } 987 } 988 #else 989 #ifdef DISTRHO_OS_WINDOWS 990 const DWORD attr = GetFileAttributesA(tmpPath + DISTRHO_OS_SEP_STR "resources"); 991 if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0) 992 #else 993 if (access(tmpPath + DISTRHO_OS_SEP_STR "resources", F_OK) == 0) 994 #endif 995 { 996 bundlePath = tmpPath; 997 d_nextBundlePath = bundlePath.buffer(); 998 } 999 #endif 1000 } 1001 #endif 1002 1003 #ifdef DPF_USING_LD_LINUX_WEBVIEW 1004 if (argc >= 2 && std::strcmp(argv[1], "dpf-ld-linux-webview") == 0) 1005 return dpf_webview_start(argc - 1, argv + 1); 1006 #endif 1007 1008 if (argc == 2 && std::strcmp(argv[1], "selftest") == 0) 1009 { 1010 #ifdef DPF_RUNTIME_TESTING 1011 return runSelfTests() ? 0 : 1; 1012 #else 1013 d_stderr2("Code was built without DPF_RUNTIME_TESTING macro enabled, selftest option is not available"); 1014 return 1; 1015 #endif 1016 } 1017 1018 #if defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI 1019 /* the code below is based on 1020 * https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/ 1021 */ 1022 bool hasConsole = false; 1023 1024 HANDLE consoleHandleOut, consoleHandleError; 1025 1026 if (AttachConsole(ATTACH_PARENT_PROCESS)) 1027 { 1028 // Redirect unbuffered STDOUT to the console 1029 consoleHandleOut = GetStdHandle(STD_OUTPUT_HANDLE); 1030 if (consoleHandleOut != INVALID_HANDLE_VALUE) 1031 { 1032 freopen("CONOUT$", "w", stdout); 1033 setvbuf(stdout, NULL, _IONBF, 0); 1034 } 1035 1036 // Redirect unbuffered STDERR to the console 1037 consoleHandleError = GetStdHandle(STD_ERROR_HANDLE); 1038 if (consoleHandleError != INVALID_HANDLE_VALUE) 1039 { 1040 freopen("CONOUT$", "w", stderr); 1041 setvbuf(stderr, NULL, _IONBF, 0); 1042 } 1043 1044 hasConsole = true; 1045 } 1046 #endif 1047 1048 jack_status_t status = jack_status_t(0x0); 1049 jack_client_t* client = jackbridge_client_open(DISTRHO_PLUGIN_NAME, JackNoStartServer, &status); 1050 1051 #ifdef HAVE_JACK 1052 #define STANDALONE_NAME "JACK client" 1053 #else 1054 #define STANDALONE_NAME "Native audio driver" 1055 #endif 1056 1057 if (client == nullptr) 1058 { 1059 String errorString; 1060 1061 if (status & JackFailure) 1062 errorString += "Overall operation failed;\n"; 1063 if (status & JackInvalidOption) 1064 errorString += "The operation contained an invalid or unsupported option;\n"; 1065 if (status & JackNameNotUnique) 1066 errorString += "The desired client name was not unique;\n"; 1067 if (status & JackServerStarted) 1068 errorString += "The JACK server was started as a result of this operation;\n"; 1069 if (status & JackServerFailed) 1070 errorString += "Unable to connect to the JACK server;\n"; 1071 if (status & JackServerError) 1072 errorString += "Communication error with the JACK server;\n"; 1073 if (status & JackNoSuchClient) 1074 errorString += "Requested client does not exist;\n"; 1075 if (status & JackLoadFailure) 1076 errorString += "Unable to load internal client;\n"; 1077 if (status & JackInitFailure) 1078 errorString += "Unable to initialize client;\n"; 1079 if (status & JackShmFailure) 1080 errorString += "Unable to access shared memory;\n"; 1081 if (status & JackVersionError) 1082 errorString += "Client's protocol version does not match;\n"; 1083 if (status & JackBackendError) 1084 errorString += "Backend Error;\n"; 1085 if (status & JackClientZombie) 1086 errorString += "Client is being shutdown against its will;\n"; 1087 if (status & JackBridgeNativeFailed) 1088 errorString += "Native audio driver was unable to start;\n"; 1089 1090 if (errorString.isNotEmpty()) 1091 { 1092 errorString[errorString.length()-2] = '.'; 1093 d_stderr("Failed to create the " STANDALONE_NAME ", reason was:\n%s", errorString.buffer()); 1094 } 1095 else 1096 d_stderr("Failed to create the " STANDALONE_NAME ", cannot continue!"); 1097 1098 #if defined(DISTRHO_OS_MAC) 1099 CFStringRef errorTitleRef = CFStringCreateWithCString(nullptr, 1100 DISTRHO_PLUGIN_NAME ": Error", kCFStringEncodingUTF8); 1101 CFStringRef errorStringRef = CFStringCreateWithCString(nullptr, 1102 String("Failed to create " STANDALONE_NAME ", reason was:\n" + errorString).buffer(), kCFStringEncodingUTF8); 1103 1104 CFUserNotificationDisplayAlert(0, kCFUserNotificationCautionAlertLevel, 1105 nullptr, nullptr, nullptr, 1106 errorTitleRef, errorStringRef, 1107 nullptr, nullptr, nullptr, nullptr); 1108 #elif defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI 1109 // make sure message box is high-dpi aware 1110 if (const HMODULE user32 = LoadLibrary("user32.dll")) 1111 { 1112 typedef BOOL(WINAPI* SPDA)(void); 1113 #if defined(__GNUC__) && (__GNUC__ >= 9) 1114 # pragma GCC diagnostic push 1115 # pragma GCC diagnostic ignored "-Wcast-function-type" 1116 #endif 1117 const SPDA SetProcessDPIAware = (SPDA)GetProcAddress(user32, "SetProcessDPIAware"); 1118 #if defined(__GNUC__) && (__GNUC__ >= 9) 1119 # pragma GCC diagnostic pop 1120 #endif 1121 if (SetProcessDPIAware) 1122 SetProcessDPIAware(); 1123 FreeLibrary(user32); 1124 } 1125 1126 const String win32error = "Failed to create " STANDALONE_NAME ", reason was:\n" + errorString; 1127 MessageBoxA(nullptr, win32error.buffer(), "", MB_ICONERROR); 1128 #endif 1129 1130 return 1; 1131 } 1132 1133 d_nextBufferSize = jackbridge_get_buffer_size(client); 1134 d_nextSampleRate = jackbridge_get_sample_rate(client); 1135 d_nextCanRequestParameterValueChanges = true; 1136 1137 uintptr_t winId = 0; 1138 #if DISTRHO_PLUGIN_HAS_UI 1139 if (argc == 3 && std::strcmp(argv[1], "embed") == 0) 1140 winId = static_cast<uintptr_t>(std::atoll(argv[2])); 1141 #endif 1142 1143 const PluginJack p(client, winId); 1144 1145 #if defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI 1146 /* the code below is based on 1147 * https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/ 1148 */ 1149 1150 // Send "enter" to release application from the console 1151 // This is a hack, but if not used the console doesn't know the application has 1152 // returned. The "enter" key only sent if the console window is in focus. 1153 if (hasConsole && (GetConsoleWindow() == GetForegroundWindow() || SetFocus(GetConsoleWindow()) != nullptr)) 1154 { 1155 INPUT ip; 1156 // Set up a generic keyboard event. 1157 ip.type = INPUT_KEYBOARD; 1158 ip.ki.wScan = 0; // hardware scan code for key 1159 ip.ki.time = 0; 1160 ip.ki.dwExtraInfo = 0; 1161 1162 // Send the "Enter" key 1163 ip.ki.wVk = 0x0D; // virtual-key code for the "Enter" key 1164 ip.ki.dwFlags = 0; // 0 for key press 1165 SendInput(1, &ip, sizeof(INPUT)); 1166 1167 // Release the "Enter" key 1168 ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release 1169 SendInput(1, &ip, sizeof(INPUT)); 1170 } 1171 #endif 1172 1173 return 0; 1174 } 1175 1176 // -----------------------------------------------------------------------