Skip to main content

music21/alpha/analysis/
fixer.rs

1//! A score read by optical music recognition corrected by a performance of
2//! it: music21's `alpha.analysis.fixer`.
3//!
4//! The two are aligned by [`StreamAligner`], its target the performance
5//! (MIDI) and its source the reading (OMR), and each fixer reads the
6//! changes the alignment found: [`fix_enharmonics`] respells a note read
7//! with the wrong accidental, [`delete_measures`] takes out a measure
8//! holding a note read wrong, and an [`OrnamentFixer`] writes the trill or
9//! turn a performance plays where the reading has a single note.
10
11use crate::alpha::analysis::aligner::{Change, ChangeOp, StreamAligner};
12use crate::alpha::analysis::hasher::HashReference;
13use crate::alpha::analysis::ornament_recognizer::{TrillRecognizer, TurnRecognizer};
14use crate::braille::equality::equal;
15use crate::defaults::{FloatType, IntegerType};
16use crate::error::{Error, Result};
17use crate::expressions::{Expression, Ornament};
18use crate::interval::notes_to_chromatic;
19use crate::note::Note;
20use crate::pitch::{Accidental, Pitch};
21use crate::stream::{Stream, StreamElement, StreamKind};
22
23/// What a reference names: an element, or a note of a chord hashed note by
24/// note.
25enum Named<'a> {
26    Element(&'a StreamElement),
27    InChord(&'a Note),
28}
29
30impl Named<'_> {
31    fn note(&self) -> Option<&Note> {
32        match self {
33            Self::Element(StreamElement::Note(note)) => Some(note),
34            Self::InChord(note) => Some(*note),
35            Self::Element(_) => None,
36        }
37    }
38
39    fn quarter_length(&self) -> FloatType {
40        match self {
41            Self::Element(element) => element.quarter_length(),
42            Self::InChord(note) => StreamElement::Note((*note).clone()).quarter_length(),
43        }
44    }
45
46    fn element(&self) -> StreamElement {
47        match self {
48            Self::Element(element) => (*element).clone(),
49            Self::InChord(note) => StreamElement::Note((*note).clone()),
50        }
51    }
52}
53
54/// The element at a place in [`Stream::recurse`], walking the stream as it
55/// does: each element, then what it holds.
56fn element_at(stream: &Stream, place: usize) -> Option<&StreamElement> {
57    stream.recurse().get(place).map(|(_, element)| *element)
58}
59
60/// The same, to be changed.
61fn element_at_mut(stream: &mut Stream, place: usize) -> Option<&mut StreamElement> {
62    fn walk<'a>(
63        stream: &'a mut Stream,
64        place: usize,
65        seen: &mut usize,
66    ) -> Option<&'a mut StreamElement> {
67        for event in stream.events_mut() {
68            if *seen == place {
69                return Some(event.element_mut());
70            }
71            *seen += 1;
72            if let StreamElement::Stream(inner) = event.element_mut()
73                && let Some(found) = walk(inner, place, seen)
74            {
75                return Some(found);
76            }
77        }
78        None
79    }
80    walk(stream, place, &mut 0)
81}
82
83/// The stream directly holding the element at a place in
84/// [`Stream::recurse`], and the element's place among its events.
85fn container_of(stream: &Stream, place: usize) -> Option<(&Stream, usize)> {
86    fn walk<'a>(stream: &'a Stream, place: usize, seen: &mut usize) -> Option<(&'a Stream, usize)> {
87        for (index, event) in stream.events().iter().enumerate() {
88            if *seen == place {
89                return Some((stream, index));
90            }
91            *seen += 1;
92            if let StreamElement::Stream(inner) = event.element()
93                && let Some(found) = walk(inner, place, seen)
94            {
95                return Some(found);
96            }
97        }
98        None
99    }
100    walk(stream, place, &mut 0)
101}
102
103fn named(stream: &Stream, reference: HashReference) -> Result<Named<'_>> {
104    let element = element_at(stream, reference.element).ok_or_else(|| {
105        Error::Analysis(format!(
106            "no element at place {} of the stream",
107            reference.element
108        ))
109    })?;
110    match (reference.component, element) {
111        (None, element) => Ok(Named::Element(element)),
112        (Some(component), StreamElement::Chord(chord)) => chord
113            .notes()
114            .get(component)
115            .map(Named::InChord)
116            .ok_or_else(|| Error::Analysis(format!("the chord has no note {component}"))),
117        (Some(_), _) => Err(Error::Analysis(
118            "a reference to a note of something that is not a chord".to_string(),
119        )),
120    }
121}
122
123/// The note a reference names, to be changed, where it names a note.
124fn note_mut(stream: &mut Stream, reference: HashReference) -> Option<&mut Note> {
125    match (
126        reference.component,
127        element_at_mut(stream, reference.element)?,
128    ) {
129        (None, StreamElement::Note(note)) => Some(note),
130        (Some(component), StreamElement::Chord(chord)) => chord.notes_mut().get_mut(component),
131        _ => None,
132    }
133}
134
135/// The references a change makes, which music21 always has: a hash made
136/// with no reference has nothing to fix.
137fn references(change: &Change) -> Result<(HashReference, HashReference)> {
138    match (change.target, change.source) {
139        (Some(midi), Some(omr)) => Ok((midi, omr)),
140        _ => Err(Error::Analysis(
141            "a hash made with no reference has nothing to fix".to_string(),
142        )),
143    }
144}
145
146/// Colours what a reference names, where it can be coloured: a note, a
147/// note of a chord, or a chord.
148fn colour(stream: &mut Stream, reference: HashReference, colour: &str) {
149    let colour = Some(colour.to_string());
150    if reference.component.is_some() {
151        if let Some(note) = note_mut(stream, reference) {
152            note.set_color(colour);
153        }
154        return;
155    }
156    match element_at_mut(stream, reference.element) {
157        Some(StreamElement::Note(note)) => note.set_color(colour),
158        Some(StreamElement::Chord(chord)) => chord.set_color(colour),
159        _ => {}
160    }
161}
162
163/// What becomes of one note read where the performance plays another
164/// spelling of it: the write music21's `EnharmonicFixer` makes for one
165/// change.
166///
167/// music21 writes in two ways, and which it is matters to a caller
168/// holding the pitch: its first four cases set the *accidental* on the
169/// pitch the note already has, and its fifth hands the note the pitch
170/// played whole.
171#[derive(Clone, Debug, PartialEq)]
172#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173pub enum EnharmonicFix {
174    /// The accidental written on the note read becomes this one, or none
175    /// at all where music21 drops it.
176    Accidental(Option<Accidental>),
177    /// The note read takes the pitch played whole, which is music21's
178    /// case for a reading that missed a key signature.
179    Played,
180}
181
182/// Whether two notes are too far apart to be two spellings of one sound:
183/// music21's `intervalTooBig`, which reads the interval *class* and not
184/// the interval itself.
185///
186/// `limit` is music21's `setInt`, five in every call it makes.
187///
188/// # Errors
189///
190/// An interval neither pitch spells.
191pub fn interval_too_big(played: &Pitch, read: &Pitch, limit: IntegerType) -> Result<bool> {
192    Ok(notes_to_chromatic(played, read)?.interval_class() > limit)
193}
194
195/// What becomes of one note read, given the note the performance plays
196/// and what the alignment made of the pair: nothing where the spelling
197/// read stands.
198///
199/// This is music21's `EnharmonicFixer.fix` for one change, and its five
200/// cases in music21's order. Of two notes no more than a fifth apart by
201/// interval class:
202///
203/// 1. a reading's natural is dropped where the notes sound alike, and
204///    where the reading is a semitone off it becomes the flat above or
205///    the sharp below the note played;
206/// 2. a reading's sharp or flat on the letter played takes the played
207///    note's accidental;
208/// 3. a sharp read a whole tone above the note played becomes a flat, and
209///    a flat read a whole tone below a sharp;
210/// 4. a note played sharp or flat on the letter read, but read otherwise,
211///    takes the played pitch.
212///
213/// # Errors
214///
215/// An interval neither pitch spells, or an accidental no name spells.
216pub fn enharmonic_fix(played: &Pitch, read: &Pitch, op: ChangeOp) -> Result<Option<EnharmonicFix>> {
217    if op == ChangeOp::NoChange {
218        return Ok(None);
219    }
220    if interval_too_big(played, read, 5)? {
221        return Ok(None);
222    }
223    let accidental = read.written_accidental().map(Accidental::name);
224    let has_sharp_or_flat = accidental.is_some_and(|name| name != "natural");
225    let same_step = played.step() == read.step();
226    let accidental_named = |name: &str| -> Result<Option<EnharmonicFix>> {
227        Ok(Some(EnharmonicFix::Accidental(Some(Accidental::new(
228            name,
229        )?))))
230    };
231    if accidental == Some("natural") {
232        if played.ps() == read.ps() {
233            return Ok(Some(EnharmonicFix::Accidental(None)));
234        }
235        if read.ps() > played.ps() {
236            if read.ps() - 1.0 == played.ps() {
237                return accidental_named("flat");
238            }
239        } else if read.ps() < played.ps() && read.ps() + 1.0 == played.ps() {
240            return accidental_named("sharp");
241        }
242    } else if has_sharp_or_flat && same_step {
243        return Ok(Some(EnharmonicFix::Accidental(
244            played.written_accidental().cloned(),
245        )));
246    } else if has_sharp_or_flat {
247        if read.ps() > played.ps() {
248            if accidental == Some("sharp") && read.ps() - 2.0 == played.ps() {
249                return accidental_named("flat");
250            }
251        } else if read.ps() < played.ps()
252            && accidental == Some("flat")
253            && read.ps() + 2.0 == played.ps()
254        {
255            return accidental_named("sharp");
256        }
257    } else if read != played
258        && played
259            .written_accidental()
260            .is_some_and(|accidental| accidental.name() != "natural")
261        && same_step
262    {
263        return Ok(Some(EnharmonicFix::Played));
264    }
265    Ok(None)
266}
267
268/// Respells each note of the reading the performance plays enharmonically
269/// to it: music21's `EnharmonicFixer`.
270///
271/// Every element the changes name in the reading is coloured black, and
272/// each change is then [`enharmonic_fix`]'s to answer.
273///
274/// # Errors
275///
276/// A change with no references, or one naming nothing in its stream.
277pub fn fix_enharmonics(changes: &[Change], midi: &Stream, omr: &mut Stream) -> Result<()> {
278    for change in changes {
279        let (midi_reference, omr_reference) = references(change)?;
280        colour(omr, omr_reference, "black");
281        let Some(played) = named(midi, midi_reference)?.note().cloned() else {
282            continue;
283        };
284        let Some(read) = named(omr, omr_reference)?.note().cloned() else {
285            continue;
286        };
287        let Some(fix) = enharmonic_fix(played.pitch(), read.pitch(), change.op)? else {
288            continue;
289        };
290        let Some(fixed) = note_mut(omr, omr_reference) else {
291            continue;
292        };
293        match fix {
294            EnharmonicFix::Accidental(accidental) => {
295                let mut pitch = read.pitch().clone();
296                pitch.set_written_accidental(accidental);
297                fixed.set_pitch(pitch);
298            }
299            EnharmonicFix::Played => fixed.set_pitch(played.pitch().clone()),
300        }
301    }
302    Ok(())
303}
304
305/// Takes out of the reading each measure holding a note changed from the
306/// performance's: music21's `DeleteFixer`, which "does really weird things
307/// still".
308///
309/// Only a measure standing in the reading itself is taken out, as
310/// music21's `remove` finds nothing deeper, so the measures of a score's
311/// parts stay where they are.
312///
313/// # Errors
314///
315/// A changed note in no measure, which music21 asks to remove nothing, or
316/// a change with no references.
317pub fn delete_measures(changes: &[Change], midi: &Stream, omr: &mut Stream) -> Result<()> {
318    let mut removed: Vec<usize> = Vec::new();
319    let mut result = Ok(());
320    for change in changes {
321        let (midi_reference, omr_reference) = references(change)?;
322        if named(midi, midi_reference)?.note().is_none()
323            || named(omr, omr_reference)?.note().is_none()
324            || change.op == ChangeOp::NoChange
325        {
326            continue;
327        }
328        match measure_holding(omr, omr_reference.element) {
329            Some(Some(top)) => {
330                if !removed.contains(&top) {
331                    removed.push(top);
332                }
333            }
334            Some(None) => {}
335            None => {
336                result = Err(Error::Analysis("a change refers to no element".to_string()));
337                break;
338            }
339        }
340    }
341    removed.sort_unstable();
342    for top in removed.into_iter().rev() {
343        omr.remove_event(top);
344    }
345    result
346}
347
348/// The measure holding the element at a place in [`Stream::recurse`], as
349/// its place among the stream's own events where it stands there, and
350/// nothing where it stands deeper; `None` where no measure holds it.
351fn measure_holding(stream: &Stream, place: usize) -> Option<Option<usize>> {
352    /// The places among their streams' events of the element and of each
353    /// stream holding it, outermost first.
354    fn path(stream: &Stream, place: usize, seen: &mut usize, out: &mut Vec<usize>) -> bool {
355        for (index, event) in stream.events().iter().enumerate() {
356            out.push(index);
357            if *seen == place {
358                return true;
359            }
360            *seen += 1;
361            if let StreamElement::Stream(inner) = event.element()
362                && path(inner, place, seen, out)
363            {
364                return true;
365            }
366            out.pop();
367        }
368        false
369    }
370    let mut places = Vec::new();
371    if !path(stream, place, &mut 0, &mut places) {
372        return None;
373    }
374    // The streams holding the element, each with its depth.
375    let mut holders = Vec::new();
376    let mut current = stream;
377    for (depth, &index) in places[..places.len() - 1].iter().enumerate() {
378        let StreamElement::Stream(inner) = current.events()[index].element() else {
379            return None;
380        };
381        holders.push((depth, inner.kind()));
382        current = inner;
383    }
384    let (depth, _) = holders
385        .into_iter()
386        .rev()
387        .find(|(_, kind)| *kind == StreamKind::Measure)?;
388    Some((depth == 0).then_some(places[0]))
389}
390
391/// The notes, rests and chords from one on, as many as fit in a length,
392/// each a copy: music21's `getNotesWithinDuration`, read from the stream
393/// directly holding the first.
394///
395/// Nothing where the first is longer than the length; else it and each
396/// that follows it in its stream while the length left holds it.
397pub fn notes_within_duration(
398    container: &Stream,
399    index: usize,
400    total: FloatType,
401) -> Vec<StreamElement> {
402    let events = container.events();
403    let Some(first) = events.get(index) else {
404        return Vec::new();
405    };
406    let first = first.element();
407    if first.quarter_length() > total {
408        return Vec::new();
409    }
410    let mut left = total - first.quarter_length();
411    let mut out = vec![first.clone()];
412    for event in &events[index + 1..] {
413        let element = event.element();
414        if !is_general_note(element) {
415            continue;
416        }
417        if left < element.quarter_length() {
418            break;
419        }
420        left -= element.quarter_length();
421        out.push(element.clone());
422    }
423    out
424}
425
426/// music21's `GeneralNote`: anything written that sounds or rests.
427fn is_general_note(element: &StreamElement) -> bool {
428    matches!(
429        element,
430        StreamElement::Note(_)
431            | StreamElement::Chord(_)
432            | StreamElement::Rest(_)
433            | StreamElement::Unpitched(_)
434            | StreamElement::PercussionChord(_)
435            | StreamElement::ChordSymbol(_)
436    )
437}
438
439/// The busy notes a reference names the start of, as many as last no
440/// longer than `total`.
441fn busy_notes(
442    stream: &Stream,
443    reference: HashReference,
444    total: FloatType,
445) -> Result<Vec<StreamElement>> {
446    if reference.component.is_some() {
447        // A note of a chord stands in no stream, so nothing follows it.
448        let note = named(stream, reference)?;
449        return Ok(if note.quarter_length() > total {
450            Vec::new()
451        } else {
452            vec![note.element()]
453        });
454    }
455    let (container, index) = container_of(stream, reference.element).ok_or_else(|| {
456        Error::Analysis(format!(
457            "no element at place {} of the stream",
458            reference.element
459        ))
460    })?;
461    Ok(notes_within_duration(container, index, total))
462}
463
464/// A recognizer an [`OrnamentFixer`] tries.
465#[derive(Clone, Debug, PartialEq)]
466pub enum Recognizer {
467    /// A trill.
468    Trill(TrillRecognizer),
469    /// A turn.
470    Turn(TurnRecognizer),
471}
472
473impl Recognizer {
474    fn recognize(
475        &self,
476        busy: &[StreamElement],
477        simple: &[StreamElement],
478    ) -> Result<Option<Ornament>> {
479        match self {
480            Self::Trill(recognizer) => recognizer.recognize(busy, simple),
481            Self::Turn(recognizer) => recognizer.recognize(busy, simple),
482        }
483    }
484}
485
486/// Writes the ornaments a performance plays on the notes of a reading
487/// that has none: music21's `OrnamentFixer`.
488#[derive(Clone, Debug, PartialEq)]
489pub struct OrnamentFixer {
490    /// The recognizers tried, in order.
491    pub recognizers: Vec<Recognizer>,
492    /// The colour a note given an ornament is marked in: music21's
493    /// `markChangeColor`.
494    pub mark_change_color: String,
495}
496
497impl OrnamentFixer {
498    /// Recognizes trills, plain and then with a nachschlag: music21's
499    /// `TrillFixer`.
500    pub fn trills() -> Self {
501        Self {
502            recognizers: vec![
503                Recognizer::Trill(TrillRecognizer::default()),
504                Recognizer::Trill(TrillRecognizer {
505                    check_nachschlag: true,
506                    ..TrillRecognizer::default()
507                }),
508            ],
509            mark_change_color: "blue".to_string(),
510        }
511    }
512
513    /// Recognizes turns: music21's `TurnFixer`.
514    pub fn turns() -> Self {
515        Self {
516            recognizers: vec![Recognizer::Turn(TurnRecognizer::default())],
517            mark_change_color: "blue".to_string(),
518        }
519    }
520
521    /// The ornament the first recognizer to find one finds: music21's
522    /// `findOrnament`.
523    ///
524    /// # Errors
525    ///
526    /// As the recognizers.
527    pub fn find_ornament(
528        &self,
529        busy: &[StreamElement],
530        simple: &[StreamElement],
531    ) -> Result<Option<Ornament>> {
532        for recognizer in &self.recognizers {
533            if let Some(found) = recognizer.recognize(busy, simple)? {
534                return Ok(Some(found));
535            }
536        }
537        Ok(None)
538    }
539
540    /// Gives each note of the reading an inserted or substituted note
541    /// stands for the ornament the performance plays there, from the
542    /// performed note on for as long as the read note lasts: music21's
543    /// `fix` in place. A read note given an ornament is not tried again,
544    /// nor are performed notes already made into one -- each compared as
545    /// music21 compares notes, by what they are rather than which they are.
546    /// A note with an ornament already keeps it; with `mark`, one given an
547    /// ornament is coloured [`OrnamentFixer::mark_change_color`].
548    ///
549    /// # Errors
550    ///
551    /// A change with no references or naming nothing, or a recognizer's
552    /// error.
553    pub fn fix(
554        &self,
555        changes: &[Change],
556        midi: &Stream,
557        omr: &mut Stream,
558        mark: bool,
559    ) -> Result<()> {
560        let mut labelled: Vec<StreamElement> = Vec::new();
561        let mut used: Vec<StreamElement> = Vec::new();
562        for change in changes {
563            if matches!(change.op, ChangeOp::NoChange | ChangeOp::Deletion) {
564                continue;
565            }
566            let (midi_reference, omr_reference) = references(change)?;
567            let read = named(omr, omr_reference)?;
568            let read_element = read.element();
569            if labelled.iter().any(|done| equal(done, &read_element)) {
570                continue;
571            }
572            let busy = busy_notes(midi, midi_reference, read.quarter_length())?;
573            if busy
574                .iter()
575                .any(|note| used.iter().any(|done| equal(done, note)))
576            {
577                continue;
578            }
579            let Some(ornament) = self.find_ornament(&busy, std::slice::from_ref(&read_element))?
580            else {
581                continue;
582            };
583            used.extend(busy);
584            if let Some(note) = note_mut(omr, omr_reference) {
585                let already = note
586                    .expressions()
587                    .iter()
588                    .any(|expression| matches!(expression, Expression::Ornament(_)));
589                if !already {
590                    note.expressions_mut()
591                        .push(Expression::Ornament(Box::new(ornament)));
592                    if mark {
593                        note.set_color(Some(self.mark_change_color.clone()));
594                    }
595                }
596                labelled.push(StreamElement::Note(note.clone()));
597            }
598        }
599        Ok(())
600    }
601
602    /// The reading with its ornaments written, the two streams copied and
603    /// aligned afresh by a [`StreamAligner`]: music21's `fix` with
604    /// `inPlace=False`, whose new fixer holds this reading.
605    ///
606    /// # Errors
607    ///
608    /// As [`OrnamentFixer::fix`], or streams that cannot be aligned.
609    pub fn fixed(&self, midi: &Stream, omr: &Stream) -> Result<Stream> {
610        let mut aligner = StreamAligner::new();
611        aligner.align(midi, omr)?;
612        let mut fixed = omr.clone();
613        self.fix(&aligner.changes, midi, &mut fixed, false)?;
614        Ok(fixed)
615    }
616}
617
618#[cfg(test)]
619mod tests {
620    use super::*;
621    use crate::duration::Duration;
622    use crate::expressions::OrnamentKind;
623
624    fn note(name: &str, length: FloatType) -> Note {
625        Note::from_name(name)
626            .unwrap()
627            .with_duration(Duration::new(length).unwrap())
628    }
629
630    fn measure(notes: &[(&str, FloatType)]) -> Stream {
631        let mut measure = Stream::with_kind(StreamKind::Measure);
632        for (name, length) in notes {
633            measure.push(note(name, *length));
634        }
635        measure
636    }
637
638    fn reference(element: usize) -> Option<HashReference> {
639        Some(HashReference {
640            element,
641            component: None,
642        })
643    }
644
645    /// music21's EnharmonicFixer doctests: a played note and the note
646    /// read, and what the read note becomes.
647    #[test]
648    fn enharmonics_are_respelled_as_music21_respells_them() {
649        for (played, read, fixed) in [
650            ("B-4", "A#4", "A#4"),
651            ("A#4", "A#4", "A#4"),
652            ("A4", "An4", "A4"),
653            ("G#4", "An4", "A-4"),
654            ("G-4", "Fn4", "F#4"),
655            ("G#4", "Gn4", "G#4"),
656            ("G#4", "G-4", "G#4"),
657            ("G#4", "A#4", "A-4"),
658            ("A-4", "G-4", "G#4"),
659        ] {
660            let midi = measure(&[(played, 1.0)]);
661            let mut omr = measure(&[(read, 1.0)]);
662            let op = if played == read {
663                ChangeOp::NoChange
664            } else {
665                ChangeOp::Substitution
666            };
667            let changes = [Change {
668                target: reference(0),
669                source: reference(0),
670                op,
671            }];
672            fix_enharmonics(&changes, &midi, &mut omr).unwrap();
673            let StreamElement::Note(result) = omr.events()[0].element() else {
674                unreachable!("the measure holds a note")
675            };
676            let named = result.pitch().name_with_octave();
677            let expected = Note::from_name(fixed).unwrap().pitch().name_with_octave();
678            assert_eq!(named, expected, "{played} played, {read} read");
679            assert_eq!(result.color(), Some("black"));
680            if read == "An4" && played == "A4" {
681                assert!(result.pitch().written_accidental().is_none());
682            }
683        }
684    }
685
686    /// The same cases asked one change at a time, which is what a caller
687    /// with two notes and no streams asks -- music21's own examples hand
688    /// its fixer `None` for both streams.
689    #[test]
690    fn one_change_says_what_the_note_read_becomes() -> Result<()> {
691        let pitch = |name: &str| -> Result<Pitch> { Ok(Note::from_name(name)?.pitch().clone()) };
692        let written = |name: &str| -> Result<Option<EnharmonicFix>> {
693            Ok(Some(EnharmonicFix::Accidental(Some(Accidental::new(
694                name,
695            )?))))
696        };
697        for (played, read, fix) in [
698            ("A4", "An4", Some(EnharmonicFix::Accidental(None))),
699            ("G#4", "An4", written("flat")?),
700            ("G-4", "Fn4", written("sharp")?),
701            ("G#4", "Gn4", written("sharp")?),
702            ("G#4", "G-4", written("sharp")?),
703            ("G#4", "A#4", written("flat")?),
704            ("A-4", "G-4", written("sharp")?),
705            // Two spellings of one sound on different letters: music21
706            // writes nothing.
707            ("B-4", "A#4", None),
708        ] {
709            let (played, read) = (pitch(played)?, pitch(read)?);
710            assert_eq!(
711                enharmonic_fix(&played, &read, ChangeOp::Substitution)?,
712                fix,
713                "{} played, {} read",
714                played.name_with_octave(),
715                read.name_with_octave()
716            );
717            // Nothing is written where the alignment found no change.
718            assert_eq!(enharmonic_fix(&played, &read, ChangeOp::NoChange)?, None);
719        }
720        // A reading that missed a key signature takes the pitch played
721        // whole, which is music21's fifth case.
722        assert_eq!(
723            enharmonic_fix(&pitch("B-4")?, &pitch("B4")?, ChangeOp::Substitution)?,
724            Some(EnharmonicFix::Played)
725        );
726        // Too far apart to be two spellings of one sound, read as the
727        // interval class and not the interval.
728        assert!(!interval_too_big(&pitch("C4")?, &pitch("F4")?, 5)?);
729        assert!(interval_too_big(&pitch("C4")?, &pitch("F#4")?, 5)?);
730        assert!(interval_too_big(&pitch("C4")?, &pitch("F4")?, 4)?);
731        assert_eq!(
732            enharmonic_fix(&pitch("C4")?, &pitch("F#4")?, ChangeOp::Substitution)?,
733            None
734        );
735        Ok(())
736    }
737
738    /// music21's testGetNotesWithinDuration, its measure m2.
739    #[test]
740    fn notes_within_a_length_are_the_ones_that_fit() {
741        let m2 = measure(&[("C", 1.0), ("D", 0.5), ("E", 0.5)]);
742        let names = |total: FloatType| -> Vec<String> {
743            notes_within_duration(&m2, 0, total)
744                .iter()
745                .map(|element| match element {
746                    StreamElement::Note(note) => note.pitch().name(),
747                    _ => "other".to_string(),
748                })
749                .collect()
750        };
751        assert_eq!(names(1.0), ["C"]);
752        assert_eq!(names(2.0), ["C", "D", "E"]);
753        assert_eq!(names(4.0), ["C", "D", "E"]);
754        assert_eq!(names(1.5), ["C", "D"]);
755        assert_eq!(names(1.75), ["C", "D"]);
756        assert!(names(0.5).is_empty());
757    }
758
759    fn ornaments(stream: &Stream) -> Vec<Vec<(OrnamentKind, FloatType, bool)>> {
760        stream
761            .events()
762            .iter()
763            .map(|event| match event.element() {
764                StreamElement::Note(note) => note
765                    .expressions()
766                    .iter()
767                    .filter_map(|expression| match expression {
768                        Expression::Ornament(ornament) => Some((
769                            ornament.kind(),
770                            ornament.quarter_length(),
771                            ornament.nachschlag(),
772                        )),
773                        _ => None,
774                    })
775                    .collect(),
776                _ => Vec::new(),
777            })
778            .collect()
779    }
780
781    fn fixed(
782        fixer: &OrnamentFixer,
783        midi: &Stream,
784        omr: &Stream,
785    ) -> Vec<Vec<(OrnamentKind, FloatType, bool)>> {
786        // In place and on copies alike.
787        let copied = fixer.fixed(midi, omr).unwrap();
788        let mut aligner = StreamAligner::new();
789        aligner.align(midi, omr).unwrap();
790        let mut in_place = omr.clone();
791        fixer
792            .fix(&aligner.changes, midi, &mut in_place, false)
793            .unwrap();
794        assert_eq!(ornaments(&copied), ornaments(&in_place));
795        ornaments(&in_place)
796    }
797
798    /// music21's testTrillFixer.
799    #[test]
800    fn trills_are_written_as_music21_writes_them() {
801        use OrnamentKind::Trill;
802        let trills = OrnamentFixer::trills();
803        let mut played = measure(&[("G", 0.25), ("A", 0.25), ("G", 0.25), ("A", 0.25)]);
804        for name in ["C", "B3", "C", "B3", "C", "B3", "C", "B3"] {
805            played.push(note(name, 0.0625));
806        }
807        let read = measure(&[("G", 1.0), ("B3", 1.0)]);
808        assert_eq!(
809            fixed(&trills, &played, &read),
810            // Each trilled note lasts the read note's length shared among
811            // the eight played.
812            [vec![(Trill, 0.25, false)], vec![(Trill, 0.125, false)]]
813        );
814
815        let wrong = measure(&[("C", 0.25), ("A", 0.25), ("C", 0.25), ("A", 0.25)]);
816        assert_eq!(fixed(&trills, &wrong, &measure(&[("C", 1.0)])), [vec![]]);
817        let other = measure(&[("C", 0.25), ("D", 0.25), ("C", 0.25), ("D", 0.25)]);
818        assert_eq!(fixed(&trills, &other, &measure(&[("A", 1.0)])), [vec![]]);
819
820        let nachschlag = measure(&[
821            ("E", 0.125),
822            ("F", 0.125),
823            ("E", 0.125),
824            ("F", 0.125),
825            ("E", 0.125),
826            ("F", 0.125),
827            ("E", 0.125),
828            ("D", 0.125),
829        ]);
830        assert_eq!(
831            fixed(&trills, &nachschlag, &measure(&[("E", 1.0)])),
832            [vec![(Trill, 0.125, true)]]
833        );
834
835        let mut trilled = measure(&[("F", 1.0)]);
836        let mut written = Ornament::of_kind(Trill);
837        written.set_quarter_length(0.125);
838        if let StreamElement::Note(note) = trilled.events_mut()[0].element_mut() {
839            note.expressions_mut()
840                .push(Expression::Ornament(Box::new(written)));
841        }
842        let played = measure(&[("F", 0.125), ("G", 0.125), ("F", 0.125), ("G", 0.125)]);
843        assert_eq!(
844            fixed(&trills, &played, &trilled),
845            [vec![(Trill, 0.125, false)]]
846        );
847    }
848
849    /// music21's testTurnFixer.
850    #[test]
851    fn turns_are_written_as_music21_writes_them() {
852        use OrnamentKind::{InvertedTurn, Turn};
853        let turns = OrnamentFixer::turns();
854        let played = measure(&[("G", 1.0), ("F", 1.0), ("E", 1.0), ("F", 1.0)]);
855        assert_eq!(
856            fixed(&turns, &played, &measure(&[("F", 4.0)])),
857            [vec![(Turn, 1.0, false)]]
858        );
859
860        let read = measure(&[("B-", 1.0), ("G", 1.0), ("B-", 1.0)]);
861        let played = measure(&[
862            ("A", 0.25),
863            ("B-", 0.25),
864            ("C5", 0.25),
865            ("B-", 0.25),
866            ("G", 1.0),
867            ("G#", 0.25),
868            ("A#", 0.25),
869            ("B", 0.25),
870            ("A#", 0.25),
871        ]);
872        assert_eq!(
873            fixed(&turns, &played, &read),
874            [
875                vec![(InvertedTurn, 0.25, false)],
876                vec![],
877                vec![(InvertedTurn, 0.25, false)]
878            ]
879        );
880
881        let played = measure(&[("B", 1.0), ("A", 1.0), ("G", 1.0), ("F", 1.0)]);
882        assert_eq!(fixed(&turns, &played, &measure(&[("A", 4.0)])), [vec![]]);
883    }
884}