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
22pub fn notes_sorted(stream: &Stream) -> Vec<Searched<'_>> {
30 let mut notes: Vec<Searched<'_>> = recursed(stream)
31 .into_iter()
32 .filter(|found| {
33 matches!(
34 found.element,
35 StreamElement::Note(_)
36 | StreamElement::Chord(_)
37 | StreamElement::Unpitched(_)
38 | StreamElement::PercussionChord(_)
39 | StreamElement::ChordSymbol(_)
40 )
41 })
42 .collect();
43 let grace = |element: &StreamElement| {
44 element
45 .duration()
46 .is_some_and(crate::duration::Duration::is_grace)
47 };
48 notes.sort_by(|left, right| {
49 left.offset
50 .total_cmp(&right.offset)
51 .then_with(|| {
52 left.element
53 .class_sort_order()
54 .cmp(&right.element.class_sort_order())
55 })
56 .then_with(|| grace(right.element).cmp(&grace(left.element)))
57 });
58 notes
59}
60
61pub fn translate_monophonic_part_to_segments(
78 part: &Stream,
79 segment_length: usize,
80 overlap: usize,
81 translate: fn(&[Searched<'_>]) -> Translation,
82) -> Result<Segments> {
83 let notes = notes_sorted(part);
84 cut_translation_into_segments(&translate(¬es), segment_length, overlap)
85}
86
87pub fn cut_translation_into_segments(
98 translation: &Translation,
99 segment_length: usize,
100 overlap: usize,
101) -> Result<Segments> {
102 let step = segment_length
103 .checked_sub(overlap)
104 .filter(|step| *step > 0)
105 .ok_or_else(|| Error::Search("the overlap must be shorter than a segment".to_string()))?;
106 let text: Vec<char> = translation.text.chars().collect();
107 let total = text.len();
108 let count = total.div_ceil(step);
109 let measure_at = |index: usize| -> Result<Option<IntegerType>> {
110 translation
111 .measures
112 .get(index)
113 .copied()
114 .ok_or_else(|| Error::Search(format!("no measure is recorded for note {index}")))
115 };
116 let mut segments = Segments {
117 segments: Vec::with_capacity(count),
118 measures: Vec::with_capacity(count),
119 };
120 for number in 0..count {
121 let start = (number * step).min(total - 1);
122 let end = (start + segment_length).min(total);
123 segments.segments.push(text[start..end].iter().collect());
124 segments
125 .measures
126 .push((measure_at(start)?, measure_at(end - 1)?));
127 }
128 Ok(segments)
129}
130
131pub fn index_score_parts(score: &Stream) -> Result<Vec<Segments>> {
139 index_score_parts_with(score, 30, 12, translate_stream_to_string_no_rhythm)
140}
141
142pub fn index_score_parts_with(
148 score: &Stream,
149 segment_length: usize,
150 overlap: usize,
151 translate: fn(&[Searched<'_>]) -> Translation,
152) -> Result<Vec<Segments>> {
153 score
154 .parts()
155 .into_iter()
156 .map(|part| translate_monophonic_part_to_segments(part, segment_length, overlap, translate))
157 .collect()
158}
159
160#[derive(Clone, Debug, PartialEq, Eq)]
162pub struct SegmentAt {
163 pub score: String,
165 pub part: usize,
167 pub segment: usize,
169 pub measures: (Option<IntegerType>, Option<IntegerType>),
171}
172
173#[derive(Clone, Debug, PartialEq)]
175pub struct SegmentSimilarity {
176 pub this: SegmentAt,
178 pub that: SegmentAt,
180 pub ratio: crate::defaults::FloatType,
182}
183
184pub fn score_similarity(
190 index: &[(String, Vec<Segments>)],
191 minimum_length: usize,
192 include_reverse: bool,
193) -> Vec<SegmentSimilarity> {
194 let long_enough = |segment: &str| segment.chars().count() >= minimum_length;
195 let at = |score: &str, part: usize, segment: usize, segments: &Segments| SegmentAt {
196 score: score.to_string(),
197 part,
198 segment,
199 measures: segments.measures[segment],
200 };
201 let mut rows = Vec::new();
202 for (this_number, (this_name, this_score)) in index.iter().enumerate() {
203 for (this_part, this_segments) in this_score.iter().enumerate() {
204 for (this_segment, this_text) in this_segments.segments.iter().enumerate() {
205 if !long_enough(this_text) {
206 continue;
207 }
208 let this_chars: Vec<char> = this_text.chars().collect();
209 let this_at = at(this_name, this_part, this_segment, this_segments);
210 for (that_name, that_score) in &index[this_number + 1..] {
211 for (that_part, that_segments) in that_score.iter().enumerate() {
212 for (that_segment, that_text) in that_segments.segments.iter().enumerate() {
213 if !long_enough(that_text) {
214 continue;
215 }
216 let that_chars: Vec<char> = that_text.chars().collect();
217 let ratio = difflib::ratio(&that_chars, &this_chars);
220 let that_at = at(that_name, that_part, that_segment, that_segments);
221 rows.push(SegmentSimilarity {
222 this: this_at.clone(),
223 that: that_at.clone(),
224 ratio,
225 });
226 if include_reverse {
227 rows.push(SegmentSimilarity {
228 this: that_at,
229 that: this_at.clone(),
230 ratio,
231 });
232 }
233 }
234 }
235 }
236 }
237 }
238 }
239 rows
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use crate::tinynotation::from_tiny_notation;
246
247 #[test]
248 fn a_part_is_cut_into_overlapping_segments() -> Result<()> {
249 let line = from_tiny_notation("4/4 c4 d e f g a b c' d' e' f' g'")?;
252 let cut = translate_monophonic_part_to_segments(
253 &line,
254 4,
255 2,
256 translate_stream_to_string_no_rhythm,
257 )?;
258 assert_eq!(cut.segments, ["<>@A", "@ACE", "CEGH", "GHJL", "JLMO", "MO"]);
259 assert_eq!(
260 cut.measures,
261 [
262 (Some(1), Some(1)),
263 (Some(1), Some(2)),
264 (Some(2), Some(2)),
265 (Some(2), Some(3)),
266 (Some(3), Some(3)),
267 (Some(3), Some(3)),
268 ]
269 );
270 assert!(
271 translate_monophonic_part_to_segments(
272 &line,
273 4,
274 4,
275 translate_stream_to_string_no_rhythm
276 )
277 .is_err()
278 );
279 Ok(())
280 }
281}