pub mod chords;
pub mod dissonance;
pub mod inharmonicity;
pub mod measured;
pub mod tonnetz;
pub mod voice_leading;
use std::collections::HashMap;
use sva_formula::C64;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Partial {
pub hz: f64,
pub amplitude: f64,
pub hz_bounds: (f64, f64),
pub amplitude_bounds: (f64, f64),
}
#[derive(Clone, Debug, PartialEq)]
pub struct Moment {
pub t_secs: f64,
pub partials: Vec<Partial>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Framing {
pub hop_secs: f64,
pub window: Option<Window>,
}
impl Framing {
pub fn method(&self) -> &'static str {
match self.window {
None => EXACT_METHOD,
Some(_) => measured::METHOD,
}
}
}
pub const EXACT_METHOD: &str = "exact: at each instant every atom of each channel of the closed \
form is a partial at its carrier frequency, its value there \
summed with the channel's others at that frequency, and a \
cosine's two conjugate lines one partial; every channel in \
power, each partial's squared amplitude summed over the \
channels at its frequency, then divided by the channel count";
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Window {
pub size: usize,
pub secs: f64,
pub resolution_hz: f64,
}
pub fn partials(lines: impl IntoIterator<Item = (f64, C64)>) -> Vec<Partial> {
let mut at: HashMap<u64, C64> = HashMap::new();
let mut order: Vec<f64> = Vec::new();
for (hz, amp) in lines {
let summed = at.entry(hz.to_bits()).or_insert_with(|| {
order.push(hz);
C64::ZERO
});
*summed = *summed + amp;
}
let modulus = |hz: f64| at.get(&hz.to_bits()).map_or(0.0, |c| c.abs());
order
.into_iter()
.filter(|hz| *hz > 0.0)
.map(|hz| {
let amplitude = modulus(hz) + modulus(-hz);
Partial {
hz,
amplitude,
hz_bounds: (hz, hz),
amplitude_bounds: (amplitude, amplitude),
}
})
.filter(|p| p.amplitude > 0.0)
.collect()
}
pub fn averaged(per_channel: Vec<Vec<Partial>>) -> Vec<Partial> {
if per_channel.len() == 1 {
return per_channel.into_iter().flatten().collect();
}
let count = per_channel.len() as f64;
let mut power: HashMap<u64, f64> = HashMap::new();
let mut order: Vec<f64> = Vec::new();
for p in per_channel.iter().flatten() {
*power.entry(p.hz.to_bits()).or_insert_with(|| {
order.push(p.hz);
0.0
}) += p.amplitude * p.amplitude;
}
order
.into_iter()
.map(|hz| {
let amplitude = (power[&hz.to_bits()] / count).sqrt();
Partial {
hz,
amplitude,
hz_bounds: (hz, hz),
amplitude_bounds: (amplitude, amplitude),
}
})
.collect()
}
pub fn above(moment: &Moment, floor_db: f64) -> Vec<Partial> {
let floor = 10f64.powf(floor_db / 20.0);
moment
.partials
.iter()
.filter(|p| p.amplitude >= floor)
.copied()
.collect()
}