midi_toolkit/sequence/event/
stats.rs

1use std::{ops::Deref, sync::Arc, time::Duration};
2
3use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
4
5use crate::{
6    events::{Event, MIDIDelta, MIDIEventEnum, TempoEvent},
7    num::MIDINum,
8    pipe,
9    sequence::{event::merge_events_array, to_vec, to_vec_result, wrap_ok},
10};
11
12use super::Delta;
13
14struct ElementCountDebug(&'static str, usize);
15
16impl std::fmt::Debug for ElementCountDebug {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        write!(f, "[{}; {}]", self.0, self.1)
19    }
20}
21
22/// A struct to hold the statistics of a sequence.
23#[derive(Clone)]
24pub struct ChannelStatistics<D: MIDINum> {
25    note_on_count: u64,
26    note_off_count: u64,
27    total_event_count: u64,
28    total_length_ticks: D,
29    tempo_events: Arc<[Delta<D, TempoEvent>]>,
30}
31
32impl<T: MIDINum> ChannelStatistics<T> {
33    /// The number of note on events
34    pub fn note_on_count(&self) -> u64 {
35        self.note_on_count
36    }
37
38    /// Alias for [`note_on_count`](#method.note_on_count)
39    pub fn note_count(&self) -> u64 {
40        self.note_on_count()
41    }
42
43    /// The number of note off events
44    pub fn note_off_count(&self) -> u64 {
45        self.note_off_count
46    }
47
48    /// The number of events that are not note on and note off.
49    ///
50    /// Alias for ([`total_event_count()`](#method.total_event_count) - [`note_on_count()`](#method.note_on_count) - [`note_off_count()`](#method.note_off_count))
51    pub fn other_event_count(&self) -> u64 {
52        self.total_event_count - self.note_on_count - self.note_off_count
53    }
54
55    /// The total number of events
56    pub fn total_event_count(&self) -> u64 {
57        self.total_event_count
58    }
59
60    /// The sum of all delta times in each event
61    pub fn total_length_ticks(&self) -> T {
62        self.total_length_ticks
63    }
64
65    /// Calculate the length in seconds based on the tick length and the tempo events,
66    /// as well as the ppq
67    pub fn calculate_total_duration(&self, ppq: u16) -> Duration {
68        tempo_sequence_get_duration(&self.tempo_events, ppq, self.total_length_ticks)
69    }
70}
71
72impl<T: MIDINum> std::fmt::Debug for ChannelStatistics<T> {
73    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
74        f.debug_struct("ChannelStatistics")
75            .field("note_on_count", &self.note_on_count)
76            .field("note_off_count", &self.note_off_count)
77            .field("total_event_count", &self.total_event_count)
78            .field("other_event_count", &self.other_event_count())
79            .field("total_length_ticks", &self.total_length_ticks)
80            .field(
81                "tempo_events",
82                &ElementCountDebug("TempoEvent", self.tempo_events.len()),
83            )
84            .finish()
85    }
86}
87
88/// A struct to hold the statistics of a group of sequences.
89pub struct ChannelGroupStatistics<T: MIDINum> {
90    group: ChannelStatistics<T>,
91    channels: Vec<ChannelStatistics<T>>,
92}
93
94impl<T: MIDINum> ChannelGroupStatistics<T> {
95    /// The list of statistics for individual channels
96    pub fn channels(&self) -> &[ChannelStatistics<T>] {
97        &self.channels
98    }
99}
100
101impl<T: MIDINum> Deref for ChannelGroupStatistics<T> {
102    type Target = ChannelStatistics<T>;
103
104    fn deref(&self) -> &Self::Target {
105        &self.group
106    }
107}
108
109impl<T: MIDINum> std::fmt::Debug for ChannelGroupStatistics<T> {
110    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
111        f.debug_struct("ChannelGroupStatistics")
112            .field("group", &self.group)
113            .field(
114                "channels",
115                &ElementCountDebug("ChannelStatistics", self.channels.len()),
116            )
117            .finish()
118    }
119}
120
121pub fn tempo_sequence_get_duration<T: MIDINum>(
122    tempos: &[Delta<T, TempoEvent>],
123    ppq: u16,
124    ticks: T,
125) -> Duration {
126    let mut ticks = ticks;
127    let mut time = 0.0;
128    let mut multiplier = (500000.0 / ppq as f64) / 1000000.0;
129    for t in tempos {
130        let offset = t.delta();
131        if offset > ticks {
132            break;
133        }
134        ticks -= offset;
135
136        let offset: f64 = offset.midi_num_into();
137        time += multiplier * offset;
138        multiplier = (t.tempo as f64 / ppq as f64) / 1000000.0;
139    }
140    let ticks: f64 = ticks.midi_num_into();
141    time += multiplier * ticks;
142    Duration::from_secs_f64(time)
143}
144
145/// Parse the events in a single channel and return the statistics for this channel.
146///
147/// ❗ **NOTE:** Time in seconds may be inaccurate due to the channel not having the MIDI's tempo events!
148/// Make sure the iterator contains all of the MIDI's tempo events to get the accurate length in seconds.
149pub fn get_channel_statistics<D: MIDINum, E: MIDIEventEnum + MIDIDelta<D>, Err>(
150    iter: impl Iterator<Item = Result<E, Err>>,
151) -> Result<ChannelStatistics<D>, Err> {
152    let mut note_on_count = 0;
153    let mut note_off_count = 0;
154    let mut total_event_count = 0;
155    let mut total_length_ticks = D::zero();
156    let mut ticks_since_last_tempo = D::zero();
157
158    let mut tempo_events = Vec::new();
159
160    for event in iter {
161        let event = event?;
162        total_event_count += 1;
163        total_length_ticks += event.delta();
164        ticks_since_last_tempo += event.delta();
165        match event.as_event() {
166            Event::NoteOn(_) => note_on_count += 1,
167            Event::NoteOff(_) => note_off_count += 1,
168            Event::Tempo(t) => {
169                let ev = *t.clone();
170                let tempo_delta = Delta::new(ticks_since_last_tempo, ev);
171                tempo_events.push(tempo_delta);
172                ticks_since_last_tempo = D::zero();
173            }
174            _ => (),
175        }
176    }
177
178    Ok(ChannelStatistics {
179        note_on_count,
180        note_off_count,
181        total_event_count,
182        total_length_ticks,
183        tempo_events: tempo_events.into(),
184    })
185}
186
187/// Parse the events in an array of channels (multithreaded) and return the statistics for all of the channels,
188/// as well as the combined stats.
189///
190/// **NOTE:** This uses `rayon` for the threadpool, if you want to use your own rayon threadpool instance then
191/// install it before calling this function.
192pub fn get_channels_array_statistics<
193    D: MIDINum,
194    E: MIDIEventEnum + MIDIDelta<D>,
195    Err: Send,
196    I: Iterator<Item = Result<E, Err>> + Sized + Send,
197>(
198    iters: Vec<I>,
199) -> Result<ChannelGroupStatistics<D>, Err> {
200    let pool = iters
201        .into_par_iter()
202        .map(|iter| get_channel_statistics(iter));
203    let mut result = Vec::new();
204    pool.collect_into_vec(&mut result);
205    let mut channels = pipe!(result.into_iter()|>to_vec_result())?;
206
207    let tempo_vecs: Vec<_> = channels.iter().map(|c| c.tempo_events.clone()).collect();
208    let tempo_iterators = tempo_vecs
209        .into_iter()
210        .map(|tempos| pipe!(tempos.iter().cloned()|>wrap_ok()|>to_vec().into_iter()))
211        .collect();
212
213    let merge = pipe!(tempo_iterators|>merge_events_array()|>to_vec_result().unwrap());
214
215    let tempo_events: Arc<[Delta<D, TempoEvent>]> = merge.into();
216
217    for c in channels.iter_mut() {
218        c.tempo_events = tempo_events.clone();
219    }
220
221    let mut max_tick_length = D::zero();
222    for c in channels.iter() {
223        if c.total_length_ticks > max_tick_length {
224            max_tick_length = c.total_length_ticks;
225        }
226    }
227
228    let group = ChannelStatistics {
229        note_on_count: channels.iter().map(|c| c.note_on_count).sum(),
230        note_off_count: channels.iter().map(|c| c.note_off_count).sum(),
231        total_event_count: channels.iter().map(|c| c.total_event_count).sum(),
232        total_length_ticks: max_tick_length,
233        tempo_events,
234    };
235
236    Ok(ChannelGroupStatistics { group, channels })
237}