1use crate::{
5 defaults::IntegerType,
6 error::{Error, Result},
7 stream::{Stream, StreamElement},
8};
9
10use super::{Searched, Translation, difflib, recursed, translate_stream_to_string_no_rhythm};
11
12#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct Segments {
16 pub segments: Vec<String>,
18 pub measures: Vec<(Option<IntegerType>, Option<IntegerType>)>,
20}
21
22fn sorted_notes(stream: &Stream) -> Vec<Searched<'_>> {
26 let mut notes: Vec<Searched<'_>> = recursed(stream)
27 .into_iter()
28 .filter(|found| {
29 matches!(
30 found.element,
31 StreamElement::Note(_)
32 | StreamElement::Chord(_)
33 | StreamElement::Unpitched(_)
34 | StreamElement::PercussionChord(_)
35 | StreamElement::ChordSymbol(_)
36 )
37 })
38 .collect();
39 let grace = |element: &StreamElement| {
40 element
41 .duration()
42 .is_some_and(crate::duration::Duration::is_grace)
43 };
44 notes.sort_by(|left, right| {
45 left.offset
46 .total_cmp(&right.offset)
47 .then_with(|| {
48 left.element
49 .class_sort_order()
50 .cmp(&right.element.class_sort_order())
51 })
52 .then_with(|| grace(right.element).cmp(&grace(left.element)))
53 });
54 notes
55}
56
57pub fn translate_monophonic_part_to_segments(
74 part: &Stream,
75 segment_length: usize,
76 overlap: usize,
77 translate: fn(&[Searched<'_>]) -> Translation,
78) -> Result<Segments> {
79 let step = segment_length
80 .checked_sub(overlap)
81 .filter(|step| *step > 0)
82 .ok_or_else(|| Error::Search("the overlap must be shorter than a segment".to_string()))?;
83 let notes = sorted_notes(part);
84 let translation = translate(¬es);
85 let text: Vec<char> = translation.text.chars().collect();
86 let total = text.len();
87 let count = total.div_ceil(step);
88 let measure_at = |index: usize| -> Result<Option<IntegerType>> {
89 translation
90 .measures
91 .get(index)
92 .copied()
93 .ok_or_else(|| Error::Search("list index out of range".to_string()))
94 };
95 let mut segments = Segments {
96 segments: Vec::with_capacity(count),
97 measures: Vec::with_capacity(count),
98 };
99 for number in 0..count {
100 let start = (number * step).min(total - 1);
101 let end = (start + segment_length).min(total);
102 segments.segments.push(text[start..end].iter().collect());
103 segments
104 .measures
105 .push((measure_at(start)?, measure_at(end - 1)?));
106 }
107 Ok(segments)
108}
109
110pub fn index_score_parts(score: &Stream) -> Result<Vec<Segments>> {
118 index_score_parts_with(score, 30, 12, translate_stream_to_string_no_rhythm)
119}
120
121pub fn index_score_parts_with(
127 score: &Stream,
128 segment_length: usize,
129 overlap: usize,
130 translate: fn(&[Searched<'_>]) -> Translation,
131) -> Result<Vec<Segments>> {
132 score
133 .parts()
134 .into_iter()
135 .map(|part| translate_monophonic_part_to_segments(part, segment_length, overlap, translate))
136 .collect()
137}
138
139#[derive(Clone, Debug, PartialEq, Eq)]
141pub struct SegmentAt {
142 pub score: String,
144 pub part: usize,
146 pub segment: usize,
148 pub measures: (Option<IntegerType>, Option<IntegerType>),
150}
151
152#[derive(Clone, Debug, PartialEq)]
154pub struct SegmentSimilarity {
155 pub this: SegmentAt,
157 pub that: SegmentAt,
159 pub ratio: crate::defaults::FloatType,
161}
162
163pub fn score_similarity(
169 index: &[(String, Vec<Segments>)],
170 minimum_length: usize,
171 include_reverse: bool,
172) -> Vec<SegmentSimilarity> {
173 let long_enough = |segment: &str| segment.chars().count() >= minimum_length;
174 let at = |score: &str, part: usize, segment: usize, segments: &Segments| SegmentAt {
175 score: score.to_string(),
176 part,
177 segment,
178 measures: segments.measures[segment],
179 };
180 let mut rows = Vec::new();
181 for (this_number, (this_name, this_score)) in index.iter().enumerate() {
182 for (this_part, this_segments) in this_score.iter().enumerate() {
183 for (this_segment, this_text) in this_segments.segments.iter().enumerate() {
184 if !long_enough(this_text) {
185 continue;
186 }
187 let this_chars: Vec<char> = this_text.chars().collect();
188 let this_at = at(this_name, this_part, this_segment, this_segments);
189 for (that_name, that_score) in &index[this_number + 1..] {
190 for (that_part, that_segments) in that_score.iter().enumerate() {
191 for (that_segment, that_text) in that_segments.segments.iter().enumerate() {
192 if !long_enough(that_text) {
193 continue;
194 }
195 let that_chars: Vec<char> = that_text.chars().collect();
196 let ratio = difflib::ratio(&that_chars, &this_chars);
199 let that_at = at(that_name, that_part, that_segment, that_segments);
200 rows.push(SegmentSimilarity {
201 this: this_at.clone(),
202 that: that_at.clone(),
203 ratio,
204 });
205 if include_reverse {
206 rows.push(SegmentSimilarity {
207 this: that_at,
208 that: this_at.clone(),
209 ratio,
210 });
211 }
212 }
213 }
214 }
215 }
216 }
217 }
218 rows
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use crate::tinynotation::from_tiny_notation;
225
226 #[test]
227 fn a_part_is_cut_into_overlapping_segments() -> Result<()> {
228 let line = from_tiny_notation("4/4 c4 d e f g a b c' d' e' f' g'")?;
231 let cut = translate_monophonic_part_to_segments(
232 &line,
233 4,
234 2,
235 translate_stream_to_string_no_rhythm,
236 )?;
237 assert_eq!(cut.segments, ["<>@A", "@ACE", "CEGH", "GHJL", "JLMO", "MO"]);
238 assert_eq!(
239 cut.measures,
240 [
241 (Some(1), Some(1)),
242 (Some(1), Some(2)),
243 (Some(2), Some(2)),
244 (Some(2), Some(3)),
245 (Some(3), Some(3)),
246 (Some(3), Some(3)),
247 ]
248 );
249 assert!(
250 translate_monophonic_part_to_segments(
251 &line,
252 4,
253 4,
254 translate_stream_to_string_no_rhythm
255 )
256 .is_err()
257 );
258 Ok(())
259 }
260}