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.

306 lines
9.4KB

  1. /*
  2. * Carla Native Plugins
  3. * Copyright (C) 2012-2019 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. // NOTE WIP
  28. kParameterInfoLength,
  29. kParameterCount
  30. };
  31. MidiFilePlugin(const NativeHostDescriptor* const host)
  32. : NativePluginWithMidiPrograms<FileMIDI>(host, fPrograms, 0),
  33. fMidiOut(this),
  34. fFileLength(0.0),
  35. fNeedsAllNotesOff(false),
  36. fWasPlayingBefore(false),
  37. fPrograms(hostGetFilePath("midi"), "*.mid;*.midi")
  38. {
  39. }
  40. protected:
  41. // -------------------------------------------------------------------
  42. // Plugin parameter calls
  43. uint32_t getParameterCount() const override
  44. {
  45. return kParameterCount;
  46. }
  47. const NativeParameter* getParameterInfo(const uint32_t index) const override
  48. {
  49. static NativeParameter param;
  50. param.scalePointCount = 0;
  51. param.scalePoints = nullptr;
  52. param.unit = nullptr;
  53. param.ranges.step = 1.0f;
  54. param.ranges.stepSmall = 1.0f;
  55. param.ranges.stepLarge = 1.0f;
  56. switch (index)
  57. {
  58. case kParameterInfoLength:
  59. param.name = "Length";
  60. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMABLE|
  61. NATIVE_PARAMETER_IS_ENABLED|
  62. NATIVE_PARAMETER_IS_OUTPUT);
  63. param.ranges.def = 0.0f;
  64. param.ranges.min = 0.0f;
  65. param.ranges.max = (float)INT64_MAX;
  66. param.unit = "s";
  67. break;
  68. default:
  69. return nullptr;
  70. }
  71. return &param;
  72. }
  73. float getParameterValue(const uint32_t index) const override
  74. {
  75. switch (index)
  76. {
  77. case kParameterInfoLength:
  78. return static_cast<float>(fFileLength);
  79. default:
  80. return 0.0f;
  81. }
  82. }
  83. // -------------------------------------------------------------------
  84. // Plugin state calls
  85. void setCustomData(const char* const key, const char* const value) override
  86. {
  87. CARLA_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',);
  88. CARLA_SAFE_ASSERT_RETURN(value != nullptr && value[0] != '\0',);
  89. if (std::strcmp(key, "file") != 0)
  90. return;
  91. invalidateNextFilename();
  92. _loadMidiFile(value);
  93. }
  94. // -------------------------------------------------------------------
  95. // Plugin process calls
  96. void process2(const float* const*, float**, const uint32_t frames, const NativeMidiEvent* const, const uint32_t) override
  97. {
  98. const NativeTimeInfo* const timePos(getTimeInfo());
  99. if (timePos == nullptr)
  100. return;
  101. if (fWasPlayingBefore != timePos->playing)
  102. {
  103. fNeedsAllNotesOff = true;
  104. fWasPlayingBefore = timePos->playing;
  105. }
  106. if (fNeedsAllNotesOff)
  107. {
  108. NativeMidiEvent midiEvent;
  109. midiEvent.port = 0;
  110. midiEvent.time = 0;
  111. midiEvent.data[0] = 0;
  112. midiEvent.data[1] = MIDI_CONTROL_ALL_NOTES_OFF;
  113. midiEvent.data[2] = 0;
  114. midiEvent.data[3] = 0;
  115. midiEvent.size = 3;
  116. for (int channel=MAX_MIDI_CHANNELS; --channel >= 0;)
  117. {
  118. midiEvent.data[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (channel & MIDI_CHANNEL_BIT));
  119. NativePluginClass::writeMidiEvent(&midiEvent);
  120. }
  121. fNeedsAllNotesOff = false;
  122. }
  123. if (fWasPlayingBefore)
  124. if (! fMidiOut.play(timePos->frame, frames))
  125. fNeedsAllNotesOff = true;
  126. }
  127. // -------------------------------------------------------------------
  128. // Plugin UI calls
  129. void uiShow(const bool show) override
  130. {
  131. if (! show)
  132. return;
  133. if (const char* const filename = uiOpenFile(false, "Open MIDI File", "MIDI Files (*.mid *.midi);;"))
  134. uiCustomDataChanged("file", filename);
  135. uiClosed();
  136. }
  137. // -------------------------------------------------------------------
  138. // Plugin state calls
  139. char* getState() const override
  140. {
  141. return fMidiOut.getState();
  142. }
  143. void setState(const char* const data) override
  144. {
  145. fMidiOut.setState(data);
  146. }
  147. void setStateFromFile(const char* const filename) override
  148. {
  149. _loadMidiFile(filename);
  150. }
  151. // -------------------------------------------------------------------
  152. // AbstractMidiPlayer calls
  153. void writeMidiEvent(const uint8_t port, const long double timePosFrame, const RawMidiEvent* const event) override
  154. {
  155. NativeMidiEvent midiEvent;
  156. midiEvent.port = port;
  157. midiEvent.time = uint32_t(timePosFrame);
  158. midiEvent.size = event->size;
  159. midiEvent.data[0] = event->data[0];
  160. midiEvent.data[1] = event->data[1];
  161. midiEvent.data[2] = event->data[2];
  162. midiEvent.data[3] = event->data[3];
  163. NativePluginClass::writeMidiEvent(&midiEvent);
  164. }
  165. // -------------------------------------------------------------------
  166. private:
  167. MidiPattern fMidiOut;
  168. double fFileLength;
  169. bool fNeedsAllNotesOff;
  170. bool fWasPlayingBefore;
  171. NativeMidiPrograms fPrograms;
  172. void _loadMidiFile(const char* const filename)
  173. {
  174. fMidiOut.clear();
  175. using namespace water;
  176. const String jfilename = String(CharPointer_UTF8(filename));
  177. File file(jfilename);
  178. if (! file.existsAsFile())
  179. return;
  180. FileInputStream fileStream(file);
  181. MidiFile midiFile;
  182. if (! midiFile.readFrom(fileStream))
  183. return;
  184. midiFile.convertTimestampTicksToSeconds();
  185. const double sampleRate = getSampleRate();
  186. for (size_t i=0, numTracks = midiFile.getNumTracks(); i<numTracks; ++i)
  187. {
  188. const MidiMessageSequence* const track(midiFile.getTrack(i));
  189. CARLA_SAFE_ASSERT_CONTINUE(track != nullptr);
  190. for (int j=0, numEvents = track->getNumEvents(); j<numEvents; ++j)
  191. {
  192. const MidiMessageSequence::MidiEventHolder* const midiEventHolder(track->getEventPointer(j));
  193. CARLA_SAFE_ASSERT_CONTINUE(midiEventHolder != nullptr);
  194. const MidiMessage& midiMessage(midiEventHolder->message);
  195. const int dataSize = midiMessage.getRawDataSize();
  196. if (dataSize <= 0 || dataSize > MAX_EVENT_DATA_SIZE)
  197. continue;
  198. const uint8_t* const data = midiMessage.getRawData();
  199. if (! MIDI_IS_CHANNEL_MESSAGE(data[0]))
  200. continue;
  201. const double time = midiMessage.getTimeStamp() * sampleRate;
  202. // const double time = track->getEventTime(i) * sampleRate;
  203. CARLA_SAFE_ASSERT_CONTINUE(time >= 0.0);
  204. fMidiOut.addRaw(static_cast<uint64_t>(time), midiMessage.getRawData(), static_cast<uint8_t>(dataSize));
  205. }
  206. }
  207. fFileLength = midiFile.getLastTimestamp();
  208. fNeedsAllNotesOff = true;
  209. }
  210. PluginClassEND(MidiFilePlugin)
  211. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiFilePlugin)
  212. };
  213. // -----------------------------------------------------------------------
  214. static const NativePluginDescriptor midifileDesc = {
  215. /* category */ NATIVE_PLUGIN_CATEGORY_UTILITY,
  216. /* hints */ static_cast<NativePluginHints>(NATIVE_PLUGIN_IS_RTSAFE
  217. |NATIVE_PLUGIN_HAS_UI
  218. |NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE
  219. |NATIVE_PLUGIN_REQUESTS_IDLE
  220. |NATIVE_PLUGIN_USES_STATE
  221. |NATIVE_PLUGIN_USES_TIME),
  222. /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING,
  223. /* audioIns */ 0,
  224. /* audioOuts */ 0,
  225. /* midiIns */ 0,
  226. /* midiOuts */ 1,
  227. /* paramIns */ 0,
  228. /* paramOuts */ 0,
  229. /* name */ "MIDI File",
  230. /* label */ "midifile",
  231. /* maker */ "falkTX",
  232. /* copyright */ "GNU GPL v2+",
  233. PluginDescriptorFILL(MidiFilePlugin)
  234. };
  235. // -----------------------------------------------------------------------
  236. CARLA_EXPORT
  237. void carla_register_native_plugin_midifile();
  238. CARLA_EXPORT
  239. void carla_register_native_plugin_midifile()
  240. {
  241. carla_register_native_plugin(&midifileDesc);
  242. }
  243. // -----------------------------------------------------------------------