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.

442 lines
14KB

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