OpenShot Audio Library | OpenShotAudio: juce_MidiFile.cpp Source File

OpenShot Audio Library | OpenShotAudio 0.3.2

Loading...
Searching...
No Matches
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) {}
156MidiFile::~MidiFile() {}
157
158 MidiFile::MidiFile (const MidiFile& other) : timeFormat (other.timeFormat)
159{
160 tracks.addCopiesOf (other.tracks);
161}
162
163 MidiFile& MidiFile::operator= (const MidiFile& other)
164{
165 tracks.clear();
166 tracks.addCopiesOf (other.tracks);
167 timeFormat = other.timeFormat;
168 return *this;
169}
170
171 MidiFile::MidiFile (MidiFile&& other)
172 : tracks (std::move (other.tracks)),
173 timeFormat (other.timeFormat)
174{
175}
176
177 MidiFile& MidiFile::operator= (MidiFile&& other)
178{
179 tracks = std::move (other.tracks);
180 timeFormat = other.timeFormat;
181 return *this;
182}
183
184 void MidiFile::clear()
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//==============================================================================
222 void MidiFile::findAllTempoEvents (MidiMessageSequence& results) const
223{
224 MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTempoMetaEvent);
225}
226
227 void MidiFile::findAllTimeSigEvents (MidiMessageSequence& results) const
228{
229 MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTimeSignatureMetaEvent);
230}
231
232 void MidiFile::findAllKeySigEvents (MidiMessageSequence& results) const
233{
234 MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isKeySignatureMetaEvent);
235}
236
237 double MidiFile::getLastTimestamp() const
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
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//==============================================================================
347 void MidiFile::convertTimestampTicksToSeconds()
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
385bool 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
juce::ByteOrder::bigEndianShort
static JUCE_CONSTEXPR uint16 bigEndianShort(const void *bytes) noexcept
Definition: juce_ByteOrder.h:206
juce::ByteOrder::bigEndianInt
static JUCE_CONSTEXPR uint32 bigEndianInt(const void *bytes) noexcept
Definition: juce_ByteOrder.h:207
juce::InputStream
Definition: juce_InputStream.h:37
juce::InputStream::readIntoMemoryBlock
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
Definition: juce_InputStream.cpp:223
juce::MemoryBlock
Definition: juce_MemoryBlock.h:33
juce::MemoryBlock::getData
void * getData() noexcept
Definition: juce_MemoryBlock.h:91
juce::MemoryBlock::getSize
size_t getSize() const noexcept
Definition: juce_MemoryBlock.h:124
juce::MemoryOutputStream
Definition: juce_MemoryOutputStream.h:36
juce::MemoryOutputStream::getDataSize
size_t getDataSize() const noexcept
Definition: juce_MemoryOutputStream.h:80
juce::MemoryOutputStream::write
bool write(const void *, size_t) override
Definition: juce_MemoryOutputStream.cpp:102
juce::MidiFile
Definition: juce_MidiFile.h:42
juce::MidiFile::convertTimestampTicksToSeconds
void convertTimestampTicksToSeconds()
Definition: juce_MidiFile.cpp:347
juce::MidiFile::addTrack
void addTrack(const MidiMessageSequence &trackSequence)
Definition: juce_MidiFile.cpp:200
juce::MidiFile::setTicksPerQuarterNote
void setTicksPerQuarterNote(int ticksPerQuarterNote) noexcept
Definition: juce_MidiFile.cpp:211
juce::MidiFile::getNumTracks
int getNumTracks() const noexcept
Definition: juce_MidiFile.cpp:190
juce::MidiFile::getTimeFormat
short getTimeFormat() const noexcept
Definition: juce_MidiFile.cpp:206
juce::MidiFile::setSmpteTimeFormat
void setSmpteTimeFormat(int framesPerSecond, int subframeResolution) noexcept
Definition: juce_MidiFile.cpp:216
juce::MidiFile::getLastTimestamp
double getLastTimestamp() const
Definition: juce_MidiFile.cpp:237
juce::MidiFile::readFrom
bool readFrom(InputStream &sourceStream, bool createMatchingNoteOffs=true)
Definition: juce_MidiFile.cpp:248
juce::MidiFile::operator=
MidiFile & operator=(const MidiFile &)
Definition: juce_MidiFile.cpp:163
juce::MidiFile::~MidiFile
~MidiFile()
Definition: juce_MidiFile.cpp:156
juce::MidiFile::MidiFile
MidiFile()
Definition: juce_MidiFile.cpp:155
juce::MidiFile::findAllTimeSigEvents
void findAllTimeSigEvents(MidiMessageSequence &timeSigEvents) const
Definition: juce_MidiFile.cpp:227
juce::MidiFile::findAllKeySigEvents
void findAllKeySigEvents(MidiMessageSequence &keySigEvents) const
Definition: juce_MidiFile.cpp:232
juce::MidiFile::findAllTempoEvents
void findAllTempoEvents(MidiMessageSequence &tempoChangeEvents) const
Definition: juce_MidiFile.cpp:222
juce::MidiFile::clear
void clear()
Definition: juce_MidiFile.cpp:184
juce::MidiFile::getTrack
const MidiMessageSequence * getTrack(int index) const noexcept
Definition: juce_MidiFile.cpp:195
juce::MidiFile::writeTo
bool writeTo(OutputStream &destStream, int midiFileType=1) const
Definition: juce_MidiFile.cpp:367
juce::MidiMessageSequence::MidiEventHolder::message
MidiMessage message
Definition: juce_MidiMessageSequence.h:75
juce::MidiMessageSequence
Definition: juce_MidiMessageSequence.h:38
juce::MidiMessageSequence::addEvent
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
Definition: juce_MidiMessageSequence.cpp:172
juce::MidiMessageSequence::getEventPointer
MidiEventHolder * getEventPointer(int index) const noexcept
Definition: juce_MidiMessageSequence.cpp:85
juce::MidiMessageSequence::getNumEvents
int getNumEvents() const noexcept
Definition: juce_MidiMessageSequence.cpp:80
juce::MidiMessage
Definition: juce_MidiMessage.h:35
juce::MidiMessage::readVariableLengthVal
static int readVariableLengthVal(const uint8 *data, int &numBytesUsed) noexcept
Definition: juce_MidiMessage.cpp:60
juce::MidiMessage::isKeySignatureMetaEvent
bool isKeySignatureMetaEvent() const noexcept
Definition: juce_MidiMessage.cpp:855
juce::MidiMessage::isTimeSignatureMetaEvent
bool isTimeSignatureMetaEvent() const noexcept
Definition: juce_MidiMessage.cpp:815
juce::MidiMessage::isTempoMetaEvent
bool isTempoMetaEvent() const noexcept
Definition: juce_MidiMessage.cpp:760
juce::MidiMessage::endOfTrack
static MidiMessage endOfTrack() noexcept
Definition: juce_MidiMessage.cpp:877
juce::OutputStream
Definition: juce_OutputStream.h:38
juce::OutputStream::writeByte
virtual bool writeByte(char byte)
Definition: juce_OutputStream.cpp:83
juce::OutputStream::writeIntBigEndian
virtual bool writeIntBigEndian(int value)
Definition: juce_OutputStream.cpp:115
juce::OutputStream::writeShortBigEndian
virtual bool writeShortBigEndian(short value)
Definition: juce_OutputStream.cpp:103
juce::OutputStream::flush
virtual void flush()=0