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.

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