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) {}
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());
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 {
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;
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)
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{
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//==============================================================================
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())
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
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
int size() const noexcept
Definition juce_Array.h:215
ElementType * begin() noexcept
Definition juce_Array.h:328
ElementType * end() noexcept
Definition juce_Array.h:344
void add(const ElementType &newElement)
Definition juce_Array.h:418
void clear()
Definition juce_Array.h:188
static JUCE_CONSTEXPR uint16 bigEndianShort(const void *bytes) noexcept
static JUCE_CONSTEXPR uint32 bigEndianInt(const void *bytes) noexcept
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
void * getData() noexcept
size_t getSize() const noexcept
size_t getDataSize() const noexcept
bool write(const void *, size_t) override
void convertTimestampTicksToSeconds()
void addTrack(const MidiMessageSequence &trackSequence)
void setTicksPerQuarterNote(int ticksPerQuarterNote) noexcept
int getNumTracks() const noexcept
short getTimeFormat() const noexcept
void setSmpteTimeFormat(int framesPerSecond, int subframeResolution) noexcept
double getLastTimestamp() const
bool readFrom(InputStream &sourceStream, bool createMatchingNoteOffs=true)
MidiFile & operator=(const MidiFile &)
void findAllTimeSigEvents(MidiMessageSequence &timeSigEvents) const
void findAllKeySigEvents(MidiMessageSequence &keySigEvents) const
void findAllTempoEvents(MidiMessageSequence &tempoChangeEvents) const
const MidiMessageSequence * getTrack(int index) const noexcept
bool writeTo(OutputStream &destStream, int midiFileType=1) const
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
static int readVariableLengthVal(const uint8 *data, int &numBytesUsed) noexcept
bool isKeySignatureMetaEvent() const noexcept
bool isTimeSignatureMetaEvent() const noexcept
bool isTempoMetaEvent() const noexcept
static MidiMessage endOfTrack() noexcept
virtual bool writeByte(char byte)
virtual bool writeIntBigEndian(int value)