Skip to main content

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