Skip to main content

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