use crate::common::SampleRate;
use std::time::Duration;
pub(crate) const NANOS_PER_SEC: u64 = 1_000_000_000;
#[cfg(not(feature = "64bit"))]
pub use std::f32::consts::{E, LN_10, LN_2, LOG10_2, LOG10_E, LOG2_10, LOG2_E, PI, TAU};
#[cfg(feature = "64bit")]
pub use std::f64::consts::{E, LN_10, LN_2, LOG10_2, LOG10_E, LOG2_10, LOG2_E, PI, TAU};
#[inline]
pub(crate) fn lerp(first: Sample, second: Sample, numerator: u32, denominator: u32) -> Sample {
first + (second - first) * numerator as Float / denominator as Float
}
#[inline]
pub fn db_to_linear(decibels: Float) -> Float {
Float::powf(2.0, decibels * 0.05 * LOG2_10)
}
#[inline]
pub fn linear_to_db(linear: Float) -> Float {
linear.log2() * LOG10_2 * 20.0
}
#[must_use]
pub(crate) fn duration_to_coefficient(duration: Duration, sample_rate: SampleRate) -> Float {
Float::exp(-1.0 / (duration_to_float(duration) * sample_rate.get() as Float))
}
#[inline]
#[must_use]
pub(crate) fn duration_to_float(duration: Duration) -> Float {
#[cfg(not(feature = "64bit"))]
{
duration.as_secs_f32()
}
#[cfg(feature = "64bit")]
{
duration.as_secs_f64()
}
}
#[must_use]
pub(crate) fn nearest_multiple_of_two(n: u32) -> u32 {
if n <= 1 {
return 1;
}
let next = n.next_power_of_two();
let prev = next >> 1;
if n - prev <= next - n {
prev
} else {
next
}
}
#[macro_export]
macro_rules! nz {
($n:literal) => {
const { core::num::NonZero::new($n).unwrap() }
};
}
pub use nz;
use crate::{common::Float, Sample};
#[cfg(test)]
mod test {
use super::*;
use num_rational::Ratio;
use quickcheck::{quickcheck, TestResult};
quickcheck! {
fn lerp_random(first: Sample, second: Sample, numerator: u32, denominator: u32) -> TestResult {
if denominator == 0 { return TestResult::discard(); }
if first.abs() > 1.0 || second.abs() > 1.0 { return TestResult::discard(); }
if !first.is_finite() || !second.is_finite() { return TestResult::discard(); }
let (numerator, denominator) = Ratio::new(numerator, denominator).into_raw();
if numerator > 1000 { return TestResult::discard(); }
let a = first as f64;
let b = second as f64;
let c = numerator as f64 / denominator as f64;
if !(0.0..=1.0).contains(&c) { return TestResult::discard(); };
let reference = a * (1.0 - c) + b * c;
let x = lerp(first, second, numerator, denominator);
let tolerance = 1e-6;
TestResult::from_bool((x as f64 - reference).abs() < tolerance)
}
}
const DECIBELS_LINEAR_TABLE: [(Float, Float); 27] = [
(100., 100000.),
(90., 31623.),
(80., 10000.),
(70., 3162.),
(60., 1000.),
(50., 316.2),
(40., 100.),
(30., 31.62),
(20., 10.),
(10., 3.162),
(5.998, 1.995),
(3.003, 1.413),
(1.002, 1.122),
(0., 1.),
(-1.002, 0.891),
(-3.003, 0.708),
(-5.998, 0.501),
(-10., 0.3162),
(-20., 0.1),
(-30., 0.03162),
(-40., 0.01),
(-50., 0.003162),
(-60., 0.001),
(-70., 0.0003162),
(-80., 0.0001),
(-90., 0.00003162),
(-100., 0.00001),
];
#[test]
fn convert_decibels_to_linear() {
for (db, wikipedia_linear) in DECIBELS_LINEAR_TABLE {
let actual_linear = db_to_linear(db);
let magnitude_ratio = actual_linear / wikipedia_linear;
assert!(
magnitude_ratio > 0.99 && magnitude_ratio < 1.01,
"Result magnitude differs significantly from Wikipedia reference for {db}dB: Wikipedia {wikipedia_linear}, got {actual_linear}, ratio: {magnitude_ratio:.4}"
);
}
}
#[test]
fn convert_linear_to_decibels() {
for (expected_db, linear) in DECIBELS_LINEAR_TABLE {
let actual_db = linear_to_db(linear);
let magnitude_ratio = if expected_db.abs() > 10.0 * Float::EPSILON {
actual_db / expected_db
} else {
1.0 };
if expected_db.abs() > 10.0 * Float::EPSILON {
assert!(
magnitude_ratio > 0.99 && magnitude_ratio < 1.01,
"Result differs significantly from table reference for linear {linear}: expected {expected_db}dB, got {actual_db}dB, ratio: {magnitude_ratio:.4}"
);
}
}
}
#[test]
fn round_trip_conversion_accuracy() {
let test_db_values = [-60.0, -20.0, -6.0, 0.0, 6.0, 20.0, 40.0];
for &original_db in &test_db_values {
let linear = db_to_linear(original_db);
let round_trip_db = linear_to_db(linear);
let error = (round_trip_db - original_db).abs();
const MAX_ROUND_TRIP_ERROR: Float = 16.0 * Float::EPSILON;
assert!(
error < MAX_ROUND_TRIP_ERROR,
"Round-trip conversion failed for {original_db}dB: got {round_trip_db:.8}dB, error: {error:.2e}"
);
}
let test_linear_values = [0.001, 0.1, 1.0, 10.0, 100.0];
for &original_linear in &test_linear_values {
let db = linear_to_db(original_linear);
let round_trip_linear = db_to_linear(db);
let relative_error = ((round_trip_linear - original_linear) / original_linear).abs();
const MAX_ROUND_TRIP_RELATIVE_ERROR: Float = 16.0 * Float::EPSILON;
assert!(
relative_error < MAX_ROUND_TRIP_RELATIVE_ERROR,
"Round-trip conversion failed for {original_linear}: got {round_trip_linear:.8}, relative error: {relative_error:.2e}"
);
}
}
}