use crate::{cent::Cent, check_float, Result};
use core::fmt;
use std::{
fmt::Display,
ops::{Add, Div, Mul, Sub},
};
#[derive(Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Frequency(pub f64);
impl Frequency {
pub fn adding(&self, cents: Cent) -> Result<Self> {
check_float(f64::powf(2.0, f64::log2(self.0) + (cents.0 / 1200.0)))
.map_err(|| format!("Adding {} cents to {} resulted in NaN", cents.0, self.0))
.map(Frequency)
}
pub fn subtracting(&self, cents: Cent) -> Result<Self> {
check_float(f64::powf(2.0, f64::log2(self.0) - (cents.0 / 1200.0)))
.map_err(|| {
format!(
"Subtracting {} cents from {} resulted in NaN",
cents.0, self.0
)
})
.map(Frequency)
}
pub fn cents_to(&self, rhs: Self) -> Result<Cent> {
check_float(f64::log2(rhs.0 / self.0) * 1200.0)
.map_err(|| {
format!(
"Calculating distance from {} to {} resulted in NaN",
self.0, rhs.0
)
})
.map(Cent)
}
pub fn halfway_to(&self, frequency: Frequency) -> Result<Self> {
let distance = self.cents_to(frequency)?;
self.adding(distance / 2)
}
}
impl Add for Frequency {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Frequency(self.0 + rhs.0)
}
}
impl Add<f64> for Frequency {
type Output = Self;
fn add(self, rhs: f64) -> Self::Output {
Self(self.0 + rhs)
}
}
impl Sub for Frequency {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Frequency(self.0 - rhs.0)
}
}
impl Sub<f64> for Frequency {
type Output = Self;
fn sub(self, rhs: f64) -> Self::Output {
Self(self.0 - rhs)
}
}
impl Mul for Frequency {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Frequency(self.0 * rhs.0)
}
}
impl Mul<f64> for Frequency {
type Output = Self;
fn mul(self, rhs: f64) -> Self::Output {
Frequency(self.0 * rhs)
}
}
impl Div for Frequency {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
Frequency(self.0 / rhs.0)
}
}
impl Div<f64> for Frequency {
type Output = Self;
fn div(self, rhs: f64) -> Self::Output {
Frequency(self.0 / rhs)
}
}
impl PartialOrd for Frequency {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl Display for Frequency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}Hz", self.0))
}
}
impl fmt::Debug for Frequency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}Hz", self.0))
}
}
#[cfg(test)]
mod tests {
use crate::{
frequency::Frequency,
pitch::Pitch,
temperaments::{equal::EqualTemperament, table::FrequencyTable},
test_utils::{assert_flt_eq, assert_flt_ne},
};
#[test]
fn test_freq_subtraction() {
let eqt = FrequencyTable::new::<EqualTemperament>(Frequency(440.0), Pitch::A4).unwrap();
println!(
"Cents from A4 ({}) to Bb4 ({}): {}",
eqt.bb4,
eqt.a4,
eqt.bb4.cents_to(eqt.a4).unwrap()
)
}
#[test]
fn test_halfway_point() {
let pitch_1 = Frequency(440.0);
let pitch_2 = Frequency(466.1637615180902);
let midpoint_hz = (pitch_1 + pitch_2) / 2.0;
let midpoint_cents = pitch_1
.adding(pitch_1.cents_to(pitch_2).unwrap() / 2)
.unwrap();
let halfway = pitch_1.halfway_to(pitch_2).unwrap();
assert_flt_ne!(halfway, midpoint_hz);
assert_flt_eq!(halfway, midpoint_cents);
}
}