melpe-rs 0.1.2

MELPe vocoder (STANAG 4591) in pure Rust — 600 bps voice codec, no_std compatible
Documentation
/// Math shim for no_std compatibility
///
/// Under `std` (default): calls native f32 methods (hardware-backed on x86_64)
/// Under `embedded`: calls libm pure-Rust implementations (works on Xtensa, etc.)
///
/// All functions take and return f32. Function names follow libm convention
/// (sinf, cosf, etc.) to make call sites explicit about the shim.

/// Sine
#[inline(always)]
pub fn sinf(x: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { x.sin() }
    #[cfg(feature = "embedded")]
    { libm::sinf(x) }
}

/// Cosine
#[inline(always)]
pub fn cosf(x: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { x.cos() }
    #[cfg(feature = "embedded")]
    { libm::cosf(x) }
}

/// Arc cosine
#[inline(always)]
pub fn acosf(x: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { x.acos() }
    #[cfg(feature = "embedded")]
    { libm::acosf(x) }
}

/// Hyperbolic sine
#[inline(always)]
pub fn sinhf(x: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { x.sinh() }
    #[cfg(feature = "embedded")]
    { libm::sinhf(x) }
}

/// Square root
#[inline(always)]
pub fn sqrtf(x: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { x.sqrt() }
    #[cfg(feature = "embedded")]
    { libm::sqrtf(x) }
}

/// Natural logarithm
#[inline(always)]
pub fn logf(x: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { x.ln() }
    #[cfg(feature = "embedded")]
    { libm::logf(x) }
}

/// Exponential (e^x)
#[inline(always)]
pub fn expf(x: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { x.exp() }
    #[cfg(feature = "embedded")]
    { libm::expf(x) }
}

/// Base-10 logarithm
#[inline(always)]
pub fn log10f(x: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { x.log10() }
    #[cfg(feature = "embedded")]
    { libm::log10f(x) }
}

/// Power: base^exp
#[inline(always)]
pub fn powf(base: f32, exp: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { base.powf(exp) }
    #[cfg(feature = "embedded")]
    { libm::powf(base, exp) }
}

/// Round to nearest integer
#[inline(always)]
pub fn roundf(x: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { x.round() }
    #[cfg(feature = "embedded")]
    { libm::roundf(x) }
}

/// Absolute value
#[inline(always)]
pub fn fabsf(x: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { x.abs() }
    #[cfg(feature = "embedded")]
    { libm::fabsf(x) }
}

/// Maximum of two floats
#[inline(always)]
pub fn fmaxf(a: f32, b: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { a.max(b) }
    #[cfg(feature = "embedded")]
    { libm::fmaxf(a, b) }
}

/// Minimum of two floats
#[inline(always)]
pub fn fminf(a: f32, b: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { a.min(b) }
    #[cfg(feature = "embedded")]
    { libm::fminf(a, b) }
}

/// Clamp a float to [lo, hi]
#[inline(always)]
pub fn clampf(x: f32, lo: f32, hi: f32) -> f32 {
    fmaxf(lo, fminf(x, hi))
}

/// Floor (truncate towards negative infinity)
#[inline(always)]
pub fn floorf(x: f32) -> f32 {
    #[cfg(not(feature = "embedded"))]
    { x.floor() }
    #[cfg(feature = "embedded")]
    { libm::floorf(x) }
}

/// Check if finite (not NaN or infinity) — works in core via to_bits
#[inline(always)]
pub fn is_finite(x: f32) -> bool {
    // Exponent bits mask for f32: bits 23..30
    // Finite if exponent is not all 1s
    let bits = x.to_bits();
    (bits & 0x7F80_0000) != 0x7F80_0000
}

