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.

midi-pattern.cpp 19KB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. /*
  2. * Carla Native Plugins
  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 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.0),
  41. fLastFrame(0),
  42. fTicksPerFrame(0.0),
  43. fMaxTicksPerSigNum(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. fMaxTicksPerSigNum = 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_AUTOMATABLE|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. fMaxTicksPerSigNum = TICKS_PER_BEAT * fTimeSigNum * static_cast<double>(fParameters[kParameterMeasures]);
  179. fNeedsAllNotesOff = true;
  180. break;
  181. }
  182. }
  183. // -------------------------------------------------------------------
  184. // Plugin process calls
  185. void process(const float* const*, float**, const uint32_t frames,
  186. const NativeMidiEvent* /*midiEvents*/, uint32_t /*midiEventCount*/) override
  187. {
  188. if (const NativeTimeInfo* const timeInfo = getTimeInfo())
  189. fTimeInfo = *timeInfo;
  190. if (fWasPlayingBefore != fTimeInfo.playing)
  191. {
  192. fLastFrame = 0;
  193. fLastPosition = 0.0;
  194. fNeedsAllNotesOff = true;
  195. fWasPlayingBefore = fTimeInfo.playing;
  196. }
  197. else if (fTimeInfo.playing && fLastFrame + frames != fTimeInfo.frame)
  198. {
  199. fNeedsAllNotesOff = true;
  200. }
  201. if (fNeedsAllNotesOff)
  202. {
  203. NativeMidiEvent midiEvent;
  204. midiEvent.port = 0;
  205. midiEvent.time = 0;
  206. midiEvent.data[0] = 0;
  207. midiEvent.data[1] = MIDI_CONTROL_ALL_NOTES_OFF;
  208. midiEvent.data[2] = 0;
  209. midiEvent.data[3] = 0;
  210. midiEvent.size = 3;
  211. for (int channel=MAX_MIDI_CHANNELS; --channel >= 0;)
  212. {
  213. midiEvent.data[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (channel & MIDI_CHANNEL_BIT));
  214. NativePluginAndUiClass::writeMidiEvent(&midiEvent);
  215. }
  216. fNeedsAllNotesOff = false;
  217. }
  218. if (fMidiQueue.isNotEmpty() && fMidiQueueRT.tryToCopyDataFrom(fMidiQueue))
  219. {
  220. uint8_t d1, d2, d3;
  221. NativeMidiEvent ev = { 0, 0, 3, { 0, 0, 0, 0 } };
  222. while (fMidiQueueRT.get(d1, d2, d3))
  223. {
  224. ev.data[0] = d1;
  225. ev.data[1] = d2;
  226. ev.data[2] = d3;
  227. NativePluginAndUiClass::writeMidiEvent(&ev);
  228. }
  229. }
  230. if (fTimeInfo.playing)
  231. {
  232. if (! fTimeInfo.bbt.valid)
  233. fTimeInfo.bbt.beatsPerMinute = 120.0;
  234. fTicksPerFrame = TICKS_PER_BEAT / (60.0 / fTimeInfo.bbt.beatsPerMinute * getSampleRate());
  235. double playPos;
  236. if (fLastFrame + frames == fTimeInfo.frame)
  237. {
  238. // continuous playback
  239. playPos = fLastPosition + fTicksPerFrame * static_cast<double>(frames);
  240. }
  241. else
  242. {
  243. // non-continuous, reset playPos
  244. playPos = fTicksPerFrame * static_cast<double>(fTimeInfo.frame);
  245. }
  246. const double endPos = playPos + fTicksPerFrame * static_cast<double>(frames);
  247. const double loopedEndPos = std::fmod(endPos, fMaxTicksPerSigNum);
  248. for (; playPos < endPos; playPos += fMaxTicksPerSigNum)
  249. {
  250. const double loopedPlayPos = std::fmod(playPos, fMaxTicksPerSigNum);
  251. if (loopedEndPos >= loopedPlayPos)
  252. {
  253. if (! fMidiOut.play(loopedPlayPos, loopedEndPos-loopedPlayPos))
  254. fNeedsAllNotesOff = true;
  255. }
  256. else
  257. {
  258. const double diff = fMaxTicksPerSigNum - loopedPlayPos;
  259. if (! (fMidiOut.play(loopedPlayPos, diff) && fMidiOut.play(0.0, loopedEndPos, diff)))
  260. fNeedsAllNotesOff = true;
  261. }
  262. }
  263. fLastPosition = playPos;
  264. }
  265. fLastFrame = fTimeInfo.frame;
  266. }
  267. // -------------------------------------------------------------------
  268. // Plugin UI calls
  269. void uiShow(const bool show) override
  270. {
  271. NativePluginAndUiClass::uiShow(show);
  272. if (show)
  273. _sendEventsToUI();
  274. }
  275. void uiIdle() override
  276. {
  277. NativePluginAndUiClass::uiIdle();
  278. // send transport
  279. if (isPipeRunning())
  280. {
  281. char strBuf[0xff+1];
  282. carla_zeroChars(strBuf, 0xff+1);
  283. const double beatsPerBar = fTimeSigNum;
  284. const double beatsPerMinute = fTimeInfo.bbt.valid ? fTimeInfo.bbt.beatsPerMinute : 120.0;
  285. const double ticksPerBeat = TICKS_PER_BEAT;
  286. const double fullTicks = fLastPosition;
  287. const double fullBeats = fullTicks / ticksPerBeat;
  288. const uint32_t tick = static_cast<uint32_t>(std::floor(std::fmod(fullTicks, ticksPerBeat)));
  289. const uint32_t beat = static_cast<uint32_t>(std::floor(std::fmod(fullBeats, beatsPerBar)));
  290. const uint32_t bar = static_cast<uint32_t>(std::floor(fullBeats/beatsPerBar));
  291. const CarlaMutexLocker cml(getPipeLock());
  292. CARLA_SAFE_ASSERT_RETURN(writeMessage("transport\n"),);
  293. std::snprintf(strBuf, 0xff, "%i:" P_UINT64 ":%i:%i:%i\n", int(fTimeInfo.playing), fTimeInfo.frame, bar, beat, tick);
  294. CARLA_SAFE_ASSERT_RETURN(writeMessage(strBuf),);
  295. {
  296. const CarlaScopedLocale csl;
  297. std::snprintf(strBuf, 0xff, "%.12g\n", beatsPerMinute);
  298. }
  299. CARLA_SAFE_ASSERT_RETURN(writeMessage(strBuf),);
  300. flushMessages();
  301. }
  302. }
  303. // -------------------------------------------------------------------
  304. // Plugin state calls
  305. char* getState() const override
  306. {
  307. return fMidiOut.getState();
  308. }
  309. void setState(const char* const data) override
  310. {
  311. fMidiOut.setState(data);
  312. if (isPipeRunning())
  313. _sendEventsToUI();
  314. }
  315. // -------------------------------------------------------------------
  316. // AbstractMidiPlayer calls
  317. void writeMidiEvent(const uint8_t port, const double timePosFrame, const RawMidiEvent* const event) override
  318. {
  319. NativeMidiEvent midiEvent;
  320. midiEvent.port = port;
  321. midiEvent.time = uint32_t(timePosFrame/fTicksPerFrame);
  322. midiEvent.data[0] = event->data[0];
  323. midiEvent.data[1] = event->data[1];
  324. midiEvent.data[2] = event->data[2];
  325. midiEvent.data[3] = event->data[3];
  326. midiEvent.size = event->size;
  327. #ifdef DEBUG
  328. carla_stdout("Playing at %f|%u :: %03X:%03i:%03i",
  329. midiEvent.time,
  330. static_cast<double>(midiEvent.time)*fTicksPerFrame,
  331. midiEvent.data[0], midiEvent.data[1], midiEvent.data[2]);
  332. #endif
  333. NativePluginAndUiClass::writeMidiEvent(&midiEvent);
  334. }
  335. // -------------------------------------------------------------------
  336. // Pipe Server calls
  337. bool msgReceived(const char* const msg) noexcept override
  338. {
  339. if (NativePluginAndUiClass::msgReceived(msg))
  340. return true;
  341. if (std::strcmp(msg, "midi-clear-all") == 0)
  342. {
  343. fMidiOut.clear();
  344. fNeedsAllNotesOff = true;
  345. return true;
  346. }
  347. if (std::strcmp(msg, "midi-note") == 0)
  348. {
  349. uint8_t note;
  350. bool on;
  351. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(note), true);
  352. CARLA_SAFE_ASSERT_RETURN(readNextLineAsBool(on), true);
  353. const uint8_t status = on ? MIDI_STATUS_NOTE_ON : MIDI_STATUS_NOTE_OFF;
  354. const uint8_t velocity = on ? 100 : 0;
  355. const CarlaMutexLocker cml(fMidiQueue.getMutex());
  356. fMidiQueue.put(status, note, velocity);
  357. return true;
  358. }
  359. if (std::strcmp(msg, "midievent-add") == 0)
  360. {
  361. uint32_t time;
  362. uint8_t size;
  363. CARLA_SAFE_ASSERT_RETURN(readNextLineAsUInt(time), true);
  364. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(size), true);
  365. CARLA_SAFE_ASSERT_RETURN(size > 0, true);
  366. uint8_t data[size], dvalue;
  367. for (uint8_t i=0; i<size; ++i)
  368. {
  369. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(dvalue), true);
  370. data[i] = dvalue;
  371. }
  372. fMidiOut.addRaw(time, data, size);
  373. return true;
  374. }
  375. if (std::strcmp(msg, "midievent-remove") == 0)
  376. {
  377. uint32_t time;
  378. uint8_t size;
  379. CARLA_SAFE_ASSERT_RETURN(readNextLineAsUInt(time), true);
  380. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(size), true);
  381. CARLA_SAFE_ASSERT_RETURN(size > 0, true);
  382. uint8_t data[size], dvalue;
  383. for (uint8_t i=0; i<size; ++i)
  384. {
  385. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(dvalue), true);
  386. data[i] = dvalue;
  387. }
  388. fMidiOut.removeRaw(time, data, size);
  389. if (MIDI_IS_STATUS_NOTE_ON(data[0]))
  390. {
  391. const uint8_t status = MIDI_STATUS_NOTE_OFF | (data[0] & MIDI_CHANNEL_BIT);
  392. const CarlaMutexLocker cml(fMidiQueue.getMutex());
  393. fMidiQueue.put(status, data[1], 0);
  394. }
  395. return true;
  396. }
  397. return false;
  398. }
  399. // -------------------------------------------------------------------
  400. private:
  401. bool fNeedsAllNotesOff;
  402. bool fWasPlayingBefore;
  403. int fTimeSigNum;
  404. double fLastPosition;
  405. uint64_t fLastFrame;
  406. double fTicksPerFrame;
  407. double fMaxTicksPerSigNum;
  408. MidiPattern fMidiOut;
  409. NativeTimeInfo fTimeInfo;
  410. MIDIEventQueue<32> fMidiQueue, fMidiQueueRT;
  411. float fParameters[kParameterCount];
  412. void _sendEventsToUI() const noexcept
  413. {
  414. char strBuf[0xff+1];
  415. carla_zeroChars(strBuf, 0xff);
  416. const CarlaMutexLocker cml1(getPipeLock());
  417. const CarlaMutexLocker cml2(fMidiOut.getWriteMutex());
  418. writeMessage("midi-clear-all\n", 15);
  419. writeMessage("parameters\n", 11);
  420. std::snprintf(strBuf, 0xff, "%i:%i:%i:%i\n",
  421. static_cast<int>(fParameters[kParameterTimeSig]),
  422. static_cast<int>(fParameters[kParameterMeasures]),
  423. static_cast<int>(fParameters[kParameterDefLength]),
  424. static_cast<int>(fParameters[kParameterQuantize]));
  425. writeMessage(strBuf);
  426. for (LinkedList<const RawMidiEvent*>::Itenerator it = fMidiOut.iteneratorBegin(); it.valid(); it.next())
  427. {
  428. const RawMidiEvent* const rawMidiEvent(it.getValue(nullptr));
  429. CARLA_SAFE_ASSERT_CONTINUE(rawMidiEvent != nullptr);
  430. writeMessage("midievent-add\n", 14);
  431. std::snprintf(strBuf, 0xff, "%u\n", rawMidiEvent->time);
  432. writeMessage(strBuf);
  433. std::snprintf(strBuf, 0xff, "%i\n", rawMidiEvent->size);
  434. writeMessage(strBuf);
  435. for (uint8_t i=0, size=rawMidiEvent->size; i<size; ++i)
  436. {
  437. std::snprintf(strBuf, 0xff, "%i\n", rawMidiEvent->data[i]);
  438. writeMessage(strBuf);
  439. }
  440. }
  441. }
  442. PluginClassEND(MidiPatternPlugin)
  443. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiPatternPlugin)
  444. };
  445. // -----------------------------------------------------------------------
  446. static const NativePluginDescriptor midipatternDesc = {
  447. /* category */ NATIVE_PLUGIN_CATEGORY_UTILITY,
  448. /* hints */ static_cast<NativePluginHints>(NATIVE_PLUGIN_IS_RTSAFE
  449. |NATIVE_PLUGIN_HAS_UI
  450. |NATIVE_PLUGIN_USES_STATE
  451. |NATIVE_PLUGIN_USES_TIME),
  452. /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING,
  453. /* audioIns */ 0,
  454. /* audioOuts */ 0,
  455. /* midiIns */ 0,
  456. /* midiOuts */ 1,
  457. /* paramIns */ MidiPatternPlugin::kParameterCount,
  458. /* paramOuts */ 0,
  459. /* name */ "MIDI Pattern",
  460. /* label */ "midipattern",
  461. /* maker */ "falkTX, tatch",
  462. /* copyright */ "GNU GPL v2+",
  463. PluginDescriptorFILL(MidiPatternPlugin)
  464. };
  465. // -----------------------------------------------------------------------
  466. CARLA_API_EXPORT
  467. void carla_register_native_plugin_midipattern();
  468. CARLA_API_EXPORT
  469. void carla_register_native_plugin_midipattern()
  470. {
  471. carla_register_native_plugin(&midipatternDesc);
  472. }
  473. // -----------------------------------------------------------------------