Skip to main content

music21_rs/search/
base.rs

1//! Streams searched for runs of elements, and turned into text to compare:
2//! music21's `search.base`.
3
4use 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/// One element of a stream as a search meets it, with where it is: what
16/// music21's stream iterators hand a search.
17#[derive(Clone, Copy, Debug)]
18pub struct Searched<'a> {
19    /// The element.
20    pub element: &'a StreamElement,
21    /// Where it starts in the stream searched.
22    pub offset: FloatType,
23    /// The number of the measure it is in, if it is in one: music21's
24    /// `measureNumber`.
25    pub measure: Option<IntegerType>,
26}
27
28/// Every element of a stream, nested ones included, each before what it
29/// holds: music21's `recurse()`.
30pub 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
37/// The elements of a stream itself, not of the streams it holds: music21's
38/// `iter()`.
39pub 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
46/// The notes, chords and rests of [`recursed`]: music21's
47/// `recurse().notesAndRests`.
48pub 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
81/// Whether an element is one of music21's `GeneralNote`s: a note, chord,
82/// rest, unpitched stroke, percussion chord or chord symbol.
83fn 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/// One place in what is searched for: music21's search list entry.
96#[derive(Clone, Debug)]
97pub enum SearchTerm {
98    /// Anything at all: music21's `Wildcard`.
99    Wildcard,
100    /// An element, compared as the search's algorithms compare it.
101    Element(StreamElement),
102    /// An element whose length does not matter: one with music21's
103    /// `WildcardDuration`.
104    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    /// Whether any length matches it: a wildcard, whose length is music21's
116    /// `WildcardDuration` too, or an element given any length.
117    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
128/// The name music21 gives an element, where it has one: a note's pitch
129/// name, `rest`, a clef's or a key's name.
130fn 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/// How a [`StreamSearcher`] compares an element of the stream with the
142/// search term it is lined up with: each answers that they match, that
143/// they do not, or nothing, leaving it to the next.
144#[derive(Clone, Copy, Debug)]
145pub enum Algorithm {
146    /// A wildcard matches anything: music21's `wildcardAlgorithm`.
147    Wildcard,
148    /// A term of any length matches; one of another length does not:
149    /// music21's `rhythmAlgorithm`.
150    Rhythm,
151    /// A term or element with no name, or of another name, does not match:
152    /// music21's `noteNameAlgorithm`.
153    NoteName,
154    /// A comparison of one's own.
155    Custom(fn(&StreamElement, &SearchTerm) -> Option<bool>),
156}
157
158impl Algorithm {
159    fn compare(self, element: &StreamElement, term: &SearchTerm) -> Option<bool> {
160        match self {
161            Self::Wildcard => matches!(term, SearchTerm::Wildcard).then_some(true),
162            Self::Rhythm => {
163                if term.any_length() {
164                    return Some(true);
165                }
166                let wanted = term.element()?.quarter_length();
167                (wanted != element.quarter_length()).then_some(false)
168            }
169            Self::NoteName => {
170                let wanted = term.element().and_then(name);
171                match (wanted, name(element)) {
172                    (Some(wanted), Some(found)) if wanted == found => None,
173                    _ => Some(false),
174                }
175            }
176            Self::Custom(compare) => compare(element, term),
177        }
178    }
179}
180
181/// Which elements of the stream a [`StreamSearcher`] looks at.
182#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183pub enum Filter {
184    /// Notes and chords alone.
185    Notes,
186    /// Notes, chords and rests: music21's `GeneralNote`s.
187    NotesAndRests,
188}
189
190/// A run of elements a search found: music21's `search.base`
191/// `SearchMatch`.
192#[derive(Clone, Debug)]
193pub struct SearchMatch<'a> {
194    /// Where the run starts among the elements searched.
195    pub index: usize,
196    /// The elements, as many as were searched for.
197    pub elements: Vec<Searched<'a>>,
198}
199
200/// Searches a stream for runs of elements matching a list of terms, the
201/// terms compared by a list of algorithms: music21's `StreamSearcher`. A
202/// run matches unless an algorithm says one of its elements does not;
203/// with only the wildcard algorithm, as it starts, every run matches.
204///
205/// ```
206/// use music21_rs::duration::Duration;
207/// use music21_rs::note::Note;
208/// use music21_rs::search::{Algorithm, Filter, SearchTerm, StreamSearcher};
209/// use music21_rs::stream::StreamElement;
210/// use music21_rs::tinynotation::from_tiny_notation;
211///
212/// let line = from_tiny_notation("3/4 c4. d8 e4 g4. a8 f4 c'8 d'4.")?;
213/// let mut searcher = StreamSearcher::new(vec![
214///     SearchTerm::Element(StreamElement::Note(
215///         Note::from_name("C4")?.with_duration(Duration::new(1.5)?),
216///     )),
217///     SearchTerm::Element(StreamElement::Note(
218///         Note::from_name("C4")?.with_duration(Duration::new(0.5)?),
219///     )),
220/// ]);
221/// searcher.recurse = true;
222/// searcher.filter = Some(Filter::Notes);
223/// searcher.algorithms.push(Algorithm::Rhythm);
224/// let found = searcher.run(&line)?;
225/// assert_eq!(found.iter().map(|found| found.index).collect::<Vec<_>>(), [0, 3]);
226/// # Ok::<(), music21_rs::Error>(())
227/// ```
228#[derive(Clone, Debug)]
229pub struct StreamSearcher {
230    /// What to look for, in order.
231    pub search: Vec<SearchTerm>,
232    /// Whether to look inside the streams the stream holds.
233    pub recurse: bool,
234    /// Which elements to look at; all of them for none.
235    pub filter: Option<Filter>,
236    /// How to compare, in order, each asked until one answers.
237    pub algorithms: Vec<Algorithm>,
238}
239
240impl StreamSearcher {
241    /// A search for these terms, of the stream's own elements, by the
242    /// wildcard algorithm alone.
243    pub fn new(search: Vec<SearchTerm>) -> Self {
244        Self {
245            search,
246            recurse: false,
247            filter: None,
248            algorithms: vec![Algorithm::Wildcard],
249        }
250    }
251
252    /// The runs of a stream's elements that match: music21's `run`.
253    ///
254    /// # Errors
255    ///
256    /// Nothing to search for.
257    pub fn run<'a>(&self, stream: &'a Stream) -> Result<Vec<SearchMatch<'a>>> {
258        let elements = if self.recurse {
259            recursed(stream)
260        } else {
261            iterated(stream)
262        };
263        let elements: Vec<Searched<'a>> = elements
264            .into_iter()
265            .filter(|found| match self.filter {
266                None => true,
267                Some(Filter::Notes) => matches!(
268                    found.element,
269                    StreamElement::Note(_)
270                        | StreamElement::Chord(_)
271                        | StreamElement::ChordSymbol(_)
272                ),
273                Some(Filter::NotesAndRests) => is_general_note(found.element),
274            })
275            .collect();
276        self.run_over(&elements)
277    }
278
279    /// The runs of these elements that match: music21's `run` of a stream
280    /// iterator.
281    ///
282    /// # Errors
283    ///
284    /// Nothing to search for.
285    pub fn run_over<'a>(&self, elements: &[Searched<'a>]) -> Result<Vec<SearchMatch<'a>>> {
286        if self.search.is_empty() {
287            return Err(empty_search());
288        }
289        let mut found = Vec::new();
290        if self.search.len() > elements.len() {
291            return Ok(found);
292        }
293        for (index, window) in elements.windows(self.search.len()).enumerate() {
294            let refused = window.iter().zip(&self.search).any(|(searched, term)| {
295                self.algorithms
296                    .iter()
297                    .find_map(|algorithm| algorithm.compare(searched.element, term))
298                    == Some(false)
299            });
300            if !refused {
301                found.push(SearchMatch {
302                    index,
303                    elements: window.to_vec(),
304                });
305            }
306        }
307        Ok(found)
308    }
309}
310
311fn empty_search() -> Error {
312    Error::Search("the search Stream or list cannot be empty".to_string())
313}
314
315/// Where each run of elements starts whose every element `matches` the term
316/// lined up with it: music21's `streamSearchBase`.
317fn search_with(
318    elements: &[Searched<'_>],
319    search: &[SearchTerm],
320    matches: impl Fn(&StreamElement, &SearchTerm) -> bool,
321) -> Result<Vec<usize>> {
322    if search.is_empty() {
323        return Err(empty_search());
324    }
325    if search.len() > elements.len() {
326        return Ok(Vec::new());
327    }
328    Ok(elements
329        .windows(search.len())
330        .enumerate()
331        .filter(|(_, window)| {
332            window
333                .iter()
334                .zip(search)
335                .all(|(searched, term)| matches(searched.element, term))
336        })
337        .map(|(index, _)| index)
338        .collect())
339}
340
341/// Where each run of elements starts whose lengths are those searched for,
342/// a wildcard or a term of any length matching any: music21's
343/// `rhythmicSearch`.
344///
345/// # Errors
346///
347/// Nothing to search for.
348pub fn rhythmic_search(elements: &[Searched<'_>], search: &[SearchTerm]) -> Result<Vec<usize>> {
349    search_with(elements, search, |element, term| {
350        term.any_length()
351            || term
352                .element()
353                .is_some_and(|wanted| wanted.quarter_length() == element.quarter_length())
354    })
355}
356
357fn same_name(element: &StreamElement, term: &SearchTerm) -> bool {
358    matches!(
359        (term.element().and_then(name), name(element)),
360        (Some(wanted), Some(found)) if wanted == found
361    )
362}
363
364/// Where each run of elements starts whose names -- a note's pitch name,
365/// `rest` -- are those searched for, a wildcard matching any: music21's
366/// `noteNameSearch`.
367///
368/// # Errors
369///
370/// Nothing to search for.
371pub fn note_name_search(elements: &[Searched<'_>], search: &[SearchTerm]) -> Result<Vec<usize>> {
372    search_with(elements, search, |element, term| {
373        matches!(term, SearchTerm::Wildcard) || same_name(element, term)
374    })
375}
376
377/// Where each run of elements starts whose names and lengths are those
378/// searched for: music21's `noteNameRhythmicSearch`.
379///
380/// # Errors
381///
382/// Nothing to search for.
383pub fn note_name_rhythmic_search(
384    elements: &[Searched<'_>],
385    search: &[SearchTerm],
386) -> Result<Vec<usize>> {
387    search_with(elements, search, |element, term| {
388        if matches!(term, SearchTerm::Wildcard) {
389            return true;
390        }
391        same_name(element, term)
392            && (term.any_length()
393                || term
394                    .element()
395                    .is_some_and(|wanted| wanted.quarter_length() == element.quarter_length()))
396    })
397}
398
399/// The first pitch of a note, chord or chord symbol, which music21's
400/// translations read.
401fn first_pitch(element: &StreamElement) -> Option<Pitch> {
402    match element {
403        StreamElement::Note(note) => Some(note.pitch().clone()),
404        StreamElement::Chord(chord) => chord.pitches().into_iter().next(),
405        StreamElement::ChordSymbol(symbol) => symbol.pitches().ok()?.into_iter().next(),
406        _ => element.pitches().into_iter().next(),
407    }
408}
409
410/// The tie an element carries, if it can carry one.
411fn tie_of(element: &StreamElement) -> Option<&Tie> {
412    match element {
413        StreamElement::Note(note) => note.tie(),
414        StreamElement::Chord(chord) => chord.tie(),
415        StreamElement::Rest(rest) => rest.tie(),
416        StreamElement::Unpitched(stroke) => stroke.written().tie(),
417        StreamElement::PercussionChord(chord) => chord.written().tie(),
418        _ => None,
419    }
420}
421
422/// An element as one character: a note's MIDI number, a chord's first
423/// pitch's, and 127 for anything else -- a rest, an unpitched stroke, a
424/// chord of nothing: music21's `translateNoteToByte`.
425pub fn translate_note_to_byte(element: &StreamElement) -> char {
426    let midi = match element {
427        StreamElement::Note(note) => Some(note.pitch().midi()),
428        StreamElement::Chord(_) | StreamElement::ChordSymbol(_) => {
429            first_pitch(element).map(|pitch| pitch.midi())
430        }
431        _ => None,
432    };
433    midi.and_then(|midi| u32::try_from(midi).ok())
434        .and_then(char::from_u32)
435        .unwrap_or('\u{7f}')
436}
437
438/// An element's length as one character, ten times the base-two logarithm
439/// of 256 times its quarter length, kept within one to 127: music21's
440/// `translateDurationToBytes`.
441pub fn translate_duration_to_bytes(element: &StreamElement) -> char {
442    let length = element.quarter_length();
443    let mut code = 1;
444    if length != 0.0 {
445        code = ((length * 256.0).log2() * 10.0) as i64;
446        code = code.clamp(1, 127);
447    }
448    char::from_u32(code as u32).unwrap_or('\u{1}')
449}
450
451/// An element's tie as text: `s` for one starting, `c` for one going on,
452/// `e` for one ending, and nothing otherwise: music21's
453/// `translateNoteTieToByte`.
454pub fn translate_note_tie_to_byte(element: &StreamElement) -> &'static str {
455    match tie_of(element).map(Tie::tie_type) {
456        Some(TieType::Start) => "s",
457        Some(TieType::Continue) => "c",
458        Some(TieType::Stop) => "e",
459        _ => "",
460    }
461}
462
463/// An element as its pitch, length and, if asked, tie characters:
464/// music21's `translateNoteWithDurationToBytes`.
465pub fn translate_note_with_duration_to_bytes(element: &StreamElement, include_tie: bool) -> String {
466    let mut text = String::new();
467    text.push(translate_note_to_byte(element));
468    text.push(translate_duration_to_bytes(element));
469    if include_tie {
470        text.push_str(translate_note_tie_to_byte(element));
471    }
472    text
473}
474
475/// Notes and rests as text to search, and the measure each piece of it
476/// comes from.
477#[derive(Clone, Debug, PartialEq, Eq)]
478pub struct Translation {
479    /// The text.
480    pub text: String,
481    /// The measure of each note or rest the text was made from, in order:
482    /// music21's `returnMeasures`.
483    pub measures: Vec<Option<IntegerType>>,
484}
485
486/// Notes and rests as text, each its pitch, length and tie characters:
487/// music21's `translateStreamToString`.
488pub fn translate_stream_to_string(elements: &[Searched<'_>]) -> Translation {
489    Translation {
490        text: elements
491            .iter()
492            .map(|found| translate_note_with_duration_to_bytes(found.element, true))
493            .collect(),
494        measures: elements.iter().map(|found| found.measure).collect(),
495    }
496}
497
498/// Notes and rests as text of their pitches alone: music21's
499/// `translateStreamToStringNoRhythm`.
500pub fn translate_stream_to_string_no_rhythm(elements: &[Searched<'_>]) -> Translation {
501    Translation {
502        text: elements
503            .iter()
504            .map(|found| translate_note_to_byte(found.element))
505            .collect(),
506        measures: elements.iter().map(|found| found.measure).collect(),
507    }
508}
509
510/// Notes and rests as text of their lengths alone: music21's
511/// `translateStreamToStringOnlyRhythm`.
512pub fn translate_stream_to_string_only_rhythm(elements: &[Searched<'_>]) -> Translation {
513    Translation {
514        text: elements
515            .iter()
516            .map(|found| translate_duration_to_bytes(found.element))
517            .collect(),
518        measures: elements.iter().map(|found| found.measure).collect(),
519    }
520}
521
522/// Walks notes and rests as music21's diatonic and interval translations
523/// do: a run of rests read once as `rest`, a note tied on from the one
524/// before passed over, and each other note handed its first pitch and
525/// whether it is as long as the note before (nought), shorter (one) or
526/// longer (two).
527fn translate_lines(
528    elements: &[Searched<'_>],
529    rest: char,
530    mut note: impl FnMut(&Pitch, usize) -> char,
531) -> Result<Translation> {
532    let mut text = String::new();
533    let mut measures = Vec::new();
534    let mut previous_rest = false;
535    let mut previous_tie = false;
536    let mut previous_length: Option<FloatType> = None;
537    for found in elements {
538        if matches!(found.element, StreamElement::Rest(_)) {
539            if !previous_rest {
540                previous_rest = true;
541                text.push(rest);
542                measures.push(found.measure);
543            }
544            continue;
545        }
546        previous_rest = false;
547        let tie = tie_of(found.element);
548        if previous_tie {
549            if tie.is_none_or(|tie| tie.tie_type() == TieType::Stop) {
550                previous_tie = false;
551            }
552            continue;
553        } else if tie.is_some() {
554            previous_tie = true;
555        }
556        let length = found.element.quarter_length();
557        let change = match previous_length {
558            None => 0,
559            Some(previous) if previous == length => 0,
560            Some(previous) if previous > length => 1,
561            Some(_) => 2,
562        };
563        previous_length = Some(length);
564        let pitch = first_pitch(found.element)
565            .ok_or_else(|| Error::Search("list index out of range".to_string()))?;
566        text.push(note(&pitch, change));
567        measures.push(found.measure);
568    }
569    Ok(Translation { text, measures })
570}
571
572/// Notes and rests as text of their steps, a letter for each note --
573/// `A` to `G` where it is as long as the note before, seven letters on
574/// where it is longer, fourteen where it is shorter -- and `Z` for a run of
575/// rests: music21's `translateDiatonicStreamToString`. A note tied on from
576/// the one before is left out.
577///
578/// # Errors
579///
580/// An element with no pitch that is not a rest.
581pub fn translate_diatonic_stream_to_string(elements: &[Searched<'_>]) -> Result<Translation> {
582    translate_lines(elements, 'Z', |pitch, change| {
583        let shift = [0, 14, 7][change];
584        char::from_u32(pitch.step().as_char() as u32 + shift).unwrap_or('?')
585    })
586}
587
588/// Notes and rests as text of the intervals between them, up to thirteen
589/// semitones either way, and whether each is as long as, shorter or longer
590/// than the one before, a space for a run of rests: music21's
591/// `translateIntervalsAndSpeed`. The first note's interval is from the
592/// first note.
593///
594/// # Errors
595///
596/// An element with no pitch that is not a rest.
597pub fn translate_intervals_and_speed(elements: &[Searched<'_>]) -> Result<Translation> {
598    let mut previous_midi = elements
599        .iter()
600        .find_map(|found| match found.element {
601            StreamElement::Note(note) => Some(note.pitch().midi()),
602            _ => None,
603        })
604        .unwrap_or(60);
605    translate_lines(elements, ' ', |pitch, change| {
606        let shift = [27 + 14, 27 * 2 + 14, 14][change];
607        let midi = pitch.midi();
608        let difference = (previous_midi - midi).clamp(-13, 13);
609        previous_midi = midi;
610        char::from_u32((32 + difference + shift) as u32).unwrap_or('?')
611    })
612}
613
614/// How alike two stretches of text are, as music21's approximate searches
615/// measure it: Python's `difflib.SequenceMatcher(None, a, b).ratio()`.
616pub fn similarity(a: &str, b: &str) -> FloatType {
617    let a: Vec<char> = a.chars().collect();
618    let b: Vec<char> = b.chars().collect();
619    difflib::ratio(&a, &b)
620}
621
622/// What `read` makes of the notes and rests of a stream flattened, every
623/// part's together in the order they sound: what music21's approximate
624/// searches translate.
625fn with_flat_notes<R>(stream: &Stream, read: impl FnOnce(&[Searched<'_>]) -> R) -> R {
626    let flat = stream.flatten();
627    let notes: Vec<Searched<'_>> = iterated(&flat)
628        .into_iter()
629        .filter(|found| is_general_note(found.element))
630        .collect();
631    read(&notes)
632}
633
634/// The streams, best match first, each with how alike it is: music21 sorts
635/// by one less the likeness, keeping the order of equals.
636fn ranked(mut ratios: Vec<(usize, FloatType)>) -> Vec<(usize, FloatType)> {
637    ratios.sort_by(|a, b| (1.0 - a.1).total_cmp(&(1.0 - b.1)));
638    ratios
639}
640
641/// How alike each other stream is to this one, as the text of their notes
642/// and rests -- pitch, length and tie -- best first: music21's
643/// `approximateNoteSearch`. Each is the other stream's index and its
644/// likeness, music21's `matchProbability`.
645pub fn approximate_note_search(this: &Stream, others: &[&Stream]) -> Vec<(usize, FloatType)> {
646    approximate_by(this, others, |elements| {
647        translate_stream_to_string(elements).text
648    })
649}
650
651/// The same, by pitch alone: music21's `approximateNoteSearchNoRhythm`.
652pub fn approximate_note_search_no_rhythm(
653    this: &Stream,
654    others: &[&Stream],
655) -> Vec<(usize, FloatType)> {
656    approximate_by(this, others, |elements| {
657        translate_stream_to_string_no_rhythm(elements).text
658    })
659}
660
661/// The same, by length alone: music21's `approximateNoteSearchOnlyRhythm`.
662pub fn approximate_note_search_only_rhythm(
663    this: &Stream,
664    others: &[&Stream],
665) -> Vec<(usize, FloatType)> {
666    approximate_by(this, others, |elements| {
667        translate_stream_to_string_only_rhythm(elements).text
668    })
669}
670
671fn approximate_by(
672    this: &Stream,
673    others: &[&Stream],
674    translate: impl Fn(&[Searched<'_>]) -> String,
675) -> Vec<(usize, FloatType)> {
676    let this_text = with_flat_notes(this, &translate);
677    ranked(
678        others
679            .iter()
680            .enumerate()
681            .map(|(index, other)| {
682                let other_text = with_flat_notes(other, &translate);
683                (index, similarity(&this_text, &other_text))
684            })
685            .collect(),
686    )
687}
688
689/// The same, three parts by pitch to one by length: music21's
690/// `approximateNoteSearchWeighted`.
691pub fn approximate_note_search_weighted(
692    this: &Stream,
693    others: &[&Stream],
694) -> Vec<(usize, FloatType)> {
695    let texts = |notes: &[Searched<'_>]| {
696        (
697            translate_stream_to_string_no_rhythm(notes).text,
698            translate_stream_to_string_only_rhythm(notes).text,
699        )
700    };
701    let (pitches, lengths) = with_flat_notes(this, texts);
702    ranked(
703        others
704            .iter()
705            .enumerate()
706            .map(|(index, other)| {
707                let (other_pitches, other_lengths) = with_flat_notes(other, texts);
708                let by_pitch = similarity(&pitches, &other_pitches);
709                let by_length = similarity(&lengths, &other_lengths);
710                (index, (3.0 * by_pitch + by_length) / 4.0)
711            })
712            .collect(),
713    )
714}
715
716/// A rhythm the measures of a stream share, and which measures share it.
717#[derive(Clone, Debug)]
718pub struct MeasureRhythm<'a> {
719    /// How many measures have it.
720    pub count: usize,
721    /// The rhythm as [`translate_stream_to_string_only_rhythm`] writes it.
722    pub rhythm: String,
723    /// The measures, in order.
724    pub measures: Vec<&'a Stream>,
725}
726
727/// The rhythms of a stream's measures, nested ones included, most common
728/// first, those as common in the order first met: music21's
729/// `mostCommonMeasureRhythms`, without the copy of each first measure
730/// transposed to start on C5 that music21 keeps beside it.
731pub fn most_common_measure_rhythms(stream: &Stream) -> Vec<MeasureRhythm<'_>> {
732    let mut rhythms: Vec<MeasureRhythm<'_>> = Vec::new();
733    for found in recursed(stream) {
734        let StreamElement::Stream(measure) = found.element else {
735            continue;
736        };
737        if measure.kind() != StreamKind::Measure {
738            continue;
739        }
740        let own: Vec<Searched<'_>> = iterated(measure)
741            .into_iter()
742            .filter(|found| is_general_note(found.element))
743            .collect();
744        let rhythm = translate_stream_to_string_only_rhythm(&own).text;
745        match rhythms.iter_mut().find(|known| known.rhythm == rhythm) {
746            Some(known) => {
747                known.count += 1;
748                known.measures.push(measure);
749            }
750            None => rhythms.push(MeasureRhythm {
751                count: 1,
752                rhythm,
753                measures: vec![measure],
754            }),
755        }
756    }
757    rhythms.sort_by_key(|rhythm| std::cmp::Reverse(rhythm.count));
758    rhythms
759}
760
761#[cfg(test)]
762mod tests {
763    use super::*;
764    use crate::note::Note;
765    use crate::tinynotation::from_tiny_notation;
766
767    fn note(name: &str, length: FloatType) -> SearchTerm {
768        SearchTerm::Element(StreamElement::Note(
769            Note::from_name(name)
770                .expect("a note")
771                .with_duration(crate::duration::Duration::new(length).expect("a length")),
772        ))
773    }
774
775    #[test]
776    fn runs_are_found_by_rhythm_and_name() -> Result<()> {
777        // music21's StreamSearcher, rhythmicSearch and noteNameSearch
778        // doctests, on "3/4 c4. d8 e4 g4. a8 f4 c'8 d'4.".
779        let line = from_tiny_notation("3/4 c4. d8 e4 g4. a8 f4 c'8 d'4.")?;
780        let notes = notes_and_rests(&line);
781        assert_eq!(
782            rhythmic_search(&notes, &[note("C4", 1.5), note("C4", 0.5)])?,
783            [0, 3]
784        );
785        assert_eq!(
786            note_name_search(
787                &notes,
788                &[note("D4", 1.0), SearchTerm::Wildcard, note("G4", 1.0)]
789            )?,
790            [1]
791        );
792        let mut searcher = StreamSearcher::new(vec![
793            note("D4", 0.5),
794            SearchTerm::Wildcard,
795            SearchTerm::AnyLength(note_element("G4")),
796        ]);
797        searcher.recurse = true;
798        searcher.filter = Some(Filter::Notes);
799        searcher.algorithms.push(Algorithm::Rhythm);
800        let found = searcher.run(&line)?;
801        assert_eq!(
802            found.iter().map(|found| found.index).collect::<Vec<_>>(),
803            [1, 4]
804        );
805        assert!(StreamSearcher::new(Vec::new()).run(&line).is_err());
806        Ok(())
807    }
808
809    fn note_element(name: &str) -> StreamElement {
810        StreamElement::Note(Note::from_name(name).expect("a note"))
811    }
812
813    #[test]
814    fn notes_are_translated_as_music21_translates_them() -> Result<()> {
815        // Each read off music21's translate functions for the same line.
816        let line = from_tiny_notation("3/4 c4 d8 r8 r4 e4~ e8 f8")?;
817        let notes = notes_and_rests(&line);
818        assert_eq!(
819            translate_stream_to_string(&notes).text,
820            "<P>F\u{7f}F\u{7f}P@Ps@FeAF"
821        );
822        assert_eq!(translate_diatonic_stream_to_string(&notes)?.text, "CRZLT");
823        assert_eq!(translate_intervals_and_speed(&notes)?.text, "Ib ,c");
824        Ok(())
825    }
826}