26namespace MidiFileHelpers
28 static void writeVariableLengthInt (OutputStream& out,
uint32 v)
30 auto buffer = v & 0x7f;
32 while ((v >>= 7) != 0)
35 buffer |= ((v & 0x7f) | 0x80);
40 out.writeByte ((
char) buffer);
49 template <
typename Integral>
58 template <
typename Integral>
62 constexpr auto size =
sizeof (
Integral);
80 short numberOfTracks = 0;
100 for (
int i = 0; i < 8; ++i)
139 HeaderDetails result;
149 static double convertTicksToSeconds (
double time,
154 return time / (-(timeFormat >> 8) * (timeFormat & 0xff));
157 auto tickLen = 1.0 / (timeFormat & 0x7fff);
163 auto& m =
tempoEvents.getEventPointer (i)->message;
164 auto eventTime = m.getTimeStamp();
166 if (eventTime >= time)
170 lastTime = eventTime;
172 if (m.isTempoMetaEvent())
182 if (
m2.isTempoMetaEvent())
192 template <
typename MethodType>
194 MidiMessageSequence& results,
197 for (
auto*
track : tracks)
203 auto& m =
track->getEventPointer (
j)->message;
206 results.addEvent (m);
211 static MidiMessageSequence readTrack (
const uint8* data,
int size)
216 MidiMessageSequence result;
222 if (! delay.isValid())
225 data += delay.bytesUsed;
226 size -= delay.bytesUsed;
241 result.addEvent (
mm);
258 tracks.addCopiesOf (
other.tracks);
264 tracks.addCopiesOf (
other.tracks);
265 timeFormat =
other.timeFormat;
270 : tracks (
std::move (
other.tracks)),
271 timeFormat (
other.timeFormat)
277 tracks = std::move (
other.tracks);
278 timeFormat =
other.timeFormat;
290 return tracks.size();
295 return tracks[index];
339 for (
auto*
ms : tracks)
340 t =
jmax (t,
ms->getEndTime());
359 auto size = data.getSize();
360 auto d =
static_cast<const uint8*
> (data.getData());
362 const auto optHeader = MidiFileHelpers::parseMidiHeader (d, size);
368 timeFormat = header.timeFormat;
370 d += header.bytesRead;
371 size -= (
size_t) header.bytesRead;
375 const auto optChunkType = MidiFileHelpers::tryRead<uint32> (d, size);
380 const auto optChunkSize = MidiFileHelpers::tryRead<uint32> (d, size);
387 if (size < chunkSize)
400 *fileType = header.fileType;
407 auto sequence = MidiFileHelpers::readTrack (data, size);
414 auto t1 = a->message.getTimeStamp();
415 auto t2 = b->message.getTimeStamp();
417 if (t1 < t2) return true;
418 if (t2 < t1) return false;
420 return a->message.isNoteOff() && b->message.isNoteOn();
424 sequence.updateMatchedPairs();
438 for (
auto*
ms : tracks)
440 for (
int j =
ms->getNumEvents(); --
j >= 0;)
442 auto& m =
ms->getEventPointer (
j)->message;
443 m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(),
tempoEvents, timeFormat));
460 for (
auto*
ms : tracks)
461 if (! writeTrack (out, *
ms))
476 for (
int i = 0; i <
ms.getNumEvents(); ++i)
478 auto&
mm =
ms.getEventPointer (i)->message;
480 if (
mm.isEndOfTrackMetaEvent())
485 MidiFileHelpers::writeVariableLengthInt (out, (
uint32) delta);
488 auto* data =
mm.getRawData();
489 auto dataSize =
mm.getRawDataSize();
507 MidiFileHelpers::writeVariableLengthInt (out, (
uint32) dataSize);
510 out.
write (data, (
size_t) dataSize);
518 out.
write (m.getRawData(), (
size_t) m.getRawDataSize());
539 void runTest()
override
541 beginTest (
"ReadTrack respects running status");
545 MidiFileHelpers::writeVariableLengthInt (
os, 100);
547 MidiFileHelpers::writeVariableLengthInt (
os, 200);
549 MidiFileHelpers::writeVariableLengthInt (
os, 300);
553 expectEquals (sequence.getNumEvents(), 3);
554 expect (sequence.getEventPointer (0)->message.isNoteOn());
555 expect (sequence.getEventPointer (1)->message.isNoteOn());
556 expect (sequence.getEventPointer (2)->message.isEndOfTrackMetaEvent());
559 beginTest (
"ReadTrack returns available messages if input is truncated");
568 expectEquals (sequence.getNumEvents(), 0);
575 MidiFileHelpers::writeVariableLengthInt (
os, 0xffff);
578 expectEquals (sequence.getNumEvents(), 0);
585 MidiFileHelpers::writeVariableLengthInt (
os, 0xffff);
589 expectEquals (sequence.getNumEvents(), 1);
590 expect (sequence.getEventPointer (0)->message.isNoteOff());
591 expectEquals (sequence.getEventPointer (0)->message.getNoteNumber(), 0x40);
592 expectEquals (sequence.getEventPointer (0)->message.getVelocity(), (
uint8) 0x00);
596 beginTest (
"Header parsing works");
600 const auto header = parseHeader ([] (OutputStream&) {});
601 expect (! header.hasValue());
606 const auto header = parseHeader ([] (OutputStream&
os)
611 expect (! header.hasValue());
616 const auto header = parseHeader ([] (OutputStream&
os)
621 expect (! header.hasValue());
626 const auto header = parseHeader ([] (OutputStream&
os)
628 writeBytes (
os, {
'M',
'T',
'h',
'd', 0, 0, 0, 6, 0, 0, 0, 16, 0, 1 });
631 expect (! header.hasValue());
636 const auto header = parseHeader ([] (OutputStream&
os)
638 writeBytes (
os, {
'M',
'T',
'h',
'd', 0, 0, 0, 6, 0, 5, 0, 16, 0, 1 });
641 expect (! header.hasValue());
646 const auto header = parseHeader ([] (OutputStream&
os)
648 writeBytes (
os, {
'M',
'T',
'h',
'd', 0, 0, 0, 6, 0, 1, 0, 16, 0, 1 });
651 expect (header.hasValue());
653 expectEquals (header->fileType, (
short) 1);
654 expectEquals (header->numberOfTracks, (
short) 16);
655 expectEquals (header->timeFormat, (
short) 1);
656 expectEquals ((
int) header->bytesRead, 14);
660 beginTest (
"Read from stream");
664 const auto file =
parseFile ([] (OutputStream&) {});
665 expect (! file.hasValue());
675 expect (! file.hasValue());
682 writeBytes (
os, {
'M',
'T',
'h',
'd', 0, 0, 0, 6, 0, 1, 0, 0, 0, 1 });
685 expect (file.hasValue());
686 expectEquals (file->getNumTracks(), 0);
693 writeBytes (
os, {
'M',
'T',
'h',
'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
697 expect (! file.hasValue());
704 writeBytes (
os, {
'M',
'T',
'h',
'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
705 writeBytes (
os, {
'M',
'T',
'r',
'k', 0, 0, 0, 1, 0xff });
708 expect (file.hasValue());
709 expectEquals (file->getNumTracks(), 1);
710 expectEquals (file->getTrack (0)->getNumEvents(), 0);
717 writeBytes (
os, {
'M',
'T',
'h',
'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
718 writeBytes (
os, {
'M',
'T',
'r',
'k', 0x0f, 0, 0, 0, 0xff });
721 expect (! file.hasValue());
728 writeBytes (
os, {
'M',
'T',
'h',
'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
731 MidiFileHelpers::writeVariableLengthInt (
os, 0x0f);
735 expect (file.hasValue());
736 expectEquals (file->getNumTracks(), 1);
738 auto&
track = *file->getTrack (0);
739 expectEquals (
track.getNumEvents(), 1);
740 expect (
track.getEventPointer (0)->message.isNoteOff());
741 expectEquals (
track.getEventPointer (0)->message.getTimeStamp(), (
double) 0x0f);
746 template <
typename Fn>
749 MemoryOutputStream
os;
752 return MidiFileHelpers::readTrack (
reinterpret_cast<const uint8*
> (
os.getData()),
753 (
int)
os.getDataSize());
756 template <
typename Fn>
759 MemoryOutputStream
os;
762 return MidiFileHelpers::parseMidiHeader (
reinterpret_cast<const uint8*
> (
os.getData()),
766 template <
typename Fn>
769 MemoryOutputStream
os;
772 MemoryInputStream is (
os.getData(),
os.getDataSize(),
false);
777 if (
mf.readFrom (is,
true, &fileType))
785 for (
const auto&
byte : bytes)
786 os.writeByte ((
char) byte);
static constexpr uint32 bigEndianInt(const void *bytes) noexcept
Turns 4 bytes into a big-endian integer.
static constexpr uint16 bigEndianShort(const void *bytes) noexcept
Turns 2 bytes into a big-endian integer.
A class to hold a resizable block of raw 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, int *midiFileType=nullptr)
Reads a midi file format stream.
MidiFile & operator=(const MidiFile &)
Copies from another MidiFile object.
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 key-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.
Structure used to hold midi events in the sequence.
A sequence of timestamped midi messages.
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.
static VariableLengthValue readVariableLengthValue(const uint8 *data, int maxBytesToUse) noexcept
Reads a midi variable-length integer, with protection against buffer overflow.
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.
virtual bool writeShortBigEndian(short value)
Writes a 16-bit integer to the stream in a big-endian byte order.
virtual void flush()=0
If the stream is using a buffer, this will ensure it gets written out to the destination.
unsigned short uint16
A platform-independent 16-bit unsigned integer type.
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Returns true if the two floating-point numbers are approximately equal.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
unsigned int uint32
A platform-independent 32-bit unsigned integer type.
unsigned char uint8
A platform-independent 8-bit unsigned integer type.
int roundToInt(const FloatType value) noexcept
Fast floating-point-to-integer conversion.