midi_file.h
Standard MIDI Files.
#include <sonore/midi_file.h>- 4types
- 10other declarations
- Guides: MIDI and MPE
On this page
Design notes
The interchange format for patterns: what an arpeggiator exports, what a chord generator imports, what a user drags out of a DAW and into a plugin. Reading and writing SMF format 0 and 1.
Two things in this format break every naive parser, and both are handled explicitly below:
VARIABLE-LENGTH QUANTITIES: delta times are 7 bits per byte with the top bit meaning "another byte follows". Assuming one byte works on short patterns and silently derails on anything longer than 127 ticks. RUNNING STATUS: a data byte where a status byte was expected means "reuse the previous status". A parser that does not implement it reads garbage from the moment a file uses it, which most real files do.
Offline by design, like wav.h: this allocates and does file IO.
Types 4
One event at an absolute position, in ticks from the start of its track.
A tempo change, so a reader can convert ticks to seconds honestly rather than assuming 120 BPM the way a lazy importer does.
One track of a file: its name (the 0x03 meta event) and its channel messages in tick order. Only channel messages travel either way: a read skips SysEx and every meta event but the name and tempo (tempo goes to MidiFileData::tempoChanges), and a write leaves out anything else it is handed.
A Standard MIDI File in memory: the tick resolution, the tracks, and one tempo map merged from all of them. What readMidiFile() fills and writeMidiFile() writes -- format 0 for one track, format 1 for more, with the tempo map on the first track.
Plain data, and offline: reading and writing allocate and do file I/O, so neither belongs on the audio thread. Times are ticks; tickToSeconds() follows the tempo map. A file timed in SMPTE frames is refused, not read.
Functions 10
- fn
inline void writeVarLen(std::vector<uint8_t>& out, uint32_t value)Appendmidifile::writeVarLen · midi_file.h:108valueas an SMF variable-length quantity: seven bits a byte, most significant first, the top bit set on all but the last. The format allows four bytes, so values to 0x0FFFFFFF; a larger value comes out as five bytes, which readVarLen and every other reader refuse. writeMidiFile() splits a longer delta so it never asks for one. - fn
inline bool readVarLen(const uint8_t* data, size_t size, size_t& pos, uint32_t* out)Read a variable-length quantity atmidifile::readVarLen · midi_file.h:124posofsizebytes, advancingpospast it. False when it runs off the end or past four bytes. - fn
inline void writeU16(std::vector<uint8_t>& out, uint16_t v)Appendmidifile::writeU16 · midi_file.h:139vbig-endian, as every SMF number is. - fn
inline void writeU32(std::vector<uint8_t>& out, uint32_t v)Appendmidifile::writeU32 · midi_file.h:145vbig-endian. - fn
inline uint16_t readU16(const uint8_t* p)Two big-endian bytes atmidifile::readU16 · midi_file.h:153p, which the caller has bounds-checked. - fn
inline uint32_t readU32(const uint8_t* p)Four big-endian bytes atmidifile::readU32 · midi_file.h:156p, which the caller has bounds-checked. - fn
inline int dataBytesFor(uint8_t status)How many data bytes a channel status takes. Program change and channel pressure are TWO-byte messages; treating everything as three is the other classic way a parser slides out of alignment.midifile::dataBytesFor · midi_file.h:164 - fn
inline bool parseTrack(const uint8_t* data, size_t size, MidiTrack* track, std::vector<MidiTempoEvent>* tempos)Parse one track chunk into events, following running status.midifile::parseTrack · midi_file.h:170 - fn
inline bool readMidiFile(const char* path, MidiFileData* out)Read a Standard MIDI File. Returns false on anything malformed rather than producing a half-parsed sequence.midi_file.h:233 - fn
inline bool writeMidiFile(const char* path, const MidiFileData& file)Write a Standard MIDI File: format 0 for one track, format 1 for several. Events are assumed sorted by tick; anything out of order would produce a negative delta, so it is clamped rather than wrapped. A gap longer than a delta can say (0x0FFFFFFF ticks) is bridged with empty text events, which readMidiFile() skips, so every event keeps its tick.midi_file.h:297