Skip to main content

music21/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    /// Whether the element and the term match, do not match, or this
160    /// algorithm has no opinion and the next is to be asked: music21's
161    /// own three answers, for a caller comparing one pair at a time
162    /// rather than running a whole search.
163    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/// Which elements of the stream a [`StreamSearcher`] looks at.
186#[derive(Clone, Copy, Debug, PartialEq, Eq)]
187pub enum Filter {
188    /// Notes and chords alone.
189    Notes,
190    /// Notes, chords and rests: music21's `GeneralNote`s.
191    NotesAndRests,
192}
193
194/// A run of elements a search found: music21's `search.base`
195/// `SearchMatch`.
196#[derive(Clone, Debug)]
197pub struct SearchMatch<'a> {
198    /// Where the run starts among the elements searched.
199    pub index: usize,
200    /// The elements, as many as were searched for.
201    pub elements: Vec<Searched<'a>>,
202}
203
204/// Searches a stream for runs of elements matching a list of terms, the
205/// terms compared by a list of algorithms: music21's `StreamSearcher`. A
206/// run matches unless an algorithm says one of its elements does not;
207/// with only the wildcard algorithm, as it starts, every run matches.
208///
209/// ```
210/// use music21::duration::Duration;
211/// use music21::note::Note;
212/// use music21::search::{Algorithm, Filter, SearchTerm, StreamSearcher};
213/// use music21::stream::StreamElement;
214/// use music21::tinynotation::from_tiny_notation;
215///
216/// let line = from_tiny_notation("3/4 c4. d8 e4 g4. a8 f4 c'8 d'4.")?;
217/// let mut searcher = StreamSearcher::new(vec![
218///     SearchTerm::Element(StreamElement::Note(
219///         Note::from_name("C4")?.with_duration(Duration::new(1.5)?),
220///     )),
221///     SearchTerm::Element(StreamElement::Note(
222///         Note::from_name("C4")?.with_duration(Duration::new(0.5)?),
223///     )),
224/// ]);
225/// searcher.recurse = true;
226/// searcher.filter = Some(Filter::Notes);
227/// searcher.algorithms.push(Algorithm::Rhythm);
228/// let found = searcher.run(&line)?;
229/// assert_eq!(found.iter().map(|found| found.index).collect::<Vec<_>>(), [0, 3]);
230/// # Ok::<(), music21::Error>(())
231/// ```
232#[derive(Clone, Debug)]
233pub struct StreamSearcher {
234    /// What to look for, in order.
235    pub search: Vec<SearchTerm>,
236    /// Whether to look inside the streams the stream holds.
237    pub recurse: bool,
238    /// Which elements to look at; all of them for none.
239    pub filter: Option<Filter>,
240    /// How to compare, in order, each asked until one answers.
241    pub algorithms: Vec<Algorithm>,
242}
243
244impl StreamSearcher {
245    /// A search for these terms, of the stream's own elements, by the
246    /// wildcard algorithm alone.
247    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    /// The runs of a stream's elements that match: music21's `run`.
257    ///
258    /// # Errors
259    ///
260    /// Nothing to search for.
261    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    /// The runs of these elements that match: music21's `run` of a stream
284    /// iterator.
285    ///
286    /// # Errors
287    ///
288    /// Nothing to search for.
289    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
319/// Where each run of elements starts whose every element `matches` the term
320/// lined up with it: music21's `streamSearchBase`.
321fn 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
345/// Where each run of elements starts whose lengths are those searched for,
346/// a wildcard or a term of any length matching any: music21's
347/// `rhythmicSearch`.
348///
349/// # Errors
350///
351/// Nothing to search for.
352pub 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
368/// Where each run of elements starts whose names -- a note's pitch name,
369/// `rest` -- are those searched for, a wildcard matching any: music21's
370/// `noteNameSearch`.
371///
372/// # Errors
373///
374/// Nothing to search for.
375pub 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
381/// Where each run of elements starts whose names and lengths are those
382/// searched for: music21's `noteNameRhythmicSearch`.
383///
384/// # Errors
385///
386/// Nothing to search for.
387pub 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
403/// The first pitch of a note, chord or chord symbol, which music21's
404/// translations read.
405fn 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
414/// The tie an element carries, if it can carry one.
415fn 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
426/// An element as one character: a note's MIDI number, a chord's first
427/// pitch's, and 127 for anything else -- a rest, an unpitched stroke, a
428/// chord of nothing: music21's `translateNoteToByte`.
429pub 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
442/// An element's length as one character, ten times the base-two logarithm
443/// of 256 times its quarter length, kept within one to 127: music21's
444/// `translateDurationToBytes`.
445pub 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
455/// An element's tie as text: `s` for one starting, `c` for one going on,
456/// `e` for one ending, and nothing otherwise: music21's
457/// `translateNoteTieToByte`.
458pub 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
467/// An element as its pitch, length and, if asked, tie characters:
468/// music21's `translateNoteWithDurationToBytes`.
469pub 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/// Notes and rests as text to search, and the measure each piece of it
480/// comes from.
481#[derive(Clone, Debug, PartialEq, Eq)]
482pub struct Translation {
483    /// The text.
484    pub text: String,
485    /// The measure of each note or rest the text was made from, in order:
486    /// music21's `returnMeasures`.
487    pub measures: Vec<Option<IntegerType>>,
488}
489
490/// Notes and rests as text, each its pitch, length and tie characters:
491/// music21's `translateStreamToString`.
492pub 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
502/// Notes and rests as text of their pitches alone: music21's
503/// `translateStreamToStringNoRhythm`.
504pub 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
514/// Notes and rests as text of their lengths alone: music21's
515/// `translateStreamToStringOnlyRhythm`.
516pub 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
526/// Walks notes and rests as music21's diatonic and interval translations
527/// do: a run of rests read once as `rest`, a note tied on from the one
528/// before passed over, and each other note handed its first pitch and
529/// whether it is as long as the note before (nought), shorter (one) or
530/// longer (two).
531fn 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
576/// Notes and rests as text of their steps, a letter for each note --
577/// `A` to `G` where it is as long as the note before, seven letters on
578/// where it is longer, fourteen where it is shorter -- and `Z` for a run of
579/// rests: music21's `translateDiatonicStreamToString`. A note tied on from
580/// the one before is left out.
581///
582/// # Errors
583///
584/// An element with no pitch that is not a rest.
585pub 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
592/// Notes and rests as text of the intervals between them, up to thirteen
593/// semitones either way, and whether each is as long as, shorter or longer
594/// than the one before, a space for a run of rests: music21's
595/// `translateIntervalsAndSpeed`. The first note's interval is from the
596/// first note.
597///
598/// # Errors
599///
600/// An element with no pitch that is not a rest.
601pub 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
618/// How alike two stretches of text are, as music21's approximate searches
619/// measure it: Python's `difflib.SequenceMatcher(None, a, b).ratio()`.
620pub 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
626/// What `read` makes of the notes and rests of a stream flattened, every
627/// part's together in the order they sound: what music21's approximate
628/// searches translate.
629fn 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(&notes)
636}
637
638/// The streams, best match first, each with how alike it is: music21 sorts
639/// by one less the likeness, keeping the order of equals.
640fn 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
645/// How alike each other stream is to this one, as the text of their notes
646/// and rests -- pitch, length and tie -- best first: music21's
647/// `approximateNoteSearch`. Each is the other stream's index and its
648/// likeness, music21's `matchProbability`.
649pub 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
655/// The same, by pitch alone: music21's `approximateNoteSearchNoRhythm`.
656pub 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
665/// The same, by length alone: music21's `approximateNoteSearchOnlyRhythm`.
666pub 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
693/// The same, three parts by pitch to one by length: music21's
694/// `approximateNoteSearchWeighted`.
695pub 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/// A rhythm the measures of a stream share, and which measures share it.
721#[derive(Clone, Debug)]
722pub struct MeasureRhythm<'a> {
723    /// How many measures have it.
724    pub count: usize,
725    /// The rhythm as [`translate_stream_to_string_only_rhythm`] writes it.
726    pub rhythm: String,
727    /// The measures, in order.
728    pub measures: Vec<&'a Stream>,
729}
730
731/// A rhythm the measures of a stream share, and where each of those
732/// measures stands: the by-position twin of [`MeasureRhythm`], for a
733/// caller holding the objects the stream was read from.
734#[derive(Clone, Debug, PartialEq, Eq)]
735pub struct MeasureRhythmPlaces {
736    /// How many measures have it.
737    pub count: usize,
738    /// The rhythm as [`translate_stream_to_string_only_rhythm`] writes it.
739    pub rhythm: String,
740    /// Where each of those measures stands among the measures
741    /// [`recursed`] meets, in order.
742    pub measures: Vec<usize>,
743}
744
745/// The rhythms of a stream's measures, nested ones included, most common
746/// first, those as common in the order first met: music21's
747/// `mostCommonMeasureRhythms`, without the copy of each first measure
748/// transposed to start on C5 that music21 keeps beside it.
749pub 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
765/// The same by position: each rhythm with where the measures that have it
766/// stand among the measures [`recursed`] meets, which is the order
767/// music21's own `streamIn[Measure]` hands them over.
768pub 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
792/// Every measure a stream holds, nested ones included, in the order
793/// [`recursed`] meets them.
794fn 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        // music21's StreamSearcher, rhythmicSearch and noteNameSearch
823        // doctests, on "3/4 c4. d8 e4 g4. a8 f4 c'8 d'4.".
824        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(&notes, &[note("C4", 1.5), note("C4", 0.5)])?,
828            [0, 3]
829        );
830        assert_eq!(
831            note_name_search(
832                &notes,
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        // Each read off music21's translate functions for the same line.
861        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(&notes).text,
865            "<P>F\u{7f}F\u{7f}P@Ps@FeAF"
866        );
867        assert_eq!(translate_diatonic_stream_to_string(&notes)?.text, "CRZLT");
868        assert_eq!(translate_intervals_and_speed(&notes)?.text, "Ib ,c");
869        Ok(())
870    }
871}