2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../core/juce_StandardHeader.h"
30 #include "juce_MidiFile.h"
31 #include "../../io/streams/juce_MemoryOutputStream.h"
34 //==============================================================================
35 namespace MidiFileHelpers
37 void writeVariableLengthInt (OutputStream
& out
, unsigned int v
)
39 unsigned int buffer
= v
& 0x7F;
41 while ((v
>>= 7) != 0)
44 buffer
|= ((v
& 0x7F) | 0x80);
49 out
.writeByte ((char) buffer
);
58 bool parseMidiHeader (const uint8
* &data
, short& timeFormat
, short& fileType
, short& numberOfTracks
) noexcept
60 unsigned int ch
= (int) ByteOrder::bigEndianInt (data
);
63 if (ch
!= ByteOrder::bigEndianInt ("MThd"))
67 if (ch
== ByteOrder::bigEndianInt ("RIFF"))
69 for (int i
= 0; i
< 8; ++i
)
71 ch
= ByteOrder::bigEndianInt (data
);
74 if (ch
== ByteOrder::bigEndianInt ("MThd"))
86 unsigned int bytesRemaining
= ByteOrder::bigEndianInt (data
);
88 fileType
= (short) ByteOrder::bigEndianShort (data
);
90 numberOfTracks
= (short) ByteOrder::bigEndianShort (data
);
92 timeFormat
= (short) ByteOrder::bigEndianShort (data
);
95 data
+= bytesRemaining
;
100 double convertTicksToSeconds (const double time
,
101 const MidiMessageSequence
& tempoEvents
,
102 const int timeFormat
)
106 int numer
= 4, denom
= 4;
107 double tempoTime
= 0.0, correctedTempoTime
= 0.0;
108 const double tickLen
= 1.0 / (timeFormat
& 0x7fff);
109 double secsPerTick
= 0.5 * tickLen
;
110 const int numEvents
= tempoEvents
.getNumEvents();
112 for (int i
= 0; i
< numEvents
; ++i
)
114 const MidiMessage
& m
= tempoEvents
.getEventPointer(i
)->message
;
116 if (time
<= m
.getTimeStamp())
121 correctedTempoTime
= correctedTempoTime
122 + (m
.getTimeStamp() - tempoTime
) * secsPerTick
;
126 correctedTempoTime
= tickLen
* m
.getTimeStamp() / (((timeFormat
& 0x7fff) >> 8) * (timeFormat
& 0xff));
129 tempoTime
= m
.getTimeStamp();
131 if (m
.isTempoMetaEvent())
132 secsPerTick
= tickLen
* m
.getTempoSecondsPerQuarterNote();
133 else if (m
.isTimeSignatureMetaEvent())
134 m
.getTimeSignatureInfo (numer
, denom
);
136 while (i
+ 1 < numEvents
)
138 const MidiMessage
& m2
= tempoEvents
.getEventPointer(i
+ 1)->message
;
139 if (m2
.getTimeStamp() == tempoTime
)
143 if (m2
.isTempoMetaEvent())
144 secsPerTick
= tickLen
* m2
.getTempoSecondsPerQuarterNote();
145 else if (m2
.isTimeSignatureMetaEvent())
146 m2
.getTimeSignatureInfo (numer
, denom
);
155 return correctedTempoTime
+ (time
- tempoTime
) * secsPerTick
;
159 return time
/ (((timeFormat
& 0x7fff) >> 8) * (timeFormat
& 0xff));
163 // a comparator that puts all the note-offs before note-ons that have the same time
166 static int compareElements (const MidiMessageSequence::MidiEventHolder
* const first
,
167 const MidiMessageSequence::MidiEventHolder
* const second
) noexcept
169 const double diff
= (first
->message
.getTimeStamp() - second
->message
.getTimeStamp());
171 if (diff
> 0) return 1;
172 if (diff
< 0) return -1;
173 if (first
->message
.isNoteOff() && second
->message
.isNoteOn()) return -1;
174 if (first
->message
.isNoteOn() && second
->message
.isNoteOff()) return 1;
181 //==============================================================================
183 : timeFormat ((short) (unsigned short) 0xe728)
187 MidiFile::~MidiFile()
192 void MidiFile::clear()
197 //==============================================================================
198 int MidiFile::getNumTracks() const noexcept
200 return tracks
.size();
203 const MidiMessageSequence
* MidiFile::getTrack (const int index
) const noexcept
205 return tracks
[index
];
208 void MidiFile::addTrack (const MidiMessageSequence
& trackSequence
)
210 tracks
.add (new MidiMessageSequence (trackSequence
));
213 //==============================================================================
214 short MidiFile::getTimeFormat() const noexcept
219 void MidiFile::setTicksPerQuarterNote (const int ticks
) noexcept
221 timeFormat
= (short) ticks
;
224 void MidiFile::setSmpteTimeFormat (const int framesPerSecond
,
225 const int subframeResolution
) noexcept
227 timeFormat
= (short) (((-framesPerSecond
) << 8) | subframeResolution
);
230 //==============================================================================
231 void MidiFile::findAllTempoEvents (MidiMessageSequence
& tempoChangeEvents
) const
233 for (int i
= tracks
.size(); --i
>= 0;)
235 const int numEvents
= tracks
.getUnchecked(i
)->getNumEvents();
237 for (int j
= 0; j
< numEvents
; ++j
)
239 const MidiMessage
& m
= tracks
.getUnchecked(i
)->getEventPointer (j
)->message
;
241 if (m
.isTempoMetaEvent())
242 tempoChangeEvents
.addEvent (m
);
247 void MidiFile::findAllTimeSigEvents (MidiMessageSequence
& timeSigEvents
) const
249 for (int i
= tracks
.size(); --i
>= 0;)
251 const int numEvents
= tracks
.getUnchecked(i
)->getNumEvents();
253 for (int j
= 0; j
< numEvents
; ++j
)
255 const MidiMessage
& m
= tracks
.getUnchecked(i
)->getEventPointer (j
)->message
;
257 if (m
.isTimeSignatureMetaEvent())
258 timeSigEvents
.addEvent (m
);
263 double MidiFile::getLastTimestamp() const
267 for (int i
= tracks
.size(); --i
>= 0;)
268 t
= jmax (t
, tracks
.getUnchecked(i
)->getEndTime());
273 //==============================================================================
274 bool MidiFile::readFrom (InputStream
& sourceStream
)
279 const int maxSensibleMidiFileSize
= 2 * 1024 * 1024;
281 // (put a sanity-check on the file size, as midi files are generally small)
282 if (sourceStream
.readIntoMemoryBlock (data
, maxSensibleMidiFileSize
))
284 size_t size
= data
.getSize();
285 const uint8
* d
= static_cast <const uint8
*> (data
.getData());
286 short fileType
, expectedTracks
;
288 if (size
> 16 && MidiFileHelpers::parseMidiHeader (d
, timeFormat
, fileType
, expectedTracks
))
290 size
-= (int) (d
- static_cast <const uint8
*> (data
.getData()));
294 while (size
> 0 && track
< expectedTracks
)
296 const int chunkType
= (int) ByteOrder::bigEndianInt (d
);
298 const int chunkSize
= (int) ByteOrder::bigEndianInt (d
);
304 if (chunkType
== (int) ByteOrder::bigEndianInt ("MTrk"))
306 readNextTrack (d
, chunkSize
);
309 size
-= chunkSize
+ 8;
321 void MidiFile::readNextTrack (const uint8
* data
, int size
)
324 char lastStatusByte
= 0;
326 MidiMessageSequence result
;
331 const int delay
= MidiMessage::readVariableLengthVal (data
, bytesUsed
);
337 const MidiMessage
mm (data
, size
, messSize
, lastStatusByte
, time
);
345 result
.addEvent (mm
);
347 const char firstByte
= *(mm
.getRawData());
348 if ((firstByte
& 0xf0) != 0xf0)
349 lastStatusByte
= firstByte
;
352 // use a sort that puts all the note-offs before note-ons that have the same time
353 MidiFileHelpers::Sorter sorter
;
354 result
.list
.sort (sorter
, true);
356 result
.updateMatchedPairs();
361 //==============================================================================
362 void MidiFile::convertTimestampTicksToSeconds()
364 MidiMessageSequence tempoEvents
;
365 findAllTempoEvents (tempoEvents
);
366 findAllTimeSigEvents (tempoEvents
);
368 for (int i
= 0; i
< tracks
.size(); ++i
)
370 MidiMessageSequence
& ms
= *tracks
.getUnchecked(i
);
372 for (int j
= ms
.getNumEvents(); --j
>= 0;)
374 MidiMessage
& m
= ms
.getEventPointer(j
)->message
;
376 m
.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m
.getTimeStamp(),
383 //==============================================================================
384 bool MidiFile::writeTo (OutputStream
& out
)
386 out
.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"));
387 out
.writeIntBigEndian (6);
388 out
.writeShortBigEndian (1); // type
389 out
.writeShortBigEndian ((short) tracks
.size());
390 out
.writeShortBigEndian (timeFormat
);
392 for (int i
= 0; i
< tracks
.size(); ++i
)
399 void MidiFile::writeTrack (OutputStream
& mainOut
, const int trackNum
)
401 MemoryOutputStream out
;
403 const MidiMessageSequence
& ms
= *tracks
[trackNum
];
406 char lastStatusByte
= 0;
408 for (int i
= 0; i
< ms
.getNumEvents(); ++i
)
410 const MidiMessage
& mm
= ms
.getEventPointer(i
)->message
;
412 const int tick
= roundToInt (mm
.getTimeStamp());
413 const int delta
= jmax (0, tick
- lastTick
);
414 MidiFileHelpers::writeVariableLengthInt (out
, delta
);
417 const char statusByte
= *(mm
.getRawData());
419 if ((statusByte
== lastStatusByte
)
420 && ((statusByte
& 0xf0) != 0xf0)
422 && mm
.getRawDataSize() > 1)
424 out
.write (mm
.getRawData() + 1, mm
.getRawDataSize() - 1);
428 out
.write (mm
.getRawData(), mm
.getRawDataSize());
431 lastStatusByte
= statusByte
;
435 const MidiMessage
m (MidiMessage::endOfTrack());
436 out
.write (m
.getRawData(),
439 mainOut
.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"));
440 mainOut
.writeIntBigEndian ((int) out
.getDataSize());