DISTRHO Plugin Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1187 lines
37KB

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