Skip to main content

music21_rs/omr/
correctors.rs

1//! Correcting the rhythm of a score read by optical music recognition:
2//! music21's `omr.correctors`.
3//!
4//! A measure whose notes do not fill its bar was most likely misread. The
5//! correctors look for the measure that most likely stands where it should
6//! -- another measure of the same part a likely distance away whose rhythm
7//! differs from it in likely ways (the horizontal model), or the measure
8//! at the same place in another part that tends to move with it (the
9//! vertical model) -- and put that measure's rhythm in its place, keeping
10//! its pitches.
11//!
12//! Rhythms are compared as text: each note, chord or rest of a measure is
13//! one character for its length, notes on even codes and rests on odd
14//! ones ([`measure_hash`]).
15
16use std::collections::HashMap;
17
18use crate::defaults::FloatType;
19use crate::error::{Error, Result};
20use crate::meter::TimeSignature;
21use crate::search::difflib;
22use crate::stream::{Stream, StreamElement, StreamEvent, StreamKind};
23
24pub use crate::search::difflib::{Opcode, OpcodeTag};
25
26/// A flagged measure and the measure found to correct it, with how likely
27/// the correction is: music21's `MeasureRelationship`.
28#[derive(Clone, Copy, Debug, PartialEq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct MeasureRelationship {
31    /// The part of the flagged measure.
32    pub flagged_measure_part: usize,
33    /// The flagged measure's index among its part's measures.
34    pub flagged_measure_index: usize,
35    /// The part of the measure correcting it.
36    pub correct_measure_part: usize,
37    /// The correcting measure's index among its part's measures.
38    pub correct_measure_index: usize,
39    /// How likely the correction is.
40    pub correction_probability: FloatType,
41}
42
43/// How many flagged measures were corrected, and by which model: music21's
44/// `PriorsIntegrationScore`.
45#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47pub struct PriorsIntegrationScore {
48    /// Flagged measures both models found a correction for.
49    pub total: usize,
50    /// Those the horizontal model's correction was used for.
51    pub horizontal: usize,
52    /// Those the vertical model's correction was used for.
53    pub vertical: usize,
54    /// Those left as they were, which music21 never counts.
55    pub ignored: usize,
56}
57
58/// How likely a note is read right: music21's `getProbabilityOnEquality`.
59pub const PROBABILITY_ON_EQUALITY: FloatType = 0.9675;
60/// How likely a note is missed: music21's `getProbabilityOnOmission`.
61pub const PROBABILITY_ON_OMISSION: FloatType = 0.009;
62/// How likely a note is read that is not there: music21's
63/// `getProbabilityOnAddition`.
64pub const PROBABILITY_ON_ADDITION: FloatType = 0.004;
65
66/// The rhythm of a measure as text, a character for each note, chord or
67/// rest, as music21's `MeasureHash.getHashString` writes it. A measure
68/// holding voices is chordified first.
69///
70/// The character is ten times the base-2 logarithm of the length in 256ths
71/// of a quarter, from 1 to 127 -- made even for a note or chord and odd for
72/// a rest -- and that of a 64th for anything of no length. A stroke with no
73/// pitch is left out.
74///
75/// ```
76/// use music21_rs::omr::correctors::measure_hash;
77/// use music21_rs::tinynotation::from_tiny_notation;
78///
79/// let line = from_tiny_notation("4/4 c4 d8 r8 e2")?;
80/// let measure = line.measures()[0];
81/// assert_eq!(measure_hash(measure)?, "PFGZ");
82/// # Ok::<(), music21_rs::Error>(())
83/// ```
84///
85/// # Errors
86///
87/// A measure of voices that cannot be chordified.
88pub fn measure_hash(measure: &Stream) -> Result<String> {
89    let flat = !measure
90        .events()
91        .iter()
92        .any(|event| matches!(event.element(), StreamElement::Stream(_)));
93    let chordified;
94    let source = if flat {
95        measure
96    } else {
97        chordified = measure.chordify()?;
98        &chordified
99    };
100    let mut hash = String::new();
101    for event in source.events() {
102        let element = event.element();
103        if !is_general_note(element) {
104            continue;
105        }
106        let length = element.quarter_length();
107        let code = if length == 0.0 {
108            Some(hash_quarter_length(0.015625))
109        } else {
110            match element {
111                StreamElement::Note(_)
112                | StreamElement::Chord(_)
113                | StreamElement::ChordSymbol(_) => {
114                    let code = hash_quarter_length(length);
115                    Some(if code.is_multiple_of(2) {
116                        code
117                    } else {
118                        code + 1
119                    })
120                }
121                StreamElement::Rest(_) => {
122                    let code = hash_quarter_length(length);
123                    Some(if code.is_multiple_of(2) {
124                        code + 1
125                    } else {
126                        code
127                    })
128                }
129                _ => None,
130            }
131        };
132        if let Some(code) = code {
133            hash.push(char::from(code));
134        }
135    }
136    Ok(hash)
137}
138
139/// music21's `notesAndRests`: what has a duration of a note.
140fn is_general_note(element: &StreamElement) -> bool {
141    matches!(
142        element,
143        StreamElement::Note(_)
144            | StreamElement::Chord(_)
145            | StreamElement::Rest(_)
146            | StreamElement::Unpitched(_)
147            | StreamElement::PercussionChord(_)
148            | StreamElement::ChordSymbol(_)
149    )
150}
151
152/// music21's `hashQuarterLength`: ten times the base-2 logarithm of a
153/// length in 256ths of a quarter, from 1 to 127, and 1 for no length.
154fn hash_quarter_length(length: FloatType) -> u8 {
155    if length == 0.0 {
156        return 1;
157    }
158    let code = ((length * 256.0).log2() * 10.0) as i64;
159    code.clamp(1, 127) as u8
160}
161
162/// How unlike two rhythms are, as music21's `getMeasureDifference` has it:
163/// one less their `difflib` likeness, except that the same rhythm is as
164/// unlike as can be, one.
165pub fn measure_difference(own: &str, other: &str) -> FloatType {
166    difference(&chars(own), &chars(other))
167}
168
169fn chars(hash: &str) -> Vec<char> {
170    hash.chars().collect()
171}
172
173fn difference(own: &[char], other: &[char]) -> FloatType {
174    let ratio = difflib::ratio(own, other);
175    1.0 - if ratio == 1.0 { 0.0 } else { ratio }
176}
177
178/// The steps that turn one rhythm into another: Python's `difflib` opcodes,
179/// music21's `getOpCodes`.
180pub fn opcodes(own: &str, other: &str) -> Vec<Opcode> {
181    difflib::opcodes(&chars(own), &chars(other))
182}
183
184/// How likely `other` was misread as `own`, from the steps that turn the
185/// one into the other: music21's `getProbabilityBasedOnChanges`. Each equal
186/// note is [`PROBABILITY_ON_EQUALITY`], each one missed
187/// [`PROBABILITY_ON_OMISSION`], each one added [`PROBABILITY_ON_ADDITION`],
188/// and a stretch replaced is [`probability_on_substitute`].
189pub fn probability_based_on_changes(own: &str, other: &str) -> FloatType {
190    changes(&chars(own), &chars(other))
191}
192
193fn changes(own: &[char], other: &[char]) -> FloatType {
194    let mut probability = 0.0;
195    for (index, step) in difflib::opcodes(own, other).iter().enumerate() {
196        let one = match step.tag {
197            OpcodeTag::Equal => PROBABILITY_ON_EQUALITY.powf((step.j2 - step.j1) as FloatType),
198            OpcodeTag::Replace => substitute(&other[step.j1..step.j2], &own[step.i1..step.i2]),
199            OpcodeTag::Insert => PROBABILITY_ON_OMISSION.powf((step.j2 - step.j1) as FloatType),
200            OpcodeTag::Delete => PROBABILITY_ON_ADDITION.powf((step.i2 - step.i1) as FloatType),
201        };
202        if index == 0 {
203            probability = one;
204        } else {
205            probability *= one;
206        }
207    }
208    probability
209}
210
211/// How likely the rhythm `source` was misread as `destination`: music21's
212/// `getProbabilityOnSubstitute`. The longer is cut to the shorter, each
213/// character cut an addition or an omission, and the rest compared
214/// character by character ([`probability_from_one_char_sub`]).
215pub fn probability_on_substitute(source: &str, destination: &str) -> FloatType {
216    substitute(&chars(source), &chars(destination))
217}
218
219fn substitute(source: &[char], destination: &[char]) -> FloatType {
220    let (mut source, mut destination) = (source, destination);
221    let mut probability = 1.0;
222    if source.len() > destination.len() {
223        let additions = source.len() - destination.len();
224        probability = PROBABILITY_ON_ADDITION.powf(additions as FloatType);
225        source = &source[..source.len() - additions];
226    } else if source.len() < destination.len() {
227        let omissions = destination.len() - source.len();
228        probability = PROBABILITY_ON_OMISSION.powf(omissions as FloatType);
229        destination = &destination[..destination.len() - omissions];
230    }
231    for (from, to) in source.iter().zip(destination) {
232        probability *= probability_from_one_char_sub(*from, *to);
233    }
234    probability
235}
236
237/// How likely one length was misread as another: music21's
238/// `getProbabilityFromOneCharSub`. A length doubled or halved any number of
239/// times is 0.0165 for each; a rest read for a note of the same length an
240/// addition, the other way an omission; a note read as a rest 0.003; and
241/// anything else an omission and an addition at once.
242pub fn probability_from_one_char_sub(source: char, destination: char) -> FloatType {
243    let difference = source as i64 - destination as i64;
244    let distance = difference.abs() as FloatType;
245    if difference == 0 {
246        1.0
247    } else if distance % 10.0 == 0.0 {
248        0.0165_f64.powf(distance / 10.0)
249    } else if difference == 6 {
250        PROBABILITY_ON_ADDITION
251    } else if difference == -6 {
252        PROBABILITY_ON_OMISSION
253    } else if distance % 2.0 != 0.0 {
254        0.003
255    } else {
256        PROBABILITY_ON_OMISSION * PROBABILITY_ON_ADDITION
257    }
258}
259
260/// The best of some probabilities, and the first that is as good: where
261/// music21 corrects a measure from.
262fn best(probabilities: &[FloatType]) -> (usize, FloatType) {
263    let most = probabilities
264        .iter()
265        .copied()
266        .fold(FloatType::NEG_INFINITY, FloatType::max);
267    let at = probabilities.iter().position(|&p| p == most).unwrap_or(0);
268    (at, most)
269}
270
271/// One part of a score being corrected, its measures' rhythms and those
272/// flagged: music21's `SinglePart`.
273#[derive(Clone, Debug, PartialEq)]
274#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
275pub struct SinglePart {
276    part_number: usize,
277    hashed_notes: Vec<String>,
278    incorrect_measures: Vec<usize>,
279    probability_distribution: Option<Vec<FloatType>>,
280    index_array: Option<Vec<i64>>,
281}
282
283impl SinglePart {
284    /// The part's place among the score's parts.
285    pub fn part_number(&self) -> usize {
286        self.part_number
287    }
288
289    /// The rhythm of each measure, as [`measure_hash`] writes it, as they
290    /// stood when the corrector was made or the part last corrected:
291    /// music21's `hashedNotes`.
292    pub fn hashed_notes(&self) -> &[String] {
293        &self.hashed_notes
294    }
295
296    /// The indices of the measures whose notes do not fill their bars:
297    /// music21's `incorrectMeasures`.
298    pub fn incorrect_measures(&self) -> &[usize] {
299        &self.incorrect_measures
300    }
301
302    /// For each distance from one measure to another, from one less than
303    /// the number of measures back to as many forward, the share of
304    /// measures whose rhythm is like the one that far away -- as music21
305    /// counts likeness, where the same rhythm and a wholly different one
306    /// both count, and the distance nought counts every measure: music21's
307    /// `horizontalProbabilityDist`. Worked out once and kept.
308    pub fn horizontal_probability_dist(&mut self) -> &[FloatType] {
309        if self.probability_distribution.is_none() {
310            let count = self.hashed_notes.len();
311            let hashes: Vec<Vec<char>> = self.hashed_notes.iter().map(|hash| chars(hash)).collect();
312            let mut all = vec![0.0; count * 2];
313            let mut indices = vec![0_i64; count * 2];
314            for i in 0..count {
315                for k in 0..count {
316                    let at = count + k - i;
317                    indices[at] = k as i64 - i as i64;
318                    if i == k {
319                        all[at] = count as FloatType;
320                    } else if difference(&hashes[i], &hashes[k]) == 1.0 {
321                        all[at] += 1.0;
322                    }
323                }
324            }
325            if !indices.is_empty() {
326                indices.remove(0);
327                all.remove(0);
328            }
329            self.index_array = Some(indices);
330            self.probability_distribution =
331                Some(all.iter().map(|value| value / count as FloatType).collect());
332        }
333        self.probability_distribution.as_deref().unwrap_or_default()
334    }
335
336    /// The distance from one measure to another each entry of
337    /// [`Self::horizontal_probability_dist`] is for, once it has been worked
338    /// out: music21's `indexArray`.
339    pub fn index_array(&self) -> Option<&[i64]> {
340        self.index_array.as_deref()
341    }
342
343    /// How likely a measure as far from another as `source` is from
344    /// `destination` has its rhythm: music21's `getProbabilityDistribution`.
345    pub fn probability_distribution(&mut self, source: usize, destination: usize) -> FloatType {
346        let count = self.hashed_notes.len();
347        let index = source + count - 1 - destination;
348        self.horizontal_probability_dist()[index]
349    }
350
351    /// The measure of this part most likely to correct the `i`th flagged
352    /// one: of those not flagged, the one most likely misread as it by its
353    /// changes, times how likely its distance is, the first of those as
354    /// likely: music21's `runHorizontalSearch`.
355    pub fn run_horizontal_search(&mut self, i: usize) -> MeasureRelationship {
356        self.horizontal_probability_dist();
357        let incorrect = self.incorrect_measures[i];
358        let own = chars(&self.hashed_notes[incorrect]);
359        let mut probabilities = Vec::with_capacity(self.hashed_notes.len());
360        for k in 0..self.hashed_notes.len() {
361            if self.incorrect_measures.contains(&k) {
362                probabilities.push(0.0);
363            } else {
364                let likely = changes(&own, &chars(&self.hashed_notes[k]));
365                probabilities.push(likely * self.probability_distribution(k, incorrect));
366            }
367        }
368        let (at, most) = best(&probabilities);
369        MeasureRelationship {
370            flagged_measure_part: self.part_number,
371            flagged_measure_index: incorrect,
372            correct_measure_part: self.part_number,
373            correct_measure_index: at,
374            correction_probability: most,
375        }
376    }
377
378    /// [`Self::run_horizontal_search`] for every flagged measure: music21's
379    /// `runHorizontalCorrectionModel`.
380    pub fn run_horizontal_correction_model(&mut self) -> Vec<MeasureRelationship> {
381        (0..self.incorrect_measures.len())
382            .map(|i| self.run_horizontal_search(i))
383            .collect()
384    }
385}
386
387/// A score being corrected, part by part: music21's `ScoreCorrector`.
388///
389/// ```
390/// use music21_rs::omr::correctors::ScoreCorrector;
391/// use music21_rs::tinynotation::from_tiny_notation;
392/// use music21_rs::{Stream, StreamKind};
393///
394/// // The third bar is a quarter short.
395/// let part = from_tiny_notation("4/4 c4 d e f g a b c' d'4 e' f'")?;
396/// let mut score = Stream::with_kind(StreamKind::Score);
397/// score.insert(0.0, part);
398///
399/// let mut corrector = ScoreCorrector::new(score)?;
400/// assert_eq!(corrector.single_parts()[0].incorrect_measures(), [2]);
401/// corrector.run()?;
402/// assert_eq!(corrector.single_parts()[0].hashed_notes()[2], "PPPP");
403/// # Ok::<(), music21_rs::Error>(())
404/// ```
405#[derive(Clone, Debug)]
406pub struct ScoreCorrector {
407    score: Stream,
408    single_parts: Vec<SinglePart>,
409    /// Where each part stands among the score's events.
410    part_events: Vec<usize>,
411    distribution: Option<Vec<Vec<FloatType>>>,
412    /// The rhythms of the measures at each place across the parts, as they
413    /// stood when that place was first searched.
414    slices: HashMap<usize, Vec<String>>,
415}
416
417impl ScoreCorrector {
418    /// A corrector for a score: each part's measures hashed and those not
419    /// filling the bar of the score's first time signature flagged.
420    ///
421    /// # Errors
422    ///
423    /// A measure of voices that cannot be chordified.
424    pub fn new(score: Stream) -> Result<Self> {
425        let part_events: Vec<usize> = score
426            .events()
427            .iter()
428            .enumerate()
429            .filter_map(|(index, event)| match event.element() {
430                StreamElement::Stream(inner)
431                    if matches!(inner.kind(), StreamKind::Part | StreamKind::PartStaff) =>
432                {
433                    Some(index)
434                }
435                _ => None,
436            })
437            .collect();
438        let mut corrector = Self {
439            score,
440            single_parts: Vec::new(),
441            part_events,
442            distribution: None,
443            slices: HashMap::new(),
444        };
445        for part_number in 0..corrector.part_events.len() {
446            let hashed_notes = corrector.hash_part(part_number)?;
447            corrector.single_parts.push(SinglePart {
448                part_number,
449                hashed_notes,
450                incorrect_measures: Vec::new(),
451                probability_distribution: None,
452                index_array: None,
453            });
454            corrector.find_incorrect_measures(part_number, true)?;
455        }
456        Ok(corrector)
457    }
458
459    /// The score, as corrected so far.
460    pub fn score(&self) -> &Stream {
461        &self.score
462    }
463
464    /// The score, as corrected so far, given back.
465    pub fn into_score(self) -> Stream {
466        self.score
467    }
468
469    /// The parts being corrected: music21's `singleParts`.
470    pub fn single_parts(&self) -> &[SinglePart] {
471        &self.single_parts
472    }
473
474    /// One part, to run its horizontal model on its own.
475    pub fn single_part_mut(&mut self, part: usize) -> &mut SinglePart {
476        &mut self.single_parts[part]
477    }
478
479    /// Every part's measure rhythms: music21's `getAllHashes`.
480    pub fn all_hashes(&self) -> Vec<&[String]> {
481        self.single_parts
482            .iter()
483            .map(SinglePart::hashed_notes)
484            .collect()
485    }
486
487    /// Every part's flagged measures: music21's `getAllIncorrectMeasures`.
488    pub fn all_incorrect_measures(&self) -> Vec<&[usize]> {
489        self.single_parts
490            .iter()
491            .map(SinglePart::incorrect_measures)
492            .collect()
493    }
494
495    fn part(&self, part: usize) -> &Stream {
496        match self.score.events()[self.part_events[part]].element() {
497            StreamElement::Stream(inner) => inner,
498            _ => unreachable!("a part's event holds the part"),
499        }
500    }
501
502    /// The events of a part that are its measures.
503    fn measure_events(part: &Stream) -> Vec<usize> {
504        part.events()
505            .iter()
506            .enumerate()
507            .filter_map(|(index, event)| match event.element() {
508                StreamElement::Stream(inner) if inner.kind() == StreamKind::Measure => Some(index),
509                _ => None,
510            })
511            .collect()
512    }
513
514    fn hash_part(&self, part: usize) -> Result<Vec<String>> {
515        let part = self.part(part);
516        Self::measure_events(part)
517            .into_iter()
518            .map(|index| match part.events()[index].element() {
519                StreamElement::Stream(measure) => measure_hash(measure),
520                _ => unreachable!("a measure's event holds the measure"),
521            })
522            .collect()
523    }
524
525    /// Flags a part's measures whose notes do not fill their bar, and
526    /// gives them back: music21's `getIncorrectMeasureIndices`. With
527    /// `run_fast`, every bar is that of the first measure's time signature,
528    /// or of 4/4 where none is in force; without, each measure's own.
529    ///
530    /// # Errors
531    ///
532    /// Without `run_fast`, a measure with no time signature in force,
533    /// which music21 fails on.
534    pub fn find_incorrect_measures(&mut self, part: usize, run_fast: bool) -> Result<&[usize]> {
535        let stream = self.part(part);
536        let measures = Self::measure_events(stream);
537        let meter_of = |index: usize| -> Option<TimeSignature> {
538            let event = &stream.events()[index];
539            let StreamElement::Stream(measure) = event.element() else {
540                return None;
541            };
542            measure
543                .events()
544                .iter()
545                .take_while(|inner| inner.offset() == 0.0)
546                .find_map(|inner| match inner.element() {
547                    StreamElement::TimeSignature(meter) => Some(meter.clone()),
548                    _ => None,
549                })
550                .or_else(|| stream.time_signature_at(event.offset()))
551        };
552        let common_time = || TimeSignature::new(4, 4);
553        let mut meter = if run_fast {
554            match measures.first().and_then(|&index| meter_of(index)) {
555                Some(meter) => meter,
556                None => common_time()?,
557            }
558        } else {
559            common_time()?
560        };
561        let mut incorrect = Vec::new();
562        for (i, &index) in measures.iter().enumerate() {
563            if !run_fast {
564                meter = meter_of(index).ok_or_else(|| {
565                    Error::Omr("'NoneType' object has no attribute 'barDuration'".to_string())
566                })?;
567            }
568            if stream.events()[index].element().quarter_length() != meter.bar_quarter_length() {
569                incorrect.push(i);
570            }
571        }
572        self.single_parts[part].incorrect_measures = incorrect;
573        Ok(&self.single_parts[part].incorrect_measures)
574    }
575
576    /// Every part's horizontal model: music21's
577    /// `runHorizontalCorrectionModel`.
578    pub fn run_horizontal_correction_model(&mut self) -> Vec<Vec<MeasureRelationship>> {
579        self.single_parts
580            .iter_mut()
581            .map(SinglePart::run_horizontal_correction_model)
582            .collect()
583    }
584
585    /// For each part, the share of its measures whose rhythm is like that
586    /// of the measure at the same place in each part, as music21 counts
587    /// likeness: music21's `verticalProbabilityDist`. Worked out once and
588    /// kept.
589    ///
590    /// # Errors
591    ///
592    /// Parts of different lengths, or one with no measures, which music21
593    /// fails on.
594    pub fn vertical_probability_dist(&mut self) -> Result<&[Vec<FloatType>]> {
595        if self.distribution.is_none() {
596            let parts = self.single_parts.len();
597            let mut distribution = Vec::with_capacity(parts);
598            let hashes: Vec<Vec<Vec<char>>> = self
599                .single_parts
600                .iter()
601                .map(|part| part.hashed_notes.iter().map(|hash| chars(hash)).collect())
602                .collect();
603            for i in 0..parts {
604                let own = &hashes[i];
605                let mut sums = vec![0.0; parts];
606                for (k, hash) in own.iter().enumerate() {
607                    for (other, sum) in sums.iter_mut().enumerate() {
608                        if other == i {
609                            *sum += 1.0;
610                            continue;
611                        }
612                        let their = hashes[other]
613                            .get(k)
614                            .ok_or_else(|| Error::Omr("list index out of range".to_string()))?;
615                        if difference(hash, their) == 1.0 {
616                            *sum += 1.0;
617                        }
618                    }
619                }
620                if own.is_empty() {
621                    return Err(Error::Omr("division by zero".to_string()));
622                }
623                distribution.push(
624                    sums.iter()
625                        .map(|sum| sum / own.len() as FloatType)
626                        .collect(),
627                );
628            }
629            self.distribution = Some(distribution);
630        }
631        Ok(self.distribution.as_deref().unwrap_or_default())
632    }
633
634    /// The measure at the same place in another part most likely to
635    /// correct a flagged one: of the parts whose measure there is not
636    /// flagged, the one most likely misread as it by its changes, times how
637    /// often the two parts' rhythms are alike, the first of those as
638    /// likely: music21's `runVerticalSearch`.
639    ///
640    /// # Errors
641    ///
642    /// As [`Self::vertical_probability_dist`], or a measure of voices that
643    /// cannot be chordified.
644    pub fn run_vertical_search(
645        &mut self,
646        measure: usize,
647        part: usize,
648    ) -> Result<MeasureRelationship> {
649        self.vertical_probability_dist()?;
650        if !self.slices.contains_key(&measure) {
651            let mut hashes = Vec::with_capacity(self.single_parts.len());
652            for number in 0..self.single_parts.len() {
653                let stream = self.part(number);
654                let index = *Self::measure_events(stream)
655                    .get(measure)
656                    .ok_or_else(|| Error::Omr("list index out of range".to_string()))?;
657                let StreamElement::Stream(inner) = stream.events()[index].element() else {
658                    unreachable!("a measure's event holds the measure");
659                };
660                hashes.push(measure_hash(inner)?);
661            }
662            self.slices.insert(measure, hashes);
663        }
664        let hashes = &self.slices[&measure];
665        let distribution = self.distribution.as_deref().unwrap_or_default();
666        let mut probabilities = Vec::with_capacity(hashes.len());
667        for (k, hash) in hashes.iter().enumerate() {
668            if k == part || self.single_parts[k].incorrect_measures.contains(&measure) {
669                probabilities.push(0.0);
670            } else {
671                let likely = probability_based_on_changes(&hashes[part], hash);
672                probabilities.push(likely * distribution[part][k]);
673            }
674        }
675        let (at, most) = best(&probabilities);
676        Ok(MeasureRelationship {
677            flagged_measure_part: part,
678            flagged_measure_index: measure,
679            correct_measure_part: at,
680            correct_measure_index: measure,
681            correction_probability: most,
682        })
683    }
684
685    /// [`Self::run_vertical_search`] for every flagged measure of every
686    /// part: music21's `runVerticalCorrectionModel`.
687    ///
688    /// # Errors
689    ///
690    /// As [`Self::run_vertical_search`].
691    pub fn run_vertical_correction_model(&mut self) -> Result<Vec<Vec<MeasureRelationship>>> {
692        self.vertical_probability_dist()?;
693        let mut all = Vec::with_capacity(self.single_parts.len());
694        for part in 0..self.single_parts.len() {
695            let flagged = self.single_parts[part].incorrect_measures.clone();
696            all.push(
697                flagged
698                    .into_iter()
699                    .map(|measure| self.run_vertical_search(measure, part))
700                    .collect::<Result<Vec<_>>>()?,
701            );
702        }
703        Ok(all)
704    }
705
706    /// Puts the contents of one measure in another's place, the notes
707    /// keeping the pitches the replaced measure's notes had, in order, as
708    /// far as they go: music21's `substituteOneMeasureContentsForAnother`.
709    ///
710    /// As music21 does, everything is appended one after another, a
711    /// measure's right barline and its voices included: a barline ends up
712    /// inside the measure, and voices one after another rather than
713    /// together. Spanners naming the replaced measure's notes name nothing
714    /// afterwards. A measure put in its own place is emptied, as music21
715    /// empties it before copying from it.
716    ///
717    /// # Errors
718    ///
719    /// A pitch whose accidental cannot be written again from its modifier.
720    ///
721    /// # Panics
722    ///
723    /// A part or measure past the last.
724    pub fn substitute_measure(
725        &mut self,
726        source_measure: usize,
727        source_part: usize,
728        destination_measure: usize,
729        destination_part: usize,
730    ) -> Result<()> {
731        let source = if (source_part, source_measure) == (destination_part, destination_measure) {
732            Stream::with_kind(StreamKind::Measure)
733        } else {
734            let stream = self.part(source_part);
735            let index = Self::measure_events(stream)[source_measure];
736            match stream.events()[index].element() {
737                StreamElement::Stream(inner) => (**inner).clone(),
738                _ => unreachable!("a measure's event holds the measure"),
739            }
740        };
741        let destination = self.part(destination_part);
742        let index = Self::measure_events(destination)[destination_measure];
743        let StreamElement::Stream(measure) = destination.events()[index].element() else {
744            unreachable!("a measure's event holds the measure");
745        };
746        let old_pitches: Vec<crate::pitch::Pitch> = measure
747            .events()
748            .iter()
749            .filter_map(|event| match event.element() {
750                StreamElement::Note(note) => Some(note.pitch().clone()),
751                _ => None,
752            })
753            .collect();
754        // What music21 iterates: the elements in order, the left barline
755        // among them at the start, and the right barline after them all.
756        let mut elements: Vec<StreamEvent> = source.events().to_vec();
757        if let Some(barline) = source.left_barline() {
758            let at = elements
759                .iter()
760                .position(|event| event.offset() > 0.0 || event.element().class_sort_order() > -5)
761                .unwrap_or(elements.len());
762            elements.insert(
763                at,
764                StreamEvent::new(0.0, StreamElement::Barline(barline.clone())),
765            );
766        }
767        if let Some(barline) = source.right_barline() {
768            elements.push(StreamEvent::new(
769                0.0,
770                StreamElement::Barline(barline.clone()),
771            ));
772        }
773        let mut contents = Stream::with_kind(StreamKind::Measure);
774        let (mut left, mut pitch_index) = (None, 0);
775        for event in elements {
776            let mut element = event.element().clone();
777            if let StreamElement::Note(note) = &mut element
778                && let Some(old) = old_pitches.get(pitch_index)
779            {
780                let mut pitch = note.pitch().clone();
781                pitch.set_octave(old.octave());
782                pitch.take_name_of(old)?;
783                note.set_pitch(pitch);
784                pitch_index += 1;
785            }
786            let offset = contents.end_offset();
787            match element {
788                // A barline at the start is the measure's left barline.
789                StreamElement::Barline(barline) if offset == 0.0 && left.is_none() => {
790                    left = Some(barline);
791                }
792                element => contents.push(element),
793            }
794        }
795        let mut replaced = measure.with_events(contents.events().to_vec());
796        replaced.set_left_barline(left);
797        replaced.set_right_barline(None);
798        let path = [self.part_events[destination_part], index];
799        self.score.replace_nested(&path, replaced);
800        Ok(())
801    }
802
803    /// Corrects each flagged measure both models found a correction for,
804    /// from the horizontal model's measure where it is more likely, and
805    /// from the vertical model's otherwise, then hashes each part's
806    /// measures again: music21's `generateCorrectedScore`. Corrections are
807    /// made in turn, so a measure corrected may correct another after it.
808    ///
809    /// # Errors
810    ///
811    /// A measure of voices that cannot be chordified.
812    pub fn generate_corrected_score(
813        &mut self,
814        horizontal: &[Vec<MeasureRelationship>],
815        vertical: &[Vec<MeasureRelationship>],
816    ) -> Result<PriorsIntegrationScore> {
817        let mut score = PriorsIntegrationScore::default();
818        for part in 0..self.single_parts.len() {
819            let out_of_range = || Error::Omr("list index out of range".to_string());
820            let across = horizontal.get(part).ok_or_else(out_of_range)?;
821            for h in across {
822                for v in vertical.get(part).ok_or_else(out_of_range)? {
823                    if h.flagged_measure_part != v.flagged_measure_part
824                        || h.flagged_measure_index != v.flagged_measure_index
825                    {
826                        continue;
827                    }
828                    score.total += 1;
829                    let source = if h.correction_probability > v.correction_probability {
830                        score.horizontal += 1;
831                        h
832                    } else {
833                        score.vertical += 1;
834                        v
835                    };
836                    self.substitute_measure(
837                        source.correct_measure_index,
838                        source.correct_measure_part,
839                        h.flagged_measure_index,
840                        h.flagged_measure_part,
841                    )?;
842                }
843            }
844            self.single_parts[part].hashed_notes = self.hash_part(part)?;
845        }
846        Ok(score)
847    }
848
849    /// Corrects the score by both models: music21's `run` and
850    /// `runPriorModel`. The score corrected is [`Self::score`].
851    ///
852    /// # Errors
853    ///
854    /// As [`Self::run_vertical_correction_model`].
855    pub fn run(&mut self) -> Result<PriorsIntegrationScore> {
856        let horizontal = self.run_horizontal_correction_model();
857        let vertical = self.run_vertical_correction_model()?;
858        self.generate_corrected_score(&horizontal, &vertical)
859    }
860}
861
862#[cfg(test)]
863mod tests {
864    use super::*;
865
866    #[test]
867    fn lengths_are_hashed_as_music21_hashes_them() {
868        assert_eq!(hash_quarter_length(1.0), 80);
869        assert_eq!(hash_quarter_length(0.015625), 20);
870        assert_eq!(hash_quarter_length(0.0), 1);
871        assert_eq!(hash_quarter_length(1000.0), 127);
872    }
873
874    #[test]
875    fn a_rhythm_misread_is_as_likely_as_music21_has_it() {
876        // Read off music21's MeasureHash methods.
877        assert_eq!(probability_from_one_char_sub('P', 'P'), 1.0);
878        assert_eq!(probability_from_one_char_sub('Z', 'P'), 0.0165);
879        assert_eq!(
880            probability_from_one_char_sub('V', 'P'),
881            PROBABILITY_ON_ADDITION
882        );
883        assert_eq!(probability_from_one_char_sub('Q', 'P'), 0.003);
884        assert_eq!(measure_difference("PPPP", "PPPP"), 1.0);
885        assert_eq!(measure_difference("", ""), 1.0);
886        assert_eq!(measure_difference("PP", "ZZ"), 1.0);
887    }
888}