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 pub fn hash_value(
275 &self,
276 key: &str,
277 element: Option<&StreamElement>,
278 chord: Option<&crate::chord::Chord>,
279 offset: FloatType,
280 previous_note: Option<&Pitch>,
281 ) -> Result<HashValue> {
282 let named: &'static str = match key {
283 "Pitch" => "Pitch",
284 "Octave" => "Octave",
285 "Duration" => "Duration",
286 "Offset" => "Offset",
287 "IntervalFromLastNote" => "IntervalFromLastNote",
288 "IsAccidental" => "IsAccidental",
289 "NormalOrderString" => "NormalOrderString",
290 "PrimeFormString" => "PrimeFormString",
291 _ => return Err(crate::Error::Analysis(format!("no hash holds a {key}"))),
292 };
293 if named == "Octave"
299 && self.hash_chords_as_chords
300 && matches!(element, Some(StreamElement::Chord(_)))
301 {
302 return Ok(HashValue::Integer(-1));
303 }
304 let hashed = match (element, chord) {
305 (Some(StreamElement::Note(note)), Some(chord)) => Hashed::InChord(note, chord),
306 (Some(element), _) => Hashed::Alone(element),
307 (None, Some(chord)) => Hashed::Chord(chord),
308 (None, None) => {
309 return Err(crate::Error::Analysis(
310 "nothing was given to hash".to_string(),
311 ));
312 }
313 };
314 let values = self.values(&[named], hashed, offset, previous_note)?;
315 values
316 .into_iter()
317 .next()
318 .ok_or_else(|| crate::Error::Analysis(format!("no hash holds a {key}")))
319 }
320
321 fn values(
322 &self,
323 keys: &[&'static str],
324 hashed: Hashed<'_>,
325 offset: FloatType,
326 previous_note: Option<&Pitch>,
327 ) -> Result<Vec<HashValue>> {
328 let as_chord =
329 matches!(hashed, Hashed::InChord(..) | Hashed::Chord(_)) && self.hash_chords_as_chords;
330 let pitch: Option<&Pitch> = match &hashed {
331 Hashed::Alone(StreamElement::Note(note)) => Some(note.pitch()),
332 Hashed::InChord(note, _) => Some(note.pitch()),
333 _ => None,
334 };
335 let is_rest = matches!(hashed, Hashed::Alone(StreamElement::Rest(_)));
336 let chord = match &hashed {
337 Hashed::InChord(_, chord) | Hashed::Chord(chord) => Some(*chord),
338 _ => None,
339 };
340 let length = match &hashed {
341 Hashed::Alone(element) => element.quarter_length(),
342 Hashed::InChord(_, chord) | Hashed::Chord(chord) => chord
343 .duration()
344 .map_or(1.0, crate::duration::Duration::quarter_length),
345 };
346 let mut values = Vec::new();
347 for key in keys {
348 values.push(match *key {
349 "Pitch" => {
350 if as_chord {
351 if self.hash_midi {
352 HashValue::Integer(1)
353 } else {
354 HashValue::Text("z".to_string())
355 }
356 } else if is_rest {
357 if self.hash_midi {
358 HashValue::Integer(0)
359 } else {
360 HashValue::Text("r".to_string())
361 }
362 } else if let Some(pitch) = pitch {
363 let name = pitch_str(pitch);
364 if self.hash_midi {
365 HashValue::Integer(i64::from(pitch.midi()))
366 } else if self.hash_note_name_octave {
367 let mut name = name;
368 name.pop();
369 HashValue::Text(name)
370 } else {
371 HashValue::Text(name)
372 }
373 } else {
374 HashValue::None
375 }
376 }
377 "IsAccidental" => HashValue::None,
378 "Octave" => {
379 if matches!(hashed, Hashed::Chord(_)) {
380 return Err(crate::error::Error::Analysis(
383 "a chord hashed whole has no octave to hash".to_string(),
384 ));
385 }
386 if is_rest {
387 HashValue::Integer(-1)
388 } else {
389 pitch
390 .and_then(Pitch::octave)
391 .map_or(HashValue::None, |octave| {
392 HashValue::Integer(i64::from(octave))
393 })
394 }
395 }
396 "NormalOrderString" => match chord {
397 Some(chord) => HashValue::Text(chord.normal_order_string()),
398 None => HashValue::Text("<>".to_string()),
399 },
400 "PrimeFormString" => match chord {
401 Some(chord) => HashValue::Text(chord.prime_form_string()),
402 None => HashValue::Text("<>".to_string()),
403 },
404 "Duration" => HashValue::Float(if self.round_duration_and_offset {
405 self.rounded(length)
406 } else {
407 length
408 }),
409 "Offset" => HashValue::Float(if self.round_duration_and_offset {
410 self.rounded(offset)
411 } else {
412 offset
413 }),
414 "IntervalFromLastNote" => match (&hashed, previous_note) {
415 (Hashed::Alone(StreamElement::Note(note)), Some(previous)) => {
416 let interval = Interval::between_pitches(previous, note.pitch())?;
417 HashValue::Integer(i64::from(interval.interval_class()))
418 }
419 _ => HashValue::None,
420 },
421 _ => HashValue::None,
422 });
423 }
424 Ok(values)
425 }
426}
427
428fn pitch_str(pitch: &Pitch) -> String {
432 let modifier = match pitch
433 .written_accidental()
434 .map(|accidental| accidental.name())
435 {
436 None | Some("natural") => String::new(),
437 Some("sharp") => "#".to_string(),
438 Some("double-sharp") => "##".to_string(),
439 Some("triple-sharp") => "###".to_string(),
440 Some("quadruple-sharp") => "####".to_string(),
441 Some("flat") => "-".to_string(),
442 Some("double-flat") => "--".to_string(),
443 Some("triple-flat") => "---".to_string(),
444 Some("quadruple-flat") => "----".to_string(),
445 Some("half-sharp") => "~".to_string(),
446 Some("one-and-a-half-sharp") => "#~".to_string(),
447 Some("half-flat") => "`".to_string(),
448 Some("one-and-a-half-flat") => "-`".to_string(),
449 Some(_) => pitch.accidental().modifier().to_string(),
450 };
451 let octave = pitch
452 .octave()
453 .map(|octave| octave.to_string())
454 .unwrap_or_default();
455 format!("{}{modifier}{octave}", pitch.step().as_char())
456}
457
458struct Placed {
461 container: Option<usize>,
462 part: Option<usize>,
463 offset: FloatType,
464 sort_order: i32,
465 grace: bool,
466 pitch: Option<Pitch>,
467 leaf: bool,
468}
469
470fn notes_before(stream: &Stream) -> Vec<Option<Pitch>> {
476 fn walk(
477 stream: &Stream,
478 container: Option<usize>,
479 part: Option<usize>,
480 base: FloatType,
481 out: &mut Vec<Placed>,
482 ) {
483 for event in stream.events() {
484 let element = event.element();
485 let offset = crate::makenotation::op_frac(base + event.offset());
486 let index = out.len();
487 out.push(Placed {
488 container,
489 part,
490 offset,
491 sort_order: element.class_sort_order(),
492 grace: element
493 .duration()
494 .is_some_and(crate::duration::Duration::is_grace),
495 pitch: match element {
496 StreamElement::Note(note) => Some(note.pitch().clone()),
497 _ => None,
498 },
499 leaf: !matches!(element, StreamElement::Stream(_)),
500 });
501 if let StreamElement::Stream(inner) = element {
502 let inner_part = if matches!(inner.kind(), StreamKind::Part | StreamKind::PartStaff)
503 {
504 Some(index)
505 } else {
506 part
507 };
508 walk(inner, Some(index), inner_part, offset, out);
509 }
510 }
511 }
512 let mut placed = Vec::new();
513 walk(stream, None, None, 0.0, &mut placed);
514 let mut flattened: std::collections::BTreeMap<Option<usize>, Vec<usize>> =
516 std::collections::BTreeMap::new();
517 for (index, item) in placed.iter().enumerate() {
518 if item.leaf {
519 flattened.entry(item.part).or_default().push(index);
520 }
521 }
522 let mut place_in_part = vec![0; placed.len()];
523 for leaves in flattened.values_mut() {
524 leaves.sort_by(|&a, &b| {
525 placed[a]
526 .offset
527 .total_cmp(&placed[b].offset)
528 .then(placed[a].sort_order.cmp(&placed[b].sort_order))
529 .then(placed[b].grace.cmp(&placed[a].grace))
530 });
531 for (place, &index) in leaves.iter().enumerate() {
532 place_in_part[index] = place;
533 }
534 }
535 let mut last_in: std::collections::HashMap<Option<usize>, usize> =
537 std::collections::HashMap::new();
538 let mut out = vec![None; placed.len()];
539 for index in 0..placed.len() {
540 if placed[index].pitch.is_none() {
541 continue;
542 }
543 let container = placed[index].container;
544 out[index] = match last_in.get(&container) {
545 Some(&before) => placed[before].pitch.clone(),
546 None => flattened[&placed[index].part][..place_in_part[index]]
547 .iter()
548 .rev()
549 .find_map(|&before| placed[before].pitch.clone()),
550 };
551 last_in.insert(container, index);
552 }
553 out
554}
555
556fn recurse_with_local_offsets(stream: &Stream) -> Vec<(FloatType, StreamElement)> {
559 fn walk(stream: &Stream, out: &mut Vec<(FloatType, StreamElement)>) {
560 for event in stream.events() {
561 out.push((event.offset(), event.element().clone()));
562 if let StreamElement::Stream(inner) = event.element() {
563 walk(inner, out);
564 }
565 }
566 }
567 let mut out = Vec::new();
568 walk(stream, &mut out);
569 out
570}
571
572#[cfg(test)]
573mod tests {
574 use super::*;
575 use crate::{chord::Chord, duration::Duration, note::Note, rest::Rest};
576
577 #[test]
578 fn notes_chords_and_rests_are_hashed_as_music21_hashes_them() -> Result<()> {
579 let mut stream = Stream::new();
581 stream.push(Note::from_name("C4")?.with_duration(Duration::new(2.0)?));
582 stream.push(Note::from_name("F#4")?);
583 stream.push(Note::from_name("B-2")?);
584 stream.push(Chord::new("C4 G4 E-5")?.with_duration(Duration::new(2.0)?));
585 stream.push(Rest::new(Duration::new(1.5)?));
586 let hashes: Vec<Vec<HashValue>> = Hasher::default()
587 .hash_stream(&stream)?
588 .into_iter()
589 .map(|hash| hash.values)
590 .collect();
591 let expected = [
592 (60, 2.0, 0.0),
593 (66, 1.0, 2.0),
594 (46, 1.0, 3.0),
595 (60, 2.0, 4.0),
596 (67, 2.0, 4.0),
597 (75, 2.0, 4.0),
598 (0, 1.5, 6.0),
599 ];
600 let expected: Vec<Vec<HashValue>> = expected
601 .iter()
602 .map(|(pitch, length, offset)| {
603 vec![
604 HashValue::Integer(*pitch),
605 HashValue::Float(*length),
606 HashValue::Float(*offset),
607 ]
608 })
609 .collect();
610 assert_eq!(hashes, expected);
611 Ok(())
612 }
613}