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.

289 lines
8.7KB

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