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}