Skip to main content

music21_rs/alpha/analysis/
ornament_recognizer.rs

1//! Ornaments recognized from the notes they are played as: music21's
2//! `alpha.analysis.ornamentRecognizer`.
3//!
4//! The notes an ornament is played as are its *busy* notes; the note it is
5//! written on, where one is given, is its *simple* note. A recognizer
6//! answers the ornament the busy notes play, or nothing where they play
7//! none.
8//!
9//! ```
10//! use music21_rs::alpha::analysis::ornament_recognizer::TrillRecognizer;
11//! use music21_rs::expressions::OrnamentKind;
12//! use music21_rs::{Duration, Note, StreamElement};
13//!
14//! let played: Vec<StreamElement> = ["G4", "A4", "G4", "A4"]
15//!     .iter()
16//!     .map(|name| Ok(Note::from_name(name)?.with_duration(Duration::new(0.25)?).into()))
17//!     .collect::<music21_rs::Result<_>>()?;
18//! let trill = TrillRecognizer::default().recognize(&played, &[])?.expect("a trill");
19//! assert_eq!(trill.kind(), OrnamentKind::Trill);
20//! assert_eq!(trill.quarter_length(), 0.25);
21//! # Ok::<(), music21_rs::Error>(())
22//! ```
23
24use crate::defaults::{FloatType, IntegerType};
25use crate::error::{Error, Result};
26use crate::expressions::{Ornament, OrnamentKind};
27use crate::interval::{Interval, IntervalDirection};
28use crate::makenotation::op_frac;
29use crate::note::Note;
30use crate::stream::StreamElement;
31
32/// How long all the busy notes last together: the first simple note's
33/// length where one is given, else the busy notes' lengths added up.
34/// music21's `calculateOrnamentTotalQl`.
35pub fn ornament_total_quarter_length(
36    busy: &[StreamElement],
37    simple: &[StreamElement],
38) -> FloatType {
39    match simple.first() {
40        Some(note) => note.quarter_length(),
41        None => op_frac(busy.iter().map(StreamElement::quarter_length).sum()),
42    }
43}
44
45/// How long each ornamental note lasts, the busy notes taken as one
46/// ornament: music21's `calculateOrnamentNoteQl`.
47pub fn ornament_note_quarter_length(busy: &[StreamElement], simple: &[StreamElement]) -> FloatType {
48    op_frac(ornament_total_quarter_length(busy, simple) / busy.len() as FloatType)
49}
50
51/// What music21 says reading the pitch of something that has none.
52fn note_of(element: &StreamElement) -> Result<&Note> {
53    match element {
54        StreamElement::Note(note) => Ok(note),
55        other => {
56            let class = match other {
57                StreamElement::Rest(_) => "Rest",
58                StreamElement::Chord(_) => "Chord",
59                StreamElement::Unpitched(_) => "Unpitched",
60                StreamElement::PercussionChord(_) => "PercussionChord",
61                StreamElement::ChordSymbol(_) => "ChordSymbol",
62                _ => "Music21Object",
63            };
64            Err(Error::Analysis(format!(
65                "'{class}' object has no attribute 'pitch'"
66            )))
67        }
68    }
69}
70
71/// Recognizes busy notes as a trill: music21's `TrillRecognizer`.
72#[derive(Clone, Debug, PartialEq, Eq)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
74pub struct TrillRecognizer {
75    /// Whether the last notes may leave the trill as a nachschlag:
76    /// music21's `checkNachschlag`.
77    pub check_nachschlag: bool,
78    /// The most semitones the two trilled notes may be apart:
79    /// `acceptableInterval`.
80    pub acceptable_interval: IntegerType,
81    /// The fewest busy notes a trill with a nachschlag may have:
82    /// `minimumLengthForNachschlag`.
83    pub minimum_length_for_nachschlag: usize,
84}
85
86impl Default for TrillRecognizer {
87    fn default() -> Self {
88        Self {
89            check_nachschlag: false,
90            acceptable_interval: 3,
91            minimum_length_for_nachschlag: 5,
92        }
93    }
94}
95
96impl TrillRecognizer {
97    /// The trill the busy notes play, or nothing: music21's `recognize`.
98    ///
99    /// More than two notes, alternating between two pitches no more than
100    /// [`TrillRecognizer::acceptable_interval`] apart, are a trill up from
101    /// the first, or down where the second is lower; with
102    /// [`TrillRecognizer::check_nachschlag`], the second half may leave the
103    /// alternation. Given a simple note sounding one of the two pitches,
104    /// the trill is from that pitch to the other. Each trilled note lasts
105    /// as long as [`ornament_note_quarter_length`] says, and the trill is
106    /// spelled with the accidental of the note it goes to.
107    ///
108    /// # Errors
109    ///
110    /// A simple note that is not a note, as music21 cannot read its pitch.
111    pub fn recognize(
112        &self,
113        busy: &[StreamElement],
114        simple: &[StreamElement],
115    ) -> Result<Option<Ornament>> {
116        if busy.len() <= 2 {
117            return Ok(None);
118        }
119        let (StreamElement::Note(first), StreamElement::Note(second)) = (&busy[0], &busy[1]) else {
120            return Ok(None);
121        };
122        if (first.pitch().midi() - second.pitch().midi()).abs() > self.acceptable_interval {
123            return Ok(None);
124        }
125        let mut alternating = true;
126        let mut reached = 0;
127        for (index, element) in busy.iter().enumerate() {
128            reached = index;
129            let StreamElement::Note(note) = element else {
130                return Ok(None);
131            };
132            let expected = if index % 2 == 0 { first } else { second };
133            if note.pitch() != expected.pitch() {
134                alternating = false;
135                break;
136            }
137        }
138        let nachschlag = if alternating {
139            false
140        } else if !self.check_nachschlag {
141            return Ok(None);
142        } else if busy.len() >= self.minimum_length_for_nachschlag
143            && reached as FloatType >= busy.len() as FloatType / 2.0
144        {
145            true
146        } else {
147            return Ok(None);
148        };
149
150        let (start, end) = match simple.first() {
151            None => (first, second),
152            Some(written) => {
153                let midi = note_of(written)?.pitch().midi();
154                if midi != first.pitch().midi() && midi != second.pitch().midi() {
155                    return Ok(None);
156                }
157                if midi == second.pitch().midi() {
158                    (second, first)
159                } else {
160                    (first, second)
161                }
162            }
163        };
164        let mut trill = Ornament::of_kind(if start.pitch().midi() <= end.pitch().midi() {
165            OrnamentKind::Trill
166        } else {
167            OrnamentKind::InvertedTrill
168        });
169        trill.set_quarter_length(ornament_note_quarter_length(busy, simple));
170        trill.set_nachschlag(nachschlag);
171        if let Some(accidental) = end.pitch().written_accidental() {
172            trill.set_accidental(Some(accidental.clone()))?;
173        }
174        Ok(Some(trill))
175    }
176}
177
178/// Recognizes busy notes as a turn: music21's `TurnRecognizer`.
179#[derive(Clone, Debug, PartialEq)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
181pub struct TurnRecognizer {
182    /// The steps a turn may take: music21's `acceptableIntervals`, minor,
183    /// major and augmented seconds up and down.
184    pub acceptable_intervals: Vec<Interval>,
185}
186
187impl Default for TurnRecognizer {
188    fn default() -> Self {
189        Self {
190            acceptable_intervals: ["M2", "M-2", "m2", "m-2", "A2", "A-2"]
191                .iter()
192                .map(|name| Interval::from_name(*name).expect("a second"))
193                .collect(),
194        }
195    }
196}
197
198impl TurnRecognizer {
199    /// Whether a turn may take this step: music21's `isAcceptableInterval`.
200    pub fn is_acceptable_interval(&self, step: &Interval) -> bool {
201        self.acceptable_intervals.contains(step)
202    }
203
204    /// The turn the busy notes play, or nothing: music21's `recognize`.
205    ///
206    /// Four notes a second apart, the second and fourth sounding the same
207    /// pitch, the first two steps one way and the last the other way, are
208    /// a turn where they start above and an inverted turn where they start
209    /// below. Given a simple note, it must sound the turn's main pitch and
210    /// last as long as the four notes, within a tenth of a quarter.
211    ///
212    /// # Errors
213    ///
214    /// A busy note or a simple note that is not a note, as music21 cannot
215    /// read its pitch.
216    pub fn recognize(
217        &self,
218        busy: &[StreamElement],
219        simple: &[StreamElement],
220    ) -> Result<Option<Ornament>> {
221        if busy.len() != 4 {
222            return Ok(None);
223        }
224        if let Some(written) = simple.first() {
225            let played: FloatType = busy.iter().map(StreamElement::quarter_length).sum();
226            if (written.quarter_length() - played).abs() > 0.1 {
227                return Ok(None);
228            }
229        }
230        let main = note_of(&busy[1])?.pitch().midi();
231        if main != note_of(&busy[3])?.pitch().midi() {
232            return Ok(None);
233        }
234        if let Some(written) = simple.first()
235            && note_of(written)?.pitch().midi() != main
236        {
237            return Ok(None);
238        }
239        let mut steps = Vec::new();
240        for pair in busy.windows(2) {
241            let step =
242                Interval::between_pitches(note_of(&pair[0])?.pitch(), note_of(&pair[1])?.pitch())?;
243            if !self.is_acceptable_interval(&step) {
244                return Ok(None);
245            }
246            steps.push(step);
247        }
248        if steps[0].direction() != steps[1].direction()
249            || steps[1].direction() == steps[2].direction()
250        {
251            return Ok(None);
252        }
253        let mut turn =
254            Ornament::of_kind(if steps[0].direction() == IntervalDirection::Descending {
255                OrnamentKind::Turn
256            } else {
257                OrnamentKind::InvertedTurn
258            });
259        turn.set_quarter_length(ornament_note_quarter_length(busy, simple));
260        Ok(Some(turn))
261    }
262}
263
264#[cfg(test)]
265mod tests {
266    use super::*;
267    use crate::duration::Duration;
268    use crate::key::KeySignature;
269    use crate::rest::Rest;
270
271    fn notes(names: &[&str], length: FloatType) -> Vec<StreamElement> {
272        names
273            .iter()
274            .map(|name| {
275                StreamElement::Note(
276                    Note::from_name(name)
277                        .unwrap()
278                        .with_duration(Duration::new(length).unwrap()),
279                )
280            })
281            .collect()
282    }
283
284    fn quarter(name: &str) -> Vec<StreamElement> {
285        notes(&[name], 1.0)
286    }
287
288    /// music21's testRecognizeTurn.
289    #[test]
290    fn turns_are_recognized_as_music21_recognizes_them() {
291        let recognizer = TurnRecognizer::default();
292        let kind = |busy: &[StreamElement], simple: &[StreamElement]| {
293            recognizer
294                .recognize(busy, simple)
295                .unwrap()
296                .map(|turn| turn.kind())
297        };
298        let even = notes(&["G", "F#", "E", "F#"], 0.25);
299        assert_eq!(kind(&even, &[]), Some(OrnamentKind::Turn));
300        assert_eq!(kind(&even, &quarter("F#")), Some(OrnamentKind::Turn));
301        assert_eq!(kind(&even, &quarter("G-")), Some(OrnamentKind::Turn));
302        assert_eq!(kind(&even, &quarter("G")), None);
303        assert_eq!(kind(&even, &quarter("A")), None);
304        let mut rubato = notes(&["G", "F#", "E", "F#"], 0.25);
305        for (element, length) in rubato.iter_mut().zip([0.25, 0.15, 0.2, 0.4]) {
306            if let StreamElement::Note(note) = element {
307                note.set_duration(Duration::new(length).unwrap());
308            }
309        }
310        assert_eq!(kind(&rubato, &[]), Some(OrnamentKind::Turn));
311        assert_eq!(
312            kind(&notes(&["E", "F#", "G", "F#"], 0.25), &[]),
313            Some(OrnamentKind::InvertedTurn)
314        );
315        for wrong in [
316            &["G", "F#", "E", "D"][..],
317            &["E", "G", "A", "G"],
318            &["G", "F#", "G", "F#"],
319            &["G", "F#", "E", "F#", "E"],
320            &["G", "F#", "E"],
321        ] {
322            assert_eq!(kind(&notes(wrong, 0.25), &[]), None, "{wrong:?}");
323        }
324        // Four quarters are longer than the quarter they ornament.
325        assert_eq!(
326            kind(&notes(&["G", "F#", "E", "F#"], 1.0), &quarter("F#")),
327            None
328        );
329    }
330
331    /// music21's testRecognizeTrill.
332    #[test]
333    fn trills_are_recognized_as_music21_recognizes_them() {
334        let plain = TrillRecognizer::default();
335        let size = |trill: &Ornament, from: &str| {
336            trill
337                .size(
338                    &crate::pitch::Pitch::from_name(from).unwrap(),
339                    &KeySignature::new(0),
340                )
341                .unwrap()
342                .directed_name()
343        };
344        let gaga = notes(&["G", "A", "G", "A"], 0.25);
345        let trill = plain.recognize(&gaga, &[]).unwrap().unwrap();
346        assert_eq!(trill.kind(), OrnamentKind::Trill);
347        assert_eq!(size(&trill, "G"), "M2");
348        let trill = plain.recognize(&gaga, &quarter("A")).unwrap().unwrap();
349        assert_eq!(trill.kind(), OrnamentKind::InvertedTrill);
350        assert_eq!(size(&trill, "A"), "M-2");
351        assert!(plain.recognize(&gaga, &quarter("G##")).unwrap().is_some());
352        assert!(plain.recognize(&gaga, &quarter("E")).unwrap().is_none());
353        let mut with_rest = gaga.clone();
354        with_rest[2] = StreamElement::Rest(Rest::new(Duration::new(0.25).unwrap()));
355        assert!(plain.recognize(&with_rest, &[]).unwrap().is_none());
356
357        let a_g = notes(&["A", "G#", "A", "G#", "A"], 0.4);
358        let trill = plain.recognize(&a_g, &[]).unwrap().unwrap();
359        assert_eq!(trill.kind(), OrnamentKind::InvertedTrill);
360        assert_eq!(size(&trill, "A"), "m-2");
361
362        let nachschlag = notes(&["C5", "B", "C5", "B", "C5", "D5", "E5", "F5"], 0.125);
363        assert!(plain.recognize(&nachschlag, &[]).unwrap().is_none());
364        let checking = TrillRecognizer {
365            check_nachschlag: true,
366            ..TrillRecognizer::default()
367        };
368        let trill = checking.recognize(&nachschlag, &[]).unwrap().unwrap();
369        assert!(trill.nachschlag());
370        assert_eq!(size(&trill, "C5"), "m-2");
371
372        assert!(plain.recognize(&notes(&["A"], 0.5), &[]).unwrap().is_none());
373        assert!(
374            plain
375                .recognize(&notes(&["A", "G"], 0.5), &[])
376                .unwrap()
377                .is_none()
378        );
379        assert!(
380            plain
381                .recognize(&notes(&["A", "C", "A", "C"], 0.5), &[])
382                .unwrap()
383                .is_none()
384        );
385        assert!(
386            plain
387                .recognize(&notes(&["F", "E", "F", "G"], 0.5), &[])
388                .unwrap()
389                .is_none()
390        );
391    }
392}