OpenShot Audio Library | OpenShotAudio  0.3.3
juce_MidiFile.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the JUCE library.
5  Copyright (c) 2017 - ROLI Ltd.
6 
7  JUCE is an open source library subject to commercial or open-source
8  licensing.
9 
10  The code included in this file is provided under the terms of the ISC license
11  http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12  To use, copy, modify, and/or distribute this software for any purpose with or
13  without fee is hereby granted provided that the above copyright notice and
14  this permission notice appear in all copies.
15 
16  JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17  EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18  DISCLAIMED.
19 
20  ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 namespace MidiFileHelpers
27 {
28  static void writeVariableLengthInt (OutputStream& out, uint32 v)
29  {
30  auto buffer = v & 0x7f;
31 
32  while ((v >>= 7) != 0)
33  {
34  buffer <<= 8;
35  buffer |= ((v & 0x7f) | 0x80);
36  }
37 
38  for (;;)
39  {
40  out.writeByte ((char) buffer);
41 
42  if (buffer & 0x80)
43  buffer >>= 8;
44  else
45  break;
46  }
47  }
48 
49  static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept
50  {
51  auto ch = ByteOrder::bigEndianInt (data);
52  data += 4;
53 
54  if (ch != ByteOrder::bigEndianInt ("MThd"))
55  {
56  bool ok = false;
57 
58  if (ch == ByteOrder::bigEndianInt ("RIFF"))
59  {
60  for (int i = 0; i < 8; ++i)
61  {
62  ch = ByteOrder::bigEndianInt (data);
63  data += 4;
64 
65  if (ch == ByteOrder::bigEndianInt ("MThd"))
66  {
67  ok = true;
68  break;
69  }
70  }
71  }
72 
73  if (! ok)
74  return false;
75  }
76 
77  auto bytesRemaining = ByteOrder::bigEndianInt (data);
78  data += 4;
79  fileType = (short) ByteOrder::bigEndianShort (data);
80  data += 2;
81  numberOfTracks = (short) ByteOrder::bigEndianShort (data);
82  data += 2;
83  timeFormat = (short) ByteOrder::bigEndianShort (data);
84  data += 2;
85  bytesRemaining -= 6;
86  data += bytesRemaining;
87 
88  return true;
89  }
90 
91  static double convertTicksToSeconds (double time,
92  const MidiMessageSequence& tempoEvents,
93  int timeFormat)
94  {
95  if (timeFormat < 0)
96  return time / (-(timeFormat >> 8) * (timeFormat & 0xff));
97 
98  double lastTime = 0, correctedTime = 0;
99  auto tickLen = 1.0 / (timeFormat & 0x7fff);
100  auto secsPerTick = 0.5 * tickLen;
101  auto numEvents = tempoEvents.getNumEvents();
102 
103  for (int i = 0; i < numEvents; ++i)
104  {
105  auto& m = tempoEvents.getEventPointer(i)->message;
106  auto eventTime = m.getTimeStamp();
107 
108  if (eventTime >= time)
109  break;
110 
111  correctedTime += (eventTime - lastTime) * secsPerTick;
112  lastTime = eventTime;
113 
114  if (m.isTempoMetaEvent())
115  secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote();
116 
117  while (i + 1 < numEvents)
118  {
119  auto& m2 = tempoEvents.getEventPointer(i + 1)->message;
120 
121  if (m2.getTimeStamp() != eventTime)
122  break;
123 
124  if (m2.isTempoMetaEvent())
125  secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote();
126 
127  ++i;
128  }
129  }
130 
131  return correctedTime + (time - lastTime) * secsPerTick;
132  }
133 
134  template <typename MethodType>
135  static void findAllMatchingEvents (const OwnedArray<MidiMessageSequence>& tracks,
136  MidiMessageSequence& results,
137  MethodType method)
138  {
139  for (auto* track : tracks)
140  {
141  auto numEvents = track->getNumEvents();
142 
143  for (int j = 0; j < numEvents; ++j)
144  {
145  auto& m = track->getEventPointer(j)->message;
146 
147  if ((m.*method)())
148  results.addEvent (m);
149  }
150  }
151  }
152 }
153 
154 //==============================================================================
155 MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) {}
157 
158 MidiFile::MidiFile (const MidiFile& other) : timeFormat (other.timeFormat)
159 {
160  tracks.addCopiesOf (other.tracks);
161 }
162 
164 {
165  tracks.clear();
166  tracks.addCopiesOf (other.tracks);
167  timeFormat = other.timeFormat;
168  return *this;
169 }
170 
172  : tracks (std::move (other.tracks)),
173  timeFormat (other.timeFormat)
174 {
175 }
176 
178 {
179  tracks = std::move (other.tracks);
180  timeFormat = other.timeFormat;
181  return *this;
182 }
183 
185 {
186  tracks.clear();
187 }
188 
189 //==============================================================================
190 int MidiFile::getNumTracks() const noexcept
191 {
192  return tracks.size();
193 }
194 
195 const MidiMessageSequence* MidiFile::getTrack (int index) const noexcept
196 {
197  return tracks[index];
198 }
199 
200 void MidiFile::addTrack (const MidiMessageSequence& trackSequence)
201 {
202  tracks.add (new MidiMessageSequence (trackSequence));
203 }
204 
205 //==============================================================================
206 short MidiFile::getTimeFormat() const noexcept
207 {
208  return timeFormat;
209 }
210 
211 void MidiFile::setTicksPerQuarterNote (int ticks) noexcept
212 {
213  timeFormat = (short) ticks;
214 }
215 
216 void MidiFile::setSmpteTimeFormat (int framesPerSecond, int subframeResolution) noexcept
217 {
218  timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution);
219 }
220 
221 //==============================================================================
223 {
224  MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTempoMetaEvent);
225 }
226 
228 {
229  MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTimeSignatureMetaEvent);
230 }
231 
233 {
234  MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isKeySignatureMetaEvent);
235 }
236 
238 {
239  double t = 0.0;
240 
241  for (auto* ms : tracks)
242  t = jmax (t, ms->getEndTime());
243 
244  return t;
245 }
246 
247 //==============================================================================
248 bool MidiFile::readFrom (InputStream& sourceStream, bool createMatchingNoteOffs)
249 {
250  clear();
251  MemoryBlock data;
252 
253  const int maxSensibleMidiFileSize = 200 * 1024 * 1024;
254 
255  // (put a sanity-check on the file size, as midi files are generally small)
256  if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize))
257  {
258  auto size = data.getSize();
259  auto d = static_cast<const uint8*> (data.getData());
260  short fileType, expectedTracks;
261 
262  if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks))
263  {
264  size -= (size_t) (d - static_cast<const uint8*> (data.getData()));
265  int track = 0;
266 
267  for (;;)
268  {
269  auto chunkType = (int) ByteOrder::bigEndianInt (d);
270  d += 4;
271  auto chunkSize = (int) ByteOrder::bigEndianInt (d);
272  d += 4;
273 
274  if (chunkSize <= 0 || (size_t) chunkSize > size)
275  break;
276 
277  if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk"))
278  readNextTrack (d, chunkSize, createMatchingNoteOffs);
279 
280  if (++track >= expectedTracks)
281  break;
282 
283  size -= (size_t) chunkSize + 8;
284  d += chunkSize;
285  }
286 
287  return true;
288  }
289  }
290 
291  return false;
292 }
293 
294 void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs)
295 {
296  double time = 0;
297  uint8 lastStatusByte = 0;
298 
299  MidiMessageSequence result;
300 
301  while (size > 0)
302  {
303  int bytesUsed;
304  auto delay = MidiMessage::readVariableLengthVal (data, bytesUsed);
305  data += bytesUsed;
306  size -= bytesUsed;
307  time += delay;
308 
309  int messSize = 0;
310  const MidiMessage mm (data, size, messSize, lastStatusByte, time);
311 
312  if (messSize <= 0)
313  break;
314 
315  size -= messSize;
316  data += messSize;
317 
318  result.addEvent (mm);
319 
320  auto firstByte = *(mm.getRawData());
321 
322  if ((firstByte & 0xf0) != 0xf0)
323  lastStatusByte = firstByte;
324  }
325 
326  // sort so that we put all the note-offs before note-ons that have the same time
327  std::stable_sort (result.list.begin(), result.list.end(),
328  [] (const MidiMessageSequence::MidiEventHolder* a,
329  const MidiMessageSequence::MidiEventHolder* b)
330  {
331  auto t1 = a->message.getTimeStamp();
332  auto t2 = b->message.getTimeStamp();
333 
334  if (t1 < t2) return true;
335  if (t2 < t1) return false;
336 
337  return a->message.isNoteOff() && b->message.isNoteOn();
338  });
339 
340  addTrack (result);
341 
342  if (createMatchingNoteOffs)
343  tracks.getLast()->updateMatchedPairs();
344 }
345 
346 //==============================================================================
348 {
349  MidiMessageSequence tempoEvents;
350  findAllTempoEvents (tempoEvents);
351  findAllTimeSigEvents (tempoEvents);
352 
353  if (timeFormat != 0)
354  {
355  for (auto* ms : tracks)
356  {
357  for (int j = ms->getNumEvents(); --j >= 0;)
358  {
359  auto& m = ms->getEventPointer(j)->message;
360  m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat));
361  }
362  }
363  }
364 }
365 
366 //==============================================================================
367 bool MidiFile::writeTo (OutputStream& out, int midiFileType) const
368 {
369  jassert (midiFileType >= 0 && midiFileType <= 2);
370 
371  if (! out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"))) return false;
372  if (! out.writeIntBigEndian (6)) return false;
373  if (! out.writeShortBigEndian ((short) midiFileType)) return false;
374  if (! out.writeShortBigEndian ((short) tracks.size())) return false;
375  if (! out.writeShortBigEndian (timeFormat)) return false;
376 
377  for (auto* ms : tracks)
378  if (! writeTrack (out, *ms))
379  return false;
380 
381  out.flush();
382  return true;
383 }
384 
385 bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) const
386 {
387  MemoryOutputStream out;
388 
389  int lastTick = 0;
390  uint8 lastStatusByte = 0;
391  bool endOfTrackEventWritten = false;
392 
393  for (int i = 0; i < ms.getNumEvents(); ++i)
394  {
395  auto& mm = ms.getEventPointer(i)->message;
396 
397  if (mm.isEndOfTrackMetaEvent())
398  endOfTrackEventWritten = true;
399 
400  auto tick = roundToInt (mm.getTimeStamp());
401  auto delta = jmax (0, tick - lastTick);
402  MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta);
403  lastTick = tick;
404 
405  auto* data = mm.getRawData();
406  auto dataSize = mm.getRawDataSize();
407  auto statusByte = data[0];
408 
409  if (statusByte == lastStatusByte
410  && (statusByte & 0xf0) != 0xf0
411  && dataSize > 1
412  && i > 0)
413  {
414  ++data;
415  --dataSize;
416  }
417  else if (statusByte == 0xf0) // Write sysex message with length bytes.
418  {
419  out.writeByte ((char) statusByte);
420 
421  ++data;
422  --dataSize;
423 
424  MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize);
425  }
426 
427  out.write (data, (size_t) dataSize);
428  lastStatusByte = statusByte;
429  }
430 
431  if (! endOfTrackEventWritten)
432  {
433  out.writeByte (0); // (tick delta)
434  auto m = MidiMessage::endOfTrack();
435  out.write (m.getRawData(), (size_t) m.getRawDataSize());
436  }
437 
438  if (! mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"))) return false;
439  if (! mainOut.writeIntBigEndian ((int) out.getDataSize())) return false;
440 
441  mainOut << out;
442 
443  return true;
444 }
445 
446 } // namespace juce
static JUCE_CONSTEXPR uint16 bigEndianShort(const void *bytes) noexcept
static JUCE_CONSTEXPR uint32 bigEndianInt(const void *bytes) noexcept
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
void * getData() noexcept
size_t getSize() const noexcept
size_t getDataSize() const noexcept
bool write(const void *, size_t) override
void convertTimestampTicksToSeconds()
void addTrack(const MidiMessageSequence &trackSequence)
void setTicksPerQuarterNote(int ticksPerQuarterNote) noexcept
int getNumTracks() const noexcept
short getTimeFormat() const noexcept
void setSmpteTimeFormat(int framesPerSecond, int subframeResolution) noexcept
double getLastTimestamp() const
bool readFrom(InputStream &sourceStream, bool createMatchingNoteOffs=true)
MidiFile & operator=(const MidiFile &)
void findAllTimeSigEvents(MidiMessageSequence &timeSigEvents) const
void findAllKeySigEvents(MidiMessageSequence &keySigEvents) const
void findAllTempoEvents(MidiMessageSequence &tempoChangeEvents) const
const MidiMessageSequence * getTrack(int index) const noexcept
bool writeTo(OutputStream &destStream, int midiFileType=1) const
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
MidiEventHolder * getEventPointer(int index) const noexcept
static int readVariableLengthVal(const uint8 *data, int &numBytesUsed) noexcept
bool isKeySignatureMetaEvent() const noexcept
bool isTimeSignatureMetaEvent() const noexcept
bool isTempoMetaEvent() const noexcept
static MidiMessage endOfTrack() noexcept
virtual bool writeByte(char byte)
virtual bool writeIntBigEndian(int value)
virtual bool writeShortBigEndian(short value)
virtual void flush()=0