lasprs 0.6.7

Library for Acoustic Signal Processing (Rust edition, with optional Python bindings via pyo3)
Documentation
use crate::config::*;

use strum::EnumMessage;
use strum_macros::Display;
/// Time weighting to use in level detection of Sound Level Meter.
#[cfg_attr(feature = "python-bindings", pyclass(eq))]
#[derive(Clone, Copy, Debug, PartialEq, Display)]
pub enum TimeWeighting {
    // I know that the curly braces here are not required and add some
    // boilerplate, but this is the only way Pyo3 swallows complex enums at the
    // moment.
    /// Slow time weighting ~ 1 s
    Slow {},
    /// Fast time weighting ~ 1/8 s
    Fast {},
    /// Impulse time weighting ~ 30 ms
    Impulse {},
    /// A custom symmetric time weighting
    CustomSymmetric {
        /// Custom time constant [s]
        t: Flt,
    },

    /// A custom symmetric time weighting
    CustomAsymmetric {
        /// Time weighting when level is increasing
        tup: Flt,
        /// Time weighting when level is decreasing
        tdown: Flt,
    },
}

#[cfg(feature = "python-bindings")]
#[cfg_attr(feature = "python-bindings", pymethods)]
impl TimeWeighting {
    fn __str__(&self) -> String {
        format!("{self}")
    }
    #[staticmethod]
    fn all_standards() -> Vec<TimeWeighting> {
        use TimeWeighting::*;
        vec![Slow {}, Fast {}, Impulse {}]
    }
    #[pyo3(name = "getLowpassPoles")]
    fn getLowpassPoles_py(&self) -> (Flt, Option<Flt>) {
        self.getLowpassPoles()
    }
}

impl Default for TimeWeighting {
    fn default() -> Self {
        TimeWeighting::Fast {}
    }
}
impl TimeWeighting {
    /// get the analog poles of the single pole lowpass filter required for
    /// getting the 'rectified' level (detection phase of SLM).
    pub fn getLowpassPoles(&self) -> (Flt, Option<Flt>) {
        use TimeWeighting::*;
        match self {
            Slow {} => (-1.0, None),
            Fast {} =>
            // Time constant is 1/8 s, pole is at -8 rad/s
            {
                (-8., None)
            }
            Impulse {} => {
                // For the impulse time weighting, some source says ~ 2.9 dB/s
                // drop for the decay
                // [https://www.nti-audio.com/en/support/know-how/fast-slow-impulse-time-weighting-what-do-they-mean].
                //
                // Other source
                // [https://support.dewesoft.com/en/support/solutions/articles/14000139949-exponential-averaging-fast-f-slow-s-impulse-i-]
                // say a time constant of 1.5 s. Are they compatible?

                // Compute decay rate in dB/s from the filter time constant. An
                // initial value drops as exp(-t/tau). So in 1 s the level drops
                // with 10*log10(exp(-1.0/tau)) = -10/ln(10)/tau ≅ -4.34/tau
                // dB/s where ln denotes the natural logarithm. So suppose we
                // have 1.5 s, we indeed get a decay rate of 2.9 dB/s
                (-1. / 35e-3, Some(-1. / 1.5))
            }
            CustomSymmetric { t } => {
                assert!(*t > 0.);
                (-*t, None)
            }
            CustomAsymmetric { tup, tdown } => {
                assert!(*tup > 0.);
                assert!(*tdown > 0.);
                (-1. / (*tup), Some(-1. / (*tdown)))
            }
        }
    }
}

#[cfg(test)]
mod test {
    use crate::slm::TimeWeighting;

    #[test]
    fn test_tw() {
        println!("Impulse: {}", TimeWeighting::Impulse {});
        println!("Fast   : {}", TimeWeighting::Fast {});
        println!("Slow   : {}", TimeWeighting::Slow {});
    }
}