use std::fmt;
use super::event::{compare_events, MidiEvent, NoteSortOrder};
use super::message::{MetaEvent, MidiMessage};
#[derive(Debug, Clone, Default)]
pub struct MidiTrack {
events: Vec<MidiEvent>,
name: Option<String>,
absolute_time: bool,
sorted: bool,
}
impl MidiTrack {
pub fn new() -> Self {
Self {
events: Vec::new(),
name: None,
absolute_time: true,
sorted: true,
}
}
pub fn with_name(name: impl Into<String>) -> Self {
Self {
events: Vec::new(),
name: Some(name.into()),
absolute_time: true,
sorted: true,
}
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn set_name(&mut self, name: impl Into<String>) {
self.name = Some(name.into());
}
pub fn events(&self) -> &[MidiEvent] {
&self.events
}
pub fn events_mut(&mut self) -> &mut Vec<MidiEvent> {
self.sorted = false;
&mut self.events
}
pub fn len(&self) -> usize {
self.events.len()
}
pub fn reserve(&mut self, additional: usize) {
self.events.reserve(additional);
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
pub fn add_event(&mut self, event: MidiEvent) {
let tick = event.tick();
self.events.push(event);
if self.sorted && self.events.len() > 1 {
let prev_tick = self.events[self.events.len() - 2].tick();
if tick < prev_tick {
self.sorted = false;
}
}
}
pub fn insert_event(&mut self, index: usize, event: MidiEvent) {
self.events.insert(index, event);
self.sorted = false;
}
pub fn remove_event(&mut self, index: usize) -> Option<MidiEvent> {
if index < self.events.len() {
Some(self.events.remove(index))
} else {
None
}
}
pub fn clear(&mut self) {
self.events.clear();
self.sorted = true;
}
pub fn sort(&mut self) {
self.sort_with_order(NoteSortOrder::NoteOnsBeforeOffs);
}
pub fn sort_with_order(&mut self, order: NoteSortOrder) {
if !self.sorted || order != NoteSortOrder::NoteOnsBeforeOffs {
self.events.sort_by(|a, b| compare_events(a, b, order));
self.sorted = order == NoteSortOrder::NoteOnsBeforeOffs;
}
}
pub fn is_sorted(&self) -> bool {
self.sorted
}
pub fn mark_sequence(&mut self) {
for (i, event) in self.events.iter_mut().enumerate() {
event.set_seq(i as u32 + 1);
}
}
pub fn clear_sequence(&mut self) {
for event in self.events.iter_mut() {
event.set_seq(0);
}
}
pub fn last_tick(&self) -> u64 {
self.events.last().map(|e| e.tick()).unwrap_or(0)
}
pub fn is_absolute_time(&self) -> bool {
self.absolute_time
}
pub fn make_absolute_times(&mut self) {
if !self.absolute_time {
let mut current_tick: u64 = 0;
for event in &mut self.events {
current_tick += event.tick();
event.set_tick(current_tick);
}
self.absolute_time = true;
}
}
pub fn make_delta_times(&mut self) {
if self.absolute_time {
self.sort(); let mut prev_tick: u64 = 0;
for event in &mut self.events {
let abs_tick = event.tick();
event.set_tick(abs_tick - prev_tick);
prev_tick = abs_tick;
}
self.absolute_time = false;
}
}
pub fn link_note_events(&mut self) {
self.sort();
let mut active_notes: Vec<Vec<usize>> = vec![vec![]; 16 * 128];
for i in 0..self.events.len() {
let event = &self.events[i];
if let Some(channel) = event.channel() {
if let Some(key) = event.key() {
let idx = (channel as usize) * 128 + (key as usize);
if event.is_note_on() {
active_notes[idx].push(i);
} else if event.is_note_off() && !active_notes[idx].is_empty() {
let on_idx = active_notes[idx].remove(0);
self.events[on_idx].set_linked_event(Some(i));
self.events[i].set_linked_event(Some(on_idx));
}
}
}
}
}
pub fn link_note_pairs_lifo(&mut self) {
self.sort();
let mut active_notes: Vec<Vec<usize>> = vec![vec![]; 16 * 128];
for i in 0..self.events.len() {
let event = &self.events[i];
if let Some(channel) = event.channel() {
if let Some(key) = event.key() {
let idx = (channel as usize) * 128 + (key as usize);
if event.is_note_on() {
active_notes[idx].push(i);
} else if event.is_note_off() {
if let Some(on_idx) = active_notes[idx].pop() {
self.events[on_idx].set_linked_event(Some(i));
self.events[i].set_linked_event(Some(on_idx));
}
}
}
}
}
}
pub fn link_controller_pairs(&mut self) {
const PEDAL_CONTROLLERS: [u8; 10] = [64, 65, 66, 67, 68, 69, 80, 81, 82, 83];
self.sort();
let mut pending: std::collections::HashMap<(u8, u8), usize> =
std::collections::HashMap::new();
for i in 0..self.events.len() {
let (channel, controller, value) = {
let event = &self.events[i];
match (
event.channel(),
event.message().get_controller_number(),
event.message().get_controller_value(),
) {
(Some(ch), Some(cc), Some(val)) if PEDAL_CONTROLLERS.contains(&cc) => {
(ch, cc, val)
}
_ => continue,
}
};
let key = (channel, controller);
if value >= 64 {
pending.insert(key, i);
} else if let Some(on_idx) = pending.remove(&key) {
self.events[on_idx].set_linked_event(Some(i));
self.events[i].set_linked_event(Some(on_idx));
}
}
}
pub fn unlink_note_events(&mut self) {
for event in &mut self.events {
event.set_linked_event(None);
}
}
pub fn add_note(
&mut self,
start_tick: u64,
duration: u64,
channel: u8,
key: u8,
velocity: u8,
) {
let on_idx = self.events.len();
let mut on_event = MidiEvent::note_on(start_tick, channel, key, velocity);
let mut off_event = MidiEvent::note_off(start_tick + duration, channel, key, 0);
on_event.set_linked_event(Some(on_idx + 1));
off_event.set_linked_event(Some(on_idx));
self.events.push(on_event);
self.events.push(off_event);
self.sorted = false;
}
pub fn add_control_change(&mut self, tick: u64, channel: u8, controller: u8, value: u8) {
self.add_event(MidiEvent::control_change(tick, channel, controller, value));
}
pub fn add_program_change(&mut self, tick: u64, channel: u8, program: u8) {
self.add_event(MidiEvent::program_change(tick, channel, program));
}
pub fn add_tempo(&mut self, tick: u64, bpm: f64) {
let meta = MetaEvent::tempo_from_bpm(bpm);
self.add_event(MidiEvent::new(tick, MidiMessage::Meta(meta)));
}
pub fn add_time_signature(&mut self, tick: u64, numerator: u8, denominator: u8) {
let meta = MetaEvent::time_signature(numerator, denominator);
self.add_event(MidiEvent::new(tick, MidiMessage::Meta(meta)));
}
pub fn add_key_signature(&mut self, tick: u64, sharps: i8, minor: bool) {
let meta = MetaEvent::key_signature(sharps, minor);
self.add_event(MidiEvent::new(tick, MidiMessage::Meta(meta)));
}
pub fn add_compound_time_signature(&mut self, tick: u64, numerator: u8, denominator: u8) {
let meta = MetaEvent::compound_time_signature(numerator, denominator);
self.add_event(MidiEvent::new(tick, MidiMessage::Meta(meta)));
}
pub fn add_pitch_bend(&mut self, tick: u64, channel: u8, value: u16) {
self.add_event(MidiEvent::new(tick, MidiMessage::pitch_bend(channel, value)));
}
pub fn add_controller(&mut self, tick: u64, channel: u8, controller: u8, value: u8) {
self.add_control_change(tick, channel, controller, value);
}
pub fn add_sustain(&mut self, tick: u64, channel: u8, value: u8) {
self.add_event(MidiEvent::new(tick, MidiMessage::make_sustain(channel, value)));
}
pub fn add_sustain_on(&mut self, tick: u64, channel: u8) {
self.add_sustain(tick, channel, 127);
}
pub fn add_sustain_off(&mut self, tick: u64, channel: u8) {
self.add_sustain(tick, channel, 0);
}
pub fn add_pedal(&mut self, tick: u64, channel: u8, value: u8) {
self.add_sustain(tick, channel, value);
}
pub fn add_pedal_on(&mut self, tick: u64, channel: u8) {
self.add_sustain_on(tick, channel);
}
pub fn add_pedal_off(&mut self, tick: u64, channel: u8) {
self.add_sustain_off(tick, channel);
}
pub fn add_patch_change(&mut self, tick: u64, channel: u8, program: u8) {
self.add_program_change(tick, channel, program);
}
pub fn add_timbre(&mut self, tick: u64, channel: u8, program: u8) {
self.add_program_change(tick, channel, program);
}
pub fn add_meta_event(&mut self, tick: u64, meta: MetaEvent) {
self.add_event(MidiEvent::new(tick, MidiMessage::Meta(meta)));
}
pub fn add_text(&mut self, tick: u64, text: impl Into<String>) {
self.add_meta_event(tick, MetaEvent::Text(text.into()));
}
pub fn add_copyright(&mut self, tick: u64, text: impl Into<String>) {
self.add_meta_event(tick, MetaEvent::Copyright(text.into()));
}
pub fn add_track_name(&mut self, tick: u64, name: impl Into<String>) {
self.add_meta_event(tick, MetaEvent::TrackName(name.into()));
}
pub fn add_instrument_name(&mut self, tick: u64, name: impl Into<String>) {
self.add_meta_event(tick, MetaEvent::InstrumentName(name.into()));
}
pub fn add_lyric(&mut self, tick: u64, text: impl Into<String>) {
self.add_meta_event(tick, MetaEvent::Lyric(text.into()));
}
pub fn add_marker(&mut self, tick: u64, text: impl Into<String>) {
self.add_meta_event(tick, MetaEvent::Marker(text.into()));
}
pub fn add_cue(&mut self, tick: u64, text: impl Into<String>) {
self.add_meta_event(tick, MetaEvent::CuePoint(text.into()));
}
pub fn add_pitch_bend_range(
&mut self,
tick: u64,
channel: u8,
semitones: u8,
cents: u8,
) {
self.add_controller(tick, channel, 101, 0); self.add_controller(tick, channel, 100, 0); self.add_controller(tick, channel, 6, semitones.min(127)); self.add_controller(tick, channel, 38, cents.min(127)); self.add_controller(tick, channel, 101, 127); self.add_controller(tick, channel, 100, 127);
}
pub fn add_end_of_track(&mut self) {
let last_tick = self.last_tick();
self.add_event(MidiEvent::new(
last_tick,
MidiMessage::Meta(MetaEvent::EndOfTrack),
));
}
pub fn ensure_end_of_track(&mut self) {
let has_eot = self.events.iter().any(|e| {
matches!(
e.message(),
MidiMessage::Meta(MetaEvent::EndOfTrack)
)
});
if !has_eot {
self.add_end_of_track();
}
}
pub fn note_events(&self) -> impl Iterator<Item = &MidiEvent> {
self.events.iter().filter(|e| e.is_note_on())
}
pub fn channel_events(&self, channel: u8) -> impl Iterator<Item = &MidiEvent> {
self.events.iter().filter(move |e| e.channel() == Some(channel))
}
pub fn meta_events(&self) -> impl Iterator<Item = &MidiEvent> {
self.events.iter().filter(|e| e.is_meta())
}
pub fn tempo_events(&self) -> impl Iterator<Item = &MidiEvent> {
self.events.iter().filter(|e| {
matches!(
e.message(),
MidiMessage::Meta(MetaEvent::Tempo(_))
)
})
}
pub fn extract_channel(&self, channel: u8) -> MidiTrack {
let mut track = MidiTrack::new();
for event in &self.events {
if event.is_meta() || event.channel() == Some(channel) {
let mut event = event.clone();
event.unlink_event();
track.add_event(event);
}
}
track.sort();
track
}
pub fn merge(&mut self, other: &MidiTrack) {
for event in &other.events {
let mut event = event.clone();
event.unlink_event();
self.add_event(event);
}
self.sorted = false;
}
}
impl fmt::Display for MidiTrack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = self.name.as_deref().unwrap_or("Unnamed");
write!(f, "Track '{}' ({} events)", name, self.events.len())
}
}
impl FromIterator<MidiEvent> for MidiTrack {
fn from_iter<T: IntoIterator<Item = MidiEvent>>(iter: T) -> Self {
let mut track = MidiTrack::new();
for event in iter {
track.add_event(event);
}
track
}
}
impl IntoIterator for MidiTrack {
type Item = MidiEvent;
type IntoIter = std::vec::IntoIter<MidiEvent>;
fn into_iter(self) -> Self::IntoIter {
self.events.into_iter()
}
}
impl<'a> IntoIterator for &'a MidiTrack {
type Item = &'a MidiEvent;
type IntoIter = std::slice::Iter<'a, MidiEvent>;
fn into_iter(self) -> Self::IntoIter {
self.events.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pitch_bend_range_rpn_sequence() {
let mut track = MidiTrack::new();
track.add_pitch_bend_range(0, 2, 12, 0);
let ccs: Vec<(u8, u8)> = track
.events()
.iter()
.filter_map(|e| match e.message() {
MidiMessage::ControlChange { controller, value, .. } => Some((*controller, *value)),
_ => None,
})
.collect();
assert_eq!(
ccs,
vec![(101, 0), (100, 0), (6, 12), (38, 0), (101, 127), (100, 127)]
);
}
#[test]
fn test_meta_convenience_adders() {
let mut track = MidiTrack::new();
track.add_text(0, "hello");
track.add_lyric(10, "la");
track.add_marker(20, "verse 1");
track.add_track_name(0, "Piano");
assert!(matches!(track.events()[0].message(), MidiMessage::Meta(MetaEvent::Text(s)) if s == "hello"));
assert!(matches!(track.events()[1].message(), MidiMessage::Meta(MetaEvent::Lyric(s)) if s == "la"));
assert!(matches!(track.events()[2].message(), MidiMessage::Meta(MetaEvent::Marker(s)) if s == "verse 1"));
assert!(matches!(track.events()[3].message(), MidiMessage::Meta(MetaEvent::TrackName(s)) if s == "Piano"));
}
#[test]
fn test_sustain_and_compound_time_signature_adders() {
let mut track = MidiTrack::new();
track.add_sustain_on(0, 0);
track.add_sustain_off(480, 0);
track.add_compound_time_signature(0, 6, 8);
assert!(track.events()[0].message().is_sustain_on());
assert!(track.events()[1].message().is_sustain_off());
assert!(matches!(
track.events()[2].message(),
MidiMessage::Meta(MetaEvent::TimeSignature { numerator: 6, clocks_per_click: 36, .. })
));
}
#[test]
fn test_link_note_pairs_lifo_vs_fifo() {
let mut track = MidiTrack::new();
track.add_event(MidiEvent::note_on(0, 0, 60, 100));
track.add_event(MidiEvent::note_on(50, 0, 60, 100));
track.add_event(MidiEvent::note_off(100, 0, 60, 0));
track.add_event(MidiEvent::note_off(150, 0, 60, 0));
track.link_note_pairs_lifo();
track.sort();
let events = track.events();
let second_on = &events[1];
assert_eq!(second_on.tick(), 50);
let linked = second_on.linked_event().unwrap();
assert_eq!(events[linked].tick(), 100);
}
#[test]
fn test_link_controller_pairs_sustain() {
let mut track = MidiTrack::new();
track.add_sustain_on(0, 0);
track.add_sustain_off(480, 0);
track.link_controller_pairs();
let on = &track.events()[0];
assert!(on.is_linked());
let off_idx = on.linked_event().unwrap();
assert_eq!(track.events()[off_idx].tick(), 480);
}
#[test]
fn test_track_creation() {
let track = MidiTrack::with_name("Piano");
assert_eq!(track.name(), Some("Piano"));
assert!(track.is_empty());
}
#[test]
fn test_track_add_events() {
let mut track = MidiTrack::new();
track.add_event(MidiEvent::note_on(0, 0, 60, 100));
track.add_event(MidiEvent::note_off(480, 0, 60, 0));
assert_eq!(track.len(), 2);
assert_eq!(track.last_tick(), 480);
}
#[test]
fn test_track_add_note() {
let mut track = MidiTrack::new();
track.add_note(0, 480, 0, 60, 100);
assert_eq!(track.len(), 2);
assert!(track.events()[0].is_note_on());
assert!(track.events()[1].is_note_off());
assert!(track.events()[0].is_linked());
}
#[test]
fn test_track_sorting() {
let mut track = MidiTrack::new();
track.add_event(MidiEvent::note_on(480, 0, 62, 100));
track.add_event(MidiEvent::note_on(0, 0, 60, 100));
track.add_event(MidiEvent::note_on(240, 0, 61, 100));
assert!(!track.is_sorted());
track.sort();
assert!(track.is_sorted());
assert_eq!(track.events()[0].tick(), 0);
assert_eq!(track.events()[1].tick(), 240);
assert_eq!(track.events()[2].tick(), 480);
}
#[test]
fn test_track_delta_absolute() {
let mut track = MidiTrack::new();
track.add_event(MidiEvent::note_on(0, 0, 60, 100));
track.add_event(MidiEvent::note_off(480, 0, 60, 0));
track.add_event(MidiEvent::note_on(480, 0, 62, 100));
track.add_event(MidiEvent::note_off(960, 0, 62, 0));
track.make_delta_times();
assert!(!track.is_absolute_time());
assert_eq!(track.events()[0].tick(), 0);
assert_eq!(track.events()[1].tick(), 480);
assert_eq!(track.events()[2].tick(), 0);
assert_eq!(track.events()[3].tick(), 480);
track.make_absolute_times();
assert!(track.is_absolute_time());
assert_eq!(track.events()[0].tick(), 0);
assert_eq!(track.events()[1].tick(), 480);
assert_eq!(track.events()[2].tick(), 480);
assert_eq!(track.events()[3].tick(), 960);
}
#[test]
fn test_track_link_notes() {
let mut track = MidiTrack::new();
track.add_event(MidiEvent::note_on(0, 0, 60, 100));
track.add_event(MidiEvent::note_off(480, 0, 60, 0));
track.link_note_events();
assert!(track.events()[0].is_linked());
assert!(track.events()[1].is_linked());
assert_eq!(track.events()[0].linked_event(), Some(1));
assert_eq!(track.events()[1].linked_event(), Some(0));
}
#[test]
fn test_track_tempo() {
let mut track = MidiTrack::new();
track.add_tempo(0, 120.0);
let tempo_events: Vec<_> = track.tempo_events().collect();
assert_eq!(tempo_events.len(), 1);
}
}