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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  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. #ifndef CARLA_OS_WASM
  268. // -------------------------------------------------------------------
  269. // Plugin UI calls
  270. void uiShow(const bool show) override
  271. {
  272. NativePluginAndUiClass::uiShow(show);
  273. if (show)
  274. _sendEventsToUI();
  275. }
  276. void uiIdle() override
  277. {
  278. NativePluginAndUiClass::uiIdle();
  279. // send transport
  280. if (isPipeRunning())
  281. {
  282. char strBuf[0xff+1];
  283. carla_zeroChars(strBuf, 0xff+1);
  284. const double beatsPerBar = fTimeSigNum;
  285. const double beatsPerMinute = fTimeInfo.bbt.valid ? fTimeInfo.bbt.beatsPerMinute : 120.0;
  286. const double ticksPerBeat = TICKS_PER_BEAT;
  287. const double fullTicks = fLastPosition;
  288. const double fullBeats = fullTicks / ticksPerBeat;
  289. const uint32_t tick = static_cast<uint32_t>(std::floor(std::fmod(fullTicks, ticksPerBeat)));
  290. const uint32_t beat = static_cast<uint32_t>(std::floor(std::fmod(fullBeats, beatsPerBar)));
  291. const uint32_t bar = static_cast<uint32_t>(std::floor(fullBeats/beatsPerBar));
  292. const CarlaMutexLocker cml(getPipeLock());
  293. CARLA_SAFE_ASSERT_RETURN(writeMessage("transport\n"),);
  294. std::snprintf(strBuf, 0xff, "%i:" P_UINT64 ":%i:%i:%i\n", int(fTimeInfo.playing), fTimeInfo.frame, bar, beat, tick);
  295. CARLA_SAFE_ASSERT_RETURN(writeMessage(strBuf),);
  296. {
  297. const CarlaScopedLocale csl;
  298. std::snprintf(strBuf, 0xff, "%.12g\n", beatsPerMinute);
  299. }
  300. CARLA_SAFE_ASSERT_RETURN(writeMessage(strBuf),);
  301. flushMessages();
  302. }
  303. }
  304. #endif
  305. // -------------------------------------------------------------------
  306. // Plugin state calls
  307. char* getState() const override
  308. {
  309. return fMidiOut.getState();
  310. }
  311. void setState(const char* const data) override
  312. {
  313. fMidiOut.setState(data);
  314. #ifndef CARLA_OS_WASM
  315. if (isPipeRunning())
  316. _sendEventsToUI();
  317. #endif
  318. }
  319. // -------------------------------------------------------------------
  320. // AbstractMidiPlayer calls
  321. void writeMidiEvent(const uint8_t port, const double timePosFrame, const RawMidiEvent* const event) override
  322. {
  323. NativeMidiEvent midiEvent;
  324. midiEvent.port = port;
  325. midiEvent.time = uint32_t(timePosFrame/fTicksPerFrame);
  326. midiEvent.data[0] = event->data[0];
  327. midiEvent.data[1] = event->data[1];
  328. midiEvent.data[2] = event->data[2];
  329. midiEvent.data[3] = event->data[3];
  330. midiEvent.size = event->size;
  331. #ifdef DEBUG
  332. carla_stdout("Playing at %f|%u :: %03X:%03i:%03i",
  333. midiEvent.time,
  334. static_cast<double>(midiEvent.time)*fTicksPerFrame,
  335. midiEvent.data[0], midiEvent.data[1], midiEvent.data[2]);
  336. #endif
  337. NativePluginAndUiClass::writeMidiEvent(&midiEvent);
  338. }
  339. #ifndef CARLA_OS_WASM
  340. // -------------------------------------------------------------------
  341. // Pipe Server calls
  342. bool msgReceived(const char* const msg) noexcept override
  343. {
  344. if (NativePluginAndUiClass::msgReceived(msg))
  345. return true;
  346. if (std::strcmp(msg, "midi-clear-all") == 0)
  347. {
  348. fMidiOut.clear();
  349. fNeedsAllNotesOff = true;
  350. return true;
  351. }
  352. if (std::strcmp(msg, "midi-note") == 0)
  353. {
  354. uint8_t note;
  355. bool on;
  356. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(note), true);
  357. CARLA_SAFE_ASSERT_RETURN(readNextLineAsBool(on), true);
  358. const uint8_t status = on ? MIDI_STATUS_NOTE_ON : MIDI_STATUS_NOTE_OFF;
  359. const uint8_t velocity = on ? 100 : 0;
  360. const CarlaMutexLocker cml(fMidiQueue.getMutex());
  361. fMidiQueue.put(status, note, velocity);
  362. return true;
  363. }
  364. if (std::strcmp(msg, "midievent-add") == 0)
  365. {
  366. uint32_t time;
  367. uint8_t size;
  368. CARLA_SAFE_ASSERT_RETURN(readNextLineAsUInt(time), true);
  369. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(size), true);
  370. CARLA_SAFE_ASSERT_RETURN(size > 0, true);
  371. uint8_t data[size], dvalue;
  372. for (uint8_t i=0; i<size; ++i)
  373. {
  374. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(dvalue), true);
  375. data[i] = dvalue;
  376. }
  377. fMidiOut.addRaw(time, data, size);
  378. return true;
  379. }
  380. if (std::strcmp(msg, "midievent-remove") == 0)
  381. {
  382. uint32_t time;
  383. uint8_t size;
  384. CARLA_SAFE_ASSERT_RETURN(readNextLineAsUInt(time), true);
  385. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(size), true);
  386. CARLA_SAFE_ASSERT_RETURN(size > 0, true);
  387. uint8_t data[size], dvalue;
  388. for (uint8_t i=0; i<size; ++i)
  389. {
  390. CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(dvalue), true);
  391. data[i] = dvalue;
  392. }
  393. fMidiOut.removeRaw(time, data, size);
  394. if (MIDI_IS_STATUS_NOTE_ON(data[0]))
  395. {
  396. const uint8_t status = MIDI_STATUS_NOTE_OFF | (data[0] & MIDI_CHANNEL_BIT);
  397. const CarlaMutexLocker cml(fMidiQueue.getMutex());
  398. fMidiQueue.put(status, data[1], 0);
  399. }
  400. return true;
  401. }
  402. return false;
  403. }
  404. #endif
  405. // -------------------------------------------------------------------
  406. private:
  407. bool fNeedsAllNotesOff;
  408. bool fWasPlayingBefore;
  409. int fTimeSigNum;
  410. double fLastPosition;
  411. uint64_t fLastFrame;
  412. double fTicksPerFrame;
  413. double fMaxTicksPerSigNum;
  414. MidiPattern fMidiOut;
  415. NativeTimeInfo fTimeInfo;
  416. MIDIEventQueue<32> fMidiQueue, fMidiQueueRT;
  417. float fParameters[kParameterCount];
  418. #ifndef CARLA_OS_WASM
  419. void _sendEventsToUI() const noexcept
  420. {
  421. char strBuf[0xff+1];
  422. carla_zeroChars(strBuf, 0xff);
  423. const CarlaMutexLocker cml1(getPipeLock());
  424. const CarlaMutexLocker cml2(fMidiOut.getWriteMutex());
  425. writeMessage("midi-clear-all\n", 15);
  426. writeMessage("parameters\n", 11);
  427. std::snprintf(strBuf, 0xff, "%i:%i:%i:%i\n",
  428. static_cast<int>(fParameters[kParameterTimeSig]),
  429. static_cast<int>(fParameters[kParameterMeasures]),
  430. static_cast<int>(fParameters[kParameterDefLength]),
  431. static_cast<int>(fParameters[kParameterQuantize]));
  432. writeMessage(strBuf);
  433. for (LinkedList<const RawMidiEvent*>::Itenerator it = fMidiOut.iteneratorBegin(); it.valid(); it.next())
  434. {
  435. const RawMidiEvent* const rawMidiEvent(it.getValue(nullptr));
  436. CARLA_SAFE_ASSERT_CONTINUE(rawMidiEvent != nullptr);
  437. writeMessage("midievent-add\n", 14);
  438. std::snprintf(strBuf, 0xff, "%u\n", rawMidiEvent->time);
  439. writeMessage(strBuf);
  440. std::snprintf(strBuf, 0xff, "%i\n", rawMidiEvent->size);
  441. writeMessage(strBuf);
  442. for (uint8_t i=0, size=rawMidiEvent->size; i<size; ++i)
  443. {
  444. std::snprintf(strBuf, 0xff, "%i\n", rawMidiEvent->data[i]);
  445. writeMessage(strBuf);
  446. }
  447. }
  448. }
  449. #endif
  450. PluginClassEND(MidiPatternPlugin)
  451. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiPatternPlugin)
  452. };
  453. // -----------------------------------------------------------------------
  454. static const NativePluginDescriptor midipatternDesc = {
  455. /* category */ NATIVE_PLUGIN_CATEGORY_UTILITY,
  456. /* hints */ static_cast<NativePluginHints>(NATIVE_PLUGIN_IS_RTSAFE
  457. |NATIVE_PLUGIN_HAS_UI
  458. |NATIVE_PLUGIN_USES_STATE
  459. |NATIVE_PLUGIN_USES_TIME),
  460. /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING,
  461. /* audioIns */ 0,
  462. /* audioOuts */ 0,
  463. /* midiIns */ 0,
  464. /* midiOuts */ 1,
  465. /* paramIns */ MidiPatternPlugin::kParameterCount,
  466. /* paramOuts */ 0,
  467. /* name */ "MIDI Pattern",
  468. /* label */ "midipattern",
  469. /* maker */ "falkTX, tatch",
  470. /* copyright */ "GNU GPL v2+",
  471. PluginDescriptorFILL(MidiPatternPlugin)
  472. };
  473. // -----------------------------------------------------------------------
  474. CARLA_API_EXPORT
  475. void carla_register_native_plugin_midipattern();
  476. CARLA_API_EXPORT
  477. void carla_register_native_plugin_midipattern()
  478. {
  479. carla_register_native_plugin(&midipatternDesc);
  480. }
  481. // -----------------------------------------------------------------------