use libm::Libm;
pub const C4_HZ: f64 = 261.625_565_300_598_6;
pub const GATE_HIGH_V: f64 = 5.0;
pub const GATE_THRESHOLD_V: f64 = 2.5;
#[inline]
pub fn voct_to_hz(voct: f64) -> f64 {
C4_HZ * Libm::<f64>::pow(2.0, voct)
}
#[inline]
pub fn env_coef(time_seconds: f64, sample_rate: f64) -> f64 {
let denom = time_seconds * sample_rate;
if denom <= 0.0 {
return 0.0;
}
Libm::<f64>::exp(-1.0 / denom)
}
#[inline]
pub fn read_interpolated(buffer: &[f64], write_pos: usize, delay_samples: f64) -> f64 {
let buffer_len = buffer.len();
let delay_int = delay_samples as usize;
let frac = delay_samples - delay_int as f64;
let read_pos1 = (write_pos + buffer_len - delay_int) % buffer_len;
let read_pos2 = (write_pos + buffer_len - delay_int - 1) % buffer_len;
let sample1 = buffer[read_pos1];
let sample2 = buffer[read_pos2];
sample1 * (1.0 - frac) + sample2 * frac
}
#[inline]
pub fn db_to_gain(db: f64) -> f64 {
Libm::<f64>::pow(10.0, db / 20.0)
}
#[inline]
pub fn gain_to_db(gain: f64) -> f64 {
20.0 * Libm::<f64>::log10(gain)
}
#[inline]
pub fn sanitize_audio(x: f64) -> f64 {
if x.is_finite() {
x
} else {
0.0
}
}
#[allow(dead_code)]
#[inline]
pub fn flush_denorm(x: f64) -> f64 {
if Libm::<f64>::fabs(x) < 1e-20 {
0.0
} else {
x
}
}
#[inline]
pub fn polyblep(t: f64, dt: f64) -> f64 {
if t < dt {
let t = t / dt;
2.0 * t - t * t - 1.0
} else if t > 1.0 - dt {
let t = (t - 1.0) / dt;
t * t + 2.0 * t + 1.0
} else {
0.0
}
}
#[inline]
pub fn polyblamp(t: f64, dt: f64) -> f64 {
if t < dt {
let t = t / dt - 1.0;
-1.0 / 3.0 * t * t * t
} else if t > 1.0 - dt {
let t = (t - 1.0) / dt + 1.0;
1.0 / 3.0 * t * t * t
} else {
0.0
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct EdgeDetector {
prev: f64,
}
impl EdgeDetector {
pub fn new() -> Self {
Self { prev: 0.0 }
}
#[inline]
pub fn rising(&mut self, v: f64) -> bool {
self.rising_above(v, GATE_THRESHOLD_V)
}
#[inline]
pub fn rising_above(&mut self, v: f64, threshold: f64) -> bool {
let rising = v > threshold && self.prev <= threshold;
self.prev = v;
rising
}
#[inline]
pub fn rising_frac(&mut self, v: f64) -> Option<f64> {
self.rising_frac_above(v, GATE_THRESHOLD_V)
}
#[inline]
pub fn rising_frac_above(&mut self, v: f64, threshold: f64) -> Option<f64> {
let prev = self.prev;
self.prev = v;
if v > threshold && prev <= threshold {
let denom = v - prev;
let frac = if denom > 0.0 {
((threshold - prev) / denom).clamp(0.0, 1.0)
} else {
1.0
};
Some(frac)
} else {
None
}
}
#[inline]
pub fn reset(&mut self) {
self.prev = 0.0;
}
}
#[cfg(test)]
pub(crate) const SAFE_AUDIO_LIMIT: f64 = 10.0;
#[cfg(test)]
pub(crate) fn measure_max_output<F>(samples: usize, mut tick_fn: F) -> f64
where
F: FnMut() -> f64,
{
let mut max_abs = 0.0f64;
for _ in 0..samples {
let out = tick_fn();
max_abs = max_abs.max(out.abs());
}
max_abs
}