MIDI Data Handling

MIDI Sources

an inheritance diagram for SMFSource, in the Ardour source code

The only instantiable data type that can be used a MIDI source is SMFSource. This inherits from 3 direct parent classes:

FileSource
This provides concepts like a path that identifies a file)
MidiSource
Provides and defines a set of generic methods to read and write MIDI data somewhere
SMF
An object that abstracts a Standard MIDI file, and provides methods to read and write MIDI data in that format. It wraps libsmf, a C library that implements the SMF specifications.

In addition, an SMFSource HAS-A MidiModel, which is an object used for editing MIDI. It provides methods to add, remove, move and alter MIDI events. This is the object that is modified when using the GUI to edit MIDI. It does not automatically flush its state to disk, but will do so (via MidiModel::sync_to_source()) when the session is saved. Reading data from an SMFSource that has a non-null MidiModel will pull information from the model, not the file on disk. So think of the MidiModel as a cache of the disk data, but specifically designed to allow for editing operations.

Capturing MIDI

When MIDI data is received during a process callback, it is written to in a ringbuffer of suitable size (we hope). When the disk writer thread calls flush_midi(), it reads from the ringbuffer and adds events to an SMFSource object that already exists. The SMFSource object will use the SMF/libsmf API(s) to get the data ready for being written to disk, and will also (if its _model member is non-null) append the events to a MidiModel.

The data is not written to disk until the recording pass finishes, and mark_midi_streaming_write_finished() is called. At this point, the total intended length of the SMF will be passed as an argument, and the libsmf API will be used (notably smf_save()) to write the SMF contents to disk. The SMF "track-end" meta-event will record the intended length.

If the length passed to mark_midi_streaming_write_finished() is std::numeric_limits::max() then the track-end meta-event will be timed to match the final event in the SMF.

Note that at the time of writing (November 2024), after a capture pass, the MIDI will be written to disk twice. Once as mark_midi_streaming_write_finished() is called, and then again when Session::save_state() is called. The latter will be written from the state of MidiModel, so it is important than the capture process ensures that the model fully represents the state written by the initial flush-to-disk.