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.

464 lines
15KB

  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 "CarlaNativePrograms.hpp"
  18. #include "midi-base.hpp"
  19. #include "water/files/FileInputStream.h"
  20. #include "water/midi/MidiFile.h"
  21. // -----------------------------------------------------------------------
  22. class MidiFilePlugin : public NativePluginWithMidiPrograms<FileMIDI>,
  23. public AbstractMidiPlayer
  24. {
  25. public:
  26. enum Parameters {
  27. kParameterRepeating,
  28. kParameterHostSync,
  29. kParameterEnabled,
  30. kParameterInfoNumTracks,
  31. kParameterInfoLength,
  32. kParameterInfoPosition,
  33. kParameterCount
  34. };
  35. MidiFilePlugin(const NativeHostDescriptor* const host)
  36. : NativePluginWithMidiPrograms<FileMIDI>(host, fPrograms, 0),
  37. fRepeatMode(false),
  38. #ifdef __MOD_DEVICES__
  39. fHostSync(false),
  40. #else
  41. fHostSync(true),
  42. #endif
  43. fEnabled(true),
  44. fNeedsAllNotesOff(false),
  45. fWasPlayingBefore(false),
  46. fLastPosition(0.0f),
  47. fMidiOut(this),
  48. fFileLength(0.0f),
  49. fNumTracks(0.0f),
  50. fInternalTransportFrame(0),
  51. fMaxFrame(0),
  52. fLastFrame(0),
  53. fPrograms(hostGetFilePath("midi"), "*.mid;*.midi")
  54. {
  55. }
  56. protected:
  57. // -------------------------------------------------------------------
  58. // Plugin parameter calls
  59. uint32_t getParameterCount() const override
  60. {
  61. return kParameterCount;
  62. }
  63. const NativeParameter* getParameterInfo(const uint32_t index) const override
  64. {
  65. static NativeParameter param;
  66. param.scalePointCount = 0;
  67. param.scalePoints = nullptr;
  68. param.unit = nullptr;
  69. param.ranges.step = 1.0f;
  70. param.ranges.stepSmall = 1.0f;
  71. param.ranges.stepLarge = 1.0f;
  72. param.designation = NATIVE_PARAMETER_DESIGNATION_NONE;
  73. switch (index)
  74. {
  75. case kParameterRepeating:
  76. param.name = "Repeat Mode";
  77. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMATABLE|
  78. NATIVE_PARAMETER_IS_ENABLED|
  79. NATIVE_PARAMETER_IS_BOOLEAN);
  80. param.ranges.def = 0.0f;
  81. param.ranges.min = 0.0f;
  82. param.ranges.max = 1.0f;
  83. break;
  84. case kParameterHostSync:
  85. param.name = "Host Sync";
  86. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMATABLE|
  87. NATIVE_PARAMETER_IS_ENABLED|
  88. NATIVE_PARAMETER_IS_BOOLEAN);
  89. #ifdef __MOD_DEVICES__
  90. param.ranges.def = 0.0f;
  91. #else
  92. param.ranges.def = 1.0f;
  93. #endif
  94. param.ranges.min = 0.0f;
  95. param.ranges.max = 1.0f;
  96. break;
  97. case kParameterEnabled:
  98. param.name = "Enabled";
  99. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMATABLE|
  100. NATIVE_PARAMETER_IS_ENABLED|
  101. NATIVE_PARAMETER_IS_BOOLEAN|
  102. NATIVE_PARAMETER_USES_DESIGNATION);
  103. param.ranges.def = 1.0f;
  104. param.ranges.min = 0.0f;
  105. param.ranges.max = 1.0f;
  106. param.designation = NATIVE_PARAMETER_DESIGNATION_ENABLED;
  107. break;
  108. case kParameterInfoNumTracks:
  109. param.name = "Num Tracks";
  110. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMATABLE|
  111. NATIVE_PARAMETER_IS_ENABLED|
  112. NATIVE_PARAMETER_IS_INTEGER|
  113. NATIVE_PARAMETER_IS_OUTPUT);
  114. param.ranges.def = 0.0f;
  115. param.ranges.min = 0.0f;
  116. param.ranges.max = 256.0f;
  117. break;
  118. case kParameterInfoLength:
  119. param.name = "Length";
  120. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMATABLE|
  121. NATIVE_PARAMETER_IS_ENABLED|
  122. NATIVE_PARAMETER_IS_OUTPUT);
  123. param.ranges.def = 0.0f;
  124. param.ranges.min = 0.0f;
  125. param.ranges.max = (float)INT64_MAX;
  126. param.unit = "s";
  127. break;
  128. case kParameterInfoPosition:
  129. param.name = "Position";
  130. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMATABLE|
  131. NATIVE_PARAMETER_IS_ENABLED|
  132. NATIVE_PARAMETER_IS_OUTPUT);
  133. param.ranges.def = 0.0f;
  134. param.ranges.min = 0.0f;
  135. param.ranges.max = 100.0f;
  136. param.unit = "%";
  137. break;
  138. default:
  139. return nullptr;
  140. }
  141. return &param;
  142. }
  143. float getParameterValue(const uint32_t index) const override
  144. {
  145. switch (index)
  146. {
  147. case kParameterRepeating:
  148. return fRepeatMode ? 1.0f : 0.0f;
  149. case kParameterHostSync:
  150. return fHostSync ? 1.0f : 0.0f;
  151. case kParameterEnabled:
  152. return fEnabled ? 1.0f : 0.0f;
  153. case kParameterInfoNumTracks:
  154. return fNumTracks;
  155. case kParameterInfoLength:
  156. return fFileLength;
  157. case kParameterInfoPosition:
  158. return fLastPosition;
  159. default:
  160. return 0.0f;
  161. }
  162. }
  163. // -------------------------------------------------------------------
  164. // Plugin state calls
  165. void setParameterValue(const uint32_t index, const float value) override
  166. {
  167. const bool b = (value > 0.5f);
  168. switch (index)
  169. {
  170. case kParameterRepeating:
  171. if (fRepeatMode != b)
  172. {
  173. fRepeatMode = b;
  174. fNeedsAllNotesOff = true;
  175. }
  176. break;
  177. case kParameterHostSync:
  178. if (fHostSync != b)
  179. {
  180. fInternalTransportFrame = 0;
  181. fHostSync = b;
  182. }
  183. break;
  184. case kParameterEnabled:
  185. if (fEnabled != b)
  186. {
  187. fInternalTransportFrame = 0;
  188. fEnabled = b;
  189. }
  190. break;
  191. default:
  192. break;
  193. }
  194. }
  195. void setCustomData(const char* const key, const char* const value) override
  196. {
  197. CARLA_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',);
  198. CARLA_SAFE_ASSERT_RETURN(value != nullptr && value[0] != '\0',);
  199. if (std::strcmp(key, "file") != 0)
  200. return;
  201. invalidateNextFilename();
  202. _loadMidiFile(value);
  203. }
  204. // -------------------------------------------------------------------
  205. // Plugin process calls
  206. void process2(const float* const*, float**, const uint32_t frames, const NativeMidiEvent* const, const uint32_t) override
  207. {
  208. const uint32_t maxFrame = fMaxFrame;
  209. bool playing;
  210. uint64_t frame;
  211. if (fHostSync)
  212. {
  213. const NativeTimeInfo* const timePos = getTimeInfo();
  214. playing = fEnabled && timePos->playing;
  215. frame = timePos->frame;
  216. }
  217. else
  218. {
  219. playing = fEnabled;
  220. frame = fInternalTransportFrame;
  221. if (playing)
  222. fInternalTransportFrame += frames;
  223. }
  224. if (fRepeatMode && maxFrame != 0 && frame >= maxFrame)
  225. frame %= maxFrame;
  226. if (fWasPlayingBefore != playing || frame < fLastFrame)
  227. {
  228. fNeedsAllNotesOff = true;
  229. fWasPlayingBefore = playing;
  230. }
  231. if (fNeedsAllNotesOff)
  232. {
  233. NativeMidiEvent midiEvent;
  234. midiEvent.port = 0;
  235. midiEvent.time = 0;
  236. midiEvent.data[0] = 0;
  237. midiEvent.data[1] = MIDI_CONTROL_ALL_NOTES_OFF;
  238. midiEvent.data[2] = 0;
  239. midiEvent.data[3] = 0;
  240. midiEvent.size = 3;
  241. for (int channel=MAX_MIDI_CHANNELS; --channel >= 0;)
  242. {
  243. midiEvent.data[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (channel & MIDI_CHANNEL_BIT));
  244. NativePluginClass::writeMidiEvent(&midiEvent);
  245. }
  246. fNeedsAllNotesOff = false;
  247. }
  248. if (fWasPlayingBefore)
  249. if (! fMidiOut.play(static_cast<uint32_t>(frame), frames))
  250. fNeedsAllNotesOff = true;
  251. fLastFrame = frame;
  252. if (frame < maxFrame)
  253. fLastPosition = static_cast<float>(frame) / static_cast<float>(maxFrame) * 100.0f;
  254. else
  255. fLastPosition = 100.0f;
  256. }
  257. // -------------------------------------------------------------------
  258. // Plugin UI calls
  259. void uiShow(const bool show) override
  260. {
  261. if (! show)
  262. return;
  263. if (const char* const filename = uiOpenFile(false, "Open MIDI File", "MIDI Files (*.mid *.midi);;"))
  264. uiCustomDataChanged("file", filename);
  265. uiClosed();
  266. }
  267. // -------------------------------------------------------------------
  268. // Plugin state calls
  269. char* getState() const override
  270. {
  271. return fMidiOut.getState();
  272. }
  273. void setState(const char* const data) override
  274. {
  275. fMidiOut.setState(data);
  276. }
  277. void setStateFromFile(const char* const filename) override
  278. {
  279. _loadMidiFile(filename);
  280. }
  281. // -------------------------------------------------------------------
  282. // AbstractMidiPlayer calls
  283. void writeMidiEvent(const uint8_t port, const double timePosFrame, const RawMidiEvent* const event) override
  284. {
  285. NativeMidiEvent midiEvent;
  286. midiEvent.port = port;
  287. midiEvent.time = static_cast<uint32_t>(timePosFrame);
  288. midiEvent.size = event->size;
  289. midiEvent.data[0] = event->data[0];
  290. midiEvent.data[1] = event->data[1];
  291. midiEvent.data[2] = event->data[2];
  292. midiEvent.data[3] = event->data[3];
  293. NativePluginClass::writeMidiEvent(&midiEvent);
  294. }
  295. // -------------------------------------------------------------------
  296. private:
  297. bool fRepeatMode;
  298. bool fHostSync;
  299. bool fEnabled;
  300. bool fNeedsAllNotesOff;
  301. bool fWasPlayingBefore;
  302. float fLastPosition;
  303. MidiPattern fMidiOut;
  304. float fFileLength;
  305. float fNumTracks;
  306. uint32_t fInternalTransportFrame;
  307. uint32_t fMaxFrame;
  308. uint64_t fLastFrame;
  309. NativeMidiPrograms fPrograms;
  310. void _loadMidiFile(const char* const filename)
  311. {
  312. fMidiOut.clear();
  313. fInternalTransportFrame = 0;
  314. fFileLength = 0.0f;
  315. fNumTracks = 0.0f;
  316. fMaxFrame = 0;
  317. fLastFrame = 0;
  318. fLastPosition = 0.0f;
  319. using namespace water;
  320. const String jfilename = String(CharPointer_UTF8(filename));
  321. File file(jfilename);
  322. if (! file.existsAsFile())
  323. return;
  324. FileInputStream fileStream(file);
  325. MidiFile midiFile;
  326. if (! midiFile.readFrom(fileStream))
  327. return;
  328. midiFile.convertTimestampTicksToSeconds();
  329. const double sampleRate = getSampleRate();
  330. const size_t numTracks = midiFile.getNumTracks();
  331. for (size_t i=0; i<numTracks; ++i)
  332. {
  333. const MidiMessageSequence* const track = midiFile.getTrack(i);
  334. CARLA_SAFE_ASSERT_CONTINUE(track != nullptr);
  335. for (int j=0, numEvents = track->getNumEvents(); j<numEvents; ++j)
  336. {
  337. const MidiMessageSequence::MidiEventHolder* const midiEventHolder = track->getEventPointer(j);
  338. CARLA_SAFE_ASSERT_CONTINUE(midiEventHolder != nullptr);
  339. const MidiMessage& midiMessage(midiEventHolder->message);
  340. const int dataSize = midiMessage.getRawDataSize();
  341. if (dataSize <= 0 || dataSize > MAX_EVENT_DATA_SIZE)
  342. continue;
  343. const uint8_t* const data = midiMessage.getRawData();
  344. if (! MIDI_IS_CHANNEL_MESSAGE(data[0]))
  345. continue;
  346. const double time = midiMessage.getTimeStamp() * sampleRate;
  347. // const double time = track->getEventTime(i) * sampleRate;
  348. CARLA_SAFE_ASSERT_CONTINUE(time >= 0.0);
  349. fMidiOut.addRaw(static_cast<uint32_t>(time + 0.5),
  350. midiMessage.getRawData(), static_cast<uint8_t>(dataSize));
  351. }
  352. }
  353. const double lastTimeStamp = midiFile.getLastTimestamp();
  354. fFileLength = static_cast<float>(lastTimeStamp);
  355. fNumTracks = static_cast<float>(numTracks);
  356. fNeedsAllNotesOff = true;
  357. fInternalTransportFrame = 0;
  358. fLastFrame = 0;
  359. fMaxFrame = static_cast<uint32_t>(lastTimeStamp * sampleRate + 0.5);
  360. }
  361. PluginClassEND(MidiFilePlugin)
  362. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiFilePlugin)
  363. };
  364. // -----------------------------------------------------------------------
  365. static const NativePluginDescriptor midifileDesc = {
  366. /* category */ NATIVE_PLUGIN_CATEGORY_UTILITY,
  367. /* hints */ static_cast<NativePluginHints>(NATIVE_PLUGIN_IS_RTSAFE
  368. |NATIVE_PLUGIN_HAS_UI
  369. |NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE
  370. |NATIVE_PLUGIN_REQUESTS_IDLE
  371. |NATIVE_PLUGIN_USES_STATE
  372. |NATIVE_PLUGIN_USES_TIME),
  373. /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING,
  374. /* audioIns */ 0,
  375. /* audioOuts */ 0,
  376. /* midiIns */ 0,
  377. /* midiOuts */ 1,
  378. /* paramIns */ 0,
  379. /* paramOuts */ 0,
  380. /* name */ "MIDI File",
  381. /* label */ "midifile",
  382. /* maker */ "falkTX",
  383. /* copyright */ "GNU GPL v2+",
  384. PluginDescriptorFILL(MidiFilePlugin)
  385. };
  386. // -----------------------------------------------------------------------
  387. CARLA_API_EXPORT
  388. void carla_register_native_plugin_midifile();
  389. CARLA_API_EXPORT
  390. void carla_register_native_plugin_midifile()
  391. {
  392. carla_register_native_plugin(&midifileDesc);
  393. }
  394. // -----------------------------------------------------------------------