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
23namespace juce
24{
25
26namespace 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//==============================================================================
155MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) {}
157
158MidiFile::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//==============================================================================
190int MidiFile::getNumTracks() const noexcept
191{
192 return tracks.size();
193}
194
195const MidiMessageSequence* MidiFile::getTrack (int index) const noexcept
196{
197 return tracks[index];
198}
199
200void MidiFile::addTrack (const MidiMessageSequence& trackSequence)
201{
202 tracks.add (new MidiMessageSequence (trackSequence));
203}
204
205//==============================================================================
206short MidiFile::getTimeFormat() const noexcept
207{
208 return timeFormat;
209}
210
211void MidiFile::setTicksPerQuarterNote (int ticks) noexcept
212{
213 timeFormat = (short) ticks;
214}
215
216void 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//==============================================================================
248bool 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
294void 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//==============================================================================
367bool 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
385bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) const
386{
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.