1use 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
23enum 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
54fn element_at(stream: &Stream, place: usize) -> Option<&StreamElement> {
57 stream.recurse().get(place).map(|(_, element)| *element)
58}
59
60fn 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
83fn 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
123fn 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
135fn 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
146fn 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#[derive(Clone, Debug, PartialEq)]
172#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173pub enum EnharmonicFix {
174 Accidental(Option<Accidental>),
177 Played,
180}
181
182pub fn interval_too_big(played: &Pitch, read: &Pitch, limit: IntegerType) -> Result<bool> {
192 Ok(notes_to_chromatic(played, read)?.interval_class() > limit)
193}
194
195pub 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
268pub 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
305pub 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
348fn measure_holding(stream: &Stream, place: usize) -> Option<Option<usize>> {
352 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 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
391pub 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
426fn 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
439fn busy_notes(
442 stream: &Stream,
443 reference: HashReference,
444 total: FloatType,
445) -> Result<Vec<StreamElement>> {
446 if reference.component.is_some() {
447 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#[derive(Clone, Debug, PartialEq)]
466pub enum Recognizer {
467 Trill(TrillRecognizer),
469 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#[derive(Clone, Debug, PartialEq)]
489pub struct OrnamentFixer {
490 pub recognizers: Vec<Recognizer>,
492 pub mark_change_color: String,
495}
496
497impl OrnamentFixer {
498 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 pub fn turns() -> Self {
515 Self {
516 recognizers: vec![Recognizer::Turn(TurnRecognizer::default())],
517 mark_change_color: "blue".to_string(),
518 }
519 }
520
521 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 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 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 #[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 #[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 ("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 assert_eq!(enharmonic_fix(&played, &read, ChangeOp::NoChange)?, None);
719 }
720 assert_eq!(
723 enharmonic_fix(&pitch("B-4")?, &pitch("B4")?, ChangeOp::Substitution)?,
724 Some(EnharmonicFix::Played)
725 );
726 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 #[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 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 #[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 [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 #[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}