1use 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#[derive(Clone, Debug)]
33pub struct ReductionOptions {
34 pub maximum_number_of_chords: usize,
37 pub closed_position: bool,
40 pub allowable_chords: Vec<Chord>,
43 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
59pub 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#[derive(Clone, Debug)]
115struct Span<'a> {
116 part: usize,
119 offset: FloatType,
120 end: FloatType,
121 parent_offset: FloatType,
124 parent_end: FloatType,
125 measure: Option<IntegerType>,
126 element: Cow<'a, StreamElement>,
127 beat_strength: Option<FloatType>,
130 seq: u64,
134}
135
136impl Span<'_> {
137 fn quarter_length(&self) -> FloatType {
138 op_frac(self.end - self.offset)
139 }
140
141 fn pitches(&self) -> Vec<Pitch> {
143 self.element.pitches()
144 }
145
146 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
157fn 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
169fn 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
175fn 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
183type ShortKey = (Option<IntegerType>, bool, Option<usize>);
186
187struct Reduction<'a> {
189 spans: Vec<Span<'a>>,
190 meters: Vec<(FloatType, TimeSignature)>,
193 parts: usize,
195 next_seq: u64,
197}
198
199impl<'a> Reduction<'a> {
200 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 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 fn remove_zero_duration(&mut self) {
255 self.spans.retain(|span| span.quarter_length() != 0.0);
256 }
257
258 fn split_by_bass(&mut self) {
261 for part in 0..self.parts {
262 self.fuse_by_part(part);
263 }
264 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 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 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 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 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 fn place_of(&self, seq: u64) -> Option<usize> {
360 self.spans.iter().position(|span| span.seq == seq)
361 }
362
363 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 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 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 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 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 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 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 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 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 fn fill_bass_gaps(&mut self) {
517 for part in 0..self.parts {
518 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 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 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 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 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 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 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 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 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 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 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 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
890fn 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
901fn 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
913fn 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
933fn 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
952fn 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 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 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 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
1071fn 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
1084fn 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 index = (index * 5 + 1) & 7;
1114 }
1115 table[index] = Some(*class);
1116 }
1117 table.into_iter().flatten().collect()
1118}
1119
1120fn 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
1132fn 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 let before = written[..index].last().map(|(at, _, _)| *at);
1148 let Some(before) = before else {
1149 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
1166fn 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}