Skip to main content

music21_rs/analysis/
elements.rs

1//! Counting what a stream's elements say: music21's `analysis.elements`.
2
3/// How often each value of some property of a stream's elements comes, in
4/// the order each value is first met: music21's `attributeCount`, with the
5/// property a function of the element rather than the name of one, and
6/// `None` where the element has no such property -- which is music21's
7/// `hasattr` test.
8///
9/// The elements are whatever reading the caller counts over: a stream's own
10/// [`events`](crate::Stream::events), a recursive one
11/// ([`recurse`](crate::Stream::recurse), [`leaves`](crate::Stream::leaves)),
12/// or either of those filtered, as music21 takes a stream or a stream
13/// iterator.
14///
15/// The counts are in counting order, as Python's `Counter` keeps them, so
16/// `most_common` is a sort by count that leaves ties as they stand.
17///
18/// ```
19/// use music21_rs::analysis::elements::attribute_count;
20/// use music21_rs::stream::StreamElement;
21/// use music21_rs::tinynotation::from_tiny_notation;
22///
23/// let line = from_tiny_notation("4/4 c4 d8 e8 f2")?;
24/// let lengths = attribute_count(line.recurse(), |(_, element)| match element {
25///     StreamElement::Note(_) => Some(element.quarter_length()),
26///     // A clef or a meter is not a note, and is not counted -- as
27///     // music21 passes over an element the attribute is not on.
28///     _ => None,
29/// });
30/// assert_eq!(lengths, [(1.0, 1), (0.5, 2), (2.0, 1)]);
31/// # Ok::<(), music21_rs::Error>(())
32/// ```
33pub fn attribute_count<T, K: PartialEq>(
34    elements: impl IntoIterator<Item = T>,
35    attribute: impl Fn(T) -> Option<K>,
36) -> Vec<(K, usize)> {
37    let mut counts: Vec<(K, usize)> = Vec::new();
38    for element in elements {
39        let Some(value) = attribute(element) else {
40            continue;
41        };
42        match counts.iter_mut().find(|(known, _)| *known == value) {
43            Some((_, count)) => *count += 1,
44            None => counts.push((value, 1)),
45        }
46    }
47    counts
48}
49
50#[cfg(test)]
51mod tests {
52    use super::attribute_count;
53    use crate::stream::StreamElement;
54    use crate::tinynotation::from_tiny_notation;
55
56    #[test]
57    fn a_value_is_counted_where_it_was_first_met() {
58        let line = from_tiny_notation("4/4 e4 c4 c4 e4").expect("the line is read");
59        let names = attribute_count(line.recurse(), |(_, element)| match element {
60            StreamElement::Note(note) => Some(note.pitch().name()),
61            _ => None,
62        });
63        assert_eq!(names, [("E".to_string(), 2), ("C".to_string(), 2)]);
64    }
65
66    #[test]
67    fn an_element_the_property_is_not_on_is_passed_over() {
68        let line = from_tiny_notation("4/4 c4 r4 d2").expect("the line is read");
69        let counted = attribute_count(line.recurse(), |(_, element)| match element {
70            StreamElement::Note(_) => Some(element.quarter_length()),
71            _ => None,
72        });
73        assert_eq!(counted, [(1.0, 1), (2.0, 1)]);
74    }
75}