OpenShot Library | OpenShotAudio  0.2.2
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
Turns 2 bytes into a big-endian integer.
static JUCE_CONSTEXPR uint32 bigEndianInt(const void *bytes) noexcept
Turns 4 bytes into a big-endian integer.
The base class for streams that read data.
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
Reads from the stream and appends the data to a MemoryBlock.
A class to hold a resizable block of raw data.
void * getData() noexcept
Returns a void pointer to the data.
size_t getSize() const noexcept
Returns the block's current allocated size, in bytes.
Writes data to an internal memory buffer, which grows as required.
size_t getDataSize() const noexcept
Returns the number of bytes of data that have been written to the stream.
bool write(const void *, size_t) override
Writes a block of data to the stream.
Reads/writes standard midi format files.
Definition: juce_MidiFile.h:46
void convertTimestampTicksToSeconds()
Converts the timestamp of all the midi events from midi ticks to seconds.
void addTrack(const MidiMessageSequence &trackSequence)
Adds a midi track to the file.
void setTicksPerQuarterNote(int ticksPerQuarterNote) noexcept
Sets the time format to use when this file is written to a stream.
int getNumTracks() const noexcept
Returns the number of tracks in the file.
short getTimeFormat() const noexcept
Returns the raw time format code that will be written to a stream.
void setSmpteTimeFormat(int framesPerSecond, int subframeResolution) noexcept
Sets the time format to use when this file is written to a stream.
double getLastTimestamp() const
Returns the latest timestamp in any of the tracks.
bool readFrom(InputStream &sourceStream, bool createMatchingNoteOffs=true)
Reads a midi file format stream.
MidiFile & operator=(const MidiFile &)
Copies from another MidiFile object.
~MidiFile()
Destructor.
MidiFile()
Creates an empty MidiFile object.
void findAllTimeSigEvents(MidiMessageSequence &timeSigEvents) const
Makes a list of all the time-signature meta-events from all tracks in the midi file.
void findAllKeySigEvents(MidiMessageSequence &keySigEvents) const
Makes a list of all the time-signature meta-events from all tracks in the midi file.
void findAllTempoEvents(MidiMessageSequence &tempoChangeEvents) const
Makes a list of all the tempo-change meta-events from all tracks in the midi file.
void clear()
Removes all midi tracks from the file.
const MidiMessageSequence * getTrack(int index) const noexcept
Returns a pointer to one of the tracks in the file.
bool writeTo(OutputStream &destStream, int midiFileType=1) const
Writes the midi tracks as a standard midi file.
MidiMessage message
The message itself, whose timestamp is used to specify the event's time.
A sequence of timestamped midi messages.
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
Inserts a midi message into the sequence.
MidiEventHolder * getEventPointer(int index) const noexcept
Returns a pointer to one of the events.
int getNumEvents() const noexcept
Returns the number of events in the sequence.
Encapsulates a MIDI message.
static int readVariableLengthVal(const uint8 *data, int &numBytesUsed) noexcept
Reads a midi variable-length integer.
bool isKeySignatureMetaEvent() const noexcept
Returns true if this is a 'key-signature' meta-event.
bool isTimeSignatureMetaEvent() const noexcept
Returns true if this is a 'time-signature' meta-event.
bool isTempoMetaEvent() const noexcept
Returns true if this is a 'tempo' meta-event.
static MidiMessage endOfTrack() noexcept
Creates an end-of-track meta-event.
The base class for streams that write data to some kind of destination.
virtual bool writeByte(char byte)
Writes a single byte to the stream.
virtual bool writeIntBigEndian(int value)
Writes a 32-bit integer to the stream in a big-endian byte order.
virtual bool writeShortBigEndian(short value)
Writes a 16-bit integer to the stream in a big-endian byte order.
virtual void flush()=0
If the stream is using a buffer, this will ensure it gets written out to the destination.