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.

1168 lines
36KB

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