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
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//==============================================================================
191{
192 return tracks.size();
193}
194
195const MidiMessageSequence* MidiFile::getTrack (int index) const noexcept
196{
197 return tracks[index];
198}
199
204
205//==============================================================================
207{
208 return timeFormat;
209}
210
212{
213 timeFormat = (short) ticks;
214}
215
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//==============================================================================
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
293void 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;
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)
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(),
327 [] (const MidiMessageSequence::MidiEventHolder* a,
328 const MidiMessageSequence::MidiEventHolder* b)
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{
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//==============================================================================
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
384bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) const
385{
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())
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
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
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:60
int size() const noexcept
Returns the current number of elements in the array.
Definition juce_Array.h:219
ElementType * begin() const noexcept
Returns a pointer to the first element in the array.
Definition juce_Array.h:309
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition juce_Array.h:375
void clear()
Removes all elements from the array.
Definition juce_Array.h:192
ElementType * end() const noexcept
Returns a pointer to the element which follows the last element in the array.
Definition juce_Array.h:317
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.
size_t getSize() const noexcept
Returns the block's current allocated size, in bytes.
void * getData() const noexcept
Returns a void pointer to the data.
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.
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.
A sequence of timestamped midi messages.
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
Inserts a midi message into 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.