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.

424 lines
12KB

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