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.

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