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