From 3c04a3c36fe841c3e0d4e07f72a6999bcf33289f Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Mon, 19 Oct 2026 11:04:51 +0000 Subject: [PATCH] Fix empty SMF export re-importing as one phantom empty track Found while testing crates/midi/src/smf.rs:207 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/midi/src/smf.rs | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/crates/midi/src/smf.rs b/crates/midi/src/smf.rs index 7747a061..3fe84678 100644 --- a/crates/midi/src/smf.rs +++ b/crates/midi/src/smf.rs @@ -204,7 +204,12 @@ pub fn read_smf(bytes: &[u8]) -> Result { tracks.push(track); } - if format == 1 && tracks.len() > 1 && tracks.first().is_some_and(|t| t.sequence.notes.is_empty() && t.sequence.ctrls.is_empty()) { + // A lone track is only a conductor track if it carried global data and has no name; otherwise it is kept. + let globals_present = !(globals.tempos.is_empty() && globals.meters.is_empty() && globals.markers.is_empty() && globals.key_sigs.is_empty()); + if format == 1 + && (tracks.len() > 1 || globals_present) + && tracks.first().is_some_and(|t| t.sequence.notes.is_empty() && t.sequence.ctrls.is_empty() && (tracks.len() > 1 || t.name.is_empty())) + { tracks.remove(0); } @@ -444,7 +449,12 @@ fn write_track(out: &mut Vec, mut events: Vec) -> Result<(), MidiErr /// Errors: invalid tempo (non-finite or <= 0 bpm), meter denominator that is not a power of two /// (1..=128), more than 65535 tracks, or a gap between events beyond the 28-bit delta limit. pub fn write_smf(smf: &Smf) -> Result, MidiError> { - let ntrks = u16::try_from(smf.tracks.len().saturating_add(1)).map_err(|_| MidiError::Write("more than 65535 tracks".into()))?; + // The conductor track is omitted when there is nothing to put in it and no content tracks, so an + // empty `Smf` is written as a file with zero tracks and reads back as zero tracks. + let has_conductor = + !(smf.tempos.is_empty() && smf.meters.is_empty() && smf.key_sigs.is_empty() && smf.markers.is_empty() && smf.tracks.is_empty()); + let ntrks = + u16::try_from(smf.tracks.len().saturating_add(usize::from(has_conductor))).map_err(|_| MidiError::Write("more than 65535 tracks".into()))?; let mut out = Vec::new(); out.extend_from_slice(b"MThd"); out.extend_from_slice(&6u32.to_be_bytes()); @@ -476,7 +486,9 @@ pub fn write_smf(smf: &Smf) -> Result, MidiError> { for (t, text) in &smf.markers { ev.push(WEvent { tick: clamp_tick(*t), order: 3, bytes: meta(0x06, text.as_bytes())? }); } - write_track(&mut out, ev)?; + if has_conductor { + write_track(&mut out, ev)?; + } for track in &smf.tracks { let mut ev = Vec::new(); @@ -527,6 +539,19 @@ mod tests { assert!(push_vlq(&mut Vec::new(), MAX_VLQ + 1).is_err()); } + #[test] + fn empty_smf_round_trips_with_no_tracks() { + let r = read_smf(&write_smf(&Smf::default()).unwrap()).unwrap(); + assert_eq!(r.tracks.len(), 0); + let one = Smf { tracks: vec![SmfTrack::default()], ..Smf::default() }; + let r = read_smf(&write_smf(&one).unwrap()).unwrap(); + assert_eq!(r.tracks.len(), 1); + let cond = Smf { tempos: vec![(0, 90.0)], ..Smf::default() }; + let r = read_smf(&write_smf(&cond).unwrap()).unwrap(); + assert_eq!(r.tracks.len(), 0); + assert_eq!(r.tempos.len(), 1); + } + #[test] fn spec_vlq_examples() { let mut b = Vec::new();