New Class to handle UI
[juce-lv2.git] / juce / source / src / audio / midi / juce_MidiFile.cpp
blob8f936f950fb5673f7c11ac4cbf6b5ec63d1ed3e2
1 /*
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"
28 BEGIN_JUCE_NAMESPACE
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)
43 buffer <<= 8;
44 buffer |= ((v & 0x7F) | 0x80);
47 for (;;)
49 out.writeByte ((char) buffer);
51 if (buffer & 0x80)
52 buffer >>= 8;
53 else
54 break;
58 bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept
60 unsigned int ch = (int) ByteOrder::bigEndianInt (data);
61 data += 4;
63 if (ch != ByteOrder::bigEndianInt ("MThd"))
65 bool ok = false;
67 if (ch == ByteOrder::bigEndianInt ("RIFF"))
69 for (int i = 0; i < 8; ++i)
71 ch = ByteOrder::bigEndianInt (data);
72 data += 4;
74 if (ch == ByteOrder::bigEndianInt ("MThd"))
76 ok = true;
77 break;
82 if (! ok)
83 return false;
86 unsigned int bytesRemaining = ByteOrder::bigEndianInt (data);
87 data += 4;
88 fileType = (short) ByteOrder::bigEndianShort (data);
89 data += 2;
90 numberOfTracks = (short) ByteOrder::bigEndianShort (data);
91 data += 2;
92 timeFormat = (short) ByteOrder::bigEndianShort (data);
93 data += 2;
94 bytesRemaining -= 6;
95 data += bytesRemaining;
97 return true;
100 double convertTicksToSeconds (const double time,
101 const MidiMessageSequence& tempoEvents,
102 const int timeFormat)
104 if (timeFormat > 0)
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())
117 break;
119 if (timeFormat > 0)
121 correctedTempoTime = correctedTempoTime
122 + (m.getTimeStamp() - tempoTime) * secsPerTick;
124 else
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)
141 ++i;
143 if (m2.isTempoMetaEvent())
144 secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote();
145 else if (m2.isTimeSignatureMetaEvent())
146 m2.getTimeSignatureInfo (numer, denom);
148 else
150 break;
155 return correctedTempoTime + (time - tempoTime) * secsPerTick;
157 else
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
164 struct Sorter
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;
176 return 0;
181 //==============================================================================
182 MidiFile::MidiFile()
183 : timeFormat ((short) (unsigned short) 0xe728)
187 MidiFile::~MidiFile()
189 clear();
192 void MidiFile::clear()
194 tracks.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
216 return timeFormat;
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
265 double t = 0.0;
267 for (int i = tracks.size(); --i >= 0;)
268 t = jmax (t, tracks.getUnchecked(i)->getEndTime());
270 return t;
273 //==============================================================================
274 bool MidiFile::readFrom (InputStream& sourceStream)
276 clear();
277 MemoryBlock data;
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()));
292 int track = 0;
294 while (size > 0 && track < expectedTracks)
296 const int chunkType = (int) ByteOrder::bigEndianInt (d);
297 d += 4;
298 const int chunkSize = (int) ByteOrder::bigEndianInt (d);
299 d += 4;
301 if (chunkSize <= 0)
302 break;
304 if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk"))
306 readNextTrack (d, chunkSize);
309 size -= chunkSize + 8;
310 d += chunkSize;
311 ++track;
314 return true;
318 return false;
321 void MidiFile::readNextTrack (const uint8* data, int size)
323 double time = 0;
324 char lastStatusByte = 0;
326 MidiMessageSequence result;
328 while (size > 0)
330 int bytesUsed;
331 const int delay = MidiMessage::readVariableLengthVal (data, bytesUsed);
332 data += bytesUsed;
333 size -= bytesUsed;
334 time += delay;
336 int messSize = 0;
337 const MidiMessage mm (data, size, messSize, lastStatusByte, time);
339 if (messSize <= 0)
340 break;
342 size -= messSize;
343 data += messSize;
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();
358 addTrack (result);
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(),
377 tempoEvents,
378 timeFormat));
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)
393 writeTrack (out, i);
395 out.flush();
396 return true;
399 void MidiFile::writeTrack (OutputStream& mainOut, const int trackNum)
401 MemoryOutputStream out;
403 const MidiMessageSequence& ms = *tracks[trackNum];
405 int lastTick = 0;
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);
415 lastTick = tick;
417 const char statusByte = *(mm.getRawData());
419 if ((statusByte == lastStatusByte)
420 && ((statusByte & 0xf0) != 0xf0)
421 && i > 0
422 && mm.getRawDataSize() > 1)
424 out.write (mm.getRawData() + 1, mm.getRawDataSize() - 1);
426 else
428 out.write (mm.getRawData(), mm.getRawDataSize());
431 lastStatusByte = statusByte;
434 out.writeByte (0);
435 const MidiMessage m (MidiMessage::endOfTrack());
436 out.write (m.getRawData(),
437 m.getRawDataSize());
439 mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"));
440 mainOut.writeIntBigEndian ((int) out.getDataSize());
441 mainOut << out;
444 END_JUCE_NAMESPACE