sva_samples/measure/harmony/
mod.rs1pub mod chords;
4pub mod dissonance;
5pub mod inharmonicity;
6pub mod measured;
7pub mod tonnetz;
8pub mod voice_leading;
9
10use std::collections::HashMap;
11
12use sva_formula::C64;
13
14#[derive(Clone, Copy, Debug, PartialEq)]
15pub struct Partial {
16 pub hz: f64,
17 pub amplitude: f64,
18 pub hz_bounds: (f64, f64),
20 pub amplitude_bounds: (f64, f64),
21}
22
23#[derive(Clone, Debug, PartialEq)]
24pub struct Moment {
25 pub t_secs: f64,
26 pub partials: Vec<Partial>,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq)]
30pub struct Framing {
31 pub hop_secs: f64,
32 pub window: Option<Window>,
33}
34
35impl Framing {
36 pub fn method(&self) -> &'static str {
37 match self.window {
38 None => EXACT_METHOD,
39 Some(_) => measured::METHOD,
40 }
41 }
42}
43
44pub const EXACT_METHOD: &str = "exact: at each instant every atom of each channel of the closed \
45 form is a partial at its carrier frequency, its value there \
46 summed with the channel's others at that frequency, and a \
47 cosine's two conjugate lines one partial; every channel in \
48 power, each partial's squared amplitude summed over the \
49 channels at its frequency, then divided by the channel count";
50
51#[derive(Clone, Copy, Debug, PartialEq)]
52pub struct Window {
53 pub size: usize,
54 pub secs: f64,
55 pub resolution_hz: f64,
56}
57
58pub fn partials(lines: impl IntoIterator<Item = (f64, C64)>) -> Vec<Partial> {
61 let mut at: HashMap<u64, C64> = HashMap::new();
62 let mut order: Vec<f64> = Vec::new();
63 for (hz, amp) in lines {
64 let summed = at.entry(hz.to_bits()).or_insert_with(|| {
65 order.push(hz);
66 C64::ZERO
67 });
68 *summed = *summed + amp;
69 }
70 let modulus = |hz: f64| at.get(&hz.to_bits()).map_or(0.0, |c| c.abs());
71 order
72 .into_iter()
73 .filter(|hz| *hz > 0.0)
74 .map(|hz| {
75 let amplitude = modulus(hz) + modulus(-hz);
76 Partial {
77 hz,
78 amplitude,
79 hz_bounds: (hz, hz),
80 amplitude_bounds: (amplitude, amplitude),
81 }
82 })
83 .filter(|p| p.amplitude > 0.0)
84 .collect()
85}
86
87pub fn averaged(per_channel: Vec<Vec<Partial>>) -> Vec<Partial> {
89 if per_channel.len() == 1 {
90 return per_channel.into_iter().flatten().collect();
91 }
92 let count = per_channel.len() as f64;
93 let mut power: HashMap<u64, f64> = HashMap::new();
94 let mut order: Vec<f64> = Vec::new();
95 for p in per_channel.iter().flatten() {
96 *power.entry(p.hz.to_bits()).or_insert_with(|| {
97 order.push(p.hz);
98 0.0
99 }) += p.amplitude * p.amplitude;
100 }
101 order
102 .into_iter()
103 .map(|hz| {
104 let amplitude = (power[&hz.to_bits()] / count).sqrt();
105 Partial {
106 hz,
107 amplitude,
108 hz_bounds: (hz, hz),
109 amplitude_bounds: (amplitude, amplitude),
110 }
111 })
112 .collect()
113}
114
115pub fn above(moment: &Moment, floor_db: f64) -> Vec<Partial> {
116 let floor = 10f64.powf(floor_db / 20.0);
117 moment
118 .partials
119 .iter()
120 .filter(|p| p.amplitude >= floor)
121 .copied()
122 .collect()
123}