sva_samples/measure/harmony/
chords.rs1use super::{Moment, Partial, above};
4use crate::measure::pitch::{Note, midi_of, name_peaks};
5use crate::measure::spectrum::{Peak, db};
6
7pub const METHOD: &str = "per moment: the partials at or above the floor, named as `pitch` names \
8 peaks (25 Hz to 5 kHz, a peak at a near-whole multiple of another in \
9 the series the greatest subharmonic sum heads, or as sharp of it as \
10 the stiff string that series fits, dropped as its harmonic), each \
11 rounded to its nearest equal-tempered note; consecutive moments naming one set are one chord, and a moment \
12 naming none ends one; a note whose partial's frequency bounds round \
13 to another note in any of its moments is listed as unresolved";
14
15#[derive(Clone, Debug, PartialEq)]
19pub struct Chord {
20 pub start_secs: f64,
21 pub end_secs: f64,
22 pub notes: Vec<i64>,
23 pub unresolved: Vec<i64>,
24}
25
26fn nearest(hz: f64) -> i64 {
27 midi_of(hz).round() as i64
28}
29
30pub fn named(moment: &Moment, floor_db: f64) -> Vec<(Partial, Note)> {
33 let mut kept = above(moment, floor_db);
34 kept.sort_by(|a, b| b.amplitude.total_cmp(&a.amplitude));
35 let found: Vec<Peak> = kept
36 .iter()
37 .map(|p| Peak {
38 hz: p.hz,
39 db: db(p.amplitude),
40 })
41 .collect();
42 name_peaks(&found, usize::MAX)
43 .into_iter()
44 .map(|note| {
45 let partial = *kept
46 .iter()
47 .find(|p| p.hz == note.hz)
48 .expect("each name is one kept partial's");
49 (partial, note)
50 })
51 .collect()
52}
53
54pub fn notes_at(moment: &Moment, floor_db: f64) -> (Vec<i64>, Vec<i64>) {
55 let (mut notes, mut unresolved) = (Vec::new(), Vec::new());
56 for (partial, named) in named(moment, floor_db) {
57 if named.harmonic_of.is_some() {
58 continue;
59 }
60 let note = nearest(named.hz);
61 notes.push(note);
62 let bounds = partial.hz_bounds;
63 if nearest(bounds.0) != note || nearest(bounds.1) != note {
64 unresolved.push(note);
65 }
66 }
67 for held in [&mut notes, &mut unresolved] {
68 held.sort_unstable();
69 held.dedup();
70 }
71 (notes, unresolved)
72}
73
74pub fn segment(moments: &[Moment], hop_secs: f64, until_secs: f64, floor_db: f64) -> Vec<Chord> {
75 let mut chords: Vec<Chord> = Vec::new();
76 let mut open = false;
77 for moment in moments {
78 let (notes, unresolved) = notes_at(moment, floor_db);
79 let end_secs = (moment.t_secs + hop_secs).min(until_secs);
80 match chords.last_mut() {
81 Some(last) if open && last.notes == notes => {
82 last.end_secs = end_secs;
83 last.unresolved.extend(unresolved);
84 last.unresolved.sort_unstable();
85 last.unresolved.dedup();
86 }
87 _ if notes.is_empty() => {}
88 _ => chords.push(Chord {
89 start_secs: moment.t_secs,
90 end_secs,
91 notes: notes.clone(),
92 unresolved,
93 }),
94 }
95 open = !notes.is_empty();
96 }
97 chords
98}