Audio plugin host https://kx.studio/carla
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.

567 lines
18KB

  1. /*
  2. * Carla Native Plugins
  3. * Copyright (C) 2012-2020 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 General Public License as
  7. * published by the Free Software Foundation; either version 2 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the doc/GPL.txt file.
  16. */
  17. #include "CarlaNativeExtUI.hpp"
  18. #include "RtLinkedList.hpp"
  19. #include "midi-base.hpp"
  20. #include "midi-queue.hpp"
  21. // matches UI side
  22. #define TICKS_PER_BEAT 48
  23. // -----------------------------------------------------------------------
  24. class MidiPatternPlugin : public NativePluginAndUiClass,
  25. public AbstractMidiPlayer
  26. {
  27. public:
  28. enum Parameters {
  29. kParameterTimeSig = 0,
  30. kParameterMeasures,
  31. kParameterDefLength,
  32. kParameterQuantize,
  33. kParameterCount
  34. };
  35. MidiPatternPlugin(const NativeHostDescriptor* const host)
  36. : NativePluginAndUiClass(host, "midipattern-ui"),
  37. fNeedsAllNotesOff(false),
  38. fWasPlayingBefore(false),
  39. fTimeSigNum(4),
  40. fLastPosition(0.0f),
  41. fLastFrame(0),
  42. fTicksPerFrame(0.0),
  43. fMaxTicks(0.0),
  44. fMidiOut(this),
  45. fTimeInfo(),
  46. fMidiQueue(),
  47. fMidiQueueRT()
  48. {
  49. carla_zeroStruct(fTimeInfo);
  50. // set default param values
  51. fParameters[kParameterTimeSig] = 3.0f;
  52. fParameters[kParameterMeasures] = 4.0f;
  53. fParameters[kParameterDefLength] = 4.0f;
  54. fParameters[kParameterQuantize] = 4.0f;
  55. fMaxTicks = TICKS_PER_BEAT * fTimeSigNum * 4 /* kParameterMeasures */;
  56. }
  57. protected:
  58. // -------------------------------------------------------------------
  59. // Plugin parameter calls
  60. uint32_t getParameterCount() const override
  61. {
  62. return kParameterCount;
  63. }
  64. const NativeParameter* getParameterInfo(const uint32_t index) const override
  65. {
  66. CARLA_SAFE_ASSERT_RETURN(index < kParameterCount, nullptr);
  67. static NativeParameter param;
  68. static NativeParameterScalePoint scalePoints[10];
  69. int hints = NATIVE_PARAMETER_IS_ENABLED|NATIVE_PARAMETER_IS_AUTOMABLE|NATIVE_PARAMETER_IS_INTEGER;
  70. switch (index)
  71. {
  72. case 0:
  73. hints |= NATIVE_PARAMETER_USES_SCALEPOINTS;
  74. param.name = "Time Signature";
  75. param.ranges.def = 3.0f;
  76. param.ranges.min = 0.0f;
  77. param.ranges.max = 5.0f;
  78. scalePoints[0].value = 0.0f;
  79. scalePoints[0].label = "1/4";
  80. scalePoints[1].value = 1.0f;
  81. scalePoints[1].label = "2/4";
  82. scalePoints[2].value = 2.0f;
  83. scalePoints[2].label = "3/4";
  84. scalePoints[3].value = 3.0f;
  85. scalePoints[3].label = "4/4";
  86. scalePoints[4].value = 4.0f;
  87. scalePoints[4].label = "5/4";
  88. scalePoints[5].value = 5.0f;
  89. scalePoints[5].label = "6/4";
  90. param.scalePointCount = 6;
  91. param.scalePoints = scalePoints;
  92. break;
  93. case 1:
  94. param.name = "Measures";
  95. param.ranges.def = 4.0f;
  96. param.ranges.min = 1.0f;
  97. param.ranges.max = 16.0f;
  98. break;
  99. case 2:
  100. hints |= NATIVE_PARAMETER_USES_SCALEPOINTS;
  101. param.name = "Default Length";
  102. param.ranges.def = 4.0f;
  103. param.ranges.min = 0.0f;
  104. param.ranges.max = 9.0f;
  105. scalePoints[0].value = 0.0f;
  106. scalePoints[0].label = "1/16";
  107. scalePoints[1].value = 1.0f;
  108. scalePoints[1].label = "1/15";
  109. scalePoints[2].value = 2.0f;
  110. scalePoints[2].label = "1/12";
  111. scalePoints[3].value = 3.0f;
  112. scalePoints[3].label = "1/9";
  113. scalePoints[4].value = 4.0f;
  114. scalePoints[4].label = "1/8";
  115. scalePoints[5].value = 5.0f;
  116. scalePoints[5].label = "1/6";
  117. scalePoints[6].value = 6.0f;
  118. scalePoints[6].label = "1/4";
  119. scalePoints[7].value = 7.0f;
  120. scalePoints[7].label = "1/3";
  121. scalePoints[8].value = 8.0f;
  122. scalePoints[8].label = "1/2";
  123. scalePoints[9].value = 9.0f;
  124. scalePoints[9].label = "1";
  125. param.scalePointCount = 10;
  126. param.scalePoints = scalePoints;
  127. break;
  128. case 3:
  129. hints |= NATIVE_PARAMETER_USES_SCALEPOINTS;
  130. param.name = "Quantize";
  131. param.ranges.def = 4.0f;
  132. param.ranges.min = 0.0f;
  133. param.ranges.max = 9.0f;
  134. scalePoints[0].value = 0.0f;
  135. scalePoints[0].label = "1/16";
  136. scalePoints[1].value = 1.0f;
  137. scalePoints[1].label = "1/15";
  138. scalePoints[2].value = 2.0f;
  139. scalePoints[2].label = "1/12";
  140. scalePoints[3].value = 3.0f;
  141. scalePoints[3].label = "1/9";
  142. scalePoints[4].value = 4.0f;
  143. scalePoints[4].label = "1/8";
  144. scalePoints[5].value = 5.0f;
  145. scalePoints[5].label = "1/6";
  146. scalePoints[6].value = 6.0f;
  147. scalePoints[6].label = "1/4";
  148. scalePoints[7].value = 7.0f;
  149. scalePoints[7].label = "1/3";
  150. scalePoints[8].value = 8.0f;
  151. scalePoints[8].label = "1/2";
  152. scalePoints[9].value = 9.0f;
  153. scalePoints[9].label = "1";
  154. param.scalePointCount = 10;
  155. param.scalePoints = scalePoints;
  156. break;
  157. }
  158. param.hints = static_cast<NativeParameterHints>(hints);
  159. return &param;
  160. }
  161. float getParameterValue(const uint32_t index) const override
  162. {
  163. CARLA_SAFE_ASSERT_RETURN(index < kParameterCount, 0.0f);
  164. return fParameters[index];
  165. }
  166. // -------------------------------------------------------------------
  167. // Plugin state calls
  168. void setParameterValue(const uint32_t index, const float value) override
  169. {
  170. CARLA_SAFE_ASSERT_RETURN(index < kParameterCount,);
  171. fParameters[index] = value;
  172. switch (index)
  173. {
  174. case kParameterTimeSig:
  175. fTimeSigNum = static_cast<int>(value + 1.5f);
  176. // fall through
  177. case kParameterMeasures:
  178. fMaxTicks = TICKS_PER_BEAT * fTimeSigNum * static_cast<double>(fParameters[kParameterMeasures]);
  179. break;
  180. }
  181. }
  182. // -------------------------------------------------------------------
  183. // Plugin process calls
  184. void process(const float* const*, float**, const uint32_t frames,
  185. const NativeMidiEvent* /*midiEvents*/, uint32_t /*midiEventCount*/) override
  186. {
  187. if (const NativeTimeInfo* const timeInfo = getTimeInfo())
  188. fTimeInfo = *timeInfo;
  189. if (fWasPlayingBefore != fTimeInfo.playing)
  190. {
  191. fLastFrame = 0;
  192. fLastPosition = 0.0f;
  193. fNeedsAllNotesOff = true;
  194. fWasPlayingBefore = fTimeInfo.playing;
  195. }
  196. if (fNeedsAllNotesOff)
  197. {
  198. NativeMidiEvent midiEvent;
  199. midiEvent.port = 0;
  200. midiEvent.time = 0;
  201. midiEvent.data[0] = 0;
  202. midiEvent.data[1] = MIDI_CONTROL_ALL_NOTES_OFF;
  203. midiEvent.data[2] = 0;
  204. midiEvent.data[3] = 0;
  205. midiEvent.size = 3;
  206. for (int channel=MAX_MIDI_CHANNELS; --channel >= 0;)
  207. {
  208. midiEvent.data[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (channel & MIDI_CHANNEL_BIT));
  209. NativePluginAndUiClass::writeMidiEvent(&midiEvent);
  210. }
  211. fNeedsAllNotesOff = false;
  212. }
  213. if (fMidiQueue.isNotEmpty() && fMidiQueueRT.tryToCopyDataFrom(fMidiQueue))
  214. {
  215. uint8_t d1, d2, d3;
  216. NativeMidiEvent ev = { 0, 0, 3, { 0, 0, 0, 0 } };
  217. while (fMidiQueueRT.get(d1, d2, d3))
  218. {
  219. ev.data[0] = d1;
  220. ev.data[1] = d2;
  221. ev.data[2] = d3;
  222. NativePluginAndUiClass::writeMidiEvent(&ev);
  223. }
  224. }
  225. if (fTimeInfo.playing)
  226. {
  227. if (! fTimeInfo.bbt.valid)
  228. fTimeInfo.bbt.beatsPerMinute = 120.0;
  229. fTicksPerFrame = TICKS_PER_BEAT / (60.0 / fTimeInfo.bbt.beatsPerMinute * getSampleRate());
  230. double playPos;
  231. if (fLastFrame + frames == fTimeInfo.frame)
  232. {
  233. // continuous playback
  234. playPos = static_cast<double>(fLastPosition) + fTicksPerFrame * static_cast<double>(frames);
  235. }
  236. else
  237. {
  238. // non-continuous, reset playPos
  239. playPos = fTicksPerFrame * static_cast<double>(fTimeInfo.frame);
  240. }
  241. const double endPos = playPos + fTicksPerFrame * static_cast<double>(frames);
  242. const double loopedEndPos = std::fmod(endPos, fMaxTicks);
  243. for (; playPos < endPos; playPos += fMaxTicks)
  244. {
  245. const double loopedPlayPos = std::fmod(playPos, fMaxTicks);
  246. if (loopedEndPos >= loopedPlayPos)
  247. {
  248. if (! fMidiOut.play(loopedPlayPos, loopedEndPos-loopedPlayPos))
  249. fNeedsAllNotesOff = true;
  250. }
  251. else
  252. {
  253. const double diff = fMaxTicks - loopedPlayPos;
  254. if (! (fMidiOut.play(loopedPlayPos, diff) && fMidiOut.play(0.0, loopedEndPos, diff)))
  255. fNeedsAllNotesOff = true;
  256. }
  257. }
  258. fLastFrame = fTimeInfo.frame;
  259. fLastPosition = static_cast<float>(playPos);
  260. }
  261. }
  262. // -------------------------------------------------------------------
  263. // Plugin UI calls
  264. void uiShow(const bool show) override
  265. {
  266. NativePluginAndUiClass::uiShow(show);
  267. if (show)
  268. _sendEventsToUI();
  269. }
  270. void uiIdle() override
  271. {
  272. NativePluginAndUiClass::uiIdle();
  273. // send transport
  274. if (isPipeRunning())
  275. {
  276. char strBuf[0xff+1];
  277. carla_zeroChars(strBuf, 0xff+1);
  278. const double beatsPerBar = fTimeSigNum;
  279. const double beatsPerMinute = fTimeInfo.bbt.valid ? fTimeInfo.bbt.beatsPerMinute : 120.0;
  280. const double ticksPerBeat = TICKS_PER_BEAT;
  281. const double fullTicks = fLastPosition;
  282. const double fullBeats = fullTicks / ticksPerBeat;
  283. const uint32_t tick = static_cast<uint32_t>(std::floor(std::fmod(fullTicks, ticksPerBeat)));
  284. const uint32_t beat = static_cast<uint32_t>(std::floor(std::fmod(fullBeats, beatsPerBar)));
  285. const uint32_t bar = static_cast<uint32_t>(std::floor(fullBeats/beatsPerBar));
  286. const CarlaMutexLocker cml(getPipeLock());
  287. CARLA_SAFE_ASSERT_RETURN(writeMessage("transport\n"),);
  288. std::snprintf(strBuf, 0xff, "%i:" P_UINT64 ":%i:%i:%i\n", int(fTimeInfo.playing), fTimeInfo.frame, bar, beat, tick);
  289. CARLA_SAFE_ASSERT_RETURN(writeMessage(strBuf),);
  290. {
  291. const CarlaScopedLocale csl;
  292. std::snprintf(strBuf, 0xff, "%.12g\n", beatsPerMinute);
  293. }
  294. CARLA_SAFE_ASSERT_RETURN(writeMessage(strBuf),);
  295. flushMessages();
  296. }
  297. }
  298. // -------------------------------------------------------------------
  299. // Plugin state calls
  300. char* getState() const override
  301. {
  302. return fMidiOut.getState();
  303. }
  304. void setState(const char* const data) override
  305. {
  306. fMidiOut.setState(data);
  307. if (isPipeRunning())
  308. _sendEventsToUI();
  309. }
  310. // -------------------------------------------------------------------
  311. // AbstractMidiPlayer calls
  312. void writeMidiEvent(const uint8_t port, const long double timePosFrame, const RawMidiEvent* const event) override
  313. {
  314. NativeMidiEvent midiEvent;
  315. midiEvent.port = port;
  316. midiEvent.time = uint32_t(timePosFrame/fTicksPerFrame);
  317. midiEvent.data[0] = event->data[0];
  318. midiEvent.data[1] = event->data[1];
  319. midiEvent.data[2] = event->data[2];
  320. midiEvent.data[3] = event->data[3];
  321. midiEvent.size = event->size;
  322. #ifdef DEBUG
  323. carla_stdout("Playing at %f|%u :: %03X:%03i:%03i",
  324. midiEvent.time,
  325. static_cast<double>(midiEvent.time)*fTicksPerFrame,
  326. midiEvent.data[0], midiEvent.data[1], midiEvent.data[2]);
  327. #endif
  328. NativePluginAndUiClass::writeMidiEvent(&midiEvent);
  329. }
  330. // -------------------------------------------------------------------
  331. // Pipe Server calls
  332. bool msgReceived(const char* const msg) noexcept override
  333. {
  334. if (NativePluginAndUiClass::msgReceived(msg))
  335. return true;
  336. if (std::strcmp(msg, "midi-clear-all") == 0)
  337. {
  338. fMidiOut.clear();
  339. fNeedsAllNotesOff = true;
  340. return true;
  341. }
  342. if (std::strcmp(msg, "midi-note") == 0)
  343. {
  344. uint8_t note;
  345. bool on;
  346. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(note), true);
  347. CARLA_SAFE_ASSERT_RETURN(readNextLineAsBool(on), true);
  348. const uint8_t status = on ? MIDI_STATUS_NOTE_ON : MIDI_STATUS_NOTE_OFF;
  349. const uint8_t velocity = on ? 100 : 0;
  350. const CarlaMutexLocker cml(fMidiQueue.getMutex());
  351. fMidiQueue.put(status, note, velocity);
  352. return true;
  353. }
  354. if (std::strcmp(msg, "midievent-add") == 0)
  355. {
  356. uint64_t time;
  357. uint8_t size;
  358. CARLA_SAFE_ASSERT_RETURN(readNextLineAsULong(time), true);
  359. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(size), true);
  360. CARLA_SAFE_ASSERT_RETURN(size > 0, true);
  361. uint8_t data[size], dvalue;
  362. for (uint8_t i=0; i<size; ++i)
  363. {
  364. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(dvalue), true);
  365. data[i] = dvalue;
  366. }
  367. fMidiOut.addRaw(time, data, size);
  368. return true;
  369. }
  370. if (std::strcmp(msg, "midievent-remove") == 0)
  371. {
  372. uint64_t time;
  373. uint8_t size;
  374. CARLA_SAFE_ASSERT_RETURN(readNextLineAsULong(time), true);
  375. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(size), true);
  376. CARLA_SAFE_ASSERT_RETURN(size > 0, true);
  377. uint8_t data[size], dvalue;
  378. for (uint8_t i=0; i<size; ++i)
  379. {
  380. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(dvalue), true);
  381. data[i] = dvalue;
  382. }
  383. fMidiOut.removeRaw(time, data, size);
  384. return true;
  385. }
  386. return false;
  387. }
  388. // -------------------------------------------------------------------
  389. private:
  390. bool fNeedsAllNotesOff;
  391. bool fWasPlayingBefore;
  392. int fTimeSigNum;
  393. float fLastPosition;
  394. uint64_t fLastFrame;
  395. double fTicksPerFrame;
  396. double fMaxTicks;
  397. MidiPattern fMidiOut;
  398. NativeTimeInfo fTimeInfo;
  399. MIDIEventQueue<32> fMidiQueue, fMidiQueueRT;
  400. float fParameters[kParameterCount];
  401. void _sendEventsToUI() const noexcept
  402. {
  403. char strBuf[0xff+1];
  404. carla_zeroChars(strBuf, 0xff);
  405. const CarlaMutexLocker cml1(getPipeLock());
  406. const CarlaMutexLocker cml2(fMidiOut.getWriteMutex());
  407. writeMessage("midi-clear-all\n", 15);
  408. writeMessage("parameters\n", 11);
  409. std::snprintf(strBuf, 0xff, "%i:%i:%i:%i\n",
  410. static_cast<int>(fParameters[kParameterTimeSig]),
  411. static_cast<int>(fParameters[kParameterMeasures]),
  412. static_cast<int>(fParameters[kParameterDefLength]),
  413. static_cast<int>(fParameters[kParameterQuantize]));
  414. writeMessage(strBuf);
  415. for (LinkedList<const RawMidiEvent*>::Itenerator it = fMidiOut.iteneratorBegin(); it.valid(); it.next())
  416. {
  417. const RawMidiEvent* const rawMidiEvent(it.getValue(nullptr));
  418. CARLA_SAFE_ASSERT_CONTINUE(rawMidiEvent != nullptr);
  419. writeMessage("midievent-add\n", 14);
  420. std::snprintf(strBuf, 0xff, P_INT64 "\n", rawMidiEvent->time);
  421. writeMessage(strBuf);
  422. std::snprintf(strBuf, 0xff, "%i\n", rawMidiEvent->size);
  423. writeMessage(strBuf);
  424. for (uint8_t i=0, size=rawMidiEvent->size; i<size; ++i)
  425. {
  426. std::snprintf(strBuf, 0xff, "%i\n", rawMidiEvent->data[i]);
  427. writeMessage(strBuf);
  428. }
  429. }
  430. }
  431. PluginClassEND(MidiPatternPlugin)
  432. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiPatternPlugin)
  433. };
  434. // -----------------------------------------------------------------------
  435. static const NativePluginDescriptor midipatternDesc = {
  436. /* category */ NATIVE_PLUGIN_CATEGORY_UTILITY,
  437. /* hints */ static_cast<NativePluginHints>(NATIVE_PLUGIN_IS_RTSAFE
  438. |NATIVE_PLUGIN_HAS_UI
  439. |NATIVE_PLUGIN_USES_STATE
  440. |NATIVE_PLUGIN_USES_TIME),
  441. /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING,
  442. /* audioIns */ 0,
  443. /* audioOuts */ 0,
  444. /* midiIns */ 0,
  445. /* midiOuts */ 1,
  446. /* paramIns */ MidiPatternPlugin::kParameterCount,
  447. /* paramOuts */ 0,
  448. /* name */ "MIDI Pattern",
  449. /* label */ "midipattern",
  450. /* maker */ "falkTX, tatch",
  451. /* copyright */ "GNU GPL v2+",
  452. PluginDescriptorFILL(MidiPatternPlugin)
  453. };
  454. // -----------------------------------------------------------------------
  455. CARLA_EXPORT
  456. void carla_register_native_plugin_midipattern();
  457. CARLA_EXPORT
  458. void carla_register_native_plugin_midipattern()
  459. {
  460. carla_register_native_plugin(&midipatternDesc);
  461. }
  462. // -----------------------------------------------------------------------