Skip to main content

music21_rs/analysis/
pitch_analysis.rs

1//! Counting a stream's pitches: music21's `analysis.pitchAnalysis`.
2
3use crate::{
4    error::Result,
5    pitch::Pitch,
6    stream::{Stream, StreamElement},
7};
8
9/// How often each value of some property of a stream's pitches comes, in
10/// the order each value is first met: music21's `pitchAttributeCount`, with
11/// the property a function of the pitch rather than the name of one.
12///
13/// The pitches are music21's `Stream.pitches`: those of every note, chord,
14/// chord symbol and pitched member of a percussion chord, through every
15/// nested stream, in the order the streams hold them. A key's pitches are
16/// not among them.
17///
18/// ```
19/// use music21_rs::analysis::pitch_analysis::pitch_attribute_count;
20/// use music21_rs::tinynotation::from_tiny_notation;
21///
22/// let line = from_tiny_notation("4/4 c4 d c e")?;
23/// let counts = pitch_attribute_count(&line, |pitch| pitch.name())?;
24/// assert_eq!(counts, [("C".to_string(), 2), ("D".to_string(), 1), ("E".to_string(), 1)]);
25/// # Ok::<(), music21_rs::Error>(())
26/// ```
27///
28/// # Errors
29///
30/// A chord symbol whose notes cannot be worked out.
31pub fn pitch_attribute_count<K: PartialEq>(
32    stream: &Stream,
33    attribute: impl Fn(&Pitch) -> K,
34) -> Result<Vec<(K, usize)>> {
35    let mut counts: Vec<(K, usize)> = Vec::new();
36    for pitch in music21_pitches(stream)? {
37        let value = attribute(&pitch);
38        match counts.iter_mut().find(|(known, _)| *known == value) {
39            Some((_, count)) => *count += 1,
40            None => counts.push((value, 1)),
41        }
42    }
43    Ok(counts)
44}
45
46pub(crate) fn music21_pitches(stream: &Stream) -> Result<Vec<Pitch>> {
47    let mut pitches = Vec::new();
48    for event in stream.events() {
49        match event.element() {
50            StreamElement::Stream(inner) => pitches.extend(music21_pitches(inner)?),
51            StreamElement::ChordSymbol(symbol) => pitches.extend(symbol.pitches()?),
52            element => pitches.extend(element.pitches()),
53        }
54    }
55    Ok(pitches)
56}