Skip to main content

music21_rs/analysis/
floating_key.rs

1//! A key for every measure, each measure's own reading smoothed by its
2//! neighbours': music21's `analysis.floatingKey`.
3
4use crate::{
5    analysis::{KeyEstimate, KeyProfile, estimate_key_of_stream},
6    defaults::{FloatType, IntegerType},
7    error::{Error, Result},
8    key::Key,
9    stream::{Stream, StreamKind},
10};
11
12/// How much a neighbouring measure's reading counts at a distance: its
13/// coefficient over the distance plus one. music21's `divide`.
14pub fn divide(coefficient: FloatType, distance: IntegerType) -> FloatType {
15    coefficient / (distance.abs() + 1) as FloatType
16}
17
18/// The keys of a stream's measures, read one measure at a time and then
19/// smoothed by the measures around each: music21's `KeyAnalyzer`.
20///
21/// A measure is read as every part's measure at that place, by the
22/// Aarden-Essen profile music21's `analyze('key')` uses, and a measure with
23/// no notes has no reading. Each reading is then given the readings of the
24/// `window_size` measures either side, each weighed by `weight_algorithm`
25/// at its distance, and the key scoring best is the measure's.
26///
27/// ```
28/// use music21_rs::analysis::floating_key::KeyAnalyzer;
29/// use music21_rs::tinynotation::from_tiny_notation;
30///
31/// let line = from_tiny_notation("4/4 c4 e g c' c e g c' g b d' g d f# a d'")?;
32/// let analyzer = KeyAnalyzer::new(&line)?;
33/// let keys: Vec<String> = analyzer
34///     .run()?
35///     .iter()
36///     .map(|key| key.tonic_pitch_name_with_case())
37///     .collect();
38/// assert_eq!(keys, ["C", "C", "G", "G"]);
39/// # Ok::<(), music21_rs::Error>(())
40/// ```
41#[derive(Clone, Debug)]
42pub struct KeyAnalyzer<'a> {
43    stream: &'a Stream,
44    measure_count: usize,
45    /// How many measures either side of each are taken into account. Four
46    /// by default; fewer for music that changes key often.
47    pub window_size: usize,
48    /// How a neighbour's reading is weighed at its distance in measures.
49    pub weight_algorithm: fn(FloatType, IntegerType) -> FloatType,
50}
51
52/// A measure's readings, each key's name with case beside its coefficient,
53/// best first.
54type Interpretations = Vec<(String, FloatType)>;
55
56impl<'a> KeyAnalyzer<'a> {
57    /// An analyzer of a stream of measures, or of a score whose first part
58    /// is in measures, which says how many there are.
59    ///
60    /// # Errors
61    ///
62    /// A stream with no measures in it.
63    pub fn new(stream: &'a Stream) -> Result<Self> {
64        let first = if stream.has_part_like_streams() {
65            stream.parts().into_iter().next().unwrap_or(stream)
66        } else {
67            stream
68        };
69        let measure_count = measures_of(first).len();
70        if measure_count == 0 {
71            return Err(Error::Analysis(
72                "Stream must have Measures inside it".to_string(),
73            ));
74        }
75        Ok(Self {
76            stream,
77            measure_count,
78            window_size: 4,
79            weight_algorithm: divide,
80        })
81    }
82
83    /// How many measures are read.
84    pub fn measure_count(&self) -> usize {
85        self.measure_count
86    }
87
88    /// Every measure's reading on its own, nothing for a measure with no
89    /// notes: music21's `getRawKeyByMeasure`.
90    pub fn raw_key_by_measure(&self) -> Vec<Option<Vec<KeyEstimate>>> {
91        (0..self.measure_count)
92            .map(|index| estimate_key_of_stream(KeyProfile::AardenEssen, &self.slice(index)))
93            .collect()
94    }
95
96    /// A measure's reading as each key's name with case and its coefficient,
97    /// best first: music21's `getInterpretationByMeasure`.
98    pub fn interpretation_by_measure(&self, index: usize) -> Option<Interpretations> {
99        estimate_key_of_stream(KeyProfile::AardenEssen, &self.slice(index))
100            .map(|estimates| interpretations(&estimates))
101    }
102
103    /// The key of every measure that has a reading, smoothed by its
104    /// neighbours': music21's `smoothInterpretationByMeasure`. A measure
105    /// with no reading has no key, and is left out. Each measure is
106    /// smoothed by its neighbours' own readings, not their smoothed ones.
107    ///
108    /// # Errors
109    ///
110    /// A best reading that names no key.
111    pub fn smooth_interpretation_by_measure(&self) -> Result<Vec<Key>> {
112        let raw: Vec<Option<Interpretations>> = self
113            .raw_key_by_measure()
114            .iter()
115            .map(|estimates| estimates.as_deref().map(interpretations))
116            .collect();
117        let mut smoothed = Vec::new();
118        for index in 0..self.measure_count {
119            let Some(mut readings) = raw[index].clone() else {
120                continue;
121            };
122            let window = self.window_size as IntegerType;
123            for distance in -window..=window {
124                let neighbour = index as IntegerType + distance;
125                if neighbour < 0 || neighbour >= self.measure_count as IntegerType || distance == 0
126                {
127                    continue;
128                }
129                let Some(theirs) = &raw[neighbour as usize] else {
130                    continue;
131                };
132                for (name, value) in readings.iter_mut() {
133                    if let Some((_, coefficient)) = theirs.iter().find(|(key, _)| key == name) {
134                        *value += (self.weight_algorithm)(*coefficient, distance);
135                    }
136                }
137            }
138            let mut best: Option<&(String, FloatType)> = None;
139            for reading in readings.iter() {
140                if best.is_none_or(|(_, value)| reading.1 > *value) {
141                    best = Some(reading);
142                }
143            }
144            if let Some((name, _)) = best {
145                smoothed.push(Key::from_tonic(name)?);
146            }
147        }
148        Ok(smoothed)
149    }
150
151    /// Every measure's key, smoothed: music21's `run`.
152    ///
153    /// # Errors
154    ///
155    /// As [`KeyAnalyzer::smooth_interpretation_by_measure`].
156    pub fn run(&self) -> Result<Vec<Key>> {
157        self.smooth_interpretation_by_measure()
158    }
159
160    /// The measure at an index of every part, as one stream; for a stream
161    /// that holds no parts, its own measure there.
162    fn slice(&self, index: usize) -> Stream {
163        let mut slice = Stream::new();
164        if self.stream.has_part_like_streams() {
165            for part in self.stream.parts() {
166                if let Some(measure) = measures_of(part).get(index) {
167                    slice.insert(0.0, (*measure).clone());
168                }
169            }
170        } else if let Some(measure) = measures_of(self.stream).get(index) {
171            slice.insert(0.0, (*measure).clone());
172        }
173        slice
174    }
175}
176
177fn measures_of(stream: &Stream) -> Vec<&Stream> {
178    stream
179        .events()
180        .iter()
181        .filter_map(|event| event.element().as_stream())
182        .filter(|inner| inner.kind() == StreamKind::Measure)
183        .collect()
184}
185
186fn interpretations(estimates: &[KeyEstimate]) -> Interpretations {
187    estimates
188        .iter()
189        .map(|estimate| {
190            (
191                estimate.key().tonic_pitch_name_with_case(),
192                estimate.score(),
193            )
194        })
195        .collect()
196}