use std::cmp::Ordering;
use std::fmt;
use super::message::{MetaEvent, MidiMessage};
#[derive(Debug, Clone, PartialEq)]
pub struct MidiEvent {
tick: u64,
message: MidiMessage,
track: usize,
seconds: Option<f64>,
seq: u32,
linked_event: Option<usize>,
}
impl MidiEvent {
pub fn new(tick: u64, message: MidiMessage) -> Self {
Self {
tick,
message,
track: 0,
seconds: None,
seq: 0,
linked_event: None,
}
}
pub fn with_track(tick: u64, message: MidiMessage, track: usize) -> Self {
Self {
tick,
message,
track,
seconds: None,
seq: 0,
linked_event: None,
}
}
pub fn tick(&self) -> u64 {
self.tick
}
pub fn set_tick(&mut self, tick: u64) {
self.tick = tick;
}
pub fn message(&self) -> &MidiMessage {
&self.message
}
pub fn message_mut(&mut self) -> &mut MidiMessage {
&mut self.message
}
pub fn set_message(&mut self, message: MidiMessage) {
self.message = message;
}
pub fn track(&self) -> usize {
self.track
}
pub fn set_track(&mut self, track: usize) {
self.track = track;
}
pub fn seconds(&self) -> Option<f64> {
self.seconds
}
pub fn set_seconds(&mut self, seconds: f64) {
self.seconds = Some(seconds);
}
pub fn seq(&self) -> u32 {
self.seq
}
pub fn set_seq(&mut self, seq: u32) {
self.seq = seq;
}
pub fn linked_event(&self) -> Option<usize> {
self.linked_event
}
pub fn set_linked_event(&mut self, index: Option<usize>) {
self.linked_event = index;
}
pub fn is_linked(&self) -> bool {
self.linked_event.is_some()
}
pub fn tick_duration(&self, events: &[MidiEvent]) -> Option<u64> {
self.linked_event.and_then(|idx| {
events.get(idx).map(|linked| {
if linked.tick() > self.tick {
linked.tick() - self.tick
} else {
0
}
})
})
}
pub fn duration_seconds(&self, events: &[MidiEvent]) -> Option<f64> {
self.linked_event.and_then(|idx| {
events.get(idx).and_then(|linked| {
match (self.seconds, linked.seconds) {
(Some(start), Some(end)) => Some(end - start),
_ => None,
}
})
})
}
pub fn is_note_on(&self) -> bool {
self.message.is_note_on()
}
pub fn is_note_off(&self) -> bool {
self.message.is_note_off()
}
pub fn is_meta(&self) -> bool {
self.message.is_meta()
}
pub fn channel(&self) -> Option<u8> {
self.message.channel()
}
pub fn key(&self) -> Option<u8> {
match &self.message {
MidiMessage::NoteOn { key, .. } | MidiMessage::NoteOff { key, .. } => Some(*key),
_ => None,
}
}
pub fn velocity(&self) -> Option<u8> {
match &self.message {
MidiMessage::NoteOn { velocity, .. } | MidiMessage::NoteOff { velocity, .. } => {
Some(*velocity)
}
_ => None,
}
}
pub fn note_on(tick: u64, channel: u8, key: u8, velocity: u8) -> Self {
Self::new(tick, MidiMessage::note_on(channel, key, velocity))
}
pub fn note_off(tick: u64, channel: u8, key: u8, velocity: u8) -> Self {
Self::new(tick, MidiMessage::note_off(channel, key, velocity))
}
pub fn control_change(tick: u64, channel: u8, controller: u8, value: u8) -> Self {
Self::new(tick, MidiMessage::control_change(channel, controller, value))
}
pub fn program_change(tick: u64, channel: u8, program: u8) -> Self {
Self::new(tick, MidiMessage::program_change(channel, program))
}
pub fn unlink_event(&mut self) {
self.linked_event = None;
}
}
impl fmt::Display for MidiEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", self.tick, self.message)
}
}
impl Eq for MidiEvent {}
impl PartialOrd for MidiEvent {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MidiEvent {
fn cmp(&self, other: &Self) -> Ordering {
compare_events(self, other, NoteSortOrder::NoteOnsBeforeOffs)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum NoteSortOrder {
#[default]
NoteOnsBeforeOffs,
NoteOffsBeforeOns,
}
pub fn compare_events(a: &MidiEvent, b: &MidiEvent, order: NoteSortOrder) -> Ordering {
match a.tick.cmp(&b.tick) {
Ordering::Equal => {}
ord => return ord,
}
if a.seq != 0 && b.seq != 0 {
return a.seq.cmp(&b.seq);
}
let priority = match order {
NoteSortOrder::NoteOnsBeforeOffs => event_priority,
NoteSortOrder::NoteOffsBeforeOns => event_priority_note_offs_before_ons,
};
match priority(&a.message).cmp(&priority(&b.message)) {
Ordering::Equal => {}
ord => return ord,
}
a.seq.cmp(&b.seq)
}
fn event_priority(msg: &MidiMessage) -> i32 {
if let MidiMessage::Meta(meta) = msg {
return if matches!(meta, MetaEvent::EndOfTrack) { i32::MAX } else { 0 };
}
if msg.is_note_on() {
return 2;
}
if msg.is_note_off() {
return 3;
}
1
}
fn event_priority_note_offs_before_ons(msg: &MidiMessage) -> i32 {
if let MidiMessage::Meta(meta) = msg {
return if matches!(meta, MetaEvent::EndOfTrack) { i32::MAX } else { 0 };
}
if msg.is_note_off() {
return 2;
}
if msg.is_note_on() {
return 3;
}
1
}
pub struct MidiEventBuilder {
tick: u64,
message: Option<MidiMessage>,
track: usize,
seq: u32,
}
impl MidiEventBuilder {
pub fn new() -> Self {
Self {
tick: 0,
message: None,
track: 0,
seq: 0,
}
}
pub fn tick(mut self, tick: u64) -> Self {
self.tick = tick;
self
}
pub fn message(mut self, message: MidiMessage) -> Self {
self.message = Some(message);
self
}
pub fn track(mut self, track: usize) -> Self {
self.track = track;
self
}
pub fn seq(mut self, seq: u32) -> Self {
self.seq = seq;
self
}
pub fn build(self) -> Option<MidiEvent> {
self.message.map(|msg| {
let mut event = MidiEvent::with_track(self.tick, msg, self.track);
event.set_seq(self.seq);
event
})
}
}
impl Default for MidiEventBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_creation() {
let event = MidiEvent::note_on(0, 0, 60, 100);
assert!(event.is_note_on());
assert_eq!(event.tick(), 0);
assert_eq!(event.key(), Some(60));
assert_eq!(event.velocity(), Some(100));
assert_eq!(event.channel(), Some(0));
}
#[test]
fn test_event_ordering() {
let mut events = vec![
MidiEvent::note_off(100, 0, 60, 0),
MidiEvent::note_on(100, 0, 60, 100),
MidiEvent::note_on(0, 0, 60, 100),
];
events.sort();
assert_eq!(events[0].tick(), 0);
assert!(events[1].is_note_on());
assert!(events[2].is_note_off());
}
#[test]
fn test_event_ordering_control_change_before_notes() {
let mut events = vec![
MidiEvent::note_on(0, 0, 60, 100),
MidiEvent::control_change(0, 0, 64, 127),
];
events.sort();
assert!(matches!(events[0].message(), MidiMessage::ControlChange { .. }));
assert!(events[1].is_note_on());
}
#[test]
fn test_event_ordering_end_of_track_always_last() {
let mut events = vec![
MidiEvent::new(10, MidiMessage::Meta(MetaEvent::EndOfTrack)),
MidiEvent::new(10, MidiMessage::Meta(MetaEvent::Marker("x".into()))),
MidiEvent::note_on(10, 0, 60, 100),
];
events.sort();
assert!(matches!(events[2].message(), MidiMessage::Meta(MetaEvent::EndOfTrack)));
}
#[test]
fn test_event_ordering_seq_overrides_type_priority() {
let mut note_on = MidiEvent::note_on(0, 0, 60, 100);
let mut cc = MidiEvent::control_change(0, 0, 64, 127);
note_on.set_seq(1);
cc.set_seq(2);
let mut events = vec![cc.clone(), note_on.clone()];
events.sort();
assert!(events[0].is_note_on());
assert!(matches!(events[1].message(), MidiMessage::ControlChange { .. }));
}
#[test]
fn test_event_linking() {
let mut on = MidiEvent::note_on(0, 0, 60, 100);
let off = MidiEvent::note_off(100, 0, 60, 0);
on.set_linked_event(Some(1));
assert!(on.is_linked());
let events = vec![on.clone(), off];
assert_eq!(on.tick_duration(&events), Some(100));
}
#[test]
fn test_event_builder() {
let event = MidiEventBuilder::new()
.tick(480)
.message(MidiMessage::note_on(0, 60, 80))
.track(1)
.seq(5)
.build()
.unwrap();
assert_eq!(event.tick(), 480);
assert_eq!(event.track(), 1);
assert_eq!(event.seq(), 5);
}
}