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.

439 lines
13KB

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