1use crate::{
38 defaults::{FloatType, IntegerType},
39 duration::Duration,
40 error::{Error, Result},
41 expressions::TextExpression,
42 instrument::Instrument,
43 makenotation::op_frac,
44 notation::{Lyric, StemDirection},
45 note::Note,
46 pitch::Pitch,
47 rest::Rest,
48 stream::{Stream, StreamElement, StreamEvent, StreamKind, TemplateOptions},
49};
50
51#[derive(Clone, Debug, Default, PartialEq, Eq)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
58pub struct ReductiveParameters {
59 pub pitch: Option<String>,
61 pub octave: Option<String>,
63 pub notehead_fill: Option<String>,
66 pub stem_direction: Option<String>,
68 pub group: Option<String>,
70 pub voice: Option<String>,
72 pub text_above: Option<String>,
74 pub text_below: Option<String>,
76}
77
78impl ReductiveParameters {
79 pub fn parse(spec: &str) -> Result<Option<Self>> {
89 let spec = spec.trim();
90 if !spec.starts_with("::") {
91 return Ok(None);
92 }
93 let mut parameters = Self {
94 stem_direction: Some("noStem".to_string()),
95 ..Self::default()
96 };
97 for argument in spec.split('/').skip(1) {
98 if !argument.contains(':') {
99 continue;
100 }
101 let [key, value] = argument.split(':').collect::<Vec<_>>()[..] else {
102 return Err(Error::Analysis(format!(
103 "too many values to unpack in {argument:?}"
104 )));
105 };
106 let key = key.trim();
107 let value = Some(value.trim().to_string());
108 let slot = match key.to_lowercase().as_str() {
109 "p" => &mut parameters.pitch,
110 "o" => &mut parameters.octave,
111 "nf" => &mut parameters.notehead_fill,
112 "sd" => &mut parameters.stem_direction,
113 "g" => &mut parameters.group,
114 "v" => &mut parameters.voice,
115 "ta" => &mut parameters.text_above,
116 "tb" => &mut parameters.text_below,
117 _ => continue,
118 };
119 if key.chars().any(char::is_uppercase) {
120 return Err(Error::Analysis(format!("KeyError: {key:?}")));
121 }
122 *slot = value;
123 }
124 Ok(Some(parameters))
125 }
126}
127
128#[derive(Clone, Debug)]
131pub struct ReductiveNote {
132 parameters: ReductiveParameters,
133 parsed: bool,
134 note: StreamElement,
135 measure_index: usize,
136 measure_offset: FloatType,
137}
138
139impl ReductiveNote {
140 pub fn new(
148 spec: &str,
149 note: StreamElement,
150 measure_index: usize,
151 measure_offset: FloatType,
152 ) -> Result<Self> {
153 let parsed = ReductiveParameters::parse(spec)?;
154 Ok(Self {
155 parsed: parsed.is_some(),
156 parameters: parsed.unwrap_or_else(|| ReductiveParameters {
157 stem_direction: Some("noStem".to_string()),
158 ..ReductiveParameters::default()
159 }),
160 note,
161 measure_index,
162 measure_offset,
163 })
164 }
165
166 pub fn is_parsed(&self) -> bool {
168 self.parsed
169 }
170
171 pub fn parameters(&self) -> &ReductiveParameters {
173 &self.parameters
174 }
175
176 pub fn note(&self) -> &StreamElement {
178 &self.note
179 }
180
181 pub fn measure_index(&self) -> usize {
183 self.measure_index
184 }
185
186 pub fn measure_offset(&self) -> FloatType {
188 self.measure_offset
189 }
190
191 pub fn note_and_text_expression(&self) -> Result<(Note, Option<TextExpression>)> {
207 let components: Vec<Note> = match &self.note {
208 StreamElement::Note(_) => Vec::new(),
209 StreamElement::Chord(chord) => chord
210 .notes()
211 .iter()
212 .map(|note| {
213 let mut note = note.clone();
214 if let Some(duration) = chord.duration() {
215 note.set_duration(duration.clone());
216 }
217 note
218 })
219 .collect(),
220 StreamElement::ChordSymbol(symbol) => symbol
221 .pitches()?
222 .into_iter()
223 .map(Note::from_pitch)
224 .collect(),
225 other => {
226 return Err(Error::Analysis(format!(
227 "{other:?} has no attribute 'pitch'"
228 )));
229 }
230 };
231 let mut note = match (&self.note, &self.parameters.pitch) {
232 (StreamElement::Note(note), _) => note.clone(),
233 (_, Some(name)) => {
234 let wanted = Pitch::from_name(name.as_str())?.name().to_lowercase();
235 components
236 .into_iter()
237 .rfind(|note| note.pitch().name().to_lowercase() == wanted)
238 .ok_or_else(|| {
239 Error::Analysis(format!(
240 "Could not find pitch, {:?} in the note",
241 self.parameters.pitch
242 ))
243 })?
244 }
245 (_, None) => components
246 .into_iter()
247 .reduce(|highest, note| {
248 if note.pitch().ps() > highest.pitch().ps() {
249 note
250 } else {
251 highest
252 }
253 })
254 .ok_or_else(|| Error::Analysis("max() iterable argument is empty".to_string()))?,
255 };
256 note.lyrics_mut().clear();
257 note.set_tie(None);
258 note.expressions_mut().clear();
259 note.articulations_mut().clear();
260 let mut duration = note.duration().cloned().unwrap_or_default();
261 duration.set_dots(0)?;
262 note.set_duration(duration);
263 let mut pitch = note.pitch().clone();
264 if let Some(accidental) = pitch.written_accidental_mut() {
265 accidental.set_display_status(Some(true));
266 }
267 if let Some(octave) = self.parameters.octave.as_deref().filter(|o| !o.is_empty()) {
268 let octave: IntegerType = octave.parse().map_err(|_| {
269 Error::Analysis(format!(
270 "invalid literal for int() with base 10: {octave:?}"
271 ))
272 })?;
273 pitch.set_octave(Some(octave));
274 }
275 note.set_pitch(pitch);
276 if let Some(stem) = &self.parameters.stem_direction {
277 let direction = StemDirection::from_name(stem)
278 .map_err(|_| Error::Analysis(format!("not a valid stem direction name: {stem}")))?;
279 note.set_stem_direction(direction);
280 }
281 if let Some(fill) = self
282 .parameters
283 .notehead_fill
284 .as_deref()
285 .filter(|f| !f.is_empty())
286 {
287 let fill = match fill {
288 "none" | "default" => None,
289 "yes" | "filled" => Some(true),
290 "no" | "notfilled" => Some(false),
291 other => {
292 return Err(Error::Analysis(format!(
293 "not a valid notehead fill value: {other:?}"
294 )));
295 }
296 };
297 note.set_notehead_fill(fill);
298 }
299 if let Some(text) = &self.parameters.text_below {
300 note.add_lyric(text, None, false)?;
301 }
302 let text = self.parameters.text_above.clone().map(TextExpression::new);
303 Ok((note, text))
304 }
305}
306
307#[derive(Clone, Debug, Default)]
315pub struct ScoreReduction {
316 score: Option<Stream>,
317 chord_reduction: Option<Stream>,
318}
319
320impl ScoreReduction {
321 pub fn new() -> Self {
323 Self::default()
324 }
325
326 pub fn score(&self) -> Option<&Stream> {
328 self.score.as_ref()
329 }
330
331 pub fn set_score(&mut self, score: Stream) {
334 self.score = Some(as_score(score));
335 }
336
337 pub fn chord_reduction(&self) -> Option<&Stream> {
340 self.chord_reduction.as_ref()
341 }
342
343 pub fn set_chord_reduction(&mut self, chords: Stream) {
346 self.chord_reduction = Some(as_score(chords));
347 }
348
349 pub fn reduce(&self) -> Result<Stream> {
369 if self.score.is_none() && self.chord_reduction.is_none() {
370 return Err(Error::Analysis("no score defined to reduce".to_string()));
371 }
372 let mut chords = self.chord_reduction.clone();
373 let mut score = self.score.clone();
374 let mut notes: Vec<(usize, String, ReductiveNote)> = Vec::new();
375 let mut serial = 0;
376 for source in [&mut chords, &mut score].into_iter().flatten() {
377 extract(source, &mut notes, &mut serial)?;
378 }
379 let mut groups: Vec<Option<String>> = Vec::new();
380 let mut voices: Vec<Option<String>> = Vec::new();
381 for (_, _, note) in ¬es {
382 for (names, name) in [
383 (&mut groups, ¬e.parameters.group),
384 (&mut voices, ¬e.parameters.voice),
385 ] {
386 if !names.contains(name) {
387 names.push(name.clone());
388 }
389 if names.len() == 2 && names.contains(&None) {
390 names.retain(Option::is_some);
391 }
392 }
393 }
394 let one_group = groups.len() == 1;
395 let one_voice = voices.len() == 1;
396
397 let source = match (&score, &chords) {
398 (Some(score), _) if !score.events().is_empty() => score,
399 (_, Some(chords)) => chords,
400 _ => return Err(Error::Analysis("no part to make a template of".to_string())),
401 };
402 let first = source
403 .parts()
404 .into_iter()
405 .next()
406 .ok_or_else(|| Error::Analysis("no part to make a template of".to_string()))?;
407 let template = first.template(&TemplateOptions {
408 retain_voices: false,
409 ..TemplateOptions::default()
410 });
411
412 let mut reduced = Stream::with_kind(StreamKind::Score);
413 let mut parts: Vec<Stream> = Vec::new();
414 for group in &groups {
415 let mut part = template.clone();
416 part.set_id(group.clone());
417 let mut instrument = Instrument::new();
418 instrument.set_part_name(group.clone());
419 part.insert_sorted(vec![StreamEvent::new(0.0, instrument)]);
420 for (_, _, note) in ¬es {
421 if one_group || note.parameters.group == *group {
422 place(&mut part, note, &voices, one_voice)?;
423 }
424 }
425 for event in part.events_mut() {
426 if let StreamElement::Stream(measure) = event.element_mut()
427 && measure.kind() == StreamKind::Measure
428 {
429 finish(measure)?;
430 }
431 }
432 parts.push(part);
433 }
434 for source in [chords, score].into_iter().flatten() {
435 parts.extend(source.parts().into_iter().cloned());
436 }
437 reduced.insert_sorted(
438 parts
439 .into_iter()
440 .map(|part| StreamEvent::new(0.0, part))
441 .collect(),
442 );
443 Ok(reduced)
444 }
445}
446
447fn as_score(stream: Stream) -> Stream {
450 if stream.has_part_like_streams() {
451 return stream;
452 }
453 let mut score = Stream::with_kind(StreamKind::Score);
454 score.insert(0.0, stream);
455 score
456}
457
458fn is_note(element: &StreamElement) -> bool {
460 matches!(
461 element,
462 StreamElement::Note(_)
463 | StreamElement::Chord(_)
464 | StreamElement::ChordSymbol(_)
465 | StreamElement::Unpitched(_)
466 | StreamElement::PercussionChord(_)
467 )
468}
469
470fn lyrics_mut(element: &mut StreamElement) -> Option<&mut Vec<Lyric>> {
472 match element {
473 StreamElement::Note(note) => Some(note.lyrics_mut()),
474 StreamElement::Chord(chord) => chord.notes_mut().first_mut().map(Note::lyrics_mut),
475 StreamElement::ChordSymbol(symbol) => Some(symbol.lyrics_mut()),
476 StreamElement::Unpitched(unpitched) => Some(unpitched.written_mut().lyrics_mut()),
477 StreamElement::PercussionChord(chord) => chord
478 .written_mut()
479 .notes_mut()
480 .first_mut()
481 .map(Note::lyrics_mut),
482 _ => None,
483 }
484}
485
486fn extract(
491 score: &mut Stream,
492 notes: &mut Vec<(usize, String, ReductiveNote)>,
493 serial: &mut usize,
494) -> Result<()> {
495 for event in score.events_mut() {
496 let StreamElement::Stream(part) = event.element_mut() else {
497 continue;
498 };
499 if !matches!(part.kind(), StreamKind::Part | StreamKind::PartStaff) {
500 continue;
501 }
502 let mut index = 0;
503 for event in part.events_mut() {
504 let StreamElement::Stream(measure) = event.element_mut() else {
505 continue;
506 };
507 if measure.kind() != StreamKind::Measure {
508 continue;
509 }
510 extract_measure(measure, index, 0, None, notes, serial)?;
511 index += 1;
512 }
513 }
514 Ok(())
515}
516
517fn extract_measure(
521 stream: &mut Stream,
522 index: usize,
523 depth: usize,
524 in_voice: Option<bool>,
525 notes: &mut Vec<(usize, String, ReductiveNote)>,
526 serial: &mut usize,
527) -> Result<()> {
528 for event in stream.events_mut() {
529 let offset = event.offset();
530 let element = event.element_mut();
531 if let StreamElement::Stream(inner) = element {
532 let voice = depth == 0 && inner.kind() == StreamKind::Voice;
533 extract_measure(inner, index, depth + 1, Some(voice), notes, serial)?;
534 continue;
535 }
536 if !is_note(element) {
537 continue;
538 }
539 let note_serial = *serial;
540 *serial += 1;
541 let Some(lyrics) = lyrics_mut(element).map(|lyrics| lyrics.clone()) else {
542 continue;
543 };
544 if lyrics.is_empty() {
545 continue;
546 }
547 let measure_offset = match (depth, in_voice) {
548 (0, _) | (1, Some(true)) => offset,
549 _ => 0.0,
550 };
551 let mut marked = Vec::new();
552 for (position, lyric) in lyrics.iter().enumerate() {
553 let text = lyric
554 .explicit_text()
555 .ok_or_else(|| Error::Analysis("a lyric with no text".to_string()))?;
556 let note = ReductiveNote::new(&text, element.clone(), index, measure_offset)?;
557 if note.is_parsed() {
558 match notes
559 .iter_mut()
560 .find(|(serial, known, _)| *serial == note_serial && *known == text)
561 {
562 Some(entry) => entry.2 = note,
563 None => notes.push((note_serial, text, note)),
564 }
565 marked.push(position);
566 }
567 }
568 if let Some(lyrics) = lyrics_mut(element) {
569 for position in marked {
570 lyrics[position] = Lyric::unsung();
571 }
572 }
573 }
574 Ok(())
575}
576
577fn place(
580 part: &mut Stream,
581 note: &ReductiveNote,
582 voices: &[Option<String>],
583 one_voice: bool,
584) -> Result<()> {
585 let measure = part
586 .events_mut()
587 .iter_mut()
588 .filter_map(|event| match event.element_mut() {
589 StreamElement::Stream(measure) if measure.kind() == StreamKind::Measure => {
590 Some(measure)
591 }
592 _ => None,
593 })
594 .nth(note.measure_index)
595 .ok_or_else(|| Error::Analysis("list index out of range".to_string()))?;
596 if measure.voices().is_empty() {
597 measure.retain_leaves(&mut |_, element| !matches!(element, StreamElement::Rest(_)));
598 for id in voices {
599 let mut voice = Stream::with_kind(StreamKind::Voice);
600 voice.set_id(id.clone());
601 measure.insert_sorted(vec![StreamEvent::new(0.0, voice)]);
602 }
603 }
604 let wanted = note
605 .parameters
606 .voice
607 .as_deref()
608 .filter(|_| !one_voice)
609 .map(str::to_lowercase);
610 let (written, text) = note.note_and_text_expression()?;
611 let mut voices_here =
612 measure
613 .events_mut()
614 .iter_mut()
615 .filter_map(|event| match event.element_mut() {
616 StreamElement::Stream(voice) if voice.kind() == StreamKind::Voice => Some(voice),
617 _ => None,
618 });
619 let voice = match &wanted {
620 Some(wanted) => {
621 let mut chosen = None;
622 let mut first = None;
623 for voice in voices_here {
624 let named = voice.id().is_some_and(|id| id.to_lowercase() == *wanted);
625 if named {
626 chosen = Some(voice);
627 break;
628 }
629 if first.is_none() {
630 first = Some(voice);
631 }
632 }
633 chosen.or(first)
634 }
635 None => voices_here.next(),
636 };
637 if let Some(voice) = voice {
638 voice.insert_into_note_or_chord(
639 note.measure_offset,
640 StreamElement::Note(written),
641 false,
642 )?;
643 }
644 if let Some(text) = text {
645 measure.insert_sorted(vec![StreamEvent::new(note.measure_offset, text)]);
646 }
647 Ok(())
648}
649
650fn finish(measure: &mut Stream) -> Result<()> {
654 for event in measure.events_mut() {
655 if let StreamElement::Stream(voice) = event.element_mut()
656 && voice.kind() == StreamKind::Voice
657 && voice.events().iter().any(|event| is_note(event.element()))
658 {
659 fill_gaps(voice)?;
660 }
661 }
662 measure.flatten_unnecessary_voices(false);
663 measure.for_each_mut(&mut |_, element| {
664 if let StreamElement::Rest(rest) = element {
665 rest.set_hidden(true);
666 }
667 });
668 Ok(())
669}
670
671fn fill_gaps(voice: &mut Stream) -> Result<()> {
674 let mut gaps = Vec::new();
675 let mut reached: FloatType = 0.0;
676 for event in voice.events() {
677 if event.offset() > reached {
678 let length = op_frac(event.offset() - reached);
679 gaps.push(StreamEvent::new(reached, Rest::new(Duration::new(length)?)));
680 }
681 reached = op_frac(reached.max(event.offset() + event.element().quarter_length()));
682 }
683 voice.insert_sorted(gaps);
684 Ok(())
685}
686
687#[derive(Clone, Debug, PartialEq, Eq)]
689#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
690pub struct PartGroup {
691 pub name: String,
693 pub color: String,
695 pub matches: Option<Vec<String>>,
700}
701
702#[derive(Clone, Debug, PartialEq, Eq)]
706#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
707pub struct PartReduction {
708 pub part_groups: Option<Vec<PartGroup>>,
710 pub fill_by_measure: bool,
714 pub segment_by_target: bool,
717 pub normalize: bool,
720 pub normalize_by_part: bool,
723}
724
725impl Default for PartReduction {
726 fn default() -> Self {
727 Self {
728 part_groups: None,
729 fill_by_measure: true,
730 segment_by_target: true,
731 normalize: true,
732 normalize_by_part: false,
733 }
734 }
735}
736
737#[derive(Clone, Debug, PartialEq)]
740#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
741pub struct WeightedSpan {
742 pub start: FloatType,
744 pub span: FloatType,
746 pub weight: FloatType,
748 pub color: String,
750}
751
752#[derive(Clone, Debug, PartialEq)]
754#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
755pub struct PartActivity {
756 pub id: Option<String>,
758 pub spans: Vec<WeightedSpan>,
760}
761
762#[derive(Clone, Debug)]
764struct Stretch {
765 start: FloatType,
766 span: FloatType,
767 weight: Option<FloatType>,
768 color: String,
769}
770
771struct Flat {
773 offset: FloatType,
774 element: StreamElement,
775}
776
777const MIN_WEIGHT: FloatType = 0.01;
780
781impl PartReduction {
782 pub fn weighted_spans(&self, score: &Stream) -> Result<Vec<PartActivity>> {
798 if score.kind() != StreamKind::Score {
799 return Err(Error::Analysis("provided Stream must be Score".to_string()));
800 }
801 let parts = score.parts();
802 let fill_by_measure =
803 self.fill_by_measure && parts.iter().all(|part| !part.measures().is_empty());
804 let mut bundles: Vec<(Option<String>, String, Vec<usize>)> = Vec::new();
806 match &self.part_groups {
807 Some(groups) => {
808 for group in groups {
809 let names = group
810 .matches
811 .clone()
812 .unwrap_or_else(|| vec![group.name.clone()]);
813 let held: Vec<usize> = parts
814 .iter()
815 .enumerate()
816 .filter(|(_, part)| {
817 let id = part.id().unwrap_or_default();
818 names.iter().any(|name| holds_word(id, name))
819 })
820 .map(|(index, _)| index)
821 .collect();
822 if !held.is_empty() {
823 bundles.push((Some(group.name.clone()), group.color.clone(), held));
824 }
825 }
826 }
827 None => {
828 for (index, part) in parts.iter().enumerate() {
829 bundles.push((
830 part.id().map(str::to_string),
831 "#666666".to_string(),
832 vec![index],
833 ));
834 }
835 }
836 }
837
838 let mut groups: Vec<Vec<Stretch>> = Vec::new();
839 for (_, color, held) in &bundles {
840 let flat = flatten_parts(&parts, held);
841 let mut stretches = if fill_by_measure {
842 measure_stretches(&parts, held)?
843 } else {
844 run_stretches(&flat)?
845 };
846 for stretch in &mut stretches {
847 stretch.color = color.clone();
848 }
849 if self.segment_by_target {
850 stretches = split_by_dynamics(&stretches, &flat);
851 } else {
852 for stretch in &mut stretches {
853 let end = op_frac(stretch.start + stretch.span);
854 let loudness: Vec<FloatType> = flat
855 .iter()
856 .filter(|item| {
857 in_range(
858 item.offset,
859 item.element.quarter_length(),
860 stretch.start,
861 end,
862 false,
863 )
864 })
865 .filter_map(|item| match &item.element {
866 StreamElement::Dynamic(dynamic) => Some(dynamic.volume_scalar()),
867 _ => None,
868 })
869 .collect();
870 stretch.weight = (!loudness.is_empty())
871 .then(|| loudness.iter().sum::<FloatType>() / loudness.len() as FloatType);
872 }
873 }
874 extend_weights(&mut stretches);
875 groups.push(stretches);
876 }
877 if self.normalize {
878 let maxima: Vec<FloatType> = groups
879 .iter()
880 .map(|stretches| {
881 stretches
882 .iter()
883 .filter_map(|stretch| stretch.weight)
884 .fold(0.0, |max, weight| if weight > max { weight } else { max })
885 })
886 .collect();
887 let overall = maxima.iter().copied().fold(0.0, FloatType::max);
888 for (stretches, maximum) in groups.iter_mut().zip(maxima) {
889 let best = if self.normalize_by_part {
890 maximum
891 } else {
892 overall
893 };
894 for stretch in stretches {
895 stretch.weight = Some(if best != 0.0 {
896 stretch.weight.unwrap_or_default() / best
897 } else {
898 1.0
899 });
900 }
901 }
902 }
903 Ok(bundles
904 .into_iter()
905 .zip(groups)
906 .map(|((id, _, _), stretches)| PartActivity {
907 id,
908 spans: stretches
909 .into_iter()
910 .map(|stretch| WeightedSpan {
911 start: stretch.start,
912 span: stretch.span,
913 weight: stretch.weight.unwrap_or_default(),
914 color: stretch.color,
915 })
916 .collect(),
917 })
918 .collect())
919 }
920}
921
922fn holds_word(id: &str, word: &str) -> bool {
926 let id: Vec<char> = id.to_lowercase().chars().collect();
927 let word: Vec<char> = word.to_lowercase().chars().collect();
928 let is_word = |c: &char| c.is_alphanumeric() || *c == '_';
929 (0..=id.len().saturating_sub(word.len()))
930 .filter(|&at| id.len() >= word.len() && id[at..at + word.len()] == word[..])
931 .any(|at| {
932 !(at > 0 && is_word(&id[at - 1])) && !id.get(at + word.len()).is_some_and(is_word)
933 })
934}
935
936fn flatten_parts(parts: &[&Stream], held: &[usize]) -> Vec<Flat> {
939 let mut flat: Vec<Flat> = Vec::new();
940 for &part in held {
941 for (offset, element) in parts[part].leaves() {
942 flat.push(Flat {
943 offset,
944 element: element.clone(),
945 });
946 }
947 }
948 let grace = |element: &StreamElement| element.duration().is_some_and(Duration::is_grace);
949 flat.sort_by(|left, right| {
950 left.offset
951 .partial_cmp(&right.offset)
952 .unwrap_or(std::cmp::Ordering::Equal)
953 .then_with(|| {
954 left.element
955 .class_sort_order()
956 .cmp(&right.element.class_sort_order())
957 })
958 .then_with(|| grace(&right.element).cmp(&grace(&left.element)))
959 });
960 flat
961}
962
963fn meter_in(measure: &Stream) -> Option<crate::meter::TimeSignature> {
965 measure
966 .events()
967 .iter()
968 .find_map(|event| match event.element() {
969 StreamElement::TimeSignature(meter) if event.offset() == 0.0 => Some(meter.clone()),
970 _ => None,
971 })
972}
973
974fn measure_stretches(parts: &[&Stream], held: &[usize]) -> Result<Vec<Stretch>> {
978 let measured: Vec<Vec<(FloatType, &Stream)>> = held
979 .iter()
980 .map(|&part| {
981 parts[part]
982 .events()
983 .iter()
984 .filter_map(|event| match event.element() {
985 StreamElement::Stream(measure) if measure.kind() == StreamKind::Measure => {
986 Some((op_frac(event.offset()), &**measure))
987 }
988 _ => None,
989 })
990 .collect()
991 })
992 .collect();
993 let first = parts[held[0]];
994 let loose: Vec<(FloatType, crate::meter::TimeSignature)> = first
995 .events()
996 .iter()
997 .filter_map(|event| match event.element() {
998 StreamElement::TimeSignature(meter) => Some((event.offset(), meter.clone())),
999 _ => None,
1000 })
1001 .collect();
1002 let mut meter: Option<crate::meter::TimeSignature> = None;
1003 let mut stretches = Vec::new();
1004 for (index, (offset, measure)) in measured[0].iter().enumerate() {
1005 if let Some(own) = meter_in(measure) {
1006 meter = Some(own);
1007 }
1008 let mut active = false;
1009 for part in &measured {
1010 let (_, other) = part
1011 .get(index)
1012 .ok_or_else(|| Error::Analysis("index out of range".to_string()))?;
1013 if other.events().iter().any(|event| is_note(event.element())) {
1014 active = true;
1015 break;
1016 }
1017 }
1018 if !active {
1019 continue;
1020 }
1021 let bar = match &meter {
1025 Some(meter) => meter.bar_quarter_length(),
1026 None => match loose.iter().rev().find(|(at, _)| at < offset) {
1027 Some((_, meter)) => meter.bar_quarter_length(),
1028 None => crate::meter::best_time_signature(measure)
1029 .map_or_else(|_| measure.end_offset(), |meter| meter.bar_quarter_length()),
1030 },
1031 };
1032 stretches.push(Stretch {
1033 start: op_frac(*offset),
1034 span: op_frac(op_frac(*offset + bar) - *offset),
1035 weight: None,
1036 color: String::new(),
1037 });
1038 }
1039 Ok(stretches)
1040}
1041
1042fn run_stretches(flat: &[Flat]) -> Result<Vec<Stretch>> {
1046 let stream = Stream::from_events(
1047 flat.iter()
1048 .map(|item| StreamEvent::new(item.offset, item.element.clone())),
1049 );
1050 let found = stream.find_consecutive_notes(&crate::stream::ConsecutiveOptions::default())?;
1051 let leaves = stream.leaves();
1052 let place = |index: usize| {
1053 let (offset, element) = leaves[index];
1054 (offset, element.quarter_length())
1055 };
1056 let mut stretches = Vec::new();
1057 let mut start: Option<FloatType> = None;
1058 let mut last: Option<usize> = None;
1059 let count = found.len();
1060 for (position, entry) in found.into_iter().enumerate() {
1061 match entry {
1062 None => {
1063 let Some(begun) = start else {
1064 continue;
1065 };
1066 let (offset, length) = place(last.expect("a run has a last note"));
1067 let end = op_frac(offset + length);
1068 stretches.push(Stretch {
1069 start: begun,
1070 span: op_frac(end - begun),
1071 weight: None,
1072 color: String::new(),
1073 });
1074 start = None;
1075 }
1076 Some(index) if position + 1 >= count => {
1077 let (offset, length) = place(index);
1078 let begun = start.unwrap_or(offset);
1079 let end = op_frac(offset + length);
1080 stretches.push(Stretch {
1081 start: begun,
1082 span: op_frac(end - begun),
1083 weight: None,
1084 color: String::new(),
1085 });
1086 start = None;
1087 }
1088 Some(index) => {
1089 if start.is_none() {
1090 start = Some(place(index).0);
1091 }
1092 last = Some(index);
1093 }
1094 }
1095 }
1096 Ok(stretches)
1097}
1098
1099fn in_range(
1103 offset: FloatType,
1104 length: FloatType,
1105 start: FloatType,
1106 end: FloatType,
1107 include_end: bool,
1108) -> bool {
1109 if offset > end {
1110 return false;
1111 }
1112 if op_frac(offset + length) < start {
1113 return false;
1114 }
1115 if end <= start && length == 0.0 {
1116 return true;
1117 }
1118 if offset < start {
1119 return false;
1120 }
1121 include_end || offset != end
1122}
1123
1124fn split_by_dynamics(stretches: &[Stretch], flat: &[Flat]) -> Vec<Stretch> {
1129 let mut cut = Vec::new();
1130 for stretch in stretches {
1131 let end = op_frac(stretch.start + stretch.span);
1132 let dynamics: Vec<(FloatType, FloatType, FloatType)> = flat
1133 .iter()
1134 .filter_map(|item| match &item.element {
1135 StreamElement::Dynamic(dynamic)
1136 if in_range(
1137 item.offset,
1138 item.element.quarter_length(),
1139 stretch.start,
1140 end,
1141 false,
1142 ) =>
1143 {
1144 Some((
1145 item.offset,
1146 item.element.quarter_length(),
1147 dynamic.volume_scalar(),
1148 ))
1149 }
1150 _ => None,
1151 })
1152 .collect();
1153 let mut first = stretch.clone();
1154 if let Some(prior) = flat.iter().rev().find_map(|item| match &item.element {
1155 StreamElement::Dynamic(dynamic) if item.offset <= stretch.start => Some(dynamic),
1156 _ => None,
1157 }) {
1158 first.weight = Some(prior.volume_scalar());
1159 }
1160 if dynamics.is_empty() {
1161 cut.push(first);
1162 continue;
1163 }
1164 let total = dynamics
1167 .iter()
1168 .map(|(offset, length, _)| op_frac(offset + length))
1169 .fold(0.0, FloatType::max);
1170 for (index, &(offset, _, loudness)) in dynamics.iter().enumerate() {
1171 let mut span = match dynamics.get(index + 1) {
1172 Some((next, _, _)) => op_frac(next - offset),
1173 None => op_frac(total - offset),
1174 };
1175 if op_frac(offset + span) > end {
1176 span = op_frac(end - offset);
1177 }
1178 if span <= 0.001 {
1179 span = op_frac(end - offset);
1180 }
1181 let piece = Stretch {
1182 start: offset,
1183 span,
1184 weight: Some(loudness),
1185 color: stretch.color.clone(),
1186 };
1187 if index == 0 && stretch.start == offset {
1188 cut.push(Stretch {
1189 start: stretch.start,
1190 ..piece
1191 });
1192 } else if index == 0 {
1193 cut.push(Stretch {
1194 span: op_frac(offset - stretch.start),
1195 ..first.clone()
1196 });
1197 cut.push(piece);
1198 } else {
1199 cut.push(piece);
1200 }
1201 }
1202 }
1203 cut
1204}
1205
1206fn extend_weights(stretches: &mut [Stretch]) {
1209 let mut last: Option<FloatType> = None;
1210 for (index, stretch) in stretches.iter_mut().enumerate() {
1211 if index == 0 {
1212 match stretch.weight {
1213 None => stretch.weight = Some(MIN_WEIGHT),
1214 Some(weight) => last = Some(weight),
1215 }
1216 continue;
1217 }
1218 match stretch.weight {
1219 Some(weight) if weight != 0.0 => last = Some(weight),
1220 weight => {
1221 if let Some(previous) = last.filter(|previous| *previous != 0.0) {
1222 stretch.weight = Some(previous);
1223 } else if weight.is_none() && last.is_none() {
1224 stretch.weight = Some(MIN_WEIGHT);
1225 }
1226 }
1227 }
1228 }
1229}
1230
1231#[cfg(test)]
1232mod tests {
1233 use super::*;
1234
1235 #[test]
1236 fn a_lyric_says_how_its_note_is_reduced() -> Result<()> {
1237 let parameters =
1238 ReductiveParameters::parse("::/o:4/nf:no/g:Ursatz/ta:3 3 200")?.expect("a mark");
1239 assert_eq!(parameters.octave.as_deref(), Some("4"));
1240 assert_eq!(parameters.notehead_fill.as_deref(), Some("no"));
1241 assert_eq!(parameters.group.as_deref(), Some("Ursatz"));
1242 assert_eq!(parameters.text_above.as_deref(), Some("3 3 200"));
1243 assert_eq!(parameters.stem_direction.as_deref(), Some("noStem"));
1244 assert_eq!(ReductiveParameters::parse("test")?, None);
1245 assert!(ReductiveParameters::parse("::/P:c").is_err());
1246 assert!(ReductiveParameters::parse("::/ta:a:b").is_err());
1247 Ok(())
1248 }
1249
1250 #[test]
1251 fn a_chord_gives_the_note_named() -> Result<()> {
1252 let chord = crate::chord::Chord::new("C4 E4 G4")?;
1254 let note = ReductiveNote::new(
1255 "::/p:e/o:6/sd:up/nf:no/ta:hi/tb:lo",
1256 StreamElement::Chord(chord.clone()),
1257 0,
1258 0.0,
1259 )?;
1260 let (written, text) = note.note_and_text_expression()?;
1261 assert_eq!(written.pitch().name_with_octave(), "E6");
1262 assert_eq!(written.stem_direction(), StemDirection::Up);
1263 assert_eq!(written.notehead_fill(), Some(false));
1264 assert_eq!(
1265 text.map(|text| text.content().to_string()),
1266 Some("hi".to_string())
1267 );
1268 let note = ReductiveNote::new("::", StreamElement::Chord(chord), 0, 0.0)?;
1270 assert_eq!(
1271 note.note_and_text_expression()?
1272 .0
1273 .pitch()
1274 .name_with_octave(),
1275 "G4"
1276 );
1277 Ok(())
1278 }
1279
1280 #[test]
1281 fn parts_are_weighed_by_their_dynamics() -> Result<()> {
1282 let mut score = Stream::with_kind(StreamKind::Score);
1285 let placed = [
1286 [(0.0, "p"), (2.0, "fff"), (6.0, "ppp")],
1287 [(0.0, "p"), (1.0, "fff"), (2.0, "ppp")],
1288 ];
1289 for (id, dynamics) in placed.iter().enumerate() {
1290 let mut part = Stream::with_kind(StreamKind::Part);
1291 part.set_id(Some(id.to_string()));
1292 for length in [1.0, 2.0, 1.0, 4.0] {
1293 part.push(Note::from_name("C4")?.with_duration(Duration::new(length)?));
1294 }
1295 for (offset, mark) in dynamics {
1296 part.insert(*offset, crate::dynamics::Dynamic::new(*mark));
1297 }
1298 score.insert(0.0, part);
1299 }
1300 let reduction = PartReduction {
1301 normalize: false,
1302 ..PartReduction::default()
1303 };
1304 let spans: Vec<Vec<(FloatType, FloatType, FloatType)>> = reduction
1305 .weighted_spans(&score)?
1306 .into_iter()
1307 .map(|activity| {
1308 activity
1309 .spans
1310 .into_iter()
1311 .map(|span| (span.start, span.span, (span.weight * 1e9).round() / 1e9))
1312 .collect()
1313 })
1314 .collect();
1315 assert_eq!(
1316 spans,
1317 [
1318 vec![(0.0, 2.0, 0.35), (2.0, 4.0, 0.9), (6.0, 2.0, 0.15)],
1319 vec![(0.0, 1.0, 0.35), (1.0, 1.0, 0.9), (2.0, 6.0, 0.15)],
1320 ]
1321 );
1322 Ok(())
1323 }
1324
1325 #[test]
1326 fn a_chord_reduction_alone_is_reduced_above_itself() -> Result<()> {
1327 let mut chord = crate::chord::Chord::new("G3 B3 D4")?;
1328 chord.add_lyric("::/p:g/tb:V", None, false)?;
1329 let mut measure = Stream::with_kind(StreamKind::Measure);
1330 measure.insert(0.0, chord);
1331 let mut part = Stream::with_kind(StreamKind::Part);
1332 part.insert(0.0, measure);
1333 let mut reduction = ScoreReduction::new();
1334 reduction.set_chord_reduction(part);
1335 let reduced = reduction.reduce()?;
1336 let parts = reduced.parts();
1337 assert_eq!(parts.len(), 2);
1338 let notes = parts[0].notes();
1339 assert_eq!(notes.len(), 1);
1340 let StreamElement::Note(note) = ¬es[0].1 else {
1341 panic!("a note");
1342 };
1343 assert_eq!(note.pitch().name_with_octave(), "G3");
1344 assert_eq!(note.lyrics()[0].text(), "V");
1345 let StreamElement::Chord(chord) = &parts[1].notes()[0].1 else {
1347 panic!("a chord");
1348 };
1349 assert_eq!(chord.lyrics()[0].text(), "");
1350 Ok(())
1351 }
1352}