7.10.14. STRUDEL-14 — MIDI Files
daStrudel parses and plays Standard MIDI Files (.mid) — the universal
sheet-music format. You get a structured view of tracks, events, and
tempo, plus a high-level midi_play entry point that runs the file
through either built-in samples or an SF2 SoundFont.
7.10.14.1. Part A: Parsing
load_midi reads a .mid file and returns a MidiFile struct:
require strudel/strudel_midi
let midi = load_midi("fur_elise.mid")
print("Format: {midi.format}\n") // 0 = single track, 1 = multi-track
print("Ticks/QN: {midi.ticks_per_qn}\n") // timing resolution
print("Tracks: {length(midi.tracks)}\n")
Each track is a MidiTrack containing an array of MidiEvent.
Events carry a tick timestamp, a MidiEventKind (note-on, note-off,
control change, tempo, etc.), a channel number, and two data bytes.
Format 0 packs everything into one track; format 1 uses parallel tracks
— daStrudel handles both. ticks_per_qn is the time resolution:
higher values mean finer timing subdivisions.
7.10.14.2. Part B: Merging and Tempo
merge_tracks collapses all tracks into one stable-sorted event
stream, which is easier to walk for analysis:
var merged <- merge_tracks(midi)
for (evt in merged) {
if (evt.kind == MidiEventKind.midi_tempo && evt.tempo > 0) {
let bpm = 60000000.0 / float(evt.tempo)
print("Tempo change at tick {evt.tick}: {bpm:.1f} BPM\n")
}
}
Tempo events store microseconds-per-quarter-note in the tempo field;
convert to BPM with 60_000_000 / tempo. Files with no tempo events
default to 120 BPM.
7.10.14.3. Part C: Playback with Sample Banks
The midi_play path uses the audio system’s streaming infrastructure
and a built-in piano + drum sample bank. The lifecycle:
require strudel/strudel_midi_player
midi_load_samples(media_path) // load built-in banks from media folder
midi_init() // start the playback thread
midi_play("music", "fur_elise.mid", [gain = 0.6, looping = false])
sleep(6000u)
midi_stop("music")
midi_shutdown()
The first argument to midi_play is an arbitrary track name — you
can play multiple MIDI files at once and control them independently
(cross-fade, stop, set volume) via that name.
7.10.14.4. Part D: Playback with SF2
For higher-quality rendering, swap the sample backend for a General MIDI
SoundFont. midi_load_sf2 activates it; subsequent midi_play
calls dispatch notes through the SoundFont presets (channel 10 uses the
GM drum kit automatically):
midi_load_sf2("FluidR3_GM.sf2")
midi_init()
midi_play("music", "Bach_Air_on_G_string.mid", [gain = 1.0])
sleep(8000u)
midi_shutdown()
The SF2 backend respects program change events — if the MIDI file switches instruments mid-song, the SoundFont follows. Pair with STRUDEL-13 — SF2 SoundFont Playback for per-instrument control from live patterns.
See also
Full source: tutorials/daStrudel/daStrudel_14_midi_files.das
Previous tutorial: STRUDEL-13 — SF2 SoundFont Playback
Next tutorial: STRUDEL-15 — Live-Reloading Patterns