/// Simple insertion sort for small f32 slices (no_std friendly).
/// Used in place of `[f32]::sort_by` which requires alloc.
pub fn sort_f32(slice: &mut [f32]) {
    let n = slice.len();
    for i in 1..n {
        let key = slice[i];
        let mut j = i;
        while j > 0 && slice[j - 1] > key {
            slice[j] = slice[j - 1];
            j -= 1;
        }
        slice[j] = key;
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use core::f32::consts::PI;

    const TOL: f32 = 1e-5;

    #[test]
    fn test_sinf() {
        assert!(sinf(0.0).abs() < TOL);
        assert!((sinf(PI / 2.0) - 1.0).abs() < TOL);
        assert!((sinf(PI) - 0.0).abs() < TOL);
    }

    #[test]
    fn test_cosf() {
        assert!((cosf(0.0) - 1.0).abs() < TOL);
        assert!(cosf(PI / 2.0).abs() < TOL);
        assert!((cosf(PI) - (-1.0)).abs() < TOL);
    }

    #[test]
    fn test_acosf() {
        assert!(acosf(1.0).abs() < TOL);
        assert!((acosf(0.0) - PI / 2.0).abs() < TOL);
        assert!((acosf(-1.0) - PI).abs() < TOL);
    }

    #[test]
    fn test_sinhf() {
        assert!(sinhf(0.0).abs() < TOL);
        assert!((sinhf(1.0) - 1.17520).abs() < 0.001);
    }

    #[test]
    fn test_sqrtf() {
        assert!((sqrtf(4.0) - 2.0).abs() < TOL);
        assert!((sqrtf(9.0) - 3.0).abs() < TOL);
        assert!(sqrtf(0.0).abs() < TOL);
    }

    #[test]
    fn test_logf() {
        assert!(logf(1.0).abs() < TOL);
        assert!((logf(core::f32::consts::E) - 1.0).abs() < TOL);
    }

    #[test]
    fn test_expf() {
        assert!((expf(0.0) - 1.0).abs() < TOL);
        assert!((expf(1.0) - core::f32::consts::E).abs() < 0.001);
    }

    #[test]
    fn test_log10f() {
        assert!(log10f(1.0).abs() < TOL);
        assert!((log10f(10.0) - 1.0).abs() < TOL);
        assert!((log10f(100.0) - 2.0).abs() < TOL);
    }

    #[test]
    fn test_powf() {
        assert!((powf(2.0, 3.0) - 8.0).abs() < TOL);
        assert!((powf(10.0, 0.0) - 1.0).abs() < TOL);
        assert!((powf(10.0, -1.0) - 0.1).abs() < 0.001);
    }

    #[test]
    fn test_roundf() {
        assert!((roundf(1.4) - 1.0).abs() < TOL);
        assert!((roundf(1.5) - 2.0).abs() < TOL);
        assert!((roundf(-0.6) - (-1.0)).abs() < TOL);
    }

    #[test]
    fn test_fabsf() {
        assert!((fabsf(-3.0) - 3.0).abs() < TOL);
        assert!((fabsf(3.0) - 3.0).abs() < TOL);
        assert!(fabsf(0.0) < TOL);
    }

    #[test]
    fn test_fmaxf() {
        assert!((fmaxf(1.0, 2.0) - 2.0).abs() < TOL);
        assert!((fmaxf(-1.0, -2.0) - (-1.0)).abs() < TOL);
    }

    #[test]
    fn test_fminf() {
        assert!((fminf(1.0, 2.0) - 1.0).abs() < TOL);
        assert!((fminf(-1.0, -2.0) - (-2.0)).abs() < TOL);
    }

    #[test]
    fn test_clampf() {
        assert!((clampf(0.5, 0.0, 1.0) - 0.5).abs() < TOL);
        assert!((clampf(-1.0, 0.0, 1.0) - 0.0).abs() < TOL);
        assert!((clampf(2.0, 0.0, 1.0) - 1.0).abs() < TOL);
    }

    #[test]
    fn test_is_finite() {
        assert!(is_finite(0.0));
        assert!(is_finite(1.0));
        assert!(is_finite(-1e30));
        assert!(!is_finite(f32::INFINITY));
        assert!(!is_finite(f32::NEG_INFINITY));
        assert!(!is_finite(f32::NAN));
    }

    #[test]
    fn test_sort_f32() {
        let mut a = [3.0, 1.0, 4.0, 1.5, 2.0];
        sort_f32(&mut a);
        assert!((a[0] - 1.0).abs() < TOL);
        assert!((a[1] - 1.5).abs() < TOL);
        assert!((a[2] - 2.0).abs() < TOL);
        assert!((a[3] - 3.0).abs() < TOL);
        assert!((a[4] - 4.0).abs() < TOL);

        // Already sorted
        sort_f32(&mut a);
        assert!((a[0] - 1.0).abs() < TOL);

        // Single element
        let mut b = [42.0];
        sort_f32(&mut b);
        assert!((b[0] - 42.0).abs() < TOL);
    }
}