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.

291 lines
8.8KB

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