1use crate::{
5 defaults::{FloatType, IntegerType},
6 error::{Error, Result},
7 makenotation::op_frac,
8 notation::{Tie, TieType},
9 pitch::Pitch,
10 stream::{Stream, StreamElement, StreamKind},
11};
12
13use super::difflib;
14
15#[derive(Clone, Copy, Debug)]
18pub struct Searched<'a> {
19 pub element: &'a StreamElement,
21 pub offset: FloatType,
23 pub measure: Option<IntegerType>,
26}
27
28pub fn recursed(stream: &Stream) -> Vec<Searched<'_>> {
31 let mut out = Vec::new();
32 let measure = (stream.kind() == StreamKind::Measure).then(|| stream.number());
33 walk(stream, 0.0, measure, true, &mut out);
34 out
35}
36
37pub fn iterated(stream: &Stream) -> Vec<Searched<'_>> {
40 let mut out = Vec::new();
41 let measure = (stream.kind() == StreamKind::Measure).then(|| stream.number());
42 walk(stream, 0.0, measure, false, &mut out);
43 out
44}
45
46pub fn notes_and_rests(stream: &Stream) -> Vec<Searched<'_>> {
49 recursed(stream)
50 .into_iter()
51 .filter(|found| is_general_note(found.element))
52 .collect()
53}
54
55fn walk<'a>(
56 stream: &'a Stream,
57 base: FloatType,
58 measure: Option<IntegerType>,
59 deep: bool,
60 out: &mut Vec<Searched<'a>>,
61) {
62 for event in stream.events() {
63 let offset = op_frac(base + event.offset());
64 let element = event.element();
65 out.push(Searched {
66 element,
67 offset,
68 measure,
69 });
70 if deep && let StreamElement::Stream(inner) = element {
71 let inner_measure = if inner.kind() == StreamKind::Measure {
72 Some(inner.number())
73 } else {
74 measure
75 };
76 walk(inner, offset, inner_measure, true, out);
77 }
78 }
79}
80
81fn is_general_note(element: &StreamElement) -> bool {
84 matches!(
85 element,
86 StreamElement::Note(_)
87 | StreamElement::Chord(_)
88 | StreamElement::Rest(_)
89 | StreamElement::Unpitched(_)
90 | StreamElement::PercussionChord(_)
91 | StreamElement::ChordSymbol(_)
92 )
93}
94
95#[derive(Clone, Debug)]
97pub enum SearchTerm {
98 Wildcard,
100 Element(StreamElement),
102 AnyLength(StreamElement),
105}
106
107impl SearchTerm {
108 fn element(&self) -> Option<&StreamElement> {
109 match self {
110 Self::Wildcard => None,
111 Self::Element(element) | Self::AnyLength(element) => Some(element),
112 }
113 }
114
115 fn any_length(&self) -> bool {
118 matches!(self, Self::Wildcard | Self::AnyLength(_))
119 }
120}
121
122impl From<StreamElement> for SearchTerm {
123 fn from(element: StreamElement) -> Self {
124 Self::Element(element)
125 }
126}
127
128fn name(element: &StreamElement) -> Option<String> {
131 match element {
132 StreamElement::Note(note) => Some(note.pitch().name()),
133 StreamElement::Rest(_) => Some("rest".to_string()),
134 StreamElement::Clef(clef) => Some(clef.name()),
135 StreamElement::Key(key) => Some(format!("{} {}", key.tonic().name(), key.mode())),
136 StreamElement::TextExpression(_) => Some("text expression".to_string()),
137 _ => None,
138 }
139}
140
141#[derive(Clone, Copy, Debug)]
145pub enum Algorithm {
146 Wildcard,
148 Rhythm,
151 NoteName,
154 Custom(fn(&StreamElement, &SearchTerm) -> Option<bool>),
156}
157
158impl Algorithm {
159 pub fn compare(self, element: &StreamElement, term: &SearchTerm) -> Option<bool> {
164 match self {
165 Self::Wildcard => matches!(term, SearchTerm::Wildcard).then_some(true),
166 Self::Rhythm => {
167 if term.any_length() {
168 return Some(true);
169 }
170 let wanted = term.element()?.quarter_length();
171 (wanted != element.quarter_length()).then_some(false)
172 }
173 Self::NoteName => {
174 let wanted = term.element().and_then(name);
175 match (wanted, name(element)) {
176 (Some(wanted), Some(found)) if wanted == found => None,
177 _ => Some(false),
178 }
179 }
180 Self::Custom(compare) => compare(element, term),
181 }
182 }
183}
184
185#[derive(Clone, Copy, Debug, PartialEq, Eq)]
187pub enum Filter {
188 Notes,
190 NotesAndRests,
192}
193
194#[derive(Clone, Debug)]
197pub struct SearchMatch<'a> {
198 pub index: usize,
200 pub elements: Vec<Searched<'a>>,
202}
203
204#[derive(Clone, Debug)]
233pub struct StreamSearcher {
234 pub search: Vec<SearchTerm>,
236 pub recurse: bool,
238 pub filter: Option<Filter>,
240 pub algorithms: Vec<Algorithm>,
242}
243
244impl StreamSearcher {
245 pub fn new(search: Vec<SearchTerm>) -> Self {
248 Self {
249 search,
250 recurse: false,
251 filter: None,
252 algorithms: vec![Algorithm::Wildcard],
253 }
254 }
255
256 pub fn run<'a>(&self, stream: &'a Stream) -> Result<Vec<SearchMatch<'a>>> {
262 let elements = if self.recurse {
263 recursed(stream)
264 } else {
265 iterated(stream)
266 };
267 let elements: Vec<Searched<'a>> = elements
268 .into_iter()
269 .filter(|found| match self.filter {
270 None => true,
271 Some(Filter::Notes) => matches!(
272 found.element,
273 StreamElement::Note(_)
274 | StreamElement::Chord(_)
275 | StreamElement::ChordSymbol(_)
276 ),
277 Some(Filter::NotesAndRests) => is_general_note(found.element),
278 })
279 .collect();
280 self.run_over(&elements)
281 }
282
283 pub fn run_over<'a>(&self, elements: &[Searched<'a>]) -> Result<Vec<SearchMatch<'a>>> {
290 if self.search.is_empty() {
291 return Err(empty_search());
292 }
293 let mut found = Vec::new();
294 if self.search.len() > elements.len() {
295 return Ok(found);
296 }
297 for (index, window) in elements.windows(self.search.len()).enumerate() {
298 let refused = window.iter().zip(&self.search).any(|(searched, term)| {
299 self.algorithms
300 .iter()
301 .find_map(|algorithm| algorithm.compare(searched.element, term))
302 == Some(false)
303 });
304 if !refused {
305 found.push(SearchMatch {
306 index,
307 elements: window.to_vec(),
308 });
309 }
310 }
311 Ok(found)
312 }
313}
314
315fn empty_search() -> Error {
316 Error::Search("the search Stream or list cannot be empty".to_string())
317}
318
319fn search_with(
322 elements: &[Searched<'_>],
323 search: &[SearchTerm],
324 matches: impl Fn(&StreamElement, &SearchTerm) -> bool,
325) -> Result<Vec<usize>> {
326 if search.is_empty() {
327 return Err(empty_search());
328 }
329 if search.len() > elements.len() {
330 return Ok(Vec::new());
331 }
332 Ok(elements
333 .windows(search.len())
334 .enumerate()
335 .filter(|(_, window)| {
336 window
337 .iter()
338 .zip(search)
339 .all(|(searched, term)| matches(searched.element, term))
340 })
341 .map(|(index, _)| index)
342 .collect())
343}
344
345pub fn rhythmic_search(elements: &[Searched<'_>], search: &[SearchTerm]) -> Result<Vec<usize>> {
353 search_with(elements, search, |element, term| {
354 term.any_length()
355 || term
356 .element()
357 .is_some_and(|wanted| wanted.quarter_length() == element.quarter_length())
358 })
359}
360
361fn same_name(element: &StreamElement, term: &SearchTerm) -> bool {
362 matches!(
363 (term.element().and_then(name), name(element)),
364 (Some(wanted), Some(found)) if wanted == found
365 )
366}
367
368pub fn note_name_search(elements: &[Searched<'_>], search: &[SearchTerm]) -> Result<Vec<usize>> {
376 search_with(elements, search, |element, term| {
377 matches!(term, SearchTerm::Wildcard) || same_name(element, term)
378 })
379}
380
381pub fn note_name_rhythmic_search(
388 elements: &[Searched<'_>],
389 search: &[SearchTerm],
390) -> Result<Vec<usize>> {
391 search_with(elements, search, |element, term| {
392 if matches!(term, SearchTerm::Wildcard) {
393 return true;
394 }
395 same_name(element, term)
396 && (term.any_length()
397 || term
398 .element()
399 .is_some_and(|wanted| wanted.quarter_length() == element.quarter_length()))
400 })
401}
402
403fn first_pitch(element: &StreamElement) -> Option<Pitch> {
406 match element {
407 StreamElement::Note(note) => Some(note.pitch().clone()),
408 StreamElement::Chord(chord) => chord.pitches().into_iter().next(),
409 StreamElement::ChordSymbol(symbol) => symbol.pitches().ok()?.into_iter().next(),
410 _ => element.pitches().into_iter().next(),
411 }
412}
413
414fn tie_of(element: &StreamElement) -> Option<&Tie> {
416 match element {
417 StreamElement::Note(note) => note.tie(),
418 StreamElement::Chord(chord) => chord.tie(),
419 StreamElement::Rest(rest) => rest.tie(),
420 StreamElement::Unpitched(stroke) => stroke.written().tie(),
421 StreamElement::PercussionChord(chord) => chord.written().tie(),
422 _ => None,
423 }
424}
425
426pub fn translate_note_to_byte(element: &StreamElement) -> char {
430 let midi = match element {
431 StreamElement::Note(note) => Some(note.pitch().midi()),
432 StreamElement::Chord(_) | StreamElement::ChordSymbol(_) => {
433 first_pitch(element).map(|pitch| pitch.midi())
434 }
435 _ => None,
436 };
437 midi.and_then(|midi| u32::try_from(midi).ok())
438 .and_then(char::from_u32)
439 .unwrap_or('\u{7f}')
440}
441
442pub fn translate_duration_to_bytes(element: &StreamElement) -> char {
446 let length = element.quarter_length();
447 let mut code = 1;
448 if length != 0.0 {
449 code = ((length * 256.0).log2() * 10.0) as i64;
450 code = code.clamp(1, 127);
451 }
452 char::from_u32(code as u32).unwrap_or('\u{1}')
453}
454
455pub fn translate_note_tie_to_byte(element: &StreamElement) -> &'static str {
459 match tie_of(element).map(Tie::tie_type) {
460 Some(TieType::Start) => "s",
461 Some(TieType::Continue) => "c",
462 Some(TieType::Stop) => "e",
463 _ => "",
464 }
465}
466
467pub fn translate_note_with_duration_to_bytes(element: &StreamElement, include_tie: bool) -> String {
470 let mut text = String::new();
471 text.push(translate_note_to_byte(element));
472 text.push(translate_duration_to_bytes(element));
473 if include_tie {
474 text.push_str(translate_note_tie_to_byte(element));
475 }
476 text
477}
478
479#[derive(Clone, Debug, PartialEq, Eq)]
482pub struct Translation {
483 pub text: String,
485 pub measures: Vec<Option<IntegerType>>,
488}
489
490pub fn translate_stream_to_string(elements: &[Searched<'_>]) -> Translation {
493 Translation {
494 text: elements
495 .iter()
496 .map(|found| translate_note_with_duration_to_bytes(found.element, true))
497 .collect(),
498 measures: elements.iter().map(|found| found.measure).collect(),
499 }
500}
501
502pub fn translate_stream_to_string_no_rhythm(elements: &[Searched<'_>]) -> Translation {
505 Translation {
506 text: elements
507 .iter()
508 .map(|found| translate_note_to_byte(found.element))
509 .collect(),
510 measures: elements.iter().map(|found| found.measure).collect(),
511 }
512}
513
514pub fn translate_stream_to_string_only_rhythm(elements: &[Searched<'_>]) -> Translation {
517 Translation {
518 text: elements
519 .iter()
520 .map(|found| translate_duration_to_bytes(found.element))
521 .collect(),
522 measures: elements.iter().map(|found| found.measure).collect(),
523 }
524}
525
526fn translate_lines(
532 elements: &[Searched<'_>],
533 rest: char,
534 mut note: impl FnMut(&Pitch, usize) -> char,
535) -> Result<Translation> {
536 let mut text = String::new();
537 let mut measures = Vec::new();
538 let mut previous_rest = false;
539 let mut previous_tie = false;
540 let mut previous_length: Option<FloatType> = None;
541 for found in elements {
542 if matches!(found.element, StreamElement::Rest(_)) {
543 if !previous_rest {
544 previous_rest = true;
545 text.push(rest);
546 measures.push(found.measure);
547 }
548 continue;
549 }
550 previous_rest = false;
551 let tie = tie_of(found.element);
552 if previous_tie {
553 if tie.is_none_or(|tie| tie.tie_type() == TieType::Stop) {
554 previous_tie = false;
555 }
556 continue;
557 } else if tie.is_some() {
558 previous_tie = true;
559 }
560 let length = found.element.quarter_length();
561 let change = match previous_length {
562 None => 0,
563 Some(previous) if previous == length => 0,
564 Some(previous) if previous > length => 1,
565 Some(_) => 2,
566 };
567 previous_length = Some(length);
568 let pitch = first_pitch(found.element)
569 .ok_or_else(|| Error::Search("a chord to translate holds no pitches".to_string()))?;
570 text.push(note(&pitch, change));
571 measures.push(found.measure);
572 }
573 Ok(Translation { text, measures })
574}
575
576pub fn translate_diatonic_stream_to_string(elements: &[Searched<'_>]) -> Result<Translation> {
586 translate_lines(elements, 'Z', |pitch, change| {
587 let shift = [0, 14, 7][change];
588 char::from_u32(pitch.step().as_char() as u32 + shift).unwrap_or('?')
589 })
590}
591
592pub fn translate_intervals_and_speed(elements: &[Searched<'_>]) -> Result<Translation> {
602 let mut previous_midi = elements
603 .iter()
604 .find_map(|found| match found.element {
605 StreamElement::Note(note) => Some(note.pitch().midi()),
606 _ => None,
607 })
608 .unwrap_or(60);
609 translate_lines(elements, ' ', |pitch, change| {
610 let shift = [27 + 14, 27 * 2 + 14, 14][change];
611 let midi = pitch.midi();
612 let difference = (previous_midi - midi).clamp(-13, 13);
613 previous_midi = midi;
614 char::from_u32((32 + difference + shift) as u32).unwrap_or('?')
615 })
616}
617
618pub fn similarity(a: &str, b: &str) -> FloatType {
621 let a: Vec<char> = a.chars().collect();
622 let b: Vec<char> = b.chars().collect();
623 difflib::ratio(&a, &b)
624}
625
626fn with_flat_notes<R>(stream: &Stream, read: impl FnOnce(&[Searched<'_>]) -> R) -> R {
630 let flat = stream.flatten();
631 let notes: Vec<Searched<'_>> = iterated(&flat)
632 .into_iter()
633 .filter(|found| is_general_note(found.element))
634 .collect();
635 read(¬es)
636}
637
638fn ranked(mut ratios: Vec<(usize, FloatType)>) -> Vec<(usize, FloatType)> {
641 ratios.sort_by(|a, b| (1.0 - a.1).total_cmp(&(1.0 - b.1)));
642 ratios
643}
644
645pub fn approximate_note_search(this: &Stream, others: &[&Stream]) -> Vec<(usize, FloatType)> {
650 approximate_by(this, others, |elements| {
651 translate_stream_to_string(elements).text
652 })
653}
654
655pub fn approximate_note_search_no_rhythm(
657 this: &Stream,
658 others: &[&Stream],
659) -> Vec<(usize, FloatType)> {
660 approximate_by(this, others, |elements| {
661 translate_stream_to_string_no_rhythm(elements).text
662 })
663}
664
665pub fn approximate_note_search_only_rhythm(
667 this: &Stream,
668 others: &[&Stream],
669) -> Vec<(usize, FloatType)> {
670 approximate_by(this, others, |elements| {
671 translate_stream_to_string_only_rhythm(elements).text
672 })
673}
674
675fn approximate_by(
676 this: &Stream,
677 others: &[&Stream],
678 translate: impl Fn(&[Searched<'_>]) -> String,
679) -> Vec<(usize, FloatType)> {
680 let this_text = with_flat_notes(this, &translate);
681 ranked(
682 others
683 .iter()
684 .enumerate()
685 .map(|(index, other)| {
686 let other_text = with_flat_notes(other, &translate);
687 (index, similarity(&this_text, &other_text))
688 })
689 .collect(),
690 )
691}
692
693pub fn approximate_note_search_weighted(
696 this: &Stream,
697 others: &[&Stream],
698) -> Vec<(usize, FloatType)> {
699 let texts = |notes: &[Searched<'_>]| {
700 (
701 translate_stream_to_string_no_rhythm(notes).text,
702 translate_stream_to_string_only_rhythm(notes).text,
703 )
704 };
705 let (pitches, lengths) = with_flat_notes(this, texts);
706 ranked(
707 others
708 .iter()
709 .enumerate()
710 .map(|(index, other)| {
711 let (other_pitches, other_lengths) = with_flat_notes(other, texts);
712 let by_pitch = similarity(&pitches, &other_pitches);
713 let by_length = similarity(&lengths, &other_lengths);
714 (index, (3.0 * by_pitch + by_length) / 4.0)
715 })
716 .collect(),
717 )
718}
719
720#[derive(Clone, Debug)]
722pub struct MeasureRhythm<'a> {
723 pub count: usize,
725 pub rhythm: String,
727 pub measures: Vec<&'a Stream>,
729}
730
731#[derive(Clone, Debug, PartialEq, Eq)]
735pub struct MeasureRhythmPlaces {
736 pub count: usize,
738 pub rhythm: String,
740 pub measures: Vec<usize>,
743}
744
745pub fn most_common_measure_rhythms(stream: &Stream) -> Vec<MeasureRhythm<'_>> {
750 let measures = measures_of(stream);
751 most_common_measure_rhythm_places(stream)
752 .into_iter()
753 .map(|found| MeasureRhythm {
754 count: found.count,
755 rhythm: found.rhythm,
756 measures: found
757 .measures
758 .into_iter()
759 .filter_map(|at| measures.get(at).copied())
760 .collect(),
761 })
762 .collect()
763}
764
765pub fn most_common_measure_rhythm_places(stream: &Stream) -> Vec<MeasureRhythmPlaces> {
769 let mut rhythms: Vec<MeasureRhythmPlaces> = Vec::new();
770 for (at, measure) in measures_of(stream).into_iter().enumerate() {
771 let own: Vec<Searched<'_>> = iterated(measure)
772 .into_iter()
773 .filter(|found| is_general_note(found.element))
774 .collect();
775 let rhythm = translate_stream_to_string_only_rhythm(&own).text;
776 match rhythms.iter_mut().find(|known| known.rhythm == rhythm) {
777 Some(known) => {
778 known.count += 1;
779 known.measures.push(at);
780 }
781 None => rhythms.push(MeasureRhythmPlaces {
782 count: 1,
783 rhythm,
784 measures: vec![at],
785 }),
786 }
787 }
788 rhythms.sort_by_key(|rhythm| std::cmp::Reverse(rhythm.count));
789 rhythms
790}
791
792fn measures_of(stream: &Stream) -> Vec<&Stream> {
795 recursed(stream)
796 .into_iter()
797 .filter_map(|found| match found.element {
798 StreamElement::Stream(measure) if measure.kind() == StreamKind::Measure => {
799 Some(&**measure)
800 }
801 _ => None,
802 })
803 .collect()
804}
805
806#[cfg(test)]
807mod tests {
808 use super::*;
809 use crate::note::Note;
810 use crate::tinynotation::from_tiny_notation;
811
812 fn note(name: &str, length: FloatType) -> SearchTerm {
813 SearchTerm::Element(StreamElement::Note(
814 Note::from_name(name)
815 .expect("a note")
816 .with_duration(crate::duration::Duration::new(length).expect("a length")),
817 ))
818 }
819
820 #[test]
821 fn runs_are_found_by_rhythm_and_name() -> Result<()> {
822 let line = from_tiny_notation("3/4 c4. d8 e4 g4. a8 f4 c'8 d'4.")?;
825 let notes = notes_and_rests(&line);
826 assert_eq!(
827 rhythmic_search(¬es, &[note("C4", 1.5), note("C4", 0.5)])?,
828 [0, 3]
829 );
830 assert_eq!(
831 note_name_search(
832 ¬es,
833 &[note("D4", 1.0), SearchTerm::Wildcard, note("G4", 1.0)]
834 )?,
835 [1]
836 );
837 let mut searcher = StreamSearcher::new(vec![
838 note("D4", 0.5),
839 SearchTerm::Wildcard,
840 SearchTerm::AnyLength(note_element("G4")),
841 ]);
842 searcher.recurse = true;
843 searcher.filter = Some(Filter::Notes);
844 searcher.algorithms.push(Algorithm::Rhythm);
845 let found = searcher.run(&line)?;
846 assert_eq!(
847 found.iter().map(|found| found.index).collect::<Vec<_>>(),
848 [1, 4]
849 );
850 assert!(StreamSearcher::new(Vec::new()).run(&line).is_err());
851 Ok(())
852 }
853
854 fn note_element(name: &str) -> StreamElement {
855 StreamElement::Note(Note::from_name(name).expect("a note"))
856 }
857
858 #[test]
859 fn notes_are_translated_as_music21_translates_them() -> Result<()> {
860 let line = from_tiny_notation("3/4 c4 d8 r8 r4 e4~ e8 f8")?;
862 let notes = notes_and_rests(&line);
863 assert_eq!(
864 translate_stream_to_string(¬es).text,
865 "<P>F\u{7f}F\u{7f}P@Ps@FeAF"
866 );
867 assert_eq!(translate_diatonic_stream_to_string(¬es)?.text, "CRZLT");
868 assert_eq!(translate_intervals_and_speed(¬es)?.text, "Ib ,c");
869 Ok(())
870 }
871}