libsmf

- Main Page
- Data Structures
- Files

libsmf - general usage instructions

An smf_t structure represents a "song". Every valid smf contains one or more tracks. Tracks contain zero or more events. Libsmf doesn't care about actual MIDI data, as long as it is valid from the MIDI specification point of view - it may be realtime message, SysEx, whatever.

The only field in smf_t, smf_track_t, smf_event_t and smf_tempo_t structures your code may modify is event->midi_buffer and event->midi_buffer_length. Do not modify other fields, ever. You may read them, though. Do not declare static instances of these types, i.e. never do something like this: "smf_t smf;". Always use "smf_t *smf = smf_new();". The same applies to smf_track_t and smf_event_t.

Say you want to load a Standard MIDI File (.mid) file and play it back somehow. This is (roughly) how you do this:

smf_t *smf;
smf_event_t *event;
smf = smf_load(file_name);
if (smf == NULL) {
Whoops, something went wrong.
return;
}
while ((event = smf_get_next_event(smf)) != NULL) {
if (smf_event_is_metadata(event))
continue;
wait until event->time_seconds.
feed_to_midi_output(event->midi_buffer, event->midi_buffer_length);
}
smf_delete(smf);

Saving works like this:

smf_t *smf;
smf_track_t *track;
smf_event_t *event;
smf = smf_new();
if (smf == NULL) {
Whoops.
return;
}
for (int i = 1; i <= number of tracks; i++) {
track = smf_track_new();
if (track == NULL) {
Whoops.
return;
}
smf_add_track(smf, track);
for (int j = 1; j <= number of events you want to put into this track; j++) {
event = smf_event_new_from_pointer(your MIDI message, message length);
if (event == NULL) {
Whoops.
return;
}
smf_track_add_event_seconds(track, event, seconds since start of the song);
}
}
ret = smf_save(smf, file_name);
if (ret) {
Whoops, saving failed for some reason.
return;
}
smf_delete(smf);

There are two basic ways of getting MIDI data out of smf - sequential or by track/event number. You may mix them if you need to. First one is used in the example above - seek to the point from which you want the playback to start (using smf_seek_to_seconds(), smf_seek_to_pulses() or smf_seek_to_event()) and then do smf_get_next_event() in loop, until it returns NULL. Calling smf_load() causes the smf to be rewound to the start of the song.

Getting events by number works like this:

smf_track_t *track = smf_get_track_by_number(smf, track_number);
smf_event_t *event = smf_track_get_event_by_number(track, event_number);

To create new event, use smf_event_new(), smf_event_new_from_pointer() or smf_event_new_from_bytes(). First one creates an empty event - you need to manually allocate (using malloc(3)) buffer for MIDI data, write MIDI data into it, put the address of that buffer into event->midi_buffer, and the length of MIDI data into event->midi_buffer_length. Note that deleting the event (using smf_event_delete()) will free the buffer.

Second form does most of this for you: it takes an address of the buffer containing MIDI data, allocates storage and copies MIDI data into it.

Third form is useful for manually creating short events, up to three bytes in length, for example Note On or Note Off events. It simply takes three bytes and creates MIDI event containing them. If you need to create MIDI message that takes only two bytes, pass -1 as the third byte. For one byte message (System Realtime), pass -1 as second and third byte.

To free an event, use smf_event_delete().

To add event to the track, use smf_track_add_event_delta_pulses(), smf_track_add_event_pulses(), or smf_track_add_event_seconds(). The difference between them is in the way you specify the time of the event - with the first one, you specify it as an interval, in pulses, from the previous event in this track; with the second one, you specify it as pulses from the start of the song, and with the last one, you specify it as seconds from the start of the song. Obviously, the first version can only append events at the end of the track.

To remove an event from the track it's attached to, use smf_event_remove_from_track(). You may want to free the event (using smf_event_delete()) afterwards.

To create new track, use smf_track_new(). To add track to the smf, use smf_add_track(). To remove track from its smf, use smf_track_remove_from_smf(). To free the track structure, use smf_track_delete().

Note that libsmf keeps things consistent. If you free (using smf_track_delete()) a track that is attached to an smf and contains events, libsmf will detach the events, free them, detach the track, free it etc.

Tracks and events are numbered consecutively, starting from one. If you remove a track or event, the rest of tracks/events will get renumbered. To get the number of a given event in its track, use event->event_number. To get the number of track in its smf, use track->track_number. To get the number of events in the track, use track->number_of_events. To get the number of tracks in the smf, use smf->number_of_tracks.

In SMF File Format, each track has to end with End Of Track metaevent. If you load SMF file using smf_load(), that will be the case. If you want to create or edit an SMF, you don't need to worry about EOT events; libsmf automatically takes care of them for you. If you try to save an SMF with tracks that do not end with EOTs, smf_save() will append them. If you try to add event that happens after EOT metaevent, libsmf will remove the EOT. If you want to add EOT manually, you can, of course, using smf_track_add_eot_seconds() or smf_track_add_eot_pulses().

Each event carries three time values - event->time_seconds, which is seconds since the start of the song, event->time_pulses, which is PPQN clocks since the start of the song, and event->delta_pulses, which is PPQN clocks since the previous event in that track. These values are invalid if the event is not attached to the track. If event is attached, all three values are valid. Time of the event is specified when adding the event (using smf_track_add_event_seconds(), smf_track_add_event_pulses() or smf_track_add_event_delta_pulses()); the remaining two values are computed from that.

Tempo related stuff happens automatically - when you add a metaevent that is Tempo Change or Time Signature, libsmf adds that event to the tempo map. If you remove Tempo Change event that is in the middle of the song, the rest of the events will have their event->time_seconds recomputed from event->time_pulses before smf_event_remove_from_track() function returns. Adding Tempo Change in the middle of the song works in a similar way.

MIDI data (event->midi_buffer) is always kept in normalized form - it always begins with status byte (no running status), there are no System Realtime events embedded in them etc. Events like SysExes are in "on the wire" form, without embedded length that is used in SMF file format. Obviously libsmf "normalizes" MIDI data during loading and "denormalizes" (adding length to SysExes, escaping System Common and System Realtime messages etc) during writing.

Note that you always have to first add the track to smf, and then add events to the track. Doing it the other way around will trip asserts. Also, try to add events at the end of the track and remove them from the end of the track, that's much more efficient.

All the libsmf functions have prefix "smf_". First argument for routines whose names start with "smf_event_" is "smf_event_t *", for routines whose names start with "smf_track_" - "smf_track_t *", and for plain "smf_" - "smf_t *". The only exception are smf_whatever_new routines. Library does not use any global variables and is thread-safe, as long as you don't try to work on the same SMF (smf_t and its descendant tracks and events) from several threads at once without protecting it with mutex. Library depends on glib and nothing else. License is BSD, two clause, which basically means you can use it freely in your software, both Open Source (including GPL) and closed source.