1use crate::{
5 defaults::FloatType,
6 error::Result,
7 interval::Interval,
8 pitch::Pitch,
9 stream::{Stream, StreamElement, StreamKind},
10};
11
12#[derive(Clone, Debug, PartialEq)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub enum HashValue {
16 Integer(i64),
18 Float(FloatType),
20 Text(String),
22 None,
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31pub struct HashReference {
32 pub element: usize,
34 pub component: Option<usize>,
36}
37
38#[derive(Clone, Debug, PartialEq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub struct NoteHash {
43 pub keys: Vec<String>,
45 pub values: Vec<HashValue>,
47 pub reference: Option<HashReference>,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
56pub struct Hasher {
57 pub include_reference: bool,
59 pub hash_pitch: bool,
61 pub hash_midi: bool,
63 pub hash_note_name_octave: bool,
66 pub hash_octave: bool,
68 pub hash_duration: bool,
70 pub round_duration_and_offset: bool,
72 pub hash_offset: bool,
74 pub granularity: u32,
76 pub hash_interval_from_last_note: bool,
79 pub hash_is_accidental: bool,
82 pub hash_is_tied: bool,
84 pub hash_chords_as_notes: bool,
86 pub hash_chords_as_chords: bool,
88 pub hash_normal_order_string: bool,
91 pub hash_prime_form_string: bool,
94}
95
96impl Default for Hasher {
97 fn default() -> Self {
98 Self {
99 include_reference: false,
100 hash_pitch: true,
101 hash_midi: true,
102 hash_note_name_octave: false,
103 hash_octave: false,
104 hash_duration: true,
105 round_duration_and_offset: true,
106 hash_offset: true,
107 granularity: 32,
108 hash_interval_from_last_note: false,
109 hash_is_accidental: false,
110 hash_is_tied: false,
111 hash_chords_as_notes: true,
112 hash_chords_as_chords: false,
113 hash_normal_order_string: false,
114 hash_prime_form_string: false,
115 }
116 }
117}
118
119enum Hashed<'a> {
122 Alone(&'a StreamElement),
123 InChord(&'a crate::note::Note, &'a crate::chord::Chord),
124 Chord(&'a crate::chord::Chord),
125}
126
127impl Hasher {
128 pub fn tuple_list(&self) -> Vec<&'static str> {
131 let mut keys = Vec::new();
132 if self.hash_pitch {
133 keys.push("Pitch");
134 if self.hash_is_accidental {
135 keys.push("IsAccidental");
136 }
137 }
138 if self.hash_octave {
139 keys.push("Octave");
140 }
141 if !self.hash_chords_as_notes && self.hash_chords_as_chords {
142 if self.hash_normal_order_string {
143 keys.push("NormalOrderString");
144 }
145 if self.hash_prime_form_string {
146 keys.push("PrimeFormString");
147 }
148 }
149 if self.hash_duration {
150 keys.push("Duration");
151 }
152 if self.hash_offset {
153 keys.push("Offset");
154 }
155 if self.hash_interval_from_last_note {
156 keys.push("IntervalFromLastNote");
157 }
158 keys
159 }
160
161 fn rounded(&self, value: FloatType) -> FloatType {
164 let granularity = FloatType::from(self.granularity);
165 (value * granularity).round_ties_even() / granularity
166 }
167
168 pub fn hash_stream(&self, stream: &Stream) -> Result<Vec<NoteHash>> {
182 let keys = self.tuple_list();
183 let walked = recurse_with_local_offsets(stream);
184 let mut hashes = Vec::new();
185 let previous_notes = if self.hash_interval_from_last_note {
186 notes_before(stream)
187 } else {
188 vec![None; walked.len()]
189 };
190 for (index, (offset, element)) in walked.iter().enumerate() {
191 match element {
192 StreamElement::Note(_) => {
193 let values = self.values(
194 &keys,
195 Hashed::Alone(element),
196 *offset,
197 previous_notes[index].as_ref(),
198 )?;
199 hashes.push(self.hash(&keys, values, index, None));
200 }
201 StreamElement::Rest(_) => {
202 let values = self.values(
203 &keys,
204 Hashed::Alone(element),
205 *offset,
206 previous_notes[index].as_ref(),
207 )?;
208 hashes.push(self.hash(&keys, values, index, None));
209 }
210 StreamElement::ChordSymbol(symbol) => {
211 let mut chord = crate::chord::Chord::new(symbol.pitches()?.as_slice())?;
214 chord.set_duration(symbol.duration().clone());
215 self.hash_chord(&keys, &chord, *offset, index, &mut hashes)?;
216 }
217 StreamElement::Chord(chord) => {
218 self.hash_chord(&keys, chord, *offset, index, &mut hashes)?;
219 }
220 _ => {}
221 }
222 }
223 Ok(hashes)
224 }
225
226 fn hash_chord(
227 &self,
228 keys: &[&'static str],
229 chord: &crate::chord::Chord,
230 offset: FloatType,
231 index: usize,
232 hashes: &mut Vec<NoteHash>,
233 ) -> Result<()> {
234 if self.hash_chords_as_notes {
235 for (component, note) in chord.notes().iter().enumerate() {
236 let values = self.values(keys, Hashed::InChord(note, chord), offset, None)?;
237 hashes.push(self.hash(keys, values, index, Some(component)));
238 }
239 } else if self.hash_chords_as_chords {
240 let values = self.values(keys, Hashed::Chord(chord), offset, None)?;
241 hashes.push(self.hash(keys, values, index, None));
242 }
243 Ok(())
244 }
245
246 fn hash(
247 &self,
248 keys: &[&'static str],
249 values: Vec<HashValue>,
250 element: usize,
251 component: Option<usize>,
252 ) -> NoteHash {
253 NoteHash {
254 keys: keys.iter().map(|key| (*key).to_string()).collect(),
255 values,
256 reference: self
257 .include_reference
258 .then_some(HashReference { element, component }),
259 }
260 }
261
262 fn values(
263 &self,
264 keys: &[&'static str],
265 hashed: Hashed<'_>,
266 offset: FloatType,
267 previous_note: Option<&Pitch>,
268 ) -> Result<Vec<HashValue>> {
269 let as_chord =
270 matches!(hashed, Hashed::InChord(..) | Hashed::Chord(_)) && self.hash_chords_as_chords;
271 let pitch: Option<&Pitch> = match &hashed {
272 Hashed::Alone(StreamElement::Note(note)) => Some(note.pitch()),
273 Hashed::InChord(note, _) => Some(note.pitch()),
274 _ => None,
275 };
276 let is_rest = matches!(hashed, Hashed::Alone(StreamElement::Rest(_)));
277 let chord = match &hashed {
278 Hashed::InChord(_, chord) | Hashed::Chord(chord) => Some(*chord),
279 _ => None,
280 };
281 let length = match &hashed {
282 Hashed::Alone(element) => element.quarter_length(),
283 Hashed::InChord(_, chord) | Hashed::Chord(chord) => chord
284 .duration()
285 .map_or(1.0, crate::duration::Duration::quarter_length),
286 };
287 let mut values = Vec::new();
288 for key in keys {
289 values.push(match *key {
290 "Pitch" => {
291 if as_chord {
292 if self.hash_midi {
293 HashValue::Integer(1)
294 } else {
295 HashValue::Text("z".to_string())
296 }
297 } else if is_rest {
298 if self.hash_midi {
299 HashValue::Integer(0)
300 } else {
301 HashValue::Text("r".to_string())
302 }
303 } else if let Some(pitch) = pitch {
304 let name = pitch_str(pitch);
305 if self.hash_midi {
306 HashValue::Integer(i64::from(pitch.midi()))
307 } else if self.hash_note_name_octave {
308 let mut name = name;
309 name.pop();
310 HashValue::Text(name)
311 } else {
312 HashValue::Text(name)
313 }
314 } else {
315 HashValue::None
316 }
317 }
318 "IsAccidental" => HashValue::None,
319 "Octave" => {
320 if matches!(hashed, Hashed::Chord(_)) {
321 return Err(crate::error::Error::Analysis(
324 "'NoneType' object has no attribute 'octave'".to_string(),
325 ));
326 }
327 if is_rest {
328 HashValue::Integer(-1)
329 } else {
330 pitch
331 .and_then(Pitch::octave)
332 .map_or(HashValue::None, |octave| {
333 HashValue::Integer(i64::from(octave))
334 })
335 }
336 }
337 "NormalOrderString" => match chord {
338 Some(chord) => HashValue::Text(chord.normal_order_string()),
339 None => HashValue::Text("<>".to_string()),
340 },
341 "PrimeFormString" => match chord {
342 Some(chord) => HashValue::Text(chord.prime_form_string()),
343 None => HashValue::Text("<>".to_string()),
344 },
345 "Duration" => HashValue::Float(if self.round_duration_and_offset {
346 self.rounded(length)
347 } else {
348 length
349 }),
350 "Offset" => HashValue::Float(if self.round_duration_and_offset {
351 self.rounded(offset)
352 } else {
353 offset
354 }),
355 "IntervalFromLastNote" => match (&hashed, previous_note) {
356 (Hashed::Alone(StreamElement::Note(note)), Some(previous)) => {
357 let interval = Interval::between_pitches(previous, note.pitch())?;
358 HashValue::Integer(i64::from(interval.interval_class()))
359 }
360 _ => HashValue::None,
361 },
362 _ => HashValue::None,
363 });
364 }
365 Ok(values)
366 }
367}
368
369fn pitch_str(pitch: &Pitch) -> String {
373 let modifier = match pitch
374 .written_accidental()
375 .map(|accidental| accidental.name())
376 {
377 None | Some("natural") => String::new(),
378 Some("sharp") => "#".to_string(),
379 Some("double-sharp") => "##".to_string(),
380 Some("triple-sharp") => "###".to_string(),
381 Some("quadruple-sharp") => "####".to_string(),
382 Some("flat") => "-".to_string(),
383 Some("double-flat") => "--".to_string(),
384 Some("triple-flat") => "---".to_string(),
385 Some("quadruple-flat") => "----".to_string(),
386 Some("half-sharp") => "~".to_string(),
387 Some("one-and-a-half-sharp") => "#~".to_string(),
388 Some("half-flat") => "`".to_string(),
389 Some("one-and-a-half-flat") => "-`".to_string(),
390 Some(_) => pitch.accidental().modifier().to_string(),
391 };
392 let octave = pitch
393 .octave()
394 .map(|octave| octave.to_string())
395 .unwrap_or_default();
396 format!("{}{modifier}{octave}", pitch.step().as_char())
397}
398
399struct Placed {
402 container: Option<usize>,
403 part: Option<usize>,
404 offset: FloatType,
405 sort_order: i32,
406 grace: bool,
407 pitch: Option<Pitch>,
408 leaf: bool,
409}
410
411fn notes_before(stream: &Stream) -> Vec<Option<Pitch>> {
417 fn walk(
418 stream: &Stream,
419 container: Option<usize>,
420 part: Option<usize>,
421 base: FloatType,
422 out: &mut Vec<Placed>,
423 ) {
424 for event in stream.events() {
425 let element = event.element();
426 let offset = crate::makenotation::op_frac(base + event.offset());
427 let index = out.len();
428 out.push(Placed {
429 container,
430 part,
431 offset,
432 sort_order: element.class_sort_order(),
433 grace: element
434 .duration()
435 .is_some_and(crate::duration::Duration::is_grace),
436 pitch: match element {
437 StreamElement::Note(note) => Some(note.pitch().clone()),
438 _ => None,
439 },
440 leaf: !matches!(element, StreamElement::Stream(_)),
441 });
442 if let StreamElement::Stream(inner) = element {
443 let inner_part = if matches!(inner.kind(), StreamKind::Part | StreamKind::PartStaff)
444 {
445 Some(index)
446 } else {
447 part
448 };
449 walk(inner, Some(index), inner_part, offset, out);
450 }
451 }
452 }
453 let mut placed = Vec::new();
454 walk(stream, None, None, 0.0, &mut placed);
455 let mut flattened: std::collections::BTreeMap<Option<usize>, Vec<usize>> =
457 std::collections::BTreeMap::new();
458 for (index, item) in placed.iter().enumerate() {
459 if item.leaf {
460 flattened.entry(item.part).or_default().push(index);
461 }
462 }
463 let mut place_in_part = vec![0; placed.len()];
464 for leaves in flattened.values_mut() {
465 leaves.sort_by(|&a, &b| {
466 placed[a]
467 .offset
468 .total_cmp(&placed[b].offset)
469 .then(placed[a].sort_order.cmp(&placed[b].sort_order))
470 .then(placed[b].grace.cmp(&placed[a].grace))
471 });
472 for (place, &index) in leaves.iter().enumerate() {
473 place_in_part[index] = place;
474 }
475 }
476 let mut last_in: std::collections::HashMap<Option<usize>, usize> =
478 std::collections::HashMap::new();
479 let mut out = vec![None; placed.len()];
480 for index in 0..placed.len() {
481 if placed[index].pitch.is_none() {
482 continue;
483 }
484 let container = placed[index].container;
485 out[index] = match last_in.get(&container) {
486 Some(&before) => placed[before].pitch.clone(),
487 None => flattened[&placed[index].part][..place_in_part[index]]
488 .iter()
489 .rev()
490 .find_map(|&before| placed[before].pitch.clone()),
491 };
492 last_in.insert(container, index);
493 }
494 out
495}
496
497fn recurse_with_local_offsets(stream: &Stream) -> Vec<(FloatType, StreamElement)> {
500 fn walk(stream: &Stream, out: &mut Vec<(FloatType, StreamElement)>) {
501 for event in stream.events() {
502 out.push((event.offset(), event.element().clone()));
503 if let StreamElement::Stream(inner) = event.element() {
504 walk(inner, out);
505 }
506 }
507 }
508 let mut out = Vec::new();
509 walk(stream, &mut out);
510 out
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516 use crate::{chord::Chord, duration::Duration, note::Note, rest::Rest};
517
518 #[test]
519 fn notes_chords_and_rests_are_hashed_as_music21_hashes_them() -> Result<()> {
520 let mut stream = Stream::new();
522 stream.push(Note::from_name("C4")?.with_duration(Duration::new(2.0)?));
523 stream.push(Note::from_name("F#4")?);
524 stream.push(Note::from_name("B-2")?);
525 stream.push(Chord::new("C4 G4 E-5")?.with_duration(Duration::new(2.0)?));
526 stream.push(Rest::new(Duration::new(1.5)?));
527 let hashes: Vec<Vec<HashValue>> = Hasher::default()
528 .hash_stream(&stream)?
529 .into_iter()
530 .map(|hash| hash.values)
531 .collect();
532 let expected = [
533 (60, 2.0, 0.0),
534 (66, 1.0, 2.0),
535 (46, 1.0, 3.0),
536 (60, 2.0, 4.0),
537 (67, 2.0, 4.0),
538 (75, 2.0, 4.0),
539 (0, 1.5, 6.0),
540 ];
541 let expected: Vec<Vec<HashValue>> = expected
542 .iter()
543 .map(|(pitch, length, offset)| {
544 vec![
545 HashValue::Integer(*pitch),
546 HashValue::Float(*length),
547 HashValue::Float(*offset),
548 ]
549 })
550 .collect();
551 assert_eq!(hashes, expected);
552 Ok(())
553 }
554}