use crate::{prelude::*, units::A4_MIDI};
use std::{
fmt::{Debug, Display, Formatter, Result as FmtResult},
ops::{Div, DivAssign, Mul, MulAssign},
str::FromStr,
};
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct RawFreq {
pub hz: f64,
}
impl Default for RawFreq {
fn default() -> Self {
RawFreq::A4
}
}
impl Display for RawFreq {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
if f.alternate() {
let (note, semitones) = self.midi_semitones();
let cents = semitones * 100.0;
write!(f, "{note} {cents:+.*}c", f.precision().unwrap_or_default())
} else {
write!(f, "{} Hz", self.hz)
}
}
}
impl RawFreq {
#[must_use]
pub const fn new(hz: f64) -> Self {
Self { hz }
}
pointillism_macros::freq!();
#[must_use]
pub fn period(&self) -> unt::RawTime {
unt::RawTime::new(1.0 / self.hz)
}
#[must_use]
pub fn bend_edo(self, edo: u16, bend: f64) -> Self {
unt::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_with(a4: Self, note: unt::MidiNote) -> Self {
a4.bend(f64::from(note.note) - A4_MIDI)
}
#[must_use]
pub fn new_midi(note: unt::MidiNote) -> Self {
Self::new_midi_with(RawFreq::A4, note)
}
#[must_use]
fn round_midi_aux(self, a4: Self) -> f64 {
(self.hz / a4.hz).log2() * 12.0 + A4_MIDI
}
#[must_use]
pub fn round_midi_with(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 round_midi(self) -> unt::MidiNote {
self.round_midi_with(RawFreq::A4)
}
#[must_use]
pub fn midi_semitones_with(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)
}
#[must_use]
pub fn midi_semitones(self) -> (unt::MidiNote, f64) {
self.midi_semitones_with(RawFreq::A4)
}
}
impl From<unt::MidiNote> for RawFreq {
fn from(note: unt::MidiNote) -> Self {
Self::new_midi(note)
}
}
impl FromStr for RawFreq {
type Err = crate::units::midi::NameError;
fn from_str(name: &str) -> Result<Self, Self::Err> {
unt::MidiNote::from_str(name).map(RawFreq::from)
}
}
impl Mul<f64> for RawFreq {
type Output = Self;
fn mul(self, rhs: f64) -> Self {
Self::new(self.hz * rhs)
}
}
impl Mul<RawFreq> for f64 {
type Output = RawFreq;
fn mul(self, rhs: RawFreq) -> RawFreq {
rhs * self
}
}
impl MulAssign<f64> for RawFreq {
fn mul_assign(&mut self, rhs: f64) {
self.hz *= rhs;
}
}
impl Div<f64> for RawFreq {
type Output = Self;
fn div(self, rhs: f64) -> Self {
Self::new(self.hz / rhs)
}
}
impl DivAssign<f64> for RawFreq {
fn div_assign(&mut self, rhs: f64) {
self.hz /= rhs;
}
}
impl Mul<unt::Interval> for RawFreq {
type Output = Self;
fn mul(self, rhs: unt::Interval) -> Self {
rhs.ratio * self
}
}
impl Mul<RawFreq> for unt::Interval {
type Output = RawFreq;
fn mul(self, rhs: RawFreq) -> RawFreq {
self.ratio * rhs
}
}
impl MulAssign<unt::Interval> for RawFreq {
fn mul_assign(&mut self, rhs: unt::Interval) {
self.hz *= rhs.ratio;
}
}
impl Div<unt::Interval> for RawFreq {
type Output = Self;
fn div(self, rhs: unt::Interval) -> Self {
Self::new(self.hz / rhs.ratio)
}
}
impl DivAssign<unt::Interval> for RawFreq {
fn div_assign(&mut self, rhs: unt::Interval) {
self.hz /= rhs.ratio;
}
}
impl Div for RawFreq {
type Output = unt::Interval;
fn div(self, rhs: Self) -> unt::Interval {
unt::Interval::new(self.hz / rhs.hz)
}
}