PageRenderTime 67ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.cpp

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