Skip to main content

music21_rs/analysis/
reduce_chords.rs

1//! A score reduced to the chords it is made of: music21's
2//! `analysis.reduceChords`.
3//!
4//! [`reduce_chords`] is music21's `ChordReducer.run`. It reads the score's
5//! notes as spans of time -- a part, a stretch, and the notes sounding in
6//! it -- and then thins them out: a span of no length goes, the spans of
7//! each part are fused where they sound the same notes in one measure, they
8//! are cut at every offset the bass moves, the notes above the bass of a
9//! dissonant moment go, the gaps the thinning leaves are filled, and spans
10//! shorter than half a note and then shorter than a note go unless they
11//! fill their measure. What is left is chordified against the score, and
12//! each measure is reduced to the few chords weighing most in it.
13//!
14//! The weight of a chord is music21's `qlbsmpConsonance`: how long it
15//! sounds, times how strong its beat is, times a tenth if it is dissonant,
16//! with the last chord of a measure counted as if it stood on the
17//! downbeat.
18
19use std::borrow::Cow;
20
21use crate::chord::Chord;
22use crate::defaults::{FloatType, IntegerType};
23use crate::duration::Duration;
24use crate::error::{Error, Result};
25use crate::makenotation::op_frac;
26use crate::meter::TimeSignature;
27use crate::pitch::Pitch;
28use crate::stream::{Stream, StreamElement, StreamKind};
29use crate::tree::{Flatten, as_timespans};
30
31/// What a reduction is asked for: music21's arguments to `ChordReducer.run`.
32#[derive(Clone, Debug)]
33pub struct ReductionOptions {
34    /// How many chords a measure may keep: music21's
35    /// `maximumNumberOfChords`, three by default.
36    pub maximum_number_of_chords: usize,
37    /// Whether every chord is written in closed position in the fourth
38    /// octave afterwards: music21's `closedPosition`.
39    pub closed_position: bool,
40    /// Chords whose interval classes count as consonant however they
41    /// sound: music21's `allowableChords`.
42    pub allowable_chords: Vec<Chord>,
43    /// Chords whose interval classes count as dissonant however they
44    /// sound: music21's `forbiddenChords`.
45    pub forbidden_chords: Vec<Chord>,
46}
47
48impl Default for ReductionOptions {
49    fn default() -> Self {
50        Self {
51            maximum_number_of_chords: 3,
52            closed_position: false,
53            allowable_chords: Vec::new(),
54            forbidden_chords: Vec::new(),
55        }
56    }
57}
58
59/// A score reduced to its chords: music21's `ChordReducer.run`, which hands
60/// back a score of one part.
61///
62/// # Errors
63///
64/// A stream that is not a score, a part with no instrument to order it by,
65/// or a length no duration can have.
66pub fn reduce_chords(score: &Stream, options: &ReductionOptions) -> Result<Stream> {
67    if score.kind() != StreamKind::Score {
68        return Err(Error::Analysis(
69            "a reduction is of a score, as music21's is".to_string(),
70        ));
71    }
72    let mut reduction = Reduction::of(score)?;
73    reduction.remove_zero_duration();
74    reduction.split_by_bass();
75    reduction.remove_vertical_dissonances(options);
76    reduction.fill_bass_gaps();
77    reduction.remove_short_spans(0.5);
78    reduction.fill_bass_gaps();
79    reduction.fill_measure_gaps();
80    reduction.remove_short_spans(1.0);
81    reduction.fill_bass_gaps();
82    reduction.fill_measure_gaps();
83
84    let chordified = reduction.chordified(score)?;
85    let mut part = Stream::with_kind(StreamKind::Part);
86    let mut at = 0.0;
87    for event in chordified.events() {
88        let Some(measure) = event.element().as_stream() else {
89            continue;
90        };
91        if measure.kind() != StreamKind::Measure {
92            continue;
93        }
94        let reduced = reduce_measure_to_n_chords(
95            measure,
96            options.maximum_number_of_chords,
97            0.25,
98            reduction.meter_at(event.offset()),
99        )?;
100        let length = reduced.end_offset();
101        part.insert(at, reduced);
102        at = op_frac(at + length);
103    }
104    if options.closed_position {
105        close_positions(&mut part);
106    }
107    let mut answer = Stream::with_kind(StreamKind::Score);
108    answer.insert(0.0, part);
109    Ok(answer)
110}
111
112/// One stretch of one part, and the notes sounding in it: music21's
113/// `PitchedTimespan` as the reducer keeps it.
114#[derive(Clone, Debug)]
115struct Span<'a> {
116    /// Which of the score's parts it came from, in the order the reduction
117    /// sorted them.
118    part: usize,
119    offset: FloatType,
120    end: FloatType,
121    /// Where the measure holding it stands, and where it ends: music21's
122    /// `parentOffset` and `parentEndTime`.
123    parent_offset: FloatType,
124    parent_end: FloatType,
125    measure: Option<IntegerType>,
126    element: Cow<'a, StreamElement>,
127    /// The strength of the beat it starts on, which the gap filling writes
128    /// onto a span it moves: music21's `beatStrength` on a timespan.
129    beat_strength: Option<FloatType>,
130    /// When it was made. music21's steps take a span out and put a new one
131    /// back, which lands after the spans already standing where it does,
132    /// and the number says so without relying on how a sort breaks ties.
133    seq: u64,
134}
135
136impl Span<'_> {
137    fn quarter_length(&self) -> FloatType {
138        op_frac(self.end - self.offset)
139    }
140
141    /// The notes it sounds, which are its element's: music21's `pitches`.
142    fn pitches(&self) -> Vec<Pitch> {
143        self.element.pitches()
144    }
145
146    /// Whether two spans sound the same notes, written the same way, which
147    /// is what music21's `==` on a tuple of pitches asks.
148    fn sounds_as(&self, other: &Span<'_>) -> bool {
149        same_pitches(&self.pitches(), &other.pitches())
150    }
151
152    fn lowest(&self) -> Option<Pitch> {
153        lowest_of(&self.pitches())
154    }
155}
156
157/// Whether two lists of notes are the same list, written the same way,
158/// which is what music21's `==` on a tuple of pitches asks: the crate's own
159/// `==` reads a written natural as the note without one, where music21
160/// does not.
161fn same_pitches(one: &[Pitch], other: &[Pitch]) -> bool {
162    one.len() == other.len()
163        && one
164            .iter()
165            .zip(other)
166            .all(|(left, right)| written_alike(left, right))
167}
168
169/// Whether two notes are the one note as it is written.
170fn written_alike(one: &Pitch, other: &Pitch) -> bool {
171    one.name_with_octave() == other.name_with_octave()
172        && one.written_accidental().is_some() == other.written_accidental().is_some()
173}
174
175/// The lowest note of a list, by where it sounds.
176fn lowest_of(pitches: &[Pitch]) -> Option<Pitch> {
177    pitches
178        .iter()
179        .min_by(|left, right| left.ps().total_cmp(&right.ps()))
180        .cloned()
181}
182
183/// What `remove_short_spans` groups a part's spans by: the measure, whether
184/// the span is short, and the bass note under it.
185type ShortKey = (Option<IntegerType>, bool, Option<usize>);
186
187/// The spans of a score, and the steps that thin them out.
188struct Reduction<'a> {
189    spans: Vec<Span<'a>>,
190    /// The meter each measure of the score states or is in, by the
191    /// measure's offset.
192    meters: Vec<(FloatType, TimeSignature)>,
193    /// How many parts the score has, which says which is the bass.
194    parts: usize,
195    /// The number the next span made is given.
196    next_seq: u64,
197}
198
199impl<'a> Reduction<'a> {
200    /// The score read as spans, its parts in the order music21 sorts them:
201    /// by the id of each part's instrument, as `allParts` does.
202    fn of(score: &'a Stream) -> Result<Self> {
203        let mut parts: Vec<&Stream> = score.parts().into_iter().collect();
204        parts.sort_by_key(|part| part_id(part));
205        let timespans = as_timespans(
206            score,
207            Flatten::Flat,
208            Some(|element: &StreamElement| {
209                matches!(element, StreamElement::Note(_) | StreamElement::Chord(_))
210            }),
211        );
212        let mut spans = Vec::new();
213        for span in timespans.timespans() {
214            let part = span
215                .part()
216                .and_then(|held| {
217                    parts.iter().position(|known| {
218                        std::ptr::eq(std::ptr::from_ref(*known), std::ptr::from_ref(held))
219                    })
220                })
221                .unwrap_or(0);
222            spans.push(Span {
223                part,
224                offset: span.offset(),
225                end: span.end_time(),
226                parent_offset: span.parent_offset(),
227                parent_end: span.parent_end_time(),
228                measure: span.measure_number(),
229                element: Cow::Borrowed(span.element()),
230                beat_strength: None,
231                seq: spans.len() as u64,
232            });
233        }
234        let next_seq = spans.len() as u64;
235        Ok(Self {
236            spans,
237            next_seq,
238            meters: meters_of(score),
239            parts: parts.len(),
240        })
241    }
242
243    /// The meter in force where a measure stands, which is what says how
244    /// strong each beat of it is: the last meter the score states at or
245    /// before it, as a chord's `beatStrength` reads it off its measure.
246    fn meter_at(&self, offset: FloatType) -> Option<TimeSignature> {
247        self.meters
248            .iter()
249            .rfind(|(at, _)| *at <= offset)
250            .map(|(_, meter)| meter.clone())
251    }
252
253    /// Spans of no length go: music21's `removeZeroDurationTimespans`.
254    fn remove_zero_duration(&mut self) {
255        self.spans.retain(|span| span.quarter_length() != 0.0);
256    }
257
258    /// Every part's spans fused where they sound alike, and every span cut
259    /// where the bass moves: music21's `splitByBass`.
260    fn split_by_bass(&mut self) {
261        for part in 0..self.parts {
262            self.fuse_by_part(part);
263        }
264        // The bass is the part sorting last, as music21 takes `parts[-1]`.
265        let bass = self.parts.saturating_sub(1);
266        let mut offsets: Vec<FloatType> = self
267            .spans
268            .iter()
269            .filter(|span| span.part == bass)
270            .map(|span| span.offset)
271            .collect();
272        offsets.sort_by(FloatType::total_cmp);
273        offsets.dedup();
274        self.split_at(&offsets);
275    }
276
277    /// One part's spans fused where a run of them sounds the same notes in
278    /// one measure: music21's `fuseTimespansByPart`, which takes the run
279    /// out and puts one span back in its place.
280    fn fuse_by_part(&mut self, part: usize) {
281        loop {
282            let places: Vec<usize> = self
283                .spans
284                .iter()
285                .enumerate()
286                .filter(|(_, span)| span.part == part)
287                .map(|(at, _)| at)
288                .collect();
289            // The first run of spans alike, if there is one.
290            let mut run: Vec<usize> = Vec::new();
291            for place in places {
292                match run.last() {
293                    None => run.push(place),
294                    Some(last) => {
295                        let (one, two) = (&self.spans[*last], &self.spans[place]);
296                        if one.measure == two.measure && one.sounds_as(two) {
297                            run.push(place);
298                        } else if run.len() > 1 {
299                            break;
300                        } else {
301                            run.clear();
302                            run.push(place);
303                        }
304                    }
305                }
306            }
307            if run.len() < 2 {
308                return;
309            }
310            let mut fused = self.spans[run[0]].clone();
311            fused.end = self.spans[run[run.len() - 1]].end;
312            let fused = self.made(fused);
313            let dropped: Vec<usize> = run;
314            self.drop_places(&dropped);
315            self.spans.push(fused);
316            self.sort();
317        }
318    }
319
320    /// Every span cut at each of these offsets: music21's `splitAt`, which
321    /// takes each span straddling an offset out and puts its two pieces
322    /// back -- so the pieces stand *after* the spans already there, among
323    /// those starting and ending alike.
324    fn split_at(&mut self, offsets: &[FloatType]) {
325        for offset in offsets {
326            let mut kept: Vec<Span<'a>> = Vec::new();
327            let mut pieces: Vec<Span<'a>> = Vec::new();
328            for span in self.spans.drain(..) {
329                if span.offset < *offset && *offset < span.end {
330                    let mut first = span.clone();
331                    let mut second = span;
332                    first.end = *offset;
333                    second.offset = *offset;
334                    first.seq = self.next_seq;
335                    second.seq = self.next_seq + 1;
336                    self.next_seq += 2;
337                    pieces.push(first);
338                    pieces.push(second);
339                } else {
340                    kept.push(span);
341                }
342            }
343            kept.extend(pieces);
344            self.spans = kept;
345            self.sort();
346        }
347    }
348
349    /// A span made by a step, numbered after every span before it, as
350    /// music21's insertion puts a new timespan after the ones already
351    /// standing where it does.
352    fn made(&mut self, mut span: Span<'a>) -> Span<'a> {
353        span.seq = self.next_seq;
354        self.next_seq += 1;
355        span
356    }
357
358    /// Where a span stands now, by its number.
359    fn place_of(&self, seq: u64) -> Option<usize> {
360        self.spans.iter().position(|span| span.seq == seq)
361    }
362
363    /// The spans kept in the order a tree walks them: by where each starts,
364    /// then by where it ends.
365    fn sort(&mut self) {
366        self.spans.sort_by(|left, right| {
367            left.offset
368                .total_cmp(&right.offset)
369                .then_with(|| left.end.total_cmp(&right.end))
370                .then_with(|| left.seq.cmp(&right.seq))
371        });
372    }
373
374    /// Every offset a span starts or stops at, in order: music21's
375    /// `allTimePoints`.
376    fn time_points(&self) -> Vec<FloatType> {
377        let mut points: Vec<FloatType> = self
378            .spans
379            .iter()
380            .flat_map(|span| [span.offset, span.end])
381            .collect();
382        points.sort_by(FloatType::total_cmp);
383        points.dedup();
384        points
385    }
386
387    /// The spans starting at an offset.
388    fn starting_at(&self, offset: FloatType) -> Vec<usize> {
389        self.spans
390            .iter()
391            .enumerate()
392            .filter(|(_, span)| span.offset == offset)
393            .map(|(at, _)| at)
394            .collect()
395    }
396
397    /// The spans sounding at an offset, those starting there first and
398    /// then those that began earlier: music21's
399    /// `startAndOverlapTimespans`, whose order decides which span is read
400    /// as the bass where two sound the same lowest note.
401    fn sounding_at(&self, offset: FloatType) -> Vec<usize> {
402        let mut places = self.starting_at(offset);
403        places.extend(
404            self.spans
405                .iter()
406                .enumerate()
407                .filter(|(_, span)| span.offset < offset && offset < span.end)
408                .map(|(at, _)| at),
409        );
410        places
411    }
412
413    /// The span holding the lowest note sounding at an offset: music21's
414    /// `Verticality.bassTimespan`.
415    fn bass_at(&self, offset: FloatType) -> Option<usize> {
416        let mut lowest: Option<(Pitch, usize)> = None;
417        for place in self.sounding_at(offset) {
418            let Some(pitch) = self.spans[place].lowest() else {
419                continue;
420            };
421            match &lowest {
422                None => lowest = Some((pitch, place)),
423                Some((overall, _)) if pitch.ps() <= overall.ps() => lowest = Some((pitch, place)),
424                Some(_) => {}
425            }
426        }
427        lowest.map(|(_, place)| place)
428    }
429
430    /// The notes above the bass of a dissonant moment go: music21's
431    /// `removeVerticalDissonances`.
432    ///
433    /// music21 walks the verticalities live, from the first offset onwards
434    /// by `nextVerticality`, and takes spans out as it goes -- so what
435    /// sounds at a later offset is what the earlier removals left, and the
436    /// walk steps to the next offset a span still starts at.
437    fn remove_vertical_dissonances(&mut self, options: &ReductionOptions) {
438        let allowable: Vec<Vec<u8>> = options
439            .allowable_chords
440            .iter()
441            .map(|chord| interval_classes(&chord.pitches()))
442            .collect();
443        let forbidden: Vec<Vec<u8>> = options
444            .forbidden_chords
445            .iter()
446            .map(|chord| interval_classes(&chord.pitches()))
447            .collect();
448        let mut offset = self.lowest_offset();
449        while let Some(at) = offset {
450            let pitches = self.pitch_set(at);
451            let classes = interval_classes(&pitches);
452            let mut consonant = allowable.contains(&classes);
453            if Chord::new(&pitches[..]).is_ok_and(|chord| chord.is_consonant()) {
454                consonant = true;
455            }
456            if forbidden.contains(&classes) {
457                consonant = false;
458            }
459            if !consonant && let Some(lowest) = lowest_of(&pitches) {
460                let dropped: Vec<usize> = self
461                    .starting_at(at)
462                    .into_iter()
463                    .filter(|place| {
464                        self.spans[*place]
465                            .lowest()
466                            .is_some_and(|pitch| !written_alike(&pitch, &lowest))
467                    })
468                    .collect();
469                self.drop_places(&dropped);
470            }
471            offset = self.offset_after(at);
472        }
473    }
474
475    /// The first offset a span starts at.
476    fn lowest_offset(&self) -> Option<FloatType> {
477        self.spans
478            .iter()
479            .map(|span| span.offset)
480            .min_by(FloatType::total_cmp)
481    }
482
483    /// The next offset a span starts at: music21's `nextStartOffset`.
484    fn offset_after(&self, offset: FloatType) -> Option<FloatType> {
485        self.spans
486            .iter()
487            .map(|span| span.offset)
488            .filter(|at| *at > offset)
489            .min_by(FloatType::total_cmp)
490    }
491
492    /// The notes sounding at an offset, each written name once: music21's
493    /// `Verticality.pitchSet`.
494    fn pitch_set(&self, offset: FloatType) -> Vec<Pitch> {
495        let mut pitches: Vec<Pitch> = Vec::new();
496        for place in self.sounding_at(offset) {
497            for pitch in self.spans[place].pitches() {
498                if !pitches.iter().any(|known| written_alike(known, &pitch)) {
499                    pitches.push(pitch);
500                }
501            }
502        }
503        pitches.sort_by(|left, right| left.ps().total_cmp(&right.ps()));
504        pitches
505    }
506
507    /// The gaps the thinning left under each bass note filled: music21's
508    /// `fillBassGaps`.
509    ///
510    /// Part by part, over a snapshot of that part's spans grouped by the
511    /// bass note sounding under each: the first span of a group is moved
512    /// back to where the bass note starts, the last is stretched to where
513    /// it ends, and neighbours that sound alike or leave a gap are one
514    /// span. Each change takes a span out and puts a new one back, as
515    /// music21 does, so it stands after the spans it starts and ends with.
516    fn fill_bass_gaps(&mut self) {
517        for part in 0..self.parts {
518            // A snapshot, as music21 takes `list(subtree)` before it
519            // changes anything, with the bass note under each span.
520            let mut snapshot: Vec<(u64, Option<u64>)> = Vec::new();
521            for place in 0..self.spans.len() {
522                if self.spans[place].part != part {
523                    continue;
524                }
525                let seq = self.spans[place].seq;
526                let bass = self
527                    .bass_at(self.spans[place].offset)
528                    .map(|under| self.spans[under].seq);
529                snapshot.push((seq, bass));
530            }
531            let mut runs: Vec<(Option<u64>, Vec<u64>)> = Vec::new();
532            for (seq, bass) in snapshot {
533                match runs.last_mut() {
534                    Some((known, run)) if *known == bass => run.push(seq),
535                    _ => runs.push((bass, vec![seq])),
536                }
537            }
538            for (bass, mut group) in runs {
539                let Some(bass) = bass.and_then(|seq| self.place_of(seq)) else {
540                    continue;
541                };
542                let bass_offset = self.spans[bass].offset;
543                let bass_end = self.spans[bass].end;
544                let bass_strength = self.spans[bass].beat_strength;
545                if let Some(place) = self.place_of(group[0])
546                    && bass_offset < self.spans[place].offset
547                {
548                    let mut offset = bass_offset;
549                    if let Some(previous) = self.previous_in_part(place) {
550                        offset = offset.max(self.spans[previous].end);
551                    }
552                    let mut moved = self.spans[place].clone();
553                    moved.offset = offset;
554                    moved.beat_strength = bass_strength;
555                    let moved = self.made(moved);
556                    group[0] = moved.seq;
557                    self.spans.remove(place);
558                    self.spans.push(moved);
559                    self.sort();
560                }
561                let last = group.len() - 1;
562                if let Some(place) = self.place_of(group[last])
563                    && self.spans[place].end < bass_end
564                {
565                    let mut stretched = self.spans[place].clone();
566                    stretched.end = bass_end;
567                    let stretched = self.made(stretched);
568                    group[last] = stretched.seq;
569                    self.spans.remove(place);
570                    self.spans.push(stretched);
571                    self.sort();
572                }
573                self.fuse_neighbours(&mut group);
574            }
575        }
576    }
577
578    /// Neighbours of a group that sound alike, or that leave a gap between
579    /// them, made one span: the last pass of music21's `fillBassGaps` and
580    /// of its `fillMeasureGaps`, which writes the new span into both slots
581    /// so that the next pair starts from it.
582    fn fuse_neighbours(&mut self, group: &mut [u64]) {
583        if group.len() < 2 {
584            return;
585        }
586        for index in 0..group.len() - 1 {
587            let (Some(one), Some(two)) =
588                (self.place_of(group[index]), self.place_of(group[index + 1]))
589            else {
590                continue;
591            };
592            if one == two {
593                continue;
594            }
595            let alike = self.spans[one].sounds_as(&self.spans[two]);
596            let gap = self.spans[one].end != self.spans[two].offset;
597            if !(alike || gap) {
598                continue;
599            }
600            let mut fused = self.spans[one].clone();
601            fused.end = self.spans[two].end;
602            let fused = self.made(fused);
603            group[index] = fused.seq;
604            group[index + 1] = fused.seq;
605            let (first, second) = (one.min(two), one.max(two));
606            self.spans.remove(second);
607            self.spans.remove(first);
608            self.spans.push(fused);
609            self.sort();
610        }
611    }
612
613    /// The span of the same part standing before this one: music21's
614    /// `findPreviousPitchedTimespanInSameStreamByClass`, which steps back
615    /// verticality by verticality and takes the first span of the part
616    /// *starting* there -- so a span that started earlier and is still
617    /// sounding is the answer, however far past this one it reaches.
618    fn previous_in_part(&self, place: usize) -> Option<usize> {
619        let span = &self.spans[place];
620        let offset = self
621            .spans
622            .iter()
623            .filter(|other| other.part == span.part && other.offset < span.offset)
624            .map(|other| other.offset)
625            .max_by(FloatType::total_cmp)?;
626        self.spans
627            .iter()
628            .position(|other| other.part == span.part && other.offset == offset)
629    }
630
631    /// Spans shorter than a length go, unless a run of them fills its
632    /// measure or its bass note, where the notes sounding longest stay:
633    /// music21's `removeShortTimespans`.
634    fn remove_short_spans(&mut self, duration: FloatType) {
635        let mut dropped: Vec<usize> = Vec::new();
636        for part in 0..self.parts {
637            let places: Vec<usize> = self
638                .spans
639                .iter()
640                .enumerate()
641                .filter(|(_, span)| span.part == part)
642                .map(|(at, _)| at)
643                .collect();
644            let key = |place: usize| -> ShortKey {
645                let span = &self.spans[place];
646                let short = span.quarter_length() < duration;
647                let bass = self
648                    .bass_at(span.offset)
649                    .filter(|bass| self.spans[*bass].quarter_length() >= duration);
650                (span.measure, short, bass)
651            };
652            let mut runs: Vec<(ShortKey, Vec<usize>)> = Vec::new();
653            for place in places {
654                let said = key(place);
655                match runs.last_mut() {
656                    Some((known, run)) if *known == said => run.push(place),
657                    _ => runs.push((said, vec![place])),
658                }
659            }
660            for ((_, short, bass), run) in runs {
661                if !short {
662                    continue;
663                }
664                let first = &self.spans[run[0]];
665                let last = &self.spans[run[run.len() - 1]];
666                let mut whole_measure =
667                    first.offset == first.parent_offset && last.end == first.parent_end;
668                if let Some(bass) = bass
669                    && first.offset == self.spans[bass].offset
670                    && last.end == self.spans[bass].end
671                {
672                    whole_measure = true;
673                }
674                if !whole_measure {
675                    dropped.extend(run);
676                    continue;
677                }
678                // The notes the run sounds longest stay, as music21's
679                // counter picks the most common.
680                let mut counted: Vec<(Vec<Pitch>, FloatType)> = Vec::new();
681                for place in &run {
682                    let pitches = self.spans[*place].pitches();
683                    let length = self.spans[*place].quarter_length();
684                    match counted
685                        .iter_mut()
686                        .find(|(known, _)| same_pitches(known, &pitches))
687                    {
688                        Some((_, total)) => *total += length,
689                        None => counted.push((pitches, length)),
690                    }
691                }
692                // The notes sounding longest, and on a tie the ones the
693                // run sounded first, as Python's `Counter.most_common`
694                // keeps the order they were counted in.
695                let best =
696                    counted
697                        .iter()
698                        .fold(
699                            None,
700                            |best: Option<&(Vec<Pitch>, FloatType)>, next| match best {
701                                Some(known) if known.1 >= next.1 => Some(known),
702                                _ => Some(next),
703                            },
704                        );
705                let Some((best, _)) = best else {
706                    continue;
707                };
708                for place in &run {
709                    if !same_pitches(&self.spans[*place].pitches(), best) {
710                        dropped.push(*place);
711                    }
712                }
713            }
714        }
715        self.drop_places(&dropped);
716    }
717
718    /// The gaps left inside each measure filled: music21's
719    /// `fillMeasureGaps`.
720    ///
721    /// Part by part, over the spans grouped by the measure holding them:
722    /// neighbours that sound alike or leave a gap are one span, the first
723    /// span of a measure is moved back to the measure's start and given the
724    /// downbeat's strength, and the last is stretched to its end.
725    ///
726    /// music21 keeps what to put in and what to take out in two sets and
727    /// applies them only when the part is done, so the moving and the
728    /// stretching both read the *fused* span rather than each other's
729    /// answer -- a measure whose spans fuse into one comes out of this step
730    /// as two overlapping spans, one reaching back to the measure's start
731    /// and one on to its end, with the fused span itself taken out. That is
732    /// what the steps after it are given, so it is what this does.
733    fn fill_measure_gaps(&mut self) {
734        for part in 0..self.parts {
735            let mut runs: Vec<(Option<IntegerType>, Vec<Span<'a>>)> = Vec::new();
736            for span in &self.spans {
737                if span.part != part {
738                    continue;
739                }
740                match runs.last_mut() {
741                    Some((known, run)) if *known == span.measure => run.push(span.clone()),
742                    _ => runs.push((span.measure, vec![span.clone()])),
743                }
744            }
745            let mut to_remove: Vec<u64> = Vec::new();
746            let mut to_insert: Vec<Span<'a>> = Vec::new();
747            for (_, mut group) in runs {
748                for index in 0..group.len().saturating_sub(1) {
749                    let (one, two) = (group[index].clone(), group[index + 1].clone());
750                    if !(one.sounds_as(&two) || one.end != two.offset) {
751                        continue;
752                    }
753                    let mut fused = one.clone();
754                    fused.end = two.end;
755                    let fused = self.made(fused);
756                    group[index] = fused.clone();
757                    group[index + 1] = fused.clone();
758                    to_insert.push(fused);
759                    to_remove.push(one.seq);
760                    to_remove.push(two.seq);
761                }
762                if group[0].offset != group[0].parent_offset {
763                    let mut moved = group[0].clone();
764                    moved.offset = moved.parent_offset;
765                    moved.beat_strength = Some(1.0);
766                    let moved = self.made(moved);
767                    to_remove.push(group[0].seq);
768                    to_insert.push(moved.clone());
769                    group[0] = moved;
770                }
771                let last = group.len() - 1;
772                if group[last].end != group[last].parent_end {
773                    let mut stretched = group[last].clone();
774                    stretched.end = stretched.parent_end;
775                    let stretched = self.made(stretched);
776                    to_remove.push(group[last].seq);
777                    to_insert.push(stretched.clone());
778                    group[last] = stretched;
779                }
780            }
781            // What was put in and taken out again is simply gone, as
782            // music21's `difference_update` leaves it.
783            to_insert.retain(|span| !to_remove.contains(&span.seq));
784            self.spans.extend(to_insert);
785            self.spans.retain(|span| !to_remove.contains(&span.seq));
786            self.sort();
787        }
788    }
789
790    /// Takes spans out by where they stand.
791    fn drop_places(&mut self, dropped: &[usize]) {
792        if dropped.is_empty() {
793            return;
794        }
795        let dropped: std::collections::HashSet<usize> = dropped.iter().copied().collect();
796        let mut at = 0;
797        self.spans.retain(|_| {
798            let kept = !dropped.contains(&at);
799            at += 1;
800            kept
801        });
802    }
803
804    /// The spans as a part of chords, measure by measure, against the
805    /// score they came from: music21's `tree.toStream.chordified`.
806    ///
807    /// The part is the score's first part as a template -- everything but
808    /// its notes, chords, rests and voices -- the spans are cut at every
809    /// measure's start, and a chord is made for each stretch between two
810    /// time points and *appended* to the measure it falls in, as music21
811    /// appends it.
812    fn chordified(&mut self, score: &Stream) -> Result<Stream> {
813        let template = score
814            .parts()
815            .into_iter()
816            .next()
817            .ok_or_else(|| Error::Analysis("a score with no part to reduce".to_string()))?;
818        let mut measure_offsets: Vec<FloatType> = template
819            .events()
820            .iter()
821            .filter(|event| {
822                event
823                    .element()
824                    .as_stream()
825                    .is_some_and(|inner| inner.kind() == StreamKind::Measure)
826            })
827            .map(crate::stream::StreamEvent::offset)
828            .collect();
829        measure_offsets.push(score.end_offset());
830        self.split_at(&measure_offsets);
831
832        // music21's `chordified` templates the part with no
833        // `removeClasses` of its own, so the dynamics and expressions go
834        // with the notes.
835        let mut out = crate::stream::chordify::template_removing(template, true);
836        let mut filled: Vec<FloatType> = vec![0.0; measure_offsets.len()];
837        let mut points = self.time_points();
838        points.extend(measure_offsets.iter().copied());
839        points.sort_by(FloatType::total_cmp);
840        points.dedup();
841        let mut index = 0;
842        for pair in points.windows(2) {
843            let (offset, end) = (pair[0], pair[1]);
844            while index + 1 < measure_offsets.len() && measure_offsets[index + 1] <= offset {
845                index += 1;
846            }
847            let sounding = self.sounding_at(offset);
848            if sounding.is_empty() {
849                continue;
850            }
851            let spans: Vec<crate::stream::chordify::Span<'_>> = sounding
852                .iter()
853                .map(|place| crate::stream::chordify::Span {
854                    start: self.spans[*place].offset,
855                    end: self.spans[*place].end,
856                    element: &self.spans[*place].element,
857                    part: self.spans[*place].part,
858                })
859                .collect();
860            let element = crate::stream::chordify::make_element(
861                &spans,
862                offset,
863                op_frac(end - offset),
864                &crate::stream::ChordifyOptions::default(),
865            )?;
866            let mut measure = 0;
867            for event in out.events_mut() {
868                if let StreamElement::Stream(inner) = event.element_mut()
869                    && inner.kind() == StreamKind::Measure
870                {
871                    if measure == index {
872                        // Each chord goes after the one before it, from the
873                        // measure's start, which is where music21's
874                        // `append` puts them: what else the template holds
875                        // -- a barline, a layout, a mark -- takes no time
876                        // and does not move them along.
877                        let at = filled[index];
878                        filled[index] = op_frac(at + element.quarter_length());
879                        inner.insert(at, element);
880                        break;
881                    }
882                    measure += 1;
883                }
884            }
885        }
886        Ok(out)
887    }
888}
889
890/// The id of the part's first instrument, which is what music21 sorts the
891/// parts of a reduction by (`getInstrument().partId`). A part with no
892/// instrument has none, and sorts last here where music21's own sort
893/// refuses to compare it at all.
894fn part_id(part: &Stream) -> Option<String> {
895    part.leaves().iter().find_map(|(_, element)| match element {
896        StreamElement::Instrument(instrument) => instrument.part_id().map(str::to_string),
897        _ => None,
898    })
899}
900
901/// Every meter the score states, by where it stands.
902fn meters_of(score: &Stream) -> Vec<(FloatType, TimeSignature)> {
903    let mut meters = Vec::new();
904    for (offset, element) in score.leaves() {
905        if let StreamElement::TimeSignature(meter) = element {
906            meters.push((offset, meter.clone()));
907        }
908    }
909    meters.sort_by(|left, right| left.0.total_cmp(&right.0));
910    meters
911}
912
913/// Every interval class a list of notes holds, 1 to 6: music21's
914/// `_getIntervalClassSet`.
915fn interval_classes(pitches: &[Pitch]) -> Vec<u8> {
916    let mut classes: Vec<u8> = Vec::new();
917    for (index, one) in pitches.iter().enumerate() {
918        for other in &pitches[index + 1..] {
919            let mut semitones = (one.ps() - other.ps()).abs() as i64 % 12;
920            if semitones >= 6 {
921                semitones = 12 - semitones;
922            }
923            let class = semitones as u8;
924            if class != 0 && !classes.contains(&class) {
925                classes.push(class);
926            }
927        }
928    }
929    classes.sort_unstable();
930    classes
931}
932
933/// How much a chord weighs in its measure: music21's `qlbsmpConsonance`,
934/// which is how long it sounds times how strong its beat is, a tenth of
935/// that where it is dissonant, and the length alone for the last chord of
936/// the measure.
937fn weight_of(
938    chord: &Chord,
939    length: FloatType,
940    beat_strength: FloatType,
941    last_in_measure: bool,
942) -> FloatType {
943    let consonance = if chord.is_consonant() { 1.0 } else { 0.1 };
944    let weight = if last_in_measure {
945        length
946    } else {
947        length * beat_strength
948    };
949    weight * consonance
950}
951
952/// A measure reduced to the few chords weighing most in it: music21's
953/// `reduceMeasureToNChords`.
954///
955/// # Errors
956///
957/// A length no duration can have.
958fn reduce_measure_to_n_chords(
959    measure: &Stream,
960    maximum: usize,
961    trim_below: FloatType,
962    meter: Option<TimeSignature>,
963) -> Result<Stream> {
964    let chords: Vec<(FloatType, Chord)> = measure
965        .events()
966        .iter()
967        .filter_map(|event| match event.element() {
968            StreamElement::Chord(chord) => Some((event.offset(), chord.clone())),
969            _ => None,
970        })
971        .collect();
972    let mut weights: Vec<(Vec<u8>, FloatType)> = Vec::new();
973    for (index, (offset, chord)) in chords.iter().enumerate() {
974        let classes = pitch_classes(chord);
975        let length = chord
976            .duration()
977            .map_or(0.0, crate::duration::Duration::quarter_length);
978        let strength = match &meter {
979            Some(meter) => meter.accent_weight(*offset).unwrap_or(1.0),
980            None => 1.0,
981        };
982        let weight = weight_of(chord, length, strength, index + 1 == chords.len());
983        match weights.iter_mut().find(|(known, _)| *known == classes) {
984            Some((_, total)) => *total += weight,
985            None => weights.push((classes, weight)),
986        }
987    }
988    // The measure as it stands without its notes, which is what music21
989    // leaves when it removes the chords it does not keep: the clef, the
990    // key, the meter, a layout, a barline and any rest the chordifying
991    // made stay where they stood.
992    let mut answer = measure.with_events(
993        measure
994            .events()
995            .iter()
996            .filter(|event| !matches!(event.element(), StreamElement::Chord(_)))
997            .cloned()
998            .collect(),
999    );
1000    if weights.is_empty() {
1001        let length = measure.end_offset();
1002        answer.insert(0.0, crate::rest::Rest::new(Duration::new(length)?));
1003        return Ok(answer);
1004    }
1005    let mut sorted = weights.clone();
1006    sorted.sort_by(|(_, left), (_, right)| right.total_cmp(left));
1007    let keep = maximum.min(sorted.len());
1008    let highest = sorted[0].1;
1009    let kept: Vec<Vec<u8>> = sorted
1010        .into_iter()
1011        .take(keep)
1012        .take_while(|(_, weight)| *weight >= highest * trim_below)
1013        .map(|(classes, _)| classes)
1014        .collect();
1015
1016    // Each kept chord takes the length of the chords it stands for, as
1017    // music21's greedy walk gives it.
1018    let mut greedy: Option<(usize, FloatType)> = None;
1019    let mut written: Vec<(FloatType, Chord, FloatType)> = Vec::new();
1020    let mut running = 0.0;
1021    let mut last_classes: Option<Vec<u8>> = None;
1022    for (offset, chord) in &chords {
1023        let classes = pitch_classes(chord);
1024        let length = chord
1025            .duration()
1026            .map_or(0.0, crate::duration::Duration::quarter_length);
1027        if kept.contains(&classes) && Some(&classes) != last_classes.as_ref() {
1028            match greedy {
1029                None if *offset != 0.0 => running = *offset,
1030                None => {}
1031                Some((place, _)) => {
1032                    written[place].2 = running;
1033                    running = 0.0;
1034                }
1035            }
1036            let mut kept_chord = chord.clone();
1037            undecide_accidentals(&mut kept_chord);
1038            let at = if greedy.is_none() && *offset != 0.0 {
1039                0.0
1040            } else {
1041                *offset
1042            };
1043            written.push((at, kept_chord, 0.0));
1044            greedy = Some((written.len() - 1, running));
1045            last_classes = Some(classes);
1046            running = op_frac(running + length);
1047        } else {
1048            running = op_frac(running + length);
1049        }
1050    }
1051    if let Some((place, _)) = greedy {
1052        written[place].2 = running;
1053    }
1054    // music21 evens out the syncopations over the measure as it stands,
1055    // the elements it keeps beside the chords included, so the element a
1056    // syncopated chord takes its time from may be one of those -- which
1057    // changes nothing a score says, since none of them sounds.
1058    let others: Vec<FloatType> = answer
1059        .events()
1060        .iter()
1061        .map(crate::stream::StreamEvent::offset)
1062        .collect();
1063    even_out_syncopations(&mut written, &others);
1064    for (offset, mut chord, length) in written {
1065        chord.set_duration(Duration::new(length.max(0.0))?);
1066        answer.insert(offset, chord);
1067    }
1068    Ok(answer)
1069}
1070
1071/// The pitch classes a chord holds, as music21 keys its weights by them:
1072/// a set, so two chords of the same classes in another order weigh as one
1073/// -- which is what makes a chord struck again over a moving bass count
1074/// towards the chord already sounding rather than as a chord of its own.
1075fn pitch_classes(chord: &Chord) -> Vec<u8> {
1076    let classes: Vec<u8> = chord
1077        .pitches()
1078        .iter()
1079        .map(|pitch| (pitch.ps().round_ties_even() as i64).rem_euclid(12) as u8)
1080        .collect();
1081    python_set_order(&classes)
1082}
1083
1084/// Pitch classes in the order music21 reads them out of a set, which is
1085/// CPython's own order for a set of small integers.
1086///
1087/// music21 keys a measure's weights by `tuple({p.pitchClass for p in ...})`,
1088/// and that tuple's order is the order of the hash table the set was built
1089/// in -- so two voicings of the same classes weigh as *one* chord where
1090/// their tables came out alike and as *two* where they did not, which
1091/// decides what a measure keeps. A set of up to four classes lives in a
1092/// table of eight slots, each class in slot `class & 7` or, where that is
1093/// taken, in the slot CPython's perturbation walks to next; the fifth class
1094/// grows the table to thirty-two slots, where every class has a slot of its
1095/// own and the order is ascending.
1096fn python_set_order(classes: &[u8]) -> Vec<u8> {
1097    let mut distinct: Vec<u8> = Vec::new();
1098    for class in classes {
1099        if !distinct.contains(class) {
1100            distinct.push(*class);
1101        }
1102    }
1103    if distinct.len() >= 5 {
1104        distinct.sort_unstable();
1105        return distinct;
1106    }
1107    let mut table: [Option<u8>; 8] = [None; 8];
1108    for class in &distinct {
1109        let mut index = usize::from(*class) & 7;
1110        while table[index].is_some() {
1111            // A class is its own hash and is smaller than 32, so CPython's
1112            // perturbation adds nothing to the walk.
1113            index = (index * 5 + 1) & 7;
1114        }
1115        table[index] = Some(*class);
1116    }
1117    table.into_iter().flatten().collect()
1118}
1119
1120/// A chord's accidentals left undecided, as music21 leaves a kept chord's:
1121/// it clears the display status of every accidental actually written.
1122fn undecide_accidentals(chord: &mut Chord) {
1123    for note in chord.notes_mut() {
1124        let mut pitch = note.pitch().clone();
1125        if let Some(accidental) = pitch.written_accidental_mut() {
1126            accidental.set_display_status(None);
1127        }
1128        note.set_pitch(pitch);
1129    }
1130}
1131
1132/// A chord standing a quarter, an eighth or a third of the way through a
1133/// beat is moved onto it, the element before it giving up the difference:
1134/// music21's "even chord lengths" pass, which walks the measure in order
1135/// and reads each offset as it has been left by the one before.
1136fn even_out_syncopations(written: &mut [(FloatType, Chord, FloatType)], others: &[FloatType]) {
1137    for index in 0..written.len() {
1138        let offset = written[index].0;
1139        let whole = offset.trunc();
1140        let past = offset - whole;
1141        let rounded = (past * 1000.0).round() / 1000.0;
1142        if ![0.250, 0.125, 0.333, 0.063, 0.062].contains(&rounded) {
1143            continue;
1144        }
1145        // The element standing before it, which is a chord only where no
1146        // other element of the measure stands between the two.
1147        let before = written[..index].last().map(|(at, _, _)| *at);
1148        let Some(before) = before else {
1149            // music21 leaves the first element of a measure alone.
1150            if index == 0 && !others.iter().any(|at| *at < offset) {
1151                continue;
1152            }
1153            written[index].0 = whole;
1154            written[index].2 = op_frac(written[index].2 + past);
1155            continue;
1156        };
1157        let interrupted = others.iter().any(|at| *at > before && *at <= offset);
1158        if !interrupted {
1159            written[index - 1].2 = op_frac(written[index - 1].2 - past);
1160        }
1161        written[index].0 = whole;
1162        written[index].2 = op_frac(written[index].2 + past);
1163    }
1164}
1165
1166/// Every chord of the part written in closed position in the fourth
1167/// octave: music21's `closedPosition(forceOctave=4, inPlace=True)` over
1168/// every chord of the reduction.
1169fn close_positions(stream: &mut Stream) {
1170    for event in stream.events_mut() {
1171        match event.element_mut() {
1172            StreamElement::Stream(inner) => close_positions(inner),
1173            StreamElement::Chord(chord) => *chord = chord.closed_position(Some(4), false),
1174            _ => {}
1175        }
1176    }
1177}