mod interval;
mod raw;
pub use {interval::Interval, raw::RawFreq};
use crate::prelude::*;
use std::ops::{Div, DivAssign, Mul, MulAssign};
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Freq {
pub samples: f64,
}
impl Freq {
pub const ZERO: Self = Self::new(0.0);
#[must_use]
pub const fn new(samples: f64) -> Self {
Self { samples }
}
#[must_use]
pub fn from_raw(raw: RawFreq, sample_rate: unt::SampleRate) -> Self {
raw / sample_rate
}
#[must_use]
pub fn from_raw_default(raw: RawFreq) -> Self {
Self::from_raw(raw, unt::SampleRate::default())
}
#[must_use]
pub fn from_hz(hz: f64, sample_rate: unt::SampleRate) -> Self {
Self::from_raw(RawFreq::new(hz), sample_rate)
}
#[must_use]
pub fn from_hz_default(hz: f64) -> Self {
Self::from_hz(hz, unt::SampleRate::default())
}
#[must_use]
pub fn into_raw(self, sample_rate: unt::SampleRate) -> RawFreq {
RawFreq::new(self.samples * f64::from(sample_rate))
}
#[must_use]
pub fn into_raw_default(self) -> RawFreq {
self.into_raw(unt::SampleRate::default())
}
#[must_use]
pub fn angular(self) -> f64 {
self.samples * std::f64::consts::TAU
}
#[must_use]
pub fn bend_edo(self, edo: u16, bend: f64) -> Self {
Interval::edo_note(edo, bend) * self
}
#[must_use]
pub fn bend(self, bend: f64) -> Self {
self.bend_edo(12, bend)
}
#[must_use]
pub fn new_midi(a4: Self, note: unt::MidiNote) -> Self {
a4.bend(f64::from(note.note) - unt::A4_MIDI)
}
#[must_use]
fn round_midi_aux(self, a4: Self) -> f64 {
(self.samples / a4.samples).log2() * 12.0 + unt::A4_MIDI
}
#[must_use]
pub fn round_midi(self, a4: Self) -> unt::MidiNote {
#[allow(clippy::cast_possible_truncation)]
unt::MidiNote::new((self.round_midi_aux(a4).round()) as i16)
}
#[must_use]
pub fn midi_semitones(self, a4: Self) -> (unt::MidiNote, f64) {
let note = self.round_midi_aux(a4);
let round = note.round();
#[allow(clippy::cast_possible_truncation)]
(unt::MidiNote::new(round as i16), note - round)
}
}
impl RawFreq {
#[must_use]
pub fn from_freq_with(freq: Freq, sample_rate: unt::SampleRate) -> Self {
Self::new(freq.samples * f64::from(sample_rate))
}
#[must_use]
pub fn from_freq(freq: Freq) -> Self {
Self::from_freq_with(freq, unt::SampleRate::default())
}
}
impl Default for Freq {
fn default() -> Self {
Freq::from_raw(RawFreq::default(), unt::SampleRate::default())
}
}
impl std::fmt::Display for Freq {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:.*} samples^-1",
f.precision().unwrap_or_default(),
self.samples,
)
}
}
impl Mul<f64> for Freq {
type Output = Self;
fn mul(self, rhs: f64) -> Self {
Self::new(self.samples * rhs)
}
}
impl Mul<Freq> for f64 {
type Output = Freq;
fn mul(self, rhs: Freq) -> Freq {
rhs * self
}
}
impl MulAssign<f64> for Freq {
fn mul_assign(&mut self, rhs: f64) {
self.samples *= rhs;
}
}
impl Div<f64> for Freq {
type Output = Self;
fn div(self, rhs: f64) -> Self {
Self::new(self.samples / rhs)
}
}
impl DivAssign<f64> for Freq {
fn div_assign(&mut self, rhs: f64) {
self.samples /= rhs;
}
}
impl Mul<Interval> for Freq {
type Output = Self;
fn mul(self, rhs: Interval) -> Self {
rhs.ratio * self
}
}
impl Mul<Freq> for Interval {
type Output = Freq;
fn mul(self, rhs: Freq) -> Freq {
self.ratio * rhs
}
}
impl MulAssign<Interval> for Freq {
fn mul_assign(&mut self, rhs: Interval) {
self.samples *= rhs.ratio;
}
}
impl Div<Interval> for Freq {
type Output = Self;
fn div(self, rhs: Interval) -> Self {
Self::new(self.samples / rhs.ratio)
}
}
impl DivAssign<Interval> for Freq {
fn div_assign(&mut self, rhs: Interval) {
self.samples /= rhs.ratio;
}
}
impl Div for Freq {
type Output = Interval;
fn div(self, rhs: Freq) -> Interval {
Interval::new(self.samples / rhs.samples)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn print_a4() {
assert_eq!(format!("{:#}", RawFreq::A4.bend(0.4)), "A4 +40c");
assert_eq!(format!("{:#.2}", RawFreq::A4.bend(0.4)), "A4 +40.00c");
}
#[test]
fn parse_a4() {
let a4: RawFreq = "A4".parse().expect("note could not be parsed");
assert_approx_eq::assert_approx_eq!(a4.hz, RawFreq::A4.hz);
}
#[test]
fn print_freq() {
assert_eq!(format!("{:.6}", Freq::default()), "0.009977 samples^-1")
}
}