use std::collections::BTreeMap;
use midly::{
num::{u24, u4, u7},
MetaMessage, MidiMessage, Smf, Timing, TrackEvent, TrackEventKind,
};
use crate::{MeasureInfo, TempoMap};
pub mod gm {
#[rustfmt::skip]
const PROGRAMS: [&str; 128] = [
"Acoustic Grand Piano", "Bright Acoustic Piano", "Electric Grand Piano", "Honky-tonk Piano",
"Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavi",
"Celesta", "Glockenspiel", "Music Box", "Vibraphone",
"Marimba", "Xylophone", "Tubular Bells", "Dulcimer",
"Drawbar Organ", "Percussive Organ", "Rock Organ", "Church Organ",
"Reed Organ", "Accordion", "Harmonica", "Tango Accordion",
"Acoustic Guitar (nylon)", "Acoustic Guitar (steel)", "Electric Guitar (jazz)", "Electric Guitar (clean)",
"Electric Guitar (muted)", "Overdriven Guitar", "Distortion Guitar", "Guitar harmonics",
"Acoustic Bass", "Electric Bass (finger)", "Electric Bass (pick)", "Fretless Bass",
"Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2",
"Violin", "Viola", "Cello", "Contrabass",
"Tremolo Strings", "Pizzicato Strings", "Orchestral Harp", "Timpani",
"String Ensemble 1", "String Ensemble 2", "SynthStrings 1", "SynthStrings 2",
"Choir Aahs", "Voice Oohs", "Synth Voice", "Orchestra Hit",
"Trumpet", "Trombone", "Tuba", "Muted Trumpet",
"French Horn", "Brass Section", "SynthBrass 1", "SynthBrass 2",
"Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax",
"Oboe", "English Horn", "Bassoon", "Clarinet",
"Piccolo", "Flute", "Recorder", "Pan Flute",
"Blown Bottle", "Shakuhachi", "Whistle", "Ocarina",
"Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (calliope)", "Lead 4 (chiff)",
"Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", "Lead 8 (bass + lead)",
"Pad 1 (new age)", "Pad 2 (warm)", "Pad 3 (polysynth)", "Pad 4 (choir)",
"Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)", "Pad 8 (sweep)",
"FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", "FX 4 (atmosphere)",
"FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)", "FX 8 (sci-fi)",
"Sitar", "Banjo", "Shamisen", "Koto",
"Kalimba", "Bagpipe", "Fiddle", "Shanai",
"Tinkle Bell", "Agogo", "Steel Drums", "Woodblock",
"Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal",
"Guitar Fret Noise", "Breath Noise", "Seashore", "Bird Tweet",
"Telephone Ring", "Helicopter", "Applause", "Gunshot",
];
pub fn program_name(program: u8) -> Option<&'static str> {
PROGRAMS.get(program as usize).copied()
}
pub fn all_programs() -> &'static [&'static str; 128] {
&PROGRAMS
}
const NOTE_NAMES_SHARP: [&str; 12] = [
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
];
pub fn note_name(key: u8) -> Option<String> {
let pc = (key % 12) as usize;
let octave = (key as i32 / 12) - 1;
Some(format!("{}{}", NOTE_NAMES_SHARP[pc], octave))
}
pub fn midi_key_from_name(name: &str) -> Option<u8> {
let bytes = name.as_bytes();
if bytes.is_empty() {
return None;
}
let letter = bytes[0].to_ascii_uppercase();
let mut pc: i32 = match letter {
b'C' => 0,
b'D' => 2,
b'E' => 4,
b'F' => 5,
b'G' => 7,
b'A' => 9,
b'B' => 11,
_ => return None,
};
let mut idx = 1;
if idx < bytes.len() {
match bytes[idx] {
b'#' => {
pc += 1;
idx += 1;
}
b'b' => {
pc -= 1;
idx += 1;
}
_ => {}
}
}
if idx >= bytes.len() {
return None;
}
let octave: i32 = name[idx..].parse().ok()?;
let key = (octave + 1) * 12 + pc;
if (0..=127).contains(&key) {
Some(key as u8)
} else {
None
}
}
pub fn drum_key_name(key: u8) -> Option<&'static str> {
match key {
35 => Some("Acoustic Bass Drum"),
36 => Some("Bass Drum 1"),
37 => Some("Side Stick"),
38 => Some("Acoustic Snare"),
39 => Some("Hand Clap"),
40 => Some("Electric Snare"),
41 => Some("Low Floor Tom"),
42 => Some("Closed Hi Hat"),
43 => Some("High Floor Tom"),
44 => Some("Pedal Hi-Hat"),
45 => Some("Low Tom"),
46 => Some("Open Hi-Hat"),
47 => Some("Low-Mid Tom"),
48 => Some("Hi-Mid Tom"),
49 => Some("Crash Cymbal 1"),
50 => Some("High Tom"),
51 => Some("Ride Cymbal 1"),
52 => Some("Chinese Cymbal"),
53 => Some("Ride Bell"),
54 => Some("Tambourine"),
55 => Some("Splash Cymbal"),
56 => Some("Cowbell"),
57 => Some("Crash Cymbal 2"),
58 => Some("Vibraslap"),
59 => Some("Ride Cymbal 2"),
60 => Some("Hi Bongo"),
61 => Some("Low Bongo"),
62 => Some("Mute Hi Conga"),
63 => Some("Open Hi Conga"),
64 => Some("Low Conga"),
65 => Some("High Timbale"),
66 => Some("Low Timbale"),
67 => Some("High Agogo"),
68 => Some("Low Agogo"),
69 => Some("Cabasa"),
70 => Some("Maracas"),
71 => Some("Short Whistle"),
72 => Some("Long Whistle"),
73 => Some("Short Guiro"),
74 => Some("Long Guiro"),
75 => Some("Claves"),
76 => Some("Hi Wood Block"),
77 => Some("Low Wood Block"),
78 => Some("Mute Cuica"),
79 => Some("Open Cuica"),
80 => Some("Mute Triangle"),
81 => Some("Open Triangle"),
_ => None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct TrackOverride {
pub channel: Option<u8>,
pub program: Option<u8>,
pub volume: Option<u8>,
pub mute: bool,
pub pan: Option<u8>,
pub name: Option<String>,
pub instrument_name: Option<String>,
pub sustain: Option<bool>,
pub transpose: Option<i8>,
pub expression: Option<u8>,
pub modulation: Option<u8>,
pub reverb: Option<u8>,
pub chorus: Option<u8>,
pub bank_select: Option<(u8, u8)>,
pub midi_port: Option<u8>,
}
impl MidiTrackPolicy {
pub fn with_solo(audible: &[u32]) -> Self {
let mut overrides = BTreeMap::new();
for idx in 1u32..32 {
if !audible.contains(&idx) {
overrides.insert(
idx,
TrackOverride {
mute: true,
..Default::default()
},
);
}
}
Self {
overrides,
..Default::default()
}
}
pub fn with_mute(silent: &[u32]) -> Self {
let mut overrides = BTreeMap::new();
for idx in silent {
overrides.insert(
*idx,
TrackOverride {
mute: true,
..Default::default()
},
);
}
Self {
overrides,
..Default::default()
}
}
pub fn with_auto_distribute_channels(mut self) -> Self {
self.auto_distribute_channels = true;
self
}
pub fn with_programs(assignments: &[(u32, u8)]) -> Self {
let mut overrides = BTreeMap::new();
for (idx, prog) in assignments {
overrides.insert(
*idx,
TrackOverride {
program: Some(*prog),
..Default::default()
},
);
}
Self {
overrides,
..Default::default()
}
}
}
#[derive(Debug, Clone, Default)]
pub struct MidiTrackPolicy {
pub overrides: BTreeMap<u32, TrackOverride>,
pub auto_distribute_channels: bool,
pub tempo_override: Option<TempoMap>,
pub time_signature: Option<(u8, u8)>,
pub key_signature: Option<i8>,
pub key_signature_minor: bool,
pub measure_markers: Option<Vec<MeasureInfo>>,
pub lyrics: Option<Vec<(f64, String)>>,
pub cue_points: Option<Vec<(f64, String)>>,
}
pub fn apply_track_policy(smf_bytes: &[u8], policy: &MidiTrackPolicy) -> Option<Vec<u8>> {
let smf = Smf::parse(smf_bytes).ok()?;
let new_smf = apply_policy_to_parsed(smf, policy);
let mut out = Vec::new();
new_smf.write_std(&mut out).ok()?;
Some(out)
}
fn absolute_ticks<'a>(track: &[TrackEvent<'a>]) -> Vec<(u64, TrackEventKind<'a>)> {
let mut out = Vec::with_capacity(track.len());
let mut tick: u64 = 0;
for ev in track {
tick += u32::from(ev.delta) as u64;
out.push((tick, ev.kind));
}
out
}
fn delta_encode<'a>(events: Vec<(u64, TrackEventKind<'a>)>) -> Vec<TrackEvent<'a>> {
let mut out = Vec::with_capacity(events.len());
let mut last_tick: u64 = 0;
for (tick, kind) in events {
let delta = (tick - last_tick) as u32;
out.push(TrackEvent {
delta: delta.into(),
kind,
});
last_tick = tick;
}
out
}
fn apply_tempo_override<'a>(meta_track: &mut Vec<TrackEvent<'a>>, tempo_map: &TempoMap, tpq: u64) {
if tempo_map.changes.is_empty() {
return;
}
let mut events_abs = absolute_ticks(meta_track);
events_abs.retain(|(_, kind)| !matches!(kind, TrackEventKind::Meta(MetaMessage::Tempo(_))));
for change in &tempo_map.changes {
let tick = (change.at_qstamp * tpq as f64).round() as u64;
let uspq = (60_000_000.0 / change.bpm).round().max(1.0) as u32;
events_abs.push((
tick,
TrackEventKind::Meta(MetaMessage::Tempo(u24::from(uspq.min(0x00FF_FFFF)))),
));
}
events_abs.sort_by_key(|(t, _)| *t);
*meta_track = delta_encode(events_abs);
}
fn apply_policy_to_parsed<'a>(mut smf: Smf<'a>, policy: &MidiTrackPolicy) -> Smf<'a> {
let tpq: u64 = match smf.header.timing {
Timing::Metrical(t) => t.as_int() as u64,
Timing::Timecode(_, _) => 480, };
if !smf.tracks.is_empty() {
let meta_track = &mut smf.tracks[0];
if let Some(tempo_map) = &policy.tempo_override {
apply_tempo_override(meta_track, tempo_map, tpq);
}
let mut prepend_meta: Vec<TrackEvent<'a>> = Vec::new();
if let Some((num, denom)) = policy.time_signature {
let denom_power = denom.trailing_zeros().min(7) as u8;
prepend_meta.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Meta(MetaMessage::TimeSignature(num, denom_power, 24, 8)),
});
}
if let Some(sf) = policy.key_signature {
prepend_meta.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Meta(MetaMessage::KeySignature(
sf,
policy.key_signature_minor,
)),
});
}
if !prepend_meta.is_empty() {
for (i, ev) in prepend_meta.into_iter().enumerate() {
meta_track.insert(i, ev);
}
}
if let Some(measures) = &policy.measure_markers {
let mut events_abs = absolute_ticks(meta_track);
for m in measures {
let denom = m.start_qfrac[1] as f64;
if denom == 0.0 {
continue;
}
let q = m.start_qfrac[0] as f64 / denom;
let tick = (q * tpq as f64).round() as u64;
let label: &'static [u8] = Box::leak(m.id.clone().into_bytes().into_boxed_slice());
events_abs.push((tick, TrackEventKind::Meta(MetaMessage::Marker(label))));
}
events_abs.sort_by_key(|(t, _)| *t);
*meta_track = delta_encode(events_abs);
}
if let Some(lyrics) = &policy.lyrics {
let mut events_abs = absolute_ticks(meta_track);
for (q, text) in lyrics {
let tick = (q * tpq as f64).round().max(0.0) as u64;
let label: &'static [u8] = Box::leak(text.clone().into_bytes().into_boxed_slice());
events_abs.push((tick, TrackEventKind::Meta(MetaMessage::Lyric(label))));
}
events_abs.sort_by_key(|(t, _)| *t);
*meta_track = delta_encode(events_abs);
}
if let Some(cues) = &policy.cue_points {
let mut events_abs = absolute_ticks(meta_track);
for (q, text) in cues {
let tick = (q * tpq as f64).round().max(0.0) as u64;
let label: &'static [u8] = Box::leak(text.clone().into_bytes().into_boxed_slice());
events_abs.push((tick, TrackEventKind::Meta(MetaMessage::CuePoint(label))));
}
events_abs.sort_by_key(|(t, _)| *t);
*meta_track = delta_encode(events_abs);
}
}
for (idx, track) in smf.tracks.iter_mut().enumerate() {
let track_index = idx as u32;
let override_ = policy.overrides.get(&track_index);
let target_channel: Option<u8> = override_.and_then(|o| o.channel).or_else(|| {
if policy.auto_distribute_channels && track_index > 0 {
Some(((track_index - 1) % 16) as u8)
} else {
None
}
});
let mute = override_.map(|o| o.mute).unwrap_or(false);
let transpose = override_.and_then(|o| o.transpose).unwrap_or(0);
if target_channel.is_some() || mute || transpose != 0 {
for ev in track.iter_mut() {
if let TrackEventKind::Midi { channel, message } = &mut ev.kind {
if let Some(ch) = target_channel {
*channel = u4::from(ch & 0x0F);
}
if mute {
if let MidiMessage::NoteOn { vel, .. } = message {
*vel = u7::from(0);
}
}
if transpose != 0 {
match message {
MidiMessage::NoteOn { key, .. }
| MidiMessage::NoteOff { key, .. }
| MidiMessage::Aftertouch { key, .. } => {
let new_key =
(key.as_int() as i16 + transpose as i16).clamp(0, 127) as u8;
*key = u7::from(new_key);
}
_ => {}
}
}
}
}
}
if let Some(o) = override_ {
let mut prepend: Vec<TrackEvent<'a>> = Vec::new();
let ch = target_channel.unwrap_or(0);
if let Some((msb, lsb)) = o.bank_select {
prepend.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Midi {
channel: u4::from(ch & 0x0F),
message: MidiMessage::Controller {
controller: u7::from(0),
value: u7::from(msb & 0x7F),
},
},
});
prepend.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Midi {
channel: u4::from(ch & 0x0F),
message: MidiMessage::Controller {
controller: u7::from(32),
value: u7::from(lsb & 0x7F),
},
},
});
}
if let Some(port) = o.midi_port {
prepend.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Meta(MetaMessage::MidiPort(u7::from(port & 0x7F))),
});
}
if let Some(program) = o.program {
prepend.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Midi {
channel: u4::from(ch & 0x0F),
message: MidiMessage::ProgramChange {
program: u7::from(program & 0x7F),
},
},
});
}
if let Some(volume) = o.volume {
prepend.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Midi {
channel: u4::from(ch & 0x0F),
message: MidiMessage::Controller {
controller: u7::from(7),
value: u7::from(volume & 0x7F),
},
},
});
}
if let Some(pan) = o.pan {
prepend.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Midi {
channel: u4::from(ch & 0x0F),
message: MidiMessage::Controller {
controller: u7::from(10),
value: u7::from(pan & 0x7F),
},
},
});
}
if let Some(sustain_down) = o.sustain {
let value: u8 = if sustain_down { 127 } else { 0 };
prepend.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Midi {
channel: u4::from(ch & 0x0F),
message: MidiMessage::Controller {
controller: u7::from(64),
value: u7::from(value & 0x7F),
},
},
});
}
if let Some(expression) = o.expression {
prepend.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Midi {
channel: u4::from(ch & 0x0F),
message: MidiMessage::Controller {
controller: u7::from(11),
value: u7::from(expression & 0x7F),
},
},
});
}
if let Some(modulation) = o.modulation {
prepend.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Midi {
channel: u4::from(ch & 0x0F),
message: MidiMessage::Controller {
controller: u7::from(1),
value: u7::from(modulation & 0x7F),
},
},
});
}
if let Some(reverb) = o.reverb {
prepend.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Midi {
channel: u4::from(ch & 0x0F),
message: MidiMessage::Controller {
controller: u7::from(91),
value: u7::from(reverb & 0x7F),
},
},
});
}
if let Some(chorus) = o.chorus {
prepend.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Midi {
channel: u4::from(ch & 0x0F),
message: MidiMessage::Controller {
controller: u7::from(93),
value: u7::from(chorus & 0x7F),
},
},
});
}
if let Some(name) = &o.name {
let leaked: &'static [u8] = Box::leak(name.clone().into_bytes().into_boxed_slice());
prepend.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Meta(MetaMessage::TrackName(leaked)),
});
}
if let Some(iname) = &o.instrument_name {
let leaked: &'static [u8] =
Box::leak(iname.clone().into_bytes().into_boxed_slice());
prepend.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Meta(MetaMessage::InstrumentName(leaked)),
});
}
if !prepend.is_empty() {
let insert_at = track
.iter()
.position(|ev| !matches!(ev.kind, TrackEventKind::Meta(_)))
.unwrap_or(track.len());
for (offset, ev) in prepend.into_iter().enumerate() {
track.insert(insert_at + offset, ev);
}
}
}
let _ = MetaMessage::EndOfTrack;
}
smf
}
#[derive(Debug, Clone, PartialEq)]
pub struct TrackInfo {
pub track_index: u32,
pub channels: Vec<u8>,
pub audible_note_count: u32,
pub program: Option<u8>,
pub volume: Option<u8>,
pub pan: Option<u8>,
pub end_tick: u64,
}
pub fn build_panic_smf() -> Vec<u8> {
use midly::{Format, Header};
let mut events: Vec<TrackEvent<'static>> = Vec::with_capacity(33);
for ch in 0u8..16 {
events.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Midi {
channel: u4::from(ch),
message: MidiMessage::Controller {
controller: u7::from(120),
value: u7::from(0),
},
},
});
events.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Midi {
channel: u4::from(ch),
message: MidiMessage::Controller {
controller: u7::from(123),
value: u7::from(0),
},
},
});
}
events.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Meta(MetaMessage::EndOfTrack),
});
let smf = Smf {
header: Header::new(Format::SingleTrack, Timing::Metrical(120.into())),
tracks: vec![events],
};
let mut out = Vec::new();
smf.write_std(&mut out).expect("static SMF write");
out
}
#[derive(Debug, Clone, PartialEq)]
pub struct TimedEvent {
pub at_ms: f64,
pub track_index: u32,
pub channel: Option<u8>,
pub message: TimedMessage,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TimedMessage {
NoteOn {
key: u8,
vel: u8,
},
NoteOff {
key: u8,
vel: u8,
},
Aftertouch {
key: u8,
vel: u8,
},
Controller {
controller: u8,
value: u8,
},
ProgramChange {
program: u8,
},
ChannelAftertouch {
vel: u8,
},
PitchBend {
value: i16,
},
Tempo {
usec_per_quarter: u32,
},
Meta {
kind: u8,
data: Vec<u8>,
},
SysEx {
data: Vec<u8>,
},
}
pub fn iter_smf_events(smf_bytes: &[u8]) -> Option<Vec<TimedEvent>> {
let smf = Smf::parse(smf_bytes).ok()?;
let tpq: u32 = match smf.header.timing {
Timing::Metrical(t) => t.as_int() as u32,
Timing::Timecode(_, _) => 480,
};
let mut tempo_points: Vec<(u64, u32)> = vec![(0, 500_000)];
if let Some(meta) = smf.tracks.first() {
let mut tick: u64 = 0;
for ev in meta {
tick += u32::from(ev.delta) as u64;
if let TrackEventKind::Meta(MetaMessage::Tempo(t)) = &ev.kind {
let us = t.as_int();
if tick == 0 && tempo_points.len() == 1 && tempo_points[0].0 == 0 {
tempo_points[0].1 = us;
} else {
tempo_points.push((tick, us));
}
}
}
}
tempo_points.sort_by_key(|(t, _)| *t);
let ticks_to_ms = |target: u64| -> f64 {
let mut ms = 0.0;
for i in 0..tempo_points.len() {
let (seg_start, usec_per_q) = tempo_points[i];
let seg_end = tempo_points.get(i + 1).map(|(t, _)| *t).unwrap_or(u64::MAX);
if target <= seg_start {
break;
}
let bound = target.min(seg_end);
let span_ticks = bound.saturating_sub(seg_start) as f64;
ms += span_ticks * usec_per_q as f64 / tpq as f64 / 1000.0;
if target <= seg_end {
break;
}
}
ms
};
let mut out: Vec<TimedEvent> = Vec::new();
for (track_idx, track) in smf.tracks.iter().enumerate() {
let mut tick: u64 = 0;
for ev in track {
tick += u32::from(ev.delta) as u64;
let at_ms = ticks_to_ms(tick);
match &ev.kind {
TrackEventKind::Midi { channel, message } => {
let ch = Some(channel.as_int());
let msg = match message {
MidiMessage::NoteOn { key, vel } => TimedMessage::NoteOn {
key: key.as_int(),
vel: vel.as_int(),
},
MidiMessage::NoteOff { key, vel } => TimedMessage::NoteOff {
key: key.as_int(),
vel: vel.as_int(),
},
MidiMessage::Aftertouch { key, vel } => TimedMessage::Aftertouch {
key: key.as_int(),
vel: vel.as_int(),
},
MidiMessage::Controller { controller, value } => TimedMessage::Controller {
controller: controller.as_int(),
value: value.as_int(),
},
MidiMessage::ProgramChange { program } => TimedMessage::ProgramChange {
program: program.as_int(),
},
MidiMessage::ChannelAftertouch { vel } => {
TimedMessage::ChannelAftertouch { vel: vel.as_int() }
}
MidiMessage::PitchBend { bend } => TimedMessage::PitchBend {
value: bend.0.as_int() as i16 - 8192,
},
};
out.push(TimedEvent {
at_ms,
track_index: track_idx as u32,
channel: ch,
message: msg,
});
}
TrackEventKind::Meta(meta) => {
let projected = match meta {
MetaMessage::Tempo(t) => Some(TimedMessage::Tempo {
usec_per_quarter: t.as_int(),
}),
MetaMessage::EndOfTrack => None, other => {
let (kind, data) = meta_kind_and_data(other);
Some(TimedMessage::Meta { kind, data })
}
};
if let Some(message) = projected {
out.push(TimedEvent {
at_ms,
track_index: track_idx as u32,
channel: None,
message,
});
}
}
TrackEventKind::SysEx(bytes) | TrackEventKind::Escape(bytes) => {
out.push(TimedEvent {
at_ms,
track_index: track_idx as u32,
channel: None,
message: TimedMessage::SysEx {
data: bytes.to_vec(),
},
});
}
}
}
}
out.sort_by(|a, b| {
a.at_ms
.partial_cmp(&b.at_ms)
.unwrap_or(std::cmp::Ordering::Equal)
});
Some(out)
}
fn meta_kind_and_data(meta: &MetaMessage) -> (u8, Vec<u8>) {
match meta {
MetaMessage::TrackNumber(n) => (
0x00,
n.map(|x| x.to_be_bytes().to_vec()).unwrap_or_default(),
),
MetaMessage::Text(b) => (0x01, b.to_vec()),
MetaMessage::Copyright(b) => (0x02, b.to_vec()),
MetaMessage::TrackName(b) => (0x03, b.to_vec()),
MetaMessage::InstrumentName(b) => (0x04, b.to_vec()),
MetaMessage::Lyric(b) => (0x05, b.to_vec()),
MetaMessage::Marker(b) => (0x06, b.to_vec()),
MetaMessage::CuePoint(b) => (0x07, b.to_vec()),
MetaMessage::ProgramName(b) => (0x08, b.to_vec()),
MetaMessage::DeviceName(b) => (0x09, b.to_vec()),
MetaMessage::MidiChannel(c) => (0x20, vec![c.as_int()]),
MetaMessage::MidiPort(p) => (0x21, vec![p.as_int()]),
MetaMessage::TimeSignature(n, d, c, b) => (0x58, vec![*n, *d, *c, *b]),
MetaMessage::KeySignature(sf, minor) => (0x59, vec![*sf as u8, *minor as u8]),
MetaMessage::SequencerSpecific(b) => (0x7F, b.to_vec()),
MetaMessage::SmpteOffset(_) => (0x54, Vec::new()),
MetaMessage::Unknown(k, b) => (*k, b.to_vec()),
_ => (0xFF, Vec::new()),
}
}
pub fn summarize(smf_bytes: &[u8]) -> Option<Vec<TrackInfo>> {
let smf = Smf::parse(smf_bytes).ok()?;
let mut out = Vec::with_capacity(smf.tracks.len());
for (idx, track) in smf.tracks.iter().enumerate() {
let mut channels: Vec<u8> = Vec::new();
let mut audible_note_count: u32 = 0;
let mut program: Option<u8> = None;
let mut volume: Option<u8> = None;
let mut pan: Option<u8> = None;
let mut end_tick: u64 = 0;
for ev in track.iter() {
end_tick += u32::from(ev.delta) as u64;
if let TrackEventKind::Midi { channel, message } = &ev.kind {
let ch = channel.as_int();
if !channels.contains(&ch) {
channels.push(ch);
}
match message {
MidiMessage::NoteOn { vel, .. } if vel.as_int() > 0 => {
audible_note_count += 1;
}
MidiMessage::ProgramChange { program: p } if program.is_none() => {
program = Some(p.as_int());
}
MidiMessage::Controller { controller, value } => match controller.as_int() {
7 if volume.is_none() => volume = Some(value.as_int()),
10 if pan.is_none() => pan = Some(value.as_int()),
_ => {}
},
_ => {}
}
}
}
out.push(TrackInfo {
track_index: idx as u32,
channels,
audible_note_count,
program,
volume,
pan,
end_tick,
});
}
Some(out)
}