Collection of DPF-based plugins for packaging
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.

666 lines
19KB

  1. /*
  2. * DISTRHO Plugin Framework (DPF)
  3. * Copyright (C) 2012-2018 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. #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EMBED_UI
  18. # undef DISTRHO_PLUGIN_HAS_UI
  19. # define DISTRHO_PLUGIN_HAS_UI 0
  20. #endif
  21. #if DISTRHO_PLUGIN_HAS_UI
  22. # include "DistrhoUIInternal.hpp"
  23. #else
  24. # include "../extra/Sleep.hpp"
  25. #endif
  26. #include "jack/jack.h"
  27. #include "jack/midiport.h"
  28. #include "jack/transport.h"
  29. #ifndef DISTRHO_OS_WINDOWS
  30. # include <signal.h>
  31. #endif
  32. // -----------------------------------------------------------------------
  33. START_NAMESPACE_DISTRHO
  34. #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_STATE
  35. static const setStateFunc setStateCallback = nullptr;
  36. #endif
  37. #if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  38. static const writeMidiFunc writeMidiCallback = nullptr;
  39. #endif
  40. // -----------------------------------------------------------------------
  41. static volatile bool gCloseSignalReceived = false;
  42. #ifdef DISTRHO_OS_WINDOWS
  43. static BOOL WINAPI winSignalHandler(DWORD dwCtrlType) noexcept
  44. {
  45. if (dwCtrlType == CTRL_C_EVENT)
  46. {
  47. gCloseSignalReceived = true;
  48. return TRUE;
  49. }
  50. return FALSE;
  51. }
  52. static void initSignalHandler()
  53. {
  54. SetConsoleCtrlHandler(winSignalHandler, TRUE);
  55. }
  56. #else
  57. static void closeSignalHandler(int) noexcept
  58. {
  59. gCloseSignalReceived = true;
  60. }
  61. static void initSignalHandler()
  62. {
  63. struct sigaction sig;
  64. memset(&sig, 0, sizeof(sig));
  65. sig.sa_handler = closeSignalHandler;
  66. sig.sa_flags = SA_RESTART;
  67. sigemptyset(&sig.sa_mask);
  68. sigaction(SIGINT, &sig, nullptr);
  69. sigaction(SIGTERM, &sig, nullptr);
  70. }
  71. #endif
  72. // -----------------------------------------------------------------------
  73. #if DISTRHO_PLUGIN_HAS_UI
  74. class PluginJack : public IdleCallback
  75. #else
  76. class PluginJack
  77. #endif
  78. {
  79. public:
  80. PluginJack(jack_client_t* const client)
  81. : fPlugin(this, writeMidiCallback),
  82. #if DISTRHO_PLUGIN_HAS_UI
  83. fUI(this, 0, nullptr, setParameterValueCallback, setStateCallback, nullptr, setSizeCallback, fPlugin.getInstancePointer()),
  84. #endif
  85. fClient(client)
  86. {
  87. #if DISTRHO_PLUGIN_NUM_INPUTS > 0 || DISTRHO_PLUGIN_NUM_OUTPUTS > 0
  88. char strBuf[0xff+1];
  89. strBuf[0xff] = '\0';
  90. # if DISTRHO_PLUGIN_NUM_INPUTS > 0
  91. for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  92. {
  93. std::snprintf(strBuf, 0xff, "in%i", i+1);
  94. fPortAudioIns[i] = jack_port_register(fClient, strBuf, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
  95. }
  96. # endif
  97. # if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
  98. for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
  99. {
  100. std::snprintf(strBuf, 0xff, "out%i", i+1);
  101. fPortAudioOuts[i] = jack_port_register(fClient, strBuf, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
  102. }
  103. # endif
  104. #endif
  105. fPortEventsIn = jack_port_register(fClient, "events-in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
  106. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  107. fPortMidiOut = jack_port_register(fClient, "midi-out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);
  108. fPortMidiOutBuffer = nullptr;
  109. #endif
  110. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  111. if (fPlugin.getProgramCount() > 0)
  112. {
  113. fPlugin.loadProgram(0);
  114. # if DISTRHO_PLUGIN_HAS_UI
  115. fUI.programLoaded(0);
  116. # endif
  117. }
  118. # if DISTRHO_PLUGIN_HAS_UI
  119. fProgramChanged = -1;
  120. # endif
  121. #endif
  122. if (const uint32_t count = fPlugin.getParameterCount())
  123. {
  124. fLastOutputValues = new float[count];
  125. #if DISTRHO_PLUGIN_HAS_UI
  126. fParametersChanged = new bool[count];
  127. std::memset(fParametersChanged, 0, sizeof(bool)*count);
  128. #endif
  129. for (uint32_t i=0; i < count; ++i)
  130. {
  131. if (fPlugin.isParameterOutput(i))
  132. {
  133. fLastOutputValues[i] = fPlugin.getParameterValue(i);
  134. }
  135. else
  136. {
  137. fLastOutputValues[i] = 0.0f;
  138. #if DISTRHO_PLUGIN_HAS_UI
  139. fUI.parameterChanged(i, fPlugin.getParameterValue(i));
  140. #endif
  141. }
  142. }
  143. }
  144. else
  145. {
  146. fLastOutputValues = nullptr;
  147. #if DISTRHO_PLUGIN_HAS_UI
  148. fParametersChanged = nullptr;
  149. #endif
  150. }
  151. jack_set_buffer_size_callback(fClient, jackBufferSizeCallback, this);
  152. jack_set_sample_rate_callback(fClient, jackSampleRateCallback, this);
  153. jack_set_process_callback(fClient, jackProcessCallback, this);
  154. jack_on_shutdown(fClient, jackShutdownCallback, this);
  155. fPlugin.activate();
  156. jack_activate(fClient);
  157. #if DISTRHO_PLUGIN_HAS_UI
  158. if (const char* const name = jack_get_client_name(fClient))
  159. fUI.setWindowTitle(name);
  160. else
  161. fUI.setWindowTitle(fPlugin.getName());
  162. fUI.exec(this);
  163. #else
  164. while (! gCloseSignalReceived)
  165. d_sleep(1);
  166. #endif
  167. }
  168. ~PluginJack()
  169. {
  170. if (fClient != nullptr)
  171. jack_deactivate(fClient);
  172. if (fLastOutputValues != nullptr)
  173. {
  174. delete[] fLastOutputValues;
  175. fLastOutputValues = nullptr;
  176. }
  177. #if DISTRHO_PLUGIN_HAS_UI
  178. if (fParametersChanged != nullptr)
  179. {
  180. delete[] fParametersChanged;
  181. fParametersChanged = nullptr;
  182. }
  183. #endif
  184. fPlugin.deactivate();
  185. if (fClient == nullptr)
  186. return;
  187. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  188. jack_port_unregister(fClient, fPortMidiOut);
  189. fPortMidiOut = nullptr;
  190. #endif
  191. jack_port_unregister(fClient, fPortEventsIn);
  192. fPortEventsIn = nullptr;
  193. #if DISTRHO_PLUGIN_NUM_INPUTS > 0
  194. for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  195. {
  196. jack_port_unregister(fClient, fPortAudioIns[i]);
  197. fPortAudioIns[i] = nullptr;
  198. }
  199. #endif
  200. #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
  201. for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
  202. {
  203. jack_port_unregister(fClient, fPortAudioOuts[i]);
  204. fPortAudioOuts[i] = nullptr;
  205. }
  206. #endif
  207. jack_client_close(fClient);
  208. }
  209. // -------------------------------------------------------------------
  210. protected:
  211. #if DISTRHO_PLUGIN_HAS_UI
  212. void idleCallback() override
  213. {
  214. if (gCloseSignalReceived)
  215. return fUI.quit();
  216. # if DISTRHO_PLUGIN_WANT_PROGRAMS
  217. if (fProgramChanged >= 0)
  218. {
  219. fUI.programLoaded(fProgramChanged);
  220. fProgramChanged = -1;
  221. }
  222. # endif
  223. for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i)
  224. {
  225. if (fPlugin.isParameterOutput(i))
  226. {
  227. const float value = fPlugin.getParameterValue(i);
  228. if (d_isEqual(fLastOutputValues[i], value))
  229. continue;
  230. fLastOutputValues[i] = value;
  231. fUI.parameterChanged(i, value);
  232. }
  233. else if (fParametersChanged[i])
  234. {
  235. fParametersChanged[i] = false;
  236. fUI.parameterChanged(i, fPlugin.getParameterValue(i));
  237. }
  238. }
  239. fUI.exec_idle();
  240. }
  241. #endif
  242. void jackBufferSize(const jack_nframes_t nframes)
  243. {
  244. fPlugin.setBufferSize(nframes, true);
  245. }
  246. void jackSampleRate(const jack_nframes_t nframes)
  247. {
  248. fPlugin.setSampleRate(nframes, true);
  249. }
  250. void jackProcess(const jack_nframes_t nframes)
  251. {
  252. #if DISTRHO_PLUGIN_NUM_INPUTS > 0
  253. const float* audioIns[DISTRHO_PLUGIN_NUM_INPUTS];
  254. for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  255. audioIns[i] = (const float*)jack_port_get_buffer(fPortAudioIns[i], nframes);
  256. #else
  257. static const float** audioIns = nullptr;
  258. #endif
  259. #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
  260. float* audioOuts[DISTRHO_PLUGIN_NUM_OUTPUTS];
  261. for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
  262. audioOuts[i] = (float*)jack_port_get_buffer(fPortAudioOuts[i], nframes);
  263. #else
  264. static float** audioOuts = nullptr;
  265. #endif
  266. #if DISTRHO_PLUGIN_WANT_TIMEPOS
  267. jack_position_t pos;
  268. fTimePosition.playing = (jack_transport_query(fClient, &pos) == JackTransportRolling);
  269. if (pos.unique_1 == pos.unique_2)
  270. {
  271. fTimePosition.frame = pos.frame;
  272. if (pos.valid & JackTransportBBT)
  273. {
  274. fTimePosition.bbt.valid = true;
  275. fTimePosition.bbt.bar = pos.bar;
  276. fTimePosition.bbt.beat = pos.beat;
  277. fTimePosition.bbt.tick = pos.tick;
  278. fTimePosition.bbt.barStartTick = pos.bar_start_tick;
  279. fTimePosition.bbt.beatsPerBar = pos.beats_per_bar;
  280. fTimePosition.bbt.beatType = pos.beat_type;
  281. fTimePosition.bbt.ticksPerBeat = pos.ticks_per_beat;
  282. fTimePosition.bbt.beatsPerMinute = pos.beats_per_minute;
  283. }
  284. else
  285. fTimePosition.bbt.valid = false;
  286. }
  287. else
  288. {
  289. fTimePosition.bbt.valid = false;
  290. fTimePosition.frame = 0;
  291. }
  292. fPlugin.setTimePosition(fTimePosition);
  293. #endif
  294. void* const midiBuf = jack_port_get_buffer(fPortEventsIn, nframes);
  295. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  296. fPortMidiOutBuffer = jack_port_get_buffer(fPortMidiOut, nframes);
  297. jack_midi_clear_buffer(fPortMidiOutBuffer);
  298. #endif
  299. if (const uint32_t eventCount = jack_midi_get_event_count(midiBuf))
  300. {
  301. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  302. uint32_t midiEventCount = 0;
  303. MidiEvent midiEvents[eventCount];
  304. #endif
  305. jack_midi_event_t jevent;
  306. for (uint32_t i=0; i < eventCount; ++i)
  307. {
  308. if (jack_midi_event_get(&jevent, midiBuf, i) != 0)
  309. break;
  310. // Check if message is control change on channel 1
  311. if (jevent.buffer[0] == 0xB0 && jevent.size == 3)
  312. {
  313. const uint8_t control = jevent.buffer[1];
  314. const uint8_t value = jevent.buffer[2];
  315. /* NOTE: This is not optimal, we're iterating all parameters on every CC message.
  316. Since the JACK standalone is more of a test tool, this will do for now. */
  317. for (uint32_t j=0, paramCount=fPlugin.getParameterCount(); j < paramCount; ++j)
  318. {
  319. if (fPlugin.isParameterOutput(j))
  320. continue;
  321. if (fPlugin.getParameterMidiCC(j) != control)
  322. continue;
  323. const float scaled = static_cast<float>(value)/127.0f;
  324. const float fvalue = fPlugin.getParameterRanges(j).getUnnormalizedValue(scaled);
  325. fPlugin.setParameterValue(j, fvalue);
  326. #if DISTRHO_PLUGIN_HAS_UI
  327. fParametersChanged[j] = true;
  328. #endif
  329. break;
  330. }
  331. }
  332. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  333. // Check if message is program change on channel 1
  334. else if (jevent.buffer[0] == 0xC0 && jevent.size == 2)
  335. {
  336. const uint8_t program = jevent.buffer[1];
  337. if (program < fPlugin.getProgramCount())
  338. {
  339. fPlugin.loadProgram(program);
  340. # if DISTRHO_PLUGIN_HAS_UI
  341. fProgramChanged = program;
  342. # endif
  343. }
  344. }
  345. #endif
  346. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  347. MidiEvent& midiEvent(midiEvents[midiEventCount++]);
  348. midiEvent.frame = jevent.time;
  349. midiEvent.size = jevent.size;
  350. if (midiEvent.size > MidiEvent::kDataSize)
  351. midiEvent.dataExt = jevent.buffer;
  352. else
  353. std::memcpy(midiEvent.data, jevent.buffer, midiEvent.size);
  354. #endif
  355. }
  356. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  357. fPlugin.run(audioIns, audioOuts, nframes, midiEvents, midiEventCount);
  358. #endif
  359. }
  360. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  361. else
  362. {
  363. fPlugin.run(audioIns, audioOuts, nframes, nullptr, 0);
  364. }
  365. #else
  366. fPlugin.run(audioIns, audioOuts, nframes);
  367. #endif
  368. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  369. fPortMidiOutBuffer = nullptr;
  370. #endif
  371. updateParameterTriggers();
  372. }
  373. void jackShutdown()
  374. {
  375. d_stderr("jack has shutdown, quitting now...");
  376. fClient = nullptr;
  377. #if DISTRHO_PLUGIN_HAS_UI
  378. fUI.quit();
  379. #endif
  380. }
  381. // -------------------------------------------------------------------
  382. void setParameterValue(const uint32_t index, const float value)
  383. {
  384. fPlugin.setParameterValue(index, value);
  385. }
  386. #if DISTRHO_PLUGIN_WANT_STATE
  387. void setState(const char* const key, const char* const value)
  388. {
  389. fPlugin.setState(key, value);
  390. }
  391. #endif
  392. #if DISTRHO_PLUGIN_HAS_UI
  393. void setSize(const uint width, const uint height)
  394. {
  395. fUI.setWindowSize(width, height);
  396. }
  397. #endif
  398. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  399. bool writeMidi(const MidiEvent& midiEvent)
  400. {
  401. DISTRHO_SAFE_ASSERT_RETURN(fPortMidiOutBuffer != nullptr, false);
  402. return jack_midi_event_write(fPortMidiOutBuffer,
  403. midiEvent.frame,
  404. midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data,
  405. midiEvent.size) == 0;
  406. }
  407. #endif
  408. // NOTE: no trigger support for JACK, simulate it here
  409. void updateParameterTriggers()
  410. {
  411. float defValue;
  412. for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i)
  413. {
  414. if ((fPlugin.getParameterHints(i) & kParameterIsTrigger) != kParameterIsTrigger)
  415. continue;
  416. defValue = fPlugin.getParameterRanges(i).def;
  417. if (d_isNotEqual(defValue, fPlugin.getParameterValue(i)))
  418. fPlugin.setParameterValue(i, defValue);
  419. }
  420. }
  421. // -------------------------------------------------------------------
  422. private:
  423. PluginExporter fPlugin;
  424. #if DISTRHO_PLUGIN_HAS_UI
  425. UIExporter fUI;
  426. #endif
  427. jack_client_t* fClient;
  428. #if DISTRHO_PLUGIN_NUM_INPUTS > 0
  429. jack_port_t* fPortAudioIns[DISTRHO_PLUGIN_NUM_INPUTS];
  430. #endif
  431. #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
  432. jack_port_t* fPortAudioOuts[DISTRHO_PLUGIN_NUM_OUTPUTS];
  433. #endif
  434. jack_port_t* fPortEventsIn;
  435. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  436. jack_port_t* fPortMidiOut;
  437. void* fPortMidiOutBuffer;
  438. #endif
  439. #if DISTRHO_PLUGIN_WANT_TIMEPOS
  440. TimePosition fTimePosition;
  441. #endif
  442. // Temporary data
  443. float* fLastOutputValues;
  444. #if DISTRHO_PLUGIN_HAS_UI
  445. // Store DSP changes to send to UI
  446. bool* fParametersChanged;
  447. # if DISTRHO_PLUGIN_WANT_PROGRAMS
  448. int fProgramChanged;
  449. # endif
  450. #endif
  451. // -------------------------------------------------------------------
  452. // Callbacks
  453. #define thisPtr ((PluginJack*)ptr)
  454. static int jackBufferSizeCallback(jack_nframes_t nframes, void* ptr)
  455. {
  456. thisPtr->jackBufferSize(nframes);
  457. return 0;
  458. }
  459. static int jackSampleRateCallback(jack_nframes_t nframes, void* ptr)
  460. {
  461. thisPtr->jackSampleRate(nframes);
  462. return 0;
  463. }
  464. static int jackProcessCallback(jack_nframes_t nframes, void* ptr)
  465. {
  466. thisPtr->jackProcess(nframes);
  467. return 0;
  468. }
  469. static void jackShutdownCallback(void* ptr)
  470. {
  471. thisPtr->jackShutdown();
  472. }
  473. static void setParameterValueCallback(void* ptr, uint32_t index, float value)
  474. {
  475. thisPtr->setParameterValue(index, value);
  476. }
  477. #if DISTRHO_PLUGIN_WANT_STATE
  478. static void setStateCallback(void* ptr, const char* key, const char* value)
  479. {
  480. thisPtr->setState(key, value);
  481. }
  482. #endif
  483. #if DISTRHO_PLUGIN_HAS_UI
  484. static void setSizeCallback(void* ptr, uint width, uint height)
  485. {
  486. thisPtr->setSize(width, height);
  487. }
  488. #endif
  489. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  490. static bool writeMidiCallback(void* ptr, const MidiEvent& midiEvent)
  491. {
  492. return thisPtr->writeMidi(midiEvent);
  493. }
  494. #endif
  495. #undef thisPtr
  496. };
  497. END_NAMESPACE_DISTRHO
  498. // -----------------------------------------------------------------------
  499. int main()
  500. {
  501. USE_NAMESPACE_DISTRHO;
  502. jack_status_t status = jack_status_t(0x0);
  503. jack_client_t* client = jack_client_open(DISTRHO_PLUGIN_NAME, JackNoStartServer, &status);
  504. if (client == nullptr)
  505. {
  506. String errorString;
  507. if (status & JackFailure)
  508. errorString += "Overall operation failed;\n";
  509. if (status & JackInvalidOption)
  510. errorString += "The operation contained an invalid or unsupported option;\n";
  511. if (status & JackNameNotUnique)
  512. errorString += "The desired client name was not unique;\n";
  513. if (status & JackServerStarted)
  514. errorString += "The JACK server was started as a result of this operation;\n";
  515. if (status & JackServerFailed)
  516. errorString += "Unable to connect to the JACK server;\n";
  517. if (status & JackServerError)
  518. errorString += "Communication error with the JACK server;\n";
  519. if (status & JackNoSuchClient)
  520. errorString += "Requested client does not exist;\n";
  521. if (status & JackLoadFailure)
  522. errorString += "Unable to load internal client;\n";
  523. if (status & JackInitFailure)
  524. errorString += "Unable to initialize client;\n";
  525. if (status & JackShmFailure)
  526. errorString += "Unable to access shared memory;\n";
  527. if (status & JackVersionError)
  528. errorString += "Client's protocol version does not match;\n";
  529. if (status & JackBackendError)
  530. errorString += "Backend Error;\n";
  531. if (status & JackClientZombie)
  532. errorString += "Client is being shutdown against its will;\n";
  533. if (errorString.isNotEmpty())
  534. {
  535. errorString[errorString.length()-2] = '.';
  536. d_stderr("Failed to create jack client, reason was:\n%s", errorString.buffer());
  537. }
  538. else
  539. d_stderr("Failed to create jack client, cannot continue!");
  540. return 1;
  541. }
  542. USE_NAMESPACE_DISTRHO;
  543. initSignalHandler();
  544. d_lastBufferSize = jack_get_buffer_size(client);
  545. d_lastSampleRate = jack_get_sample_rate(client);
  546. #if DISTRHO_PLUGIN_HAS_UI
  547. d_lastUiSampleRate = d_lastSampleRate;
  548. #endif
  549. const PluginJack p(client);
  550. return 0;
  551. }
  552. // -----------------------------------------------------------------------