1use std::borrow::Cow;
17use std::sync::Arc;
18
19use crate::chord::Chord;
20use crate::clef::Clef;
21use crate::defaults::{FloatType, IntegerType};
22use crate::duration::Duration;
23use crate::error::{Error, Result};
24use crate::makenotation::op_frac;
25use crate::meter::TimeSignature;
26use crate::notation::{Tie, TieType};
27use crate::pitch::Pitch;
28use crate::stream::{Stream, StreamElement, StreamKind};
29use crate::tree::{Flatten, TimespanTree, as_timespans};
30
31#[derive(Clone, Debug)]
33#[non_exhaustive]
34pub struct ReductionOptions {
35 pub maximum_number_of_chords: usize,
38 pub closed_position: bool,
41 pub force_octave: Option<IntegerType>,
44 pub allowable_chords: Vec<Chord>,
47 pub forbidden_chords: Vec<Chord>,
50 pub remove_dissonances: bool,
54 pub weight_algorithm: SliceWeigher,
57}
58
59impl Default for ReductionOptions {
60 fn default() -> Self {
61 Self {
62 maximum_number_of_chords: 3,
63 closed_position: false,
64 allowable_chords: Vec::new(),
65 forbidden_chords: Vec::new(),
66 remove_dissonances: false,
67 force_octave: Some(4),
68 weight_algorithm: SliceWeigher::default(),
69 }
70 }
71}
72
73#[derive(Clone)]
85pub struct SliceWeigher(Arc<SliceWeight>);
86
87type SliceWeight = dyn Fn(FloatType, FloatType, bool, bool) -> FloatType + Send + Sync;
89
90impl SliceWeigher {
91 pub fn new(
94 weigh: impl Fn(FloatType, FloatType, bool, bool) -> FloatType + Send + Sync + 'static,
95 ) -> Self {
96 Self(Arc::new(weigh))
97 }
98
99 pub fn weigh(
101 &self,
102 quarter_length: FloatType,
103 accent: FloatType,
104 is_last: bool,
105 is_consonant: bool,
106 ) -> FloatType {
107 (self.0)(quarter_length, accent, is_last, is_consonant)
108 }
109}
110
111impl Default for SliceWeigher {
112 fn default() -> Self {
113 Self::new(slice_weight)
114 }
115}
116
117impl std::fmt::Debug for SliceWeigher {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 f.write_str("SliceWeigher")
120 }
121}
122
123pub fn slice_weight(
134 quarter_length: FloatType,
135 accent: FloatType,
136 is_last: bool,
137 is_consonant: bool,
138) -> FloatType {
139 let weight = if is_last {
140 quarter_length
141 } else {
142 quarter_length * accent
143 };
144 if is_consonant { weight } else { weight * 0.1 }
145}
146
147pub fn reduce_chords(score: &Stream, options: &ReductionOptions) -> Result<Stream> {
155 if score.kind() != StreamKind::Score {
156 return Err(Error::Analysis(
157 "a reduction is of a score, as music21's is".to_string(),
158 ));
159 }
160 let template = score
161 .parts()
162 .into_iter()
163 .next()
164 .ok_or_else(|| Error::Analysis("a score with no part to reduce".to_string()))?;
165 let mut reduction = Reduction::of(score);
166 reduction.remove_zero_duration();
167 if options.remove_dissonances {
168 reduction.remove_vertical_dissonances(options);
169 }
170 let consonance = Consonance::of(options);
171
172 let mut part = crate::stream::chordify::template_of(template);
173 let points = reduction.time_points();
174 let mut meter = TimeSignature::default();
175 let mut index = 0;
176 for event in template.events() {
177 let Some(measure) = event.element().as_stream() else {
178 continue;
179 };
180 if measure.kind() != StreamKind::Measure {
181 continue;
182 }
183 if let Some(stated) = stated_meter(measure) {
184 meter = stated.clone();
185 }
186 let start = event.offset();
187 let length = match measure.end_offset() {
188 0.0 => meter.bar_quarter_length(),
189 length => length,
190 };
191 let slices = reduction.measure_slices(start, op_frac(start + length), &points);
192 let weights = weigh_slices(
193 &slices,
194 &meter,
195 measure.padding_left(),
196 &consonance,
197 &options.weight_algorithm,
198 )?;
199 let kept = keep_heaviest_slices(&slices, &weights, options.maximum_number_of_chords, 0.25);
200 let mut made = Vec::new();
201 if kept.is_empty() {
202 made.push((
203 0.0,
204 StreamElement::Rest(crate::rest::Rest::new(Duration::new(length)?)),
205 ));
206 }
207 for slice in &kept {
208 made.push((slice.offset, reduction.element_at(slice)?));
209 }
210 if let Some(reduced) = nth_measure(&mut part, index) {
211 for (offset, element) in made {
212 reduced.insert(offset, element);
213 }
214 }
215 index += 1;
216 }
217 if options.closed_position {
218 close_positions(&mut part, options.force_octave);
219 }
220 tie_across_barlines(&mut part);
221 let pitches = part.pitches();
222 part.retain_leaves(&mut |_, element| !matches!(element, StreamElement::Clef(_)));
223 if let Some(first) = nth_measure(&mut part, 0) {
224 first.insert_sorted(vec![crate::stream::StreamEvent::new(
225 0.0,
226 Clef::best_for(&pitches, true),
227 )]);
228 }
229 let mut answer = Stream::with_kind(StreamKind::Score);
230 answer.insert(0.0, part);
231 Ok(answer)
232}
233
234fn stated_meter(measure: &Stream) -> Option<&TimeSignature> {
237 measure
238 .events()
239 .iter()
240 .find_map(|event| match event.element() {
241 StreamElement::TimeSignature(meter) => Some((event.offset(), meter)),
242 _ => None,
243 })
244 .filter(|(offset, _)| *offset == 0.0)
245 .map(|(_, meter)| meter)
246}
247
248fn nth_measure(part: &mut Stream, index: usize) -> Option<&mut Stream> {
250 part.events_mut()
251 .iter_mut()
252 .filter_map(|event| match event.element_mut() {
253 StreamElement::Stream(inner) if inner.kind() == StreamKind::Measure => {
254 Some(inner.as_mut())
255 }
256 _ => None,
257 })
258 .nth(index)
259}
260
261#[derive(Clone, Debug)]
264struct Span<'a> {
265 part: usize,
267 offset: FloatType,
268 end: FloatType,
269 element: Cow<'a, StreamElement>,
270}
271
272impl Span<'_> {
273 fn quarter_length(&self) -> FloatType {
274 op_frac(self.end - self.offset)
275 }
276
277 fn pitches(&self) -> Vec<Pitch> {
279 self.element.pitches()
280 }
281
282 fn lowest(&self) -> Option<Pitch> {
283 lowest_of(&self.pitches())
284 }
285}
286
287fn written_alike(one: &Pitch, other: &Pitch) -> bool {
289 one.name_with_octave() == other.name_with_octave()
290 && one.written_accidental().is_some() == other.written_accidental().is_some()
291}
292
293fn lowest_of(pitches: &[Pitch]) -> Option<Pitch> {
295 pitches
296 .iter()
297 .min_by(|left, right| left.ps().total_cmp(&right.ps()))
298 .cloned()
299}
300
301struct Consonance {
304 allowable: Vec<Vec<u8>>,
305 forbidden: Vec<Vec<u8>>,
306}
307
308impl Consonance {
309 fn of(options: &ReductionOptions) -> Self {
310 Self::from_chords(&options.allowable_chords, &options.forbidden_chords)
311 }
312
313 fn from_chords(allowable: &[Chord], forbidden: &[Chord]) -> Self {
314 let classes = |chords: &[Chord]| {
315 chords
316 .iter()
317 .map(|chord| interval_classes(&chord.pitches()))
318 .collect()
319 };
320 Self {
321 allowable: classes(allowable),
322 forbidden: classes(forbidden),
323 }
324 }
325
326 fn from_sets(allowable: &[Vec<u8>], forbidden: &[Vec<u8>]) -> Self {
328 let sets = |given: &[Vec<u8>]| {
329 given
330 .iter()
331 .map(|classes| {
332 let mut classes = classes.clone();
333 classes.sort_unstable();
334 classes.dedup();
335 classes
336 })
337 .collect()
338 };
339 Self {
340 allowable: sets(allowable),
341 forbidden: sets(forbidden),
342 }
343 }
344
345 fn holds(&self, pitches: &[Pitch]) -> bool {
349 let classes = interval_classes(pitches);
350 if self.forbidden.contains(&classes) {
351 return false;
352 }
353 if self.allowable.contains(&classes) {
354 return true;
355 }
356 Chord::new(pitches).is_ok_and(|chord| chord.is_consonant())
357 }
358}
359
360#[derive(Clone, Debug, PartialEq)]
364#[non_exhaustive]
365pub struct MeasureSlice {
366 pub offset: FloatType,
368 pub quarter_length: FloatType,
370 pub at: FloatType,
373 pub pitches: Vec<Pitch>,
375}
376
377impl MeasureSlice {
378 fn key(&self) -> Option<Vec<u8>> {
380 if self.pitches.is_empty() {
381 return None;
382 }
383 Some(sorted_classes(&self.pitches))
384 }
385}
386
387fn cut(
390 start: FloatType,
391 end: FloatType,
392 points: &[FloatType],
393 pitches_at: impl Fn(FloatType) -> Vec<Pitch>,
394) -> Vec<MeasureSlice> {
395 let mut cuts = vec![start];
396 cuts.extend(points.iter().copied().filter(|at| start < *at && *at < end));
397 cuts.push(end);
398 cuts.windows(2)
399 .map(|pair| MeasureSlice {
400 offset: op_frac(pair[0] - start),
401 quarter_length: op_frac(pair[1] - pair[0]),
402 at: pair[0],
403 pitches: pitches_at(pair[0]),
404 })
405 .collect()
406}
407
408pub fn measure_slices(
431 tree: &TimespanTree<'_>,
432 start: FloatType,
433 end: FloatType,
434 time_points: Option<&[FloatType]>,
435) -> Vec<MeasureSlice> {
436 let points = match time_points {
437 Some(points) => Cow::Borrowed(points),
438 None => Cow::Owned(tree.all_time_points()),
439 };
440 cut(start, end, &points, |at| {
441 tree.verticality_at(at).pitch_set()
442 })
443}
444
445struct Reduction<'a> {
447 spans: Vec<Span<'a>>,
448}
449
450impl<'a> Reduction<'a> {
451 fn of(score: &'a Stream) -> Self {
454 let parts: Vec<&Stream> = score.parts().into_iter().collect();
455 let timespans = as_timespans(
456 score,
457 Flatten::Flat,
458 Some(|element: &StreamElement| {
459 matches!(element, StreamElement::Note(_) | StreamElement::Chord(_))
460 }),
461 );
462 let mut spans = Vec::new();
463 for span in timespans.timespans() {
464 let part = span
465 .part()
466 .and_then(|held| {
467 parts.iter().position(|known| {
468 std::ptr::eq(std::ptr::from_ref(*known), std::ptr::from_ref(held))
469 })
470 })
471 .unwrap_or(0);
472 spans.push(Span {
473 part,
474 offset: span.offset(),
475 end: span.end_time(),
476 element: Cow::Borrowed(span.element()),
477 });
478 }
479 Self { spans }
480 }
481
482 fn remove_zero_duration(&mut self) {
484 self.spans.retain(|span| span.quarter_length() != 0.0);
485 }
486
487 fn time_points(&self) -> Vec<FloatType> {
490 let mut points: Vec<FloatType> = self
491 .spans
492 .iter()
493 .flat_map(|span| [span.offset, span.end])
494 .collect();
495 points.sort_by(FloatType::total_cmp);
496 points.dedup();
497 points
498 }
499
500 fn starting_at(&self, offset: FloatType) -> Vec<usize> {
502 self.spans
503 .iter()
504 .enumerate()
505 .filter(|(_, span)| span.offset == offset)
506 .map(|(at, _)| at)
507 .collect()
508 }
509
510 fn sounding_at(&self, offset: FloatType) -> Vec<usize> {
514 let mut places = self.starting_at(offset);
515 places.extend(
516 self.spans
517 .iter()
518 .enumerate()
519 .filter(|(_, span)| span.offset < offset && offset < span.end)
520 .map(|(at, _)| at),
521 );
522 places
523 }
524
525 fn remove_vertical_dissonances(&mut self, options: &ReductionOptions) {
533 let consonance = Consonance::of(options);
534 let mut offset = self.lowest_offset();
535 while let Some(at) = offset {
536 let pitches = self.pitch_set(at);
537 if !consonance.holds(&pitches)
538 && let Some(lowest) = lowest_of(&pitches)
539 {
540 let dropped: Vec<usize> = self
541 .starting_at(at)
542 .into_iter()
543 .filter(|place| {
544 self.spans[*place]
545 .lowest()
546 .is_some_and(|pitch| !written_alike(&pitch, &lowest))
547 })
548 .collect();
549 self.drop_places(&dropped);
550 }
551 offset = self.offset_after(at);
552 }
553 }
554
555 fn lowest_offset(&self) -> Option<FloatType> {
557 self.spans
558 .iter()
559 .map(|span| span.offset)
560 .min_by(FloatType::total_cmp)
561 }
562
563 fn offset_after(&self, offset: FloatType) -> Option<FloatType> {
565 self.spans
566 .iter()
567 .map(|span| span.offset)
568 .filter(|at| *at > offset)
569 .min_by(FloatType::total_cmp)
570 }
571
572 fn pitch_set(&self, offset: FloatType) -> Vec<Pitch> {
575 let mut pitches: Vec<Pitch> = Vec::new();
576 for place in self.sounding_at(offset) {
577 for pitch in self.spans[place].pitches() {
578 if !pitches.iter().any(|known| written_alike(known, &pitch)) {
579 pitches.push(pitch);
580 }
581 }
582 }
583 pitches.sort_by(|left, right| left.ps().total_cmp(&right.ps()));
584 pitches
585 }
586
587 fn drop_places(&mut self, dropped: &[usize]) {
589 if dropped.is_empty() {
590 return;
591 }
592 let dropped: std::collections::HashSet<usize> = dropped.iter().copied().collect();
593 let mut at = 0;
594 self.spans.retain(|_| {
595 let kept = !dropped.contains(&at);
596 at += 1;
597 kept
598 });
599 }
600
601 fn measure_slices(
604 &self,
605 start: FloatType,
606 end: FloatType,
607 points: &[FloatType],
608 ) -> Vec<MeasureSlice> {
609 cut(start, end, points, |at| self.pitch_set(at))
610 }
611
612 fn element_at(&self, slice: &MeasureSlice) -> Result<StreamElement> {
615 let spans: Vec<crate::stream::chordify::Span<'_>> = self
616 .sounding_at(slice.at)
617 .into_iter()
618 .map(|place| crate::stream::chordify::Span {
619 start: self.spans[place].offset,
620 end: self.spans[place].end,
621 element: &self.spans[place].element,
622 part: self.spans[place].part,
623 })
624 .collect();
625 let options = crate::stream::ChordifyOptions {
626 add_ties: false,
627 ..crate::stream::ChordifyOptions::default()
628 };
629 crate::stream::chordify::make_element(&spans, slice.at, slice.quarter_length, &options)
630 }
631}
632
633pub fn slice_weights(
646 slices: &[MeasureSlice],
647 meter: &TimeSignature,
648 padding_left: FloatType,
649 weigher: &SliceWeigher,
650 allowable: &[Vec<u8>],
651 forbidden: &[Vec<u8>],
652) -> Result<Vec<(Vec<u8>, FloatType)>> {
653 let consonance = Consonance::from_sets(allowable, forbidden);
654 weigh_slices(slices, meter, padding_left, &consonance, weigher)
655}
656
657pub fn is_consonant(pitches: &[Pitch], allowable: &[Vec<u8>], forbidden: &[Vec<u8>]) -> bool {
676 Consonance::from_sets(allowable, forbidden).holds(pitches)
677}
678
679pub fn interval_class_set(pitches: &[Pitch]) -> Vec<u8> {
684 interval_classes(pitches)
685}
686
687fn weigh_slices(
689 slices: &[MeasureSlice],
690 meter: &TimeSignature,
691 padding: FloatType,
692 consonance: &Consonance,
693 weigher: &SliceWeigher,
694) -> Result<Vec<(Vec<u8>, FloatType)>> {
695 let sounding: Vec<usize> = (0..slices.len())
696 .filter(|at| !slices[*at].pitches.is_empty())
697 .collect();
698 let mut weights: Vec<(Vec<u8>, FloatType)> = Vec::new();
699 for at in &sounding {
700 let slice = &slices[*at];
701 let accent = meter.accent_weight_with(op_frac(slice.offset + padding), true, true)?;
702 let weight = weigher.weigh(
703 slice.quarter_length,
704 accent,
705 Some(at) == sounding.last(),
706 consonance.holds(&slice.pitches),
707 );
708 let classes = sorted_classes(&slice.pitches);
709 match weights.iter_mut().find(|(known, _)| *known == classes) {
710 Some((_, total)) => *total += weight,
711 None => weights.push((classes, weight)),
712 }
713 }
714 Ok(weights)
715}
716
717pub fn keep_heaviest_slices(
726 slices: &[MeasureSlice],
727 weights: &[(Vec<u8>, FloatType)],
728 maximum: usize,
729 trim_below: FloatType,
730) -> Vec<MeasureSlice> {
731 let mut sorted: Vec<&(Vec<u8>, FloatType)> = weights.iter().collect();
732 sorted.sort_by(|left, right| right.1.total_cmp(&left.1));
733 sorted.truncate(maximum);
734 let Some(heaviest) = sorted.first().map(|(_, weight)| *weight) else {
735 return Vec::new();
736 };
737 let kept_keys: Vec<&Vec<u8>> = sorted
738 .iter()
739 .filter(|(_, weight)| *weight >= heaviest * trim_below)
740 .map(|(classes, _)| classes)
741 .collect();
742
743 let mut kept: Vec<MeasureSlice> = Vec::new();
744 let mut current: Option<Vec<u8>> = None;
745 for slice in slices {
746 let key = slice.key();
747 let end = op_frac(slice.offset + slice.quarter_length);
748 if let Some(classes) = &key
749 && kept_keys.contains(&classes)
750 && key != current
751 {
752 let offset = if kept.is_empty() { 0.0 } else { slice.offset };
753 kept.push(MeasureSlice {
754 offset,
755 quarter_length: op_frac(end - offset),
756 ..slice.clone()
757 });
758 current = key;
759 } else if let Some(last) = kept.last_mut() {
760 last.quarter_length = op_frac(end - last.offset);
761 }
762 }
763 for index in 1..kept.len() {
764 let past = op_frac(kept[index].offset - kept[index].offset.trunc());
765 let rounded = (past * 1000.0).round() / 1000.0;
766 if ![0.25, 0.125, 0.333, 0.063, 0.062].contains(&rounded) {
767 continue;
768 }
769 kept[index - 1].quarter_length = op_frac(kept[index - 1].quarter_length - past);
770 kept[index].offset = op_frac(kept[index].offset - past);
771 kept[index].quarter_length = op_frac(kept[index].quarter_length + past);
772 }
773 kept.retain(|slice| slice.quarter_length != 0.0);
774 kept
775}
776
777fn sorted_classes(pitches: &[Pitch]) -> Vec<u8> {
779 let mut classes: Vec<u8> = pitches.iter().map(pitch_class).collect();
780 classes.sort_unstable();
781 classes.dedup();
782 classes
783}
784
785fn pitch_class(pitch: &Pitch) -> u8 {
787 (pitch.ps().round_ties_even() as i64).rem_euclid(12) as u8
788}
789
790fn interval_classes(pitches: &[Pitch]) -> Vec<u8> {
793 let mut classes: Vec<u8> = Vec::new();
794 for (index, one) in pitches.iter().enumerate() {
795 for other in &pitches[index + 1..] {
796 let mut semitones = (one.ps() - other.ps()).abs() as i64 % 12;
797 if semitones >= 6 {
798 semitones = 12 - semitones;
799 }
800 let class = semitones as u8;
801 if class != 0 && !classes.contains(&class) {
802 classes.push(class);
803 }
804 }
805 }
806 classes.sort_unstable();
807 classes
808}
809
810#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
814#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
815pub enum ChordWeight {
816 #[default]
820 Length,
821 LengthByBeat,
823 LengthByBeatInMeasure,
826 Consonance,
829}
830
831impl ChordWeight {
832 #[must_use]
834 pub fn name(self) -> &'static str {
835 match self {
836 Self::Length => "quarterLengthOnly",
837 Self::LengthByBeat => "quarterLengthBeatStrength",
838 Self::LengthByBeatInMeasure => "quarterLengthBeatStrengthMeasurePosition",
839 Self::Consonance => "qlbsmpConsonance",
840 }
841 }
842
843 #[must_use]
846 pub fn named(name: &str) -> Option<Self> {
847 [
848 Self::Length,
849 Self::LengthByBeat,
850 Self::LengthByBeatInMeasure,
851 Self::Consonance,
852 ]
853 .into_iter()
854 .find(|found| found.name() == name)
855 }
856
857 #[must_use]
859 pub fn of(
860 self,
861 chord: &Chord,
862 length: FloatType,
863 beat_strength: FloatType,
864 last_in_measure: bool,
865 ) -> FloatType {
866 let by_beat = if last_in_measure {
867 length
868 } else {
869 length * beat_strength
870 };
871 match self {
872 Self::Length => length,
873 Self::LengthByBeat => length * beat_strength,
874 Self::LengthByBeatInMeasure => by_beat,
875 Self::Consonance => {
876 let consonance = if chord.is_consonant() { 1.0 } else { 0.1 };
877 by_beat * consonance
878 }
879 }
880 }
881}
882
883#[derive(Clone, Copy, Debug)]
889pub struct WeighedChord<'a> {
890 pub offset: FloatType,
892 pub chord: &'a Chord,
894 pub beat_strength: FloatType,
896}
897
898#[must_use]
907pub fn measure_chord_weights(
908 chords: &[WeighedChord<'_>],
909 algorithm: ChordWeight,
910) -> Vec<(Vec<u8>, FloatType)> {
911 let mut weights: Vec<(Vec<u8>, FloatType)> = Vec::new();
912 for (index, held) in chords.iter().enumerate() {
913 let classes = pitch_classes(held.chord);
914 let length = held
915 .chord
916 .duration()
917 .map_or(0.0, crate::duration::Duration::quarter_length);
918 let weight = algorithm.of(
919 held.chord,
920 length,
921 held.beat_strength,
922 index + 1 == chords.len(),
923 );
924 match weights.iter_mut().find(|(known, _)| *known == classes) {
925 Some((_, total)) => *total += weight,
926 None => weights.push((classes, weight)),
927 }
928 }
929 weights
930}
931
932#[derive(Clone, Copy, Debug, PartialEq)]
935pub struct KeptChord {
936 pub at: usize,
938 pub offset: FloatType,
940 pub length: FloatType,
942}
943
944#[must_use]
953pub fn reduced_measure_places(
954 chords: &[WeighedChord<'_>],
955 others: &[FloatType],
956 maximum: usize,
957 trim_below: FloatType,
958 algorithm: ChordWeight,
959) -> Vec<KeptChord> {
960 let weights = measure_chord_weights(chords, algorithm);
961 if weights.is_empty() {
962 return Vec::new();
963 }
964 let mut sorted = weights;
965 sorted.sort_by(|(_, left), (_, right)| right.total_cmp(left));
966 let keep = maximum.min(sorted.len());
967 let highest = sorted[0].1;
968 let kept: Vec<Vec<u8>> = sorted
969 .into_iter()
970 .take(keep)
971 .take_while(|(_, weight)| *weight >= highest * trim_below)
972 .map(|(classes, _)| classes)
973 .collect();
974
975 let mut greedy: Option<usize> = None;
976 let mut written: Vec<KeptChord> = Vec::new();
977 let mut running = 0.0;
978 let mut last_classes: Option<Vec<u8>> = None;
979 for (at, held) in chords.iter().enumerate() {
980 let classes = pitch_classes(held.chord);
981 let length = held
982 .chord
983 .duration()
984 .map_or(0.0, crate::duration::Duration::quarter_length);
985 if kept.contains(&classes) && Some(&classes) != last_classes.as_ref() {
986 match greedy {
987 None if held.offset != 0.0 => running = held.offset,
988 None => {}
989 Some(place) => {
990 written[place].length = running;
991 running = 0.0;
992 }
993 }
994 let offset = if greedy.is_none() && held.offset != 0.0 {
995 0.0
996 } else {
997 held.offset
998 };
999 written.push(KeptChord {
1000 at,
1001 offset,
1002 length: 0.0,
1003 });
1004 greedy = Some(written.len() - 1);
1005 last_classes = Some(classes);
1006 running = op_frac(running + length);
1007 } else {
1008 running = op_frac(running + length);
1009 }
1010 }
1011 if let Some(place) = greedy {
1012 written[place].length = running;
1013 }
1014 even_out_syncopations(&mut written, others);
1015 written
1016}
1017
1018pub fn reduce_measure_to_n_chords(
1029 measure: &Stream,
1030 maximum: usize,
1031 trim_below: FloatType,
1032 meter: Option<TimeSignature>,
1033 algorithm: ChordWeight,
1034) -> Result<Stream> {
1035 let held: Vec<(FloatType, Chord)> = measure
1036 .events()
1037 .iter()
1038 .filter_map(|event| match event.element() {
1039 StreamElement::Chord(chord) => Some((event.offset(), chord.clone())),
1040 _ => None,
1041 })
1042 .collect();
1043 let mut answer = measure.with_events(
1048 measure
1049 .events()
1050 .iter()
1051 .filter(|event| !matches!(event.element(), StreamElement::Chord(_)))
1052 .cloned()
1053 .collect(),
1054 );
1055 let others: Vec<FloatType> = answer
1056 .events()
1057 .iter()
1058 .map(crate::stream::StreamEvent::offset)
1059 .collect();
1060 let chords: Vec<WeighedChord<'_>> = held
1061 .iter()
1062 .map(|(offset, chord)| WeighedChord {
1063 offset: *offset,
1064 chord,
1065 beat_strength: match &meter {
1066 Some(meter) => meter.accent_weight(*offset).unwrap_or(1.0),
1067 None => 1.0,
1068 },
1069 })
1070 .collect();
1071 let kept = reduced_measure_places(&chords, &others, maximum, trim_below, algorithm);
1072 if kept.is_empty() {
1073 let length = measure.end_offset();
1074 answer.insert(0.0, crate::rest::Rest::new(Duration::new(length)?));
1075 return Ok(answer);
1076 }
1077 for place in kept {
1078 let mut chord = held[place.at].1.clone();
1079 undecide_accidentals(&mut chord);
1080 chord.set_duration(Duration::new(place.length.max(0.0))?);
1081 answer.insert(place.offset, chord);
1082 }
1083 Ok(answer)
1084}
1085
1086fn pitch_classes(chord: &Chord) -> Vec<u8> {
1089 sorted_classes(&chord.pitches())
1090}
1091
1092fn undecide_accidentals(chord: &mut Chord) {
1095 for note in chord.notes_mut() {
1096 let mut pitch = note.pitch().clone();
1097 if let Some(accidental) = pitch.written_accidental_mut() {
1098 accidental.set_display_status(None);
1099 }
1100 note.set_pitch(pitch);
1101 }
1102}
1103
1104fn even_out_syncopations(written: &mut [KeptChord], others: &[FloatType]) {
1109 for index in 0..written.len() {
1110 let offset = written[index].offset;
1111 let whole = offset.trunc();
1112 let past = offset - whole;
1113 let rounded = (past * 1000.0).round() / 1000.0;
1114 if ![0.250, 0.125, 0.333, 0.063, 0.062].contains(&rounded) {
1115 continue;
1116 }
1117 let before = written[..index].last().map(|kept| kept.offset);
1120 let Some(before) = before else {
1121 if index == 0 && !others.iter().any(|at| *at < offset) {
1123 continue;
1124 }
1125 written[index].offset = whole;
1126 written[index].length = op_frac(written[index].length + past);
1127 continue;
1128 };
1129 let interrupted = others.iter().any(|at| *at > before && *at <= offset);
1130 if !interrupted {
1131 written[index - 1].length = op_frac(written[index - 1].length - past);
1132 }
1133 written[index].offset = whole;
1134 written[index].length = op_frac(written[index].length + past);
1135 }
1136}
1137
1138fn close_positions(stream: &mut Stream, force_octave: Option<IntegerType>) {
1142 for event in stream.events_mut() {
1143 match event.element_mut() {
1144 StreamElement::Stream(inner) => close_positions(inner, force_octave),
1145 StreamElement::Chord(chord) => *chord = chord.closed_position(force_octave, false),
1146 _ => {}
1147 }
1148 }
1149}
1150
1151fn tie_across_barlines(part: &mut Stream) {
1155 let mut previous: Option<(usize, usize)> = None;
1156 for at in 0..part.events().len() {
1157 let Some(measure) = part.events()[at].element().as_stream() else {
1158 continue;
1159 };
1160 if measure.kind() != StreamKind::Measure {
1161 continue;
1162 }
1163 let played: Vec<usize> = (0..measure.events().len())
1164 .filter(|place| {
1165 matches!(
1166 measure.events()[*place].element(),
1167 StreamElement::Note(_) | StreamElement::Chord(_) | StreamElement::Rest(_)
1168 )
1169 })
1170 .collect();
1171 let (Some(&first), Some(&last)) = (played.first(), played.last()) else {
1172 continue;
1173 };
1174 if let Some((before, place)) = previous {
1175 let pitches_before = sounded(part, before, place);
1176 if pitches_before.is_some() && pitches_before == sounded(part, at, first) {
1177 add_tie(element_of(part, before, place), TieType::Start);
1178 add_tie(element_of(part, at, first), TieType::Stop);
1179 }
1180 }
1181 previous = Some((at, last));
1182 }
1183}
1184
1185fn sounded(part: &Stream, measure: usize, place: usize) -> Option<Vec<Pitch>> {
1188 let element = part.events()[measure].element().as_stream()?.events()[place].element();
1189 match element {
1190 StreamElement::Note(_) | StreamElement::Chord(_) => Some(element.pitches()),
1191 _ => None,
1192 }
1193}
1194
1195fn element_of(part: &mut Stream, measure: usize, place: usize) -> &mut StreamElement {
1197 match part.events_mut()[measure].element_mut() {
1198 StreamElement::Stream(inner) => inner.events_mut()[place].element_mut(),
1199 element => element,
1200 }
1201}
1202
1203fn add_tie(element: &mut StreamElement, tie_type: TieType) {
1206 let existing = match element {
1207 StreamElement::Note(note) => note.tie().map(Tie::tie_type),
1208 StreamElement::Chord(chord) => chord.tie().map(Tie::tie_type),
1209 _ => return,
1210 };
1211 let tie_type = match existing {
1212 Some(known) if known != tie_type => TieType::Continue,
1213 _ => tie_type,
1214 };
1215 let tie = Some(Tie::new(tie_type));
1216 match element {
1217 StreamElement::Note(note) => note.set_tie(tie),
1218 StreamElement::Chord(chord) => chord.set_tie(tie),
1219 _ => {}
1220 }
1221}
1222
1223#[cfg(test)]
1224mod tests {
1225 use super::*;
1226 use crate::tinynotation::from_tiny_notation;
1227
1228 fn score_of(lines: &[&str]) -> Stream {
1229 let mut score = Stream::with_kind(StreamKind::Score);
1230 for line in lines {
1231 score.insert(0.0, from_tiny_notation(line).unwrap());
1232 }
1233 score
1234 }
1235
1236 fn chords_of(reduction: &Stream) -> Vec<Vec<(FloatType, FloatType, Vec<String>)>> {
1239 let part = reduction.parts().into_iter().next().unwrap();
1240 part.measures()
1241 .into_iter()
1242 .map(|measure| {
1243 measure
1244 .events()
1245 .iter()
1246 .filter_map(|event| match event.element() {
1247 StreamElement::Chord(chord) => Some((
1248 event.offset(),
1249 chord.duration().map_or(0.0, Duration::quarter_length),
1250 chord
1251 .pitches()
1252 .iter()
1253 .map(Pitch::name_with_octave)
1254 .collect(),
1255 )),
1256 _ => None,
1257 })
1258 .collect()
1259 })
1260 .collect()
1261 }
1262
1263 fn test_measure() -> Stream {
1266 let mut measure = Stream::with_kind(StreamKind::Measure);
1267 measure.insert(0.0, TimeSignature::new(4, 4).unwrap());
1268 for (offset, notes, length) in [
1269 (0.0, "C4 E4 G4 C5", 2.0),
1270 (2.0, "C4 E4 F4 B4", 1.0),
1271 (3.0, "C4 E4 G4 C5", 1.0),
1272 ] {
1273 let chord = Chord::new(notes)
1274 .unwrap()
1275 .with_duration(Duration::new(length).unwrap());
1276 measure.insert(offset, chord);
1277 }
1278 measure
1279 }
1280
1281 fn chords_only(element: &StreamElement) -> bool {
1282 matches!(element, StreamElement::Note(_) | StreamElement::Chord(_))
1283 }
1284
1285 #[test]
1286 fn a_measure_is_cut_where_its_chords_change() {
1287 let measure = test_measure();
1288 let tree = as_timespans(&measure, Flatten::Flat, Some(chords_only));
1289 let slices = measure_slices(&tree, 0.0, 4.0, None);
1290 let cuts: Vec<(FloatType, FloatType, usize)> = slices
1291 .iter()
1292 .map(|slice| (slice.offset, slice.quarter_length, slice.pitches.len()))
1293 .collect();
1294 assert_eq!(cuts, [(0.0, 2.0, 4), (2.0, 1.0, 4), (3.0, 1.0, 4)]);
1295 let later = measure_slices(&tree, 1.0, 4.0, Some(&tree.all_time_points()));
1297 assert_eq!(later[0].offset, 0.0);
1298 assert_eq!(later[0].at, 1.0);
1299 assert_eq!(later[0].quarter_length, 1.0);
1300 }
1301
1302 #[test]
1303 fn slices_are_weighed_by_length_beat_and_consonance() {
1304 let measure = test_measure();
1305 let tree = as_timespans(&measure, Flatten::Flat, Some(chords_only));
1306 let slices = measure_slices(&tree, 0.0, 4.0, None);
1307 let meter = TimeSignature::new(4, 4).unwrap();
1308 let weights =
1309 slice_weights(&slices, &meter, 0.0, &SliceWeigher::default(), &[], &[]).unwrap();
1310 assert_eq!(weights.len(), 2);
1311 assert_eq!(weights[0].0, [0, 4, 7]);
1312 assert!((weights[0].1 - 3.0).abs() < 1e-9);
1313 assert_eq!(weights[1].0, [0, 4, 5, 11]);
1314 assert!((weights[1].1 - 0.05).abs() < 1e-9);
1315
1316 let by_length = SliceWeigher::new(|quarter_length, _, _, _| quarter_length);
1317 let weights = slice_weights(&slices, &meter, 0.0, &by_length, &[], &[]).unwrap();
1318 assert_eq!(weights[1].1, 1.0);
1319
1320 let allowed = interval_class_set(&Chord::new("C4 E4 F4 B4").unwrap().pitches());
1322 let weights = slice_weights(
1323 &slices,
1324 &meter,
1325 0.0,
1326 &SliceWeigher::default(),
1327 &[allowed],
1328 &[],
1329 )
1330 .unwrap();
1331 assert!((weights[1].1 - 0.5).abs() < 1e-9);
1332 }
1333
1334 #[test]
1335 fn the_heaviest_slices_are_kept_and_lengthened() {
1336 let measure = test_measure();
1337 let tree = as_timespans(&measure, Flatten::Flat, Some(chords_only));
1338 let slices = measure_slices(&tree, 0.0, 4.0, None);
1339 let weights = vec![(vec![0, 4, 7], 3.0), (vec![0, 4, 5, 11], 1.0)];
1340 let kept: Vec<(FloatType, FloatType)> = keep_heaviest_slices(&slices, &weights, 3, 0.25)
1341 .iter()
1342 .map(|slice| (slice.offset, slice.quarter_length))
1343 .collect();
1344 assert_eq!(kept, [(0.0, 2.0), (2.0, 1.0), (3.0, 1.0)]);
1345 let kept = keep_heaviest_slices(&slices, &weights, 3, 0.5);
1348 assert_eq!(kept.len(), 1);
1349 assert_eq!(kept[0].quarter_length, 4.0);
1350 assert!(keep_heaviest_slices(&slices, &[], 3, 0.25).is_empty());
1351 }
1352
1353 #[test]
1354 fn a_forbidden_chord_is_dissonant() {
1355 let triad = Chord::new("C4 E4 G4").unwrap().pitches();
1356 assert!(is_consonant(&triad, &[], &[]));
1357 assert_eq!(interval_class_set(&triad), [3, 4, 5]);
1358 assert!(!is_consonant(&triad, &[], &[vec![5, 3, 4, 4]]));
1360 }
1361
1362 #[test]
1363 fn a_passing_dissonance_is_dropped() {
1364 let score = score_of(&["4/4 e2 f4 e4 g2 r2", "4/4 C1 C2 r2"]);
1365 let reduction = reduce_chords(&score, &ReductionOptions::default()).unwrap();
1366 let names = |names: &[&str]| names.iter().map(ToString::to_string).collect();
1367 assert_eq!(
1368 chords_of(&reduction),
1369 [
1370 vec![(0.0, 4.0, names(&["C3", "E4"]))],
1371 vec![(0.0, 4.0, names(&["C3", "G4"]))],
1372 ]
1373 );
1374 }
1375
1376 #[test]
1377 fn a_bass_gap_is_filled_up_to_the_next_span() {
1378 let score = score_of(&[
1379 "3/4 r8 r16 d'16 e'16 r16 r8 r4",
1380 "3/4 g8 G4 r8 r4",
1381 "3/4 G8 r8 r4 r16 G16 r8",
1382 ]);
1383 let reduction = reduce_chords(&score, &ReductionOptions::default()).unwrap();
1384 let names = |names: &[&str]| names.iter().map(ToString::to_string).collect();
1385 assert_eq!(
1386 chords_of(&reduction),
1387 [vec![(0.0, 3.0, names(&["G3", "G4"]))]]
1388 );
1389 }
1390
1391 #[test]
1392 fn chords_are_chosen_from_slices_of_the_score() {
1393 let score = score_of(&["4/4 c4 d4 e4 f4 g1", "4/4 C2 G,2 C1"]);
1394 let reduction = reduce_chords(&score, &ReductionOptions::default()).unwrap();
1395 let names = |names: &[&str]| names.iter().map(ToString::to_string).collect();
1396 assert_eq!(
1397 chords_of(&reduction),
1398 [
1399 vec![
1400 (0.0, 2.0, names(&["C3", "C4"])),
1401 (2.0, 2.0, names(&["C3", "E4"])),
1402 ],
1403 vec![(0.0, 2.0, names(&["C3", "G4"])), (2.0, 2.0, names(&["G4"]))],
1404 ]
1405 );
1406 let score = score_of(&["4/4 g4 a8 b8 c'2", "4/4 e4 f4 e2", "4/4 C1"]);
1407 let reduction = reduce_chords(&score, &ReductionOptions::default()).unwrap();
1408 assert_eq!(
1409 chords_of(&reduction),
1410 [vec![
1411 (0.0, 2.0, names(&["C3", "E4", "G4"])),
1412 (2.0, 2.0, names(&["C3", "E4", "C5"])),
1413 ]]
1414 );
1415 }
1416
1417 #[test]
1418 fn chords_are_tied_across_barlines_under_a_fitting_clef() {
1419 let score = score_of(&["4/4 e1 e1 e1 f1", "4/4 C1 C1 C1 C1"]);
1420 let reduction = reduce_chords(&score, &ReductionOptions::default()).unwrap();
1421 let part = reduction.parts().into_iter().next().unwrap();
1422 let ties: Vec<Vec<Option<TieType>>> = part
1423 .measures()
1424 .into_iter()
1425 .map(|measure| {
1426 measure
1427 .events()
1428 .iter()
1429 .filter_map(|event| match event.element() {
1430 StreamElement::Chord(chord) => Some(chord.tie().map(Tie::tie_type)),
1431 _ => None,
1432 })
1433 .collect()
1434 })
1435 .collect();
1436 assert_eq!(
1437 ties,
1438 [
1439 vec![Some(TieType::Start)],
1440 vec![Some(TieType::Continue)],
1441 vec![Some(TieType::Stop)],
1442 vec![None],
1443 ]
1444 );
1445 let clefs: Vec<crate::clef::ClefKind> = part
1446 .measures()
1447 .into_iter()
1448 .flat_map(|measure| {
1449 measure
1450 .events()
1451 .iter()
1452 .filter_map(|event| match event.element() {
1453 StreamElement::Clef(clef) => Some(clef.kind()),
1454 _ => None,
1455 })
1456 .collect::<Vec<_>>()
1457 })
1458 .collect();
1459 assert_eq!(clefs, [crate::clef::ClefKind::BassClef]);
1460 }
1461
1462 #[test]
1463 fn grace_notes_are_left_out() {
1464 let mut score = score_of(&["4/4 e1", "4/4 C1"]);
1465 let grace = crate::note::Note::from_name("F#4")
1466 .unwrap()
1467 .with_duration(Duration::default().grace_duration());
1468 if let StreamElement::Stream(part) = score.events_mut()[0].element_mut()
1469 && let Some(measure) = nth_measure(part, 0)
1470 {
1471 measure.insert(0.0, grace);
1472 }
1473 let reduction = reduce_chords(&score, &ReductionOptions::default()).unwrap();
1474 let names = |names: &[&str]| names.iter().map(ToString::to_string).collect();
1475 assert_eq!(
1476 chords_of(&reduction),
1477 [vec![(0.0, 4.0, names(&["C3", "E4"]))]]
1478 );
1479 }
1480
1481 #[test]
1482 fn closed_position_puts_the_lowest_note_in_the_octave_asked_for() {
1483 let score = score_of(&["4/4 e1", "4/4 C1"]);
1484 let mut options = ReductionOptions {
1485 closed_position: true,
1486 ..ReductionOptions::default()
1487 };
1488 let names = |names: &[&str]| names.iter().map(ToString::to_string).collect();
1489 let reduction = reduce_chords(&score, &options).unwrap();
1490 assert_eq!(
1491 chords_of(&reduction),
1492 [vec![(0.0, 4.0, names(&["C4", "E4"]))]]
1493 );
1494 options.force_octave = None;
1495 let reduction = reduce_chords(&score, &options).unwrap();
1496 assert_eq!(
1497 chords_of(&reduction),
1498 [vec![(0.0, 4.0, names(&["C3", "E3"]))]]
1499 );
1500 }
1501
1502 #[test]
1503 fn the_first_parts_measures_are_reduced() {
1504 let score = score_of(&["4/4 e1 f1", "2/4 C2 D2 C2 D2"]);
1505 let reduction = reduce_chords(&score, &ReductionOptions::default()).unwrap();
1506 let part = reduction.parts().into_iter().next().unwrap();
1507 let offsets: Vec<FloatType> = part
1508 .events()
1509 .iter()
1510 .filter(|event| {
1511 event
1512 .element()
1513 .as_stream()
1514 .is_some_and(|inner| inner.kind() == StreamKind::Measure)
1515 })
1516 .map(crate::stream::StreamEvent::offset)
1517 .collect();
1518 assert_eq!(offsets, [0.0, 4.0]);
1519 }
1520
1521 #[test]
1522 fn pitch_classes_in_any_order_weigh_as_one() {
1523 let half = || Duration::half();
1524 let first = Chord::new("B3 D#4").unwrap().with_duration(half());
1525 let second = Chord::new("D#3 B3").unwrap().with_duration(half());
1526 let chords = [
1527 WeighedChord {
1528 offset: 0.0,
1529 chord: &first,
1530 beat_strength: 1.0,
1531 },
1532 WeighedChord {
1533 offset: 2.0,
1534 chord: &second,
1535 beat_strength: 0.5,
1536 },
1537 ];
1538 assert_eq!(
1539 measure_chord_weights(&chords, ChordWeight::Length),
1540 [(vec![3, 11], 4.0)]
1541 );
1542 }
1543}