use serde::{Deserialize, Serialize};
#[cfg(feature = "micromath")]
#[allow(unused_imports)]
use micromath::F32Ext;
#[cfg(feature = "libm")]
#[allow(unused_imports)]
use num_traits::float::Float;
use crate::period_helper_cache::PeriodHelperCache;
#[derive(Default, Serialize, Deserialize, Clone, Copy, Debug)]
pub enum FrequencyType {
AmigaFrequencies,
#[default]
LinearFrequencies,
}
#[derive(Clone)]
pub struct PeriodHelper {
pub freq_type: FrequencyType,
historical: bool,
cache: PeriodHelperCache<10>,
}
impl Default for PeriodHelper {
fn default() -> Self {
Self {
freq_type: FrequencyType::LinearFrequencies,
historical: false,
cache: PeriodHelperCache::new(),
}
}
}
impl PeriodHelper {
pub const C4_FREQ: f32 = 8363.0;
pub fn new(freq_type: FrequencyType, historical: bool) -> Self {
Self {
freq_type,
historical,
cache: PeriodHelperCache::new(),
}
}
#[inline(always)]
fn linear_pitch_to_period(note: f32) -> f32 {
10.0 * 12.0 * 16.0 * 4.0 - note * 16.0 * 4.0
}
#[inline(always)]
fn linear_period_to_pitch(period: f32) -> f32 {
(10.0 * 12.0 * 16.0 * 4.0 - period) / (16.0 * 4.0)
}
#[inline(always)]
fn linear_period_to_frequency(period: f32) -> f32 {
Self::C4_FREQ * (2.0f32).powf((6.0 * 12.0 * 16.0 * 4.0 - period) / (12.0 * 16.0 * 4.0))
}
#[inline(always)]
fn linear_frequency_to_period(freq: f32) -> f32 {
(6.0 * 12.0 * 16.0 * 4.0) - (12.0 * 16.0 * 4.0) * (freq / Self::C4_FREQ).log2()
}
#[inline(always)]
fn amiga_pitch_to_period(note: f32) -> f32 {
6848.0 * (-0.0578 * note).exp() + 0.2782
}
#[inline(always)]
fn amiga_period_to_pitch(period: f32) -> f32 {
-f32::ln((period - 0.2782) / 6848.0) / 0.0578
}
#[inline(always)]
fn amiga_period_to_frequency(period: f32) -> f32 {
if period == 0.0 {
0.0
} else {
7093789.2 / (period * 2.0) }
}
#[inline(always)]
fn amiga_frequency_to_period(freq: f32) -> f32 {
if freq == 0.0 {
0.0
} else {
7093789.2 / (freq * 2.0) }
}
pub fn note_to_period(&self, note: f32) -> f32 {
match self.freq_type {
FrequencyType::LinearFrequencies => Self::linear_pitch_to_period(note),
FrequencyType::AmigaFrequencies => Self::amiga_pitch_to_period(note),
}
}
pub fn period_to_pitch(&self, period: f32) -> f32 {
match self.freq_type {
FrequencyType::LinearFrequencies => Self::linear_period_to_pitch(period),
FrequencyType::AmigaFrequencies => Self::amiga_period_to_pitch(period),
}
.max(0.0) }
pub fn period_to_frequency(&self, period: f32) -> f32 {
match self.freq_type {
FrequencyType::LinearFrequencies => Self::linear_period_to_frequency(period),
FrequencyType::AmigaFrequencies => Self::amiga_period_to_frequency(period),
}
}
pub fn frequency_to_period(&self, freq: f32) -> f32 {
match self.freq_type {
FrequencyType::LinearFrequencies => Self::linear_frequency_to_period(freq),
FrequencyType::AmigaFrequencies => Self::amiga_frequency_to_period(freq),
}
}
pub fn relative_pitch_to_c4freq(&self, relative_pitch: f32, finetune: f32) -> Option<f32> {
const NOTE_C4: f32 = 4.0 * 12.0;
const NOTE_B9: f32 = 10.0 * 12.0 - 1.0;
let note = NOTE_C4 + relative_pitch;
if !(0.0..=NOTE_B9).contains(¬e) {
return None;
}
let c4_period = self.note_to_period(note + finetune);
Some(self.period_to_frequency(c4_period))
}
pub fn c4freq_to_relative_pitch(&self, freq: f32) -> (i8, f32) {
const NOTE_C4: f32 = 4.0 * 12.0;
let period = self.frequency_to_period(freq);
let note = self.period_to_pitch(period);
let note_ceil = note.ceil();
let relative_pitch = note_ceil - NOTE_C4;
let finetune = note - note_ceil;
(relative_pitch as i8, finetune)
}
pub fn adjust_period(&self, period: f32, arp_pitch: f32, finetune: f32, semitone: bool) -> f32 {
let note_orig: f32 = self.period_to_pitch(period);
let note = if semitone {
note_orig.round()
} else {
note_orig
};
if self.historical && arp_pitch != 0.0 {
let mut note = note;
if note.ceil() >= 95.0 {
note = 95.0;
}
self.note_to_period(note + arp_pitch + finetune)
} else {
self.note_to_period(note + arp_pitch + finetune)
}
}
pub fn all_to_frequency(
&self,
period: f32,
arp_pitch: f32,
finetune: f32,
semitone: bool,
) -> f32 {
let period_adjusted = self.adjust_period(period, arp_pitch, finetune, semitone);
self.period_to_frequency(period_adjusted)
}
pub fn all_to_frequency_cached(
&mut self,
period: f32,
arp_pitch: f32,
finetune: f32,
semitone: bool,
) -> f32 {
if let Some(cached_freq) = self.cache.get(period, arp_pitch, finetune, semitone) {
return cached_freq;
}
let f = self.all_to_frequency(period, arp_pitch, finetune, semitone);
self.cache.insert(period, arp_pitch, finetune, semitone, f);
f
}
}