-
Notifications
You must be signed in to change notification settings - Fork 0
MIDI Playback
Verovio renders MIDI as a Format-1 SMF with one track per staff, all on channel 0 with no program-change or controller events. That's not useful for genuine multi-track playback — every voice goes through the same synth instrument.
verovio::midi rewrites the SMF post-render to fix this. The same
Toolkit::render_to_midi_bytes() produces SMF bytes; passing them
through a MidiTrackPolicy reassigns channels, inserts instrument
changes, sets volumes, and so on.
use std::collections::BTreeMap;
use verovio::midi::{MidiTrackPolicy, TrackOverride};
let policy = MidiTrackPolicy {
auto_distribute_channels: true, // staff 1 → ch 0, staff 2 → ch 1, …
overrides: BTreeMap::from([
(1, TrackOverride { program: Some(0), ..Default::default() }), // Piano
(2, TrackOverride { program: Some(42), volume: Some(96), ..Default::default() }), // Cello
]),
..MidiTrackPolicy::default()
};
let bytes = tk.render_to_midi_bytes_with_policy(&policy)?;
# Ok::<(), verovio::Error>(())TrackOverride {
// Routing
channel: Option<u8>, // 0-15 (override auto-distribute)
program: Option<u8>, // GM program 0-127
bank_select: Option<(u8, u8)>, // CC#0 + CC#32 (GS/XG bank pages)
midi_port: Option<u8>, // Meta::MidiPort (multi-port routing)
// Levels
volume: Option<u8>, // CC#7
pan: Option<u8>, // CC#10 (64 = center)
expression: Option<u8>, // CC#11
mute: bool, // zero every NoteOn velocity
// Pitch
transpose: Option<i8>, // semitones (clamped 0..=127)
// Modulation / effects
modulation: Option<u8>, // CC#1 (vibrato)
sustain: Option<bool>, // CC#64 (pedal down/up)
reverb: Option<u8>, // CC#91
chorus: Option<u8>, // CC#93
// DAW labels (Meta events)
name: Option<String>, // MetaMessage::TrackName
instrument_name: Option<String>, // MetaMessage::InstrumentName
}MidiTrackPolicy {
overrides: BTreeMap<u32, TrackOverride>,
auto_distribute_channels: bool, // staff N → ch (N-1) % 16
tempo_override: Option<TempoMap>, // replace Verovio's tempo
time_signature: Option<(u8, u8)>, // Meta::TimeSignature
key_signature: Option<i8>, // -7..=7, sharps positive
key_signature_minor: bool,
measure_markers: Option<Vec<MeasureInfo>>, // Meta::Marker per measure
lyrics: Option<Vec<(f64, String)>>, // Meta::Lyric at q-stamps
cue_points: Option<Vec<(f64, String)>>, // Meta::CuePoint
}// Practice mode — solo just track 1, mute everything else
let p = MidiTrackPolicy::with_solo(&[1]);
// Mute specific tracks
let p = MidiTrackPolicy::with_mute(&[3, 5]);
// Assign programs without building the BTreeMap by hand
let p = MidiTrackPolicy::with_programs(&[(1, 0), (2, 42)]);
// Chain
let p = MidiTrackPolicy::with_programs(&[(1, 0)])
.with_auto_distribute_channels();use verovio::midi::gm;
gm::program_name(40); // Some("Violin")
gm::all_programs(); // &[&str; 128]
gm::drum_key_name(36); // Some("Bass Drum 1")
gm::note_name(60); // Some("C4")
gm::midi_key_from_name("Bb3"); // Some(58)use verovio::midi::{summarize, TrackInfo};
let info: Vec<TrackInfo> = summarize(&bytes).unwrap();
for t in info {
println!("track {} channels={:?} notes={} program={:?}",
t.track_index, t.channels, t.audible_note_count, t.program);
}iter_smf_events walks the SMF and emits chronologically sorted
TimedEvents with wall-clock millisecond onsets:
use verovio::midi::{iter_smf_events, TimedMessage};
for ev in iter_smf_events(&bytes).unwrap() {
match ev.message {
TimedMessage::NoteOn { key, vel } if vel > 0 => {
scheduler.note_on(ev.at_ms, ev.channel, key, vel);
}
TimedMessage::Tempo { usec_per_quarter } => {
scheduler.set_tempo(ev.at_ms, usec_per_quarter);
}
_ => {}
}
}TimedMessage covers note-on/off, aftertouch (key + channel),
controllers, program changes, pitch bend, tempo, and opaque meta + sysex
projections.
Emergency-stop bytes — sends CC#120 (All Sound Off) and CC#123 (All Notes Off) on every one of the 16 channels:
let panic = verovio::midi::build_panic_smf();
synth.play(&panic); // flushes stuck voices without restarting