The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

448 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. namespace MidiFileHelpers
  18. {
  19. static void writeVariableLengthInt (OutputStream& out, unsigned int v)
  20. {
  21. unsigned int buffer = v & 0x7f;
  22. while ((v >>= 7) != 0)
  23. {
  24. buffer <<= 8;
  25. buffer |= ((v & 0x7f) | 0x80);
  26. }
  27. for (;;)
  28. {
  29. out.writeByte ((char) buffer);
  30. if (buffer & 0x80)
  31. buffer >>= 8;
  32. else
  33. break;
  34. }
  35. }
  36. static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept
  37. {
  38. unsigned int ch = ByteOrder::bigEndianInt (data);
  39. data += 4;
  40. if (ch != ByteOrder::bigEndianInt ("MThd"))
  41. {
  42. bool ok = false;
  43. if (ch == ByteOrder::bigEndianInt ("RIFF"))
  44. {
  45. for (int i = 0; i < 8; ++i)
  46. {
  47. ch = ByteOrder::bigEndianInt (data);
  48. data += 4;
  49. if (ch == ByteOrder::bigEndianInt ("MThd"))
  50. {
  51. ok = true;
  52. break;
  53. }
  54. }
  55. }
  56. if (! ok)
  57. return false;
  58. }
  59. unsigned int bytesRemaining = ByteOrder::bigEndianInt (data);
  60. data += 4;
  61. fileType = (short) ByteOrder::bigEndianShort (data);
  62. data += 2;
  63. numberOfTracks = (short) ByteOrder::bigEndianShort (data);
  64. data += 2;
  65. timeFormat = (short) ByteOrder::bigEndianShort (data);
  66. data += 2;
  67. bytesRemaining -= 6;
  68. data += bytesRemaining;
  69. return true;
  70. }
  71. static double convertTicksToSeconds (const double time,
  72. const MidiMessageSequence& tempoEvents,
  73. const int timeFormat)
  74. {
  75. if (timeFormat < 0)
  76. return time / (-(timeFormat >> 8) * (timeFormat & 0xff));
  77. double lastTime = 0.0, correctedTime = 0.0;
  78. const double tickLen = 1.0 / (timeFormat & 0x7fff);
  79. double secsPerTick = 0.5 * tickLen;
  80. const int numEvents = tempoEvents.getNumEvents();
  81. for (int i = 0; i < numEvents; ++i)
  82. {
  83. const MidiMessage& m = tempoEvents.getEventPointer(i)->message;
  84. const double eventTime = m.getTimeStamp();
  85. if (eventTime >= time)
  86. break;
  87. correctedTime += (eventTime - lastTime) * secsPerTick;
  88. lastTime = eventTime;
  89. if (m.isTempoMetaEvent())
  90. secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote();
  91. while (i + 1 < numEvents)
  92. {
  93. const MidiMessage& m2 = tempoEvents.getEventPointer(i + 1)->message;
  94. if (m2.getTimeStamp() != eventTime)
  95. break;
  96. if (m2.isTempoMetaEvent())
  97. secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote();
  98. ++i;
  99. }
  100. }
  101. return correctedTime + (time - lastTime) * secsPerTick;
  102. }
  103. // a comparator that puts all the note-offs before note-ons that have the same time
  104. struct Sorter
  105. {
  106. static int compareElements (const MidiMessageSequence::MidiEventHolder* const first,
  107. const MidiMessageSequence::MidiEventHolder* const second) noexcept
  108. {
  109. const double diff = (first->message.getTimeStamp() - second->message.getTimeStamp());
  110. if (diff > 0) return 1;
  111. if (diff < 0) return -1;
  112. if (first->message.isNoteOff() && second->message.isNoteOn()) return -1;
  113. if (first->message.isNoteOn() && second->message.isNoteOff()) return 1;
  114. return 0;
  115. }
  116. };
  117. template <typename MethodType>
  118. static void findAllMatchingEvents (const OwnedArray<MidiMessageSequence>& tracks,
  119. MidiMessageSequence& results,
  120. MethodType method)
  121. {
  122. for (int i = 0; i < tracks.size(); ++i)
  123. {
  124. const MidiMessageSequence& track = *tracks.getUnchecked(i);
  125. const int numEvents = track.getNumEvents();
  126. for (int j = 0; j < numEvents; ++j)
  127. {
  128. const MidiMessage& m = track.getEventPointer(j)->message;
  129. if ((m.*method)())
  130. results.addEvent (m);
  131. }
  132. }
  133. }
  134. }
  135. //==============================================================================
  136. MidiFile::MidiFile()
  137. : timeFormat ((short) (unsigned short) 0xe728)
  138. {
  139. }
  140. MidiFile::~MidiFile()
  141. {
  142. }
  143. MidiFile::MidiFile (const MidiFile& other)
  144. : timeFormat (other.timeFormat)
  145. {
  146. tracks.addCopiesOf (other.tracks);
  147. }
  148. MidiFile& MidiFile::operator= (const MidiFile& other)
  149. {
  150. timeFormat = other.timeFormat;
  151. tracks.clear();
  152. tracks.addCopiesOf (other.tracks);
  153. return *this;
  154. }
  155. void MidiFile::clear()
  156. {
  157. tracks.clear();
  158. }
  159. //==============================================================================
  160. int MidiFile::getNumTracks() const noexcept
  161. {
  162. return tracks.size();
  163. }
  164. const MidiMessageSequence* MidiFile::getTrack (const int index) const noexcept
  165. {
  166. return tracks [index];
  167. }
  168. void MidiFile::addTrack (const MidiMessageSequence& trackSequence)
  169. {
  170. tracks.add (new MidiMessageSequence (trackSequence));
  171. }
  172. //==============================================================================
  173. short MidiFile::getTimeFormat() const noexcept
  174. {
  175. return timeFormat;
  176. }
  177. void MidiFile::setTicksPerQuarterNote (const int ticks) noexcept
  178. {
  179. timeFormat = (short) ticks;
  180. }
  181. void MidiFile::setSmpteTimeFormat (const int framesPerSecond,
  182. const int subframeResolution) noexcept
  183. {
  184. timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution);
  185. }
  186. //==============================================================================
  187. void MidiFile::findAllTempoEvents (MidiMessageSequence& results) const
  188. {
  189. MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTempoMetaEvent);
  190. }
  191. void MidiFile::findAllTimeSigEvents (MidiMessageSequence& results) const
  192. {
  193. MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTimeSignatureMetaEvent);
  194. }
  195. void MidiFile::findAllKeySigEvents (MidiMessageSequence& results) const
  196. {
  197. MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isKeySignatureMetaEvent);
  198. }
  199. double MidiFile::getLastTimestamp() const
  200. {
  201. double t = 0.0;
  202. for (int i = tracks.size(); --i >= 0;)
  203. t = jmax (t, tracks.getUnchecked(i)->getEndTime());
  204. return t;
  205. }
  206. //==============================================================================
  207. bool MidiFile::readFrom (InputStream& sourceStream)
  208. {
  209. clear();
  210. MemoryBlock data;
  211. const int maxSensibleMidiFileSize = 200 * 1024 * 1024;
  212. // (put a sanity-check on the file size, as midi files are generally small)
  213. if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize))
  214. {
  215. size_t size = data.getSize();
  216. const uint8* d = static_cast<const uint8*> (data.getData());
  217. short fileType, expectedTracks;
  218. if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks))
  219. {
  220. size -= (size_t) (d - static_cast<const uint8*> (data.getData()));
  221. int track = 0;
  222. while (size > 0 && track < expectedTracks)
  223. {
  224. const int chunkType = (int) ByteOrder::bigEndianInt (d);
  225. d += 4;
  226. const int chunkSize = (int) ByteOrder::bigEndianInt (d);
  227. d += 4;
  228. if (chunkSize <= 0)
  229. break;
  230. if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk"))
  231. readNextTrack (d, chunkSize);
  232. size -= (size_t) chunkSize + 8;
  233. d += chunkSize;
  234. ++track;
  235. }
  236. return true;
  237. }
  238. }
  239. return false;
  240. }
  241. void MidiFile::readNextTrack (const uint8* data, int size)
  242. {
  243. double time = 0;
  244. uint8 lastStatusByte = 0;
  245. MidiMessageSequence result;
  246. while (size > 0)
  247. {
  248. int bytesUsed;
  249. const int delay = MidiMessage::readVariableLengthVal (data, bytesUsed);
  250. data += bytesUsed;
  251. size -= bytesUsed;
  252. time += delay;
  253. int messSize = 0;
  254. const MidiMessage mm (data, size, messSize, lastStatusByte, time);
  255. if (messSize <= 0)
  256. break;
  257. size -= messSize;
  258. data += messSize;
  259. result.addEvent (mm);
  260. const uint8 firstByte = *(mm.getRawData());
  261. if ((firstByte & 0xf0) != 0xf0)
  262. lastStatusByte = firstByte;
  263. }
  264. // use a sort that puts all the note-offs before note-ons that have the same time
  265. MidiFileHelpers::Sorter sorter;
  266. result.list.sort (sorter, true);
  267. addTrack (result);
  268. tracks.getLast()->updateMatchedPairs();
  269. }
  270. //==============================================================================
  271. void MidiFile::convertTimestampTicksToSeconds()
  272. {
  273. MidiMessageSequence tempoEvents;
  274. findAllTempoEvents (tempoEvents);
  275. findAllTimeSigEvents (tempoEvents);
  276. if (timeFormat != 0)
  277. {
  278. for (int i = 0; i < tracks.size(); ++i)
  279. {
  280. const MidiMessageSequence& ms = *tracks.getUnchecked(i);
  281. for (int j = ms.getNumEvents(); --j >= 0;)
  282. {
  283. MidiMessage& m = ms.getEventPointer(j)->message;
  284. m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat));
  285. }
  286. }
  287. }
  288. }
  289. //==============================================================================
  290. bool MidiFile::writeTo (OutputStream& out, int midiFileType)
  291. {
  292. jassert (midiFileType >= 0 && midiFileType <= 2);
  293. if (! out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"))) return false;
  294. if (! out.writeIntBigEndian (6)) return false;
  295. if (! out.writeShortBigEndian ((short) midiFileType)) return false;
  296. if (! out.writeShortBigEndian ((short) tracks.size())) return false;
  297. if (! out.writeShortBigEndian (timeFormat)) return false;
  298. for (int i = 0; i < tracks.size(); ++i)
  299. if (! writeTrack (out, i))
  300. return false;
  301. out.flush();
  302. return true;
  303. }
  304. bool MidiFile::writeTrack (OutputStream& mainOut, const int trackNum)
  305. {
  306. MemoryOutputStream out;
  307. const MidiMessageSequence& ms = *tracks.getUnchecked (trackNum);
  308. int lastTick = 0;
  309. uint8 lastStatusByte = 0;
  310. bool endOfTrackEventWritten = false;
  311. for (int i = 0; i < ms.getNumEvents(); ++i)
  312. {
  313. const MidiMessage& mm = ms.getEventPointer(i)->message;
  314. if (mm.isEndOfTrackMetaEvent())
  315. endOfTrackEventWritten = true;
  316. const int tick = roundToInt (mm.getTimeStamp());
  317. const int delta = jmax (0, tick - lastTick);
  318. MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta);
  319. lastTick = tick;
  320. const uint8* data = mm.getRawData();
  321. int dataSize = mm.getRawDataSize();
  322. const uint8 statusByte = data[0];
  323. if (statusByte == lastStatusByte
  324. && (statusByte & 0xf0) != 0xf0
  325. && dataSize > 1
  326. && i > 0)
  327. {
  328. ++data;
  329. --dataSize;
  330. }
  331. else if (statusByte == 0xf0) // Write sysex message with length bytes.
  332. {
  333. out.writeByte ((char) statusByte);
  334. ++data;
  335. --dataSize;
  336. MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize);
  337. }
  338. out.write (data, (size_t) dataSize);
  339. lastStatusByte = statusByte;
  340. }
  341. if (! endOfTrackEventWritten)
  342. {
  343. out.writeByte (0); // (tick delta)
  344. const MidiMessage m (MidiMessage::endOfTrack());
  345. out.write (m.getRawData(), (size_t) m.getRawDataSize());
  346. }
  347. if (! mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"))) return false;
  348. if (! mainOut.writeIntBigEndian ((int) out.getDataSize())) return false;
  349. mainOut << out;
  350. return true;
  351. }