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}