OpenShot Library | libopenshot-audio  0.2.0
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 
266  int track = 0;
267 
268  while (size > 0 && track < expectedTracks)
269  {
270  auto chunkType = (int) ByteOrder::bigEndianInt (d);
271  d += 4;
272  auto chunkSize = (int) ByteOrder::bigEndianInt (d);
273  d += 4;
274 
275  if (chunkSize <= 0)
276  break;
277 
278  if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk"))
279  readNextTrack (d, chunkSize, createMatchingNoteOffs);
280 
281  size -= (size_t) chunkSize + 8;
282  d += chunkSize;
283  ++track;
284  }
285 
286  return true;
287  }
288  }
289 
290  return false;
291 }
292 
293 void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs)
294 {
295  double time = 0;
296  uint8 lastStatusByte = 0;
297 
298  MidiMessageSequence result;
299 
300  while (size > 0)
301  {
302  int bytesUsed;
303  auto delay = MidiMessage::readVariableLengthVal (data, bytesUsed);
304  data += bytesUsed;
305  size -= bytesUsed;
306  time += delay;
307 
308  int messSize = 0;
309  const MidiMessage mm (data, size, messSize, lastStatusByte, time);
310 
311  if (messSize <= 0)
312  break;
313 
314  size -= messSize;
315  data += messSize;
316 
317  result.addEvent (mm);
318 
319  auto firstByte = *(mm.getRawData());
320 
321  if ((firstByte & 0xf0) != 0xf0)
322  lastStatusByte = firstByte;
323  }
324 
325  // sort so that we put all the note-offs before note-ons that have the same time
326  std::stable_sort (result.list.begin(), result.list.end(),
329  {
330  auto t1 = a->message.getTimeStamp();
331  auto t2 = b->message.getTimeStamp();
332 
333  if (t1 < t2) return true;
334  if (t2 < t1) return false;
335 
336  return a->message.isNoteOff() && b->message.isNoteOn();
337  });
338 
339  addTrack (result);
340 
341  if (createMatchingNoteOffs)
342  tracks.getLast()->updateMatchedPairs();
343 }
344 
345 //==============================================================================
347 {
348  MidiMessageSequence tempoEvents;
349  findAllTempoEvents (tempoEvents);
350  findAllTimeSigEvents (tempoEvents);
351 
352  if (timeFormat != 0)
353  {
354  for (auto* ms : tracks)
355  {
356  for (int j = ms->getNumEvents(); --j >= 0;)
357  {
358  auto& m = ms->getEventPointer(j)->message;
359  m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat));
360  }
361  }
362  }
363 }
364 
365 //==============================================================================
366 bool MidiFile::writeTo (OutputStream& out, int midiFileType) const
367 {
368  jassert (midiFileType >= 0 && midiFileType <= 2);
369 
370  if (! out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"))) return false;
371  if (! out.writeIntBigEndian (6)) return false;
372  if (! out.writeShortBigEndian ((short) midiFileType)) return false;
373  if (! out.writeShortBigEndian ((short) tracks.size())) return false;
374  if (! out.writeShortBigEndian (timeFormat)) return false;
375 
376  for (auto* ms : tracks)
377  if (! writeTrack (out, *ms))
378  return false;
379 
380  out.flush();
381  return true;
382 }
383 
384 bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) const
385 {
386  MemoryOutputStream out;
387 
388  int lastTick = 0;
389  uint8 lastStatusByte = 0;
390  bool endOfTrackEventWritten = false;
391 
392  for (int i = 0; i < ms.getNumEvents(); ++i)
393  {
394  auto& mm = ms.getEventPointer(i)->message;
395 
396  if (mm.isEndOfTrackMetaEvent())
397  endOfTrackEventWritten = true;
398 
399  auto tick = roundToInt (mm.getTimeStamp());
400  auto delta = jmax (0, tick - lastTick);
401  MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta);
402  lastTick = tick;
403 
404  auto* data = mm.getRawData();
405  auto dataSize = mm.getRawDataSize();
406  auto statusByte = data[0];
407 
408  if (statusByte == lastStatusByte
409  && (statusByte & 0xf0) != 0xf0
410  && dataSize > 1
411  && i > 0)
412  {
413  ++data;
414  --dataSize;
415  }
416  else if (statusByte == 0xf0) // Write sysex message with length bytes.
417  {
418  out.writeByte ((char) statusByte);
419 
420  ++data;
421  --dataSize;
422 
423  MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize);
424  }
425 
426  out.write (data, (size_t) dataSize);
427  lastStatusByte = statusByte;
428  }
429 
430  if (! endOfTrackEventWritten)
431  {
432  out.writeByte (0); // (tick delta)
433  auto m = MidiMessage::endOfTrack();
434  out.write (m.getRawData(), (size_t) m.getRawDataSize());
435  }
436 
437  if (! mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"))) return false;
438  if (! mainOut.writeIntBigEndian ((int) out.getDataSize())) return false;
439 
440  mainOut << out;
441 
442  return true;
443 }
444 
445 } // namespace juce
size_t getSize() const noexcept
Returns the block&#39;s current allocated size, in bytes.
virtual bool writeShortBigEndian(short value)
Writes a 16-bit integer to the stream in a big-endian byte order.
void findAllTempoEvents(MidiMessageSequence &tempoChangeEvents) const
Makes a list of all the tempo-change meta-events from all tracks in the midi file.
Reads/writes standard midi format files.
Definition: juce_MidiFile.h:45
Encapsulates a MIDI message.
virtual bool writeByte(char byte)
Writes a single byte to the stream.
The base class for streams that read data.
bool readFrom(InputStream &sourceStream, bool createMatchingNoteOffs=true)
Reads a midi file format stream.
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
Inserts a midi message into the sequence.
STL namespace.
void clear()
Removes all midi tracks from the file.
static JUCE_CONSTEXPR uint32 bigEndianInt(const void *bytes) noexcept
Turns 4 bytes into a big-endian integer.
int getNumTracks() const noexcept
Returns the number of tracks in the file.
MidiFile()
Creates an empty MidiFile object.
MidiEventHolder * getEventPointer(int index) const noexcept
Returns a pointer to one of the events.
bool isTimeSignatureMetaEvent() const noexcept
Returns true if this is a &#39;time-signature&#39; meta-event.
static MidiMessage endOfTrack() noexcept
Creates an end-of-track meta-event.
virtual void flush()=0
If the stream is using a buffer, this will ensure it gets written out to the destination.
void * getData() const noexcept
Returns a void pointer to the data.
double getLastTimestamp() const
Returns the latest timestamp in any of the tracks.
void addTrack(const MidiMessageSequence &trackSequence)
Adds a midi track to the file.
MidiFile & operator=(const MidiFile &)
Copies from another MidiFile object.
bool isTempoMetaEvent() const noexcept
Returns true if this is a &#39;tempo&#39; meta-event.
virtual bool writeIntBigEndian(int value)
Writes a 32-bit integer to the stream in a big-endian byte order.
bool writeTo(OutputStream &destStream, int midiFileType=1) const
Writes the midi tracks as a standard midi file.
void setSmpteTimeFormat(int framesPerSecond, int subframeResolution) noexcept
Sets the time format to use when this file is written to a stream.
bool write(const void *, size_t) override
Writes a block of data to the stream.
MidiMessage message
The message itself, whose timestamp is used to specify the event&#39;s time.
void setTicksPerQuarterNote(int ticksPerQuarterNote) noexcept
Sets the time format to use when this file is written to a stream.
The base class for streams that write data to some kind of destination.
static JUCE_CONSTEXPR uint16 bigEndianShort(const void *bytes) noexcept
Turns 2 bytes into a big-endian integer.
bool isNoteOff(bool returnTrueForNoteOnVelocity0=true) const noexcept
Returns true if this message is a &#39;key-up&#39; event.
void convertTimestampTicksToSeconds()
Converts the timestamp of all the midi events from midi ticks to seconds.
size_t getDataSize() const noexcept
Returns the number of bytes of data that have been written to the stream.
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
Reads from the stream and appends the data to a MemoryBlock.
static int readVariableLengthVal(const uint8 *data, int &numBytesUsed) noexcept
Reads a midi variable-length integer.
A sequence of timestamped midi messages.
Structure used to hold midi events in the sequence.
const uint8 * getRawData() const noexcept
Returns a pointer to the raw midi data.
double getTimeStamp() const noexcept
Returns the timestamp associated with this message.
bool isKeySignatureMetaEvent() const noexcept
Returns true if this is a &#39;key-signature&#39; meta-event.
short getTimeFormat() const noexcept
Returns the raw time format code that will be written to a stream.
void findAllTimeSigEvents(MidiMessageSequence &timeSigEvents) const
Makes a list of all the time-signature meta-events from all tracks in the midi file.
~MidiFile()
Destructor.
Writes data to an internal memory buffer, which grows as required.
void findAllKeySigEvents(MidiMessageSequence &keySigEvents) const
Makes a list of all the time-signature meta-events from all tracks in the midi file.
A class to hold a resizable block of raw data.
int getNumEvents() const noexcept
Returns the number of events in the sequence.
const MidiMessageSequence * getTrack(int index) const noexcept
Returns a pointer to one of the tracks in the file.