lasprs 0.14.2

Library for Acoustic Signal Processing (Rust edition, with optional Python bindings via pyo3)
use crate::config::*;
use serde::{Deserialize, Serialize};
use snafu::prelude::*;
use strum::{EnumMessage, IntoEnumIterator};

type Result<T> = std::result::Result<T, OverlapError>;

#[derive(Snafu, Clone, Debug, PartialEq)]
#[allow(missing_docs)]
pub enum OverlapError {
    #[snafu(display(
        "Overlap value results in hop size of zero for given \
        FFT length. Please use a larger FFT length, or a smaller overlap",
    ))]
    ValueTooClose {},
}
#[cfg(feature = "python-bindings")]
impl From<OverlapError> for PyErr {
    fn from(value: OverlapError) -> Self {
        PyValueError::new_err(format!("{}", value))
    }
}

///  Provide the overlap of blocks for computing averaged (cross) power spectra.
///  Can be provided as a percentage of the block size, or as a number of
///  samples.
#[cfg_attr(
    feature = "python-bindings",
    gen_stub_pyclass_complex_enum,
    pyclass(frozen, from_py_object)
)]
#[derive(
    Clone,
    Copy,
    Debug,
    Deserialize,
    PartialEq,
    Serialize,
    Hash,
    strum_macros::EnumMessage,
    strum_macros::EnumIter,
)]
#[non_exhaustive]
pub enum Overlap {
    /// No overlap at all
    #[strum(message = "No overlap")]
    NoOverlap {},
    /// 10% overlap
    #[strum(message = "10% overlap")]
    TenPercent {},
    /// 25% overlap
    #[strum(message = "25% overlap")]
    TwentyFivePercent {},
    /// 50% overlap
    #[strum(message = "50% overlap")]
    FiftyPercent {},
    /// 75% overlap
    #[strum(message = "75% overlap")]
    SeventyFivePercent {},
    /// 90% overlap
    #[strum(message = "90% overlap")]
    NinetyPercent {},
    /// 95% overlap
    #[strum(message = "95% overlap")]
    NinetyFivePercent {},
    // /// 99.7% overlap. Used for debugging purposes.
    // #[strum(message = "99.7% overlap")]
    // #[cfg(debug_assertions)]
    // NinetyNinePointSevenPercent {},
}
impl Default for Overlap {
    fn default() -> Self {
        Overlap::FiftyPercent {}
    }
}

impl Overlap {
    /// Returns the amount of samples to overlap
    pub fn get_overlap_samples(&self, nfft: usize) -> i64 {
        let nfft = nfft as i64;
        match self {
            Overlap::NoOverlap {} => 0,
            Overlap::TenPercent {} => nfft / 10,
            Overlap::TwentyFivePercent {} => nfft / 4,
            Overlap::FiftyPercent {} => nfft / 2,
            Overlap::SeventyFivePercent {} => (nfft * 3) / 4,
            Overlap::NinetyPercent {} => (nfft * 9) / 10,
            Overlap::NinetyFivePercent {} => (nfft * 95) / 100,
            // #[cfg(debug_assertions)]
            // Overlap::NinetyNinePointSevenPercent {} => (nfft * 997) / 1000,
        }
    }
    /// Returns the hop size (number of samples to advance between FFT blocks)
    pub fn get_hop_size(&self, nfft: usize) -> i64 {
        nfft as i64 - self.get_overlap_samples(nfft)
    }

    /// Validates the overlap configuration
    ///
    /// # Arguments
    /// * `nfft` - The FFT size
    ///
    /// # Returns
    /// * `Result<(), OverlapError>` - An error if the overlap configuration is invalid
    pub fn validate(&self, nfft: usize) -> Result<()> {
        let hop_size_unchecked = self.get_hop_size(nfft);
        ensure!(hop_size_unchecked > 0, ValueTooCloseSnafu {});
        ensure!(hop_size_unchecked <= nfft as i64, ValueTooCloseSnafu {});

        Ok(())
    }
}

#[cfg(feature = "python-bindings")]
#[cfg_attr(feature = "python-bindings", gen_stub_pymethods, pymethods)]
impl Overlap {
    #[inline]
    fn __eq__(&self, other: &Self) -> bool {
        self == other
    }

    fn __str__(&self) -> String {
        self.get_message().unwrap().into()
    }

    #[staticmethod]
    #[pyo3(name = "default")]
    fn default_py() -> Self {
        Self::default()
    }

    /// Export some typical settings to Python. Customs are also possible by
    /// directly creating them. This is to fill up the list.
    #[staticmethod]
    fn all() -> Vec<Overlap> {
        Self::iter().collect()
    }
}