lasprs 0.14.3

Library for Acoustic Signal Processing (Rust edition, with optional Python bindings via pyo3)
Documentation
use super::*;
use crate::common::*;
use crate::config::*;
use anyhow::{Error, Result, bail};
use derive_builder::Builder;

/// All settings used for computing averaged power spectra using Welch' method.
#[derive(Builder, Clone, Debug)]
#[cfg_attr(feature = "python-bindings", gen_stub_pyclass, pyclass(from_py_object))]
#[builder(build_fn(validate = "Self::validate", error = "Error"))]
pub struct ApsSettings {
    /// Mode of computation, see [ApsMode].
    #[builder(default)]
    pub mode: ApsMode,

    /// Overlap in time segments. See [Overlap].
    #[builder(default)]
    pub overlap: Overlap,

    /// Window applied to time segments. See [WindowType].
    #[builder(default)]
    pub windowType: WindowType,

    /// Kind of freqency weighting. Defaults to Z
    #[builder(default)]
    pub freqWeightingType: FreqWeighting,

    /// FFT Length
    pub nfft: usize,
    /// Sampling frequency
    pub fs: StrictlyPositive,
}

impl ApsSettingsBuilder {
    fn validate(&self) -> Result<()> {
        if self.fs.is_none() {
            bail!("Sampling frequency not given");
        }
        let fs = self.fs.unwrap();

        if !fs.is_normal() {
            bail!("Sampling frequency not a normal number")
        }
        if self.nfft.is_none() {
            bail!("nfft not specified")
        };
        let nfft = self.nfft.unwrap();
        if !nfft.is_multiple_of(2) {
            bail!("NFFT should be even")
        }
        if nfft == 0 {
            bail!("Invalid NFFT, should be > 0.")
        }
        // Perform some checks on ApsMode
        if let Some(ApsMode::ExponentialWeighting { tau }) = self.mode
            && tau <= 0.0
        {
            bail!("Invalid time weighting constant [s]. Should be > 0 if given.");
        }

        Ok(())
    }
}

#[cfg(feature = "python-bindings")]
#[cfg_attr(feature = "python-bindings", gen_stub_pymethods, pymethods)]
impl ApsSettings {
    /// Generate settings for computing Averaged power spectra
    ///
    /// # Args
    ///
    /// * `fs` - Sampling frequency
    /// * `mode` - Mode of computation, see [ApsMode]
    /// * `nfft` - Number of FFT points
    /// * `overlap` - Overlap strategy, defaults to 50%
    /// * `windowType` - Window type, defaults to Hann
    /// * `freqWeighting` - Frequency weighting, defaults to Z (no weighting)
    // I do not know why, but adding this signature results in compilation errors for the stub generation.
    // #[pyo3(signature = (fs, mode, nfft, overlap=Overlap::default(), windowType=WindowType::Hann, freqWeighting=FreqWeighting::Z))]
    #[new]
    #[gen_stub(skip)]
    fn new(
        fs: StrictlyPositive,
        mode: ApsMode,
        nfft: usize,
        overlap: Overlap,
        windowType: WindowType,
        freqWeighting: FreqWeighting,
    ) -> PyResult<Self> {
        overlap.validate(nfft)?;
        Ok(ApsSettings {
            mode,
            overlap,
            windowType,
            freqWeightingType: freqWeighting,
            nfft,
            fs,
        })
    }
}
cfg_select! {
    feature = "python-bindings" => {
    use pyo3_stub_gen::type_info::{MemberInfo, PyClassInfo};
    pyo3_stub_gen::inventory::submit! {
        gen_methods_from_python! {
            r#"
            class ApsSettings:
                def __new__(
                    cls,
                    fs: StrictlyPositive,
                    mode: ApsMode,
                    nfft: int,
                    overlap: Overlap = Overlap.default(),
                    windowType: WindowType = WindowType.Hann,
                    freqWeighting: FreqWeighting = FreqWeighting.Z,
                ) -> ApsSettings:
                    """
                    Create a new ApsSettings instance.
                    """
                    ...


            "#
        }
    }
    }
    _=>{}
}

impl ApsSettings {
    /// Get the hop size in samples.
    #[inline]
    pub fn get_hop_size(&self) -> usize {
        self.overlap.get_hop_size(self.nfft) as usize
    }

    /// Return a reasonable acoustic default with a frequency resolution around
    /// ~ 10 Hz, where nfft is still an integer power of 2.
    ///
    /// # Errors
    ///
    /// If `fs` is something odd, i.e. < 1 kHz, or higher than 1 MHz.
    ///
    pub fn reasonableAcousticDefault(fs: StrictlyPositive, mode: ApsMode) -> Result<ApsSettings> {
        if !(1e3..=1e6).contains(&*fs) {
            bail!("Sampling frequency for reasonable acoustic data is >= 1 kHz and <= 1 MHz.");
        }
        let fs_div_10_rounded = (*fs / 10.) as u32;

        // 2^30 is about 1 million. We search for a two-power of an nfft that is
        // the closest to fs/10. The frequency resolution is about fs/nfft.
        let nfft = (0..30).map(|i| 2u32.pow(i) - fs_div_10_rounded).fold(
            // Start wth a value that is always too large
            *fs as u32 * 10,
            |cur, new| cur.min(new),
        ) as usize;

        Ok(ApsSettings {
            mode,
            fs,
            nfft,
            windowType: WindowType::default(),
            overlap: Overlap::default(),
            freqWeightingType: FreqWeighting::default(),
        })
    }

    /// Return sampling frequency
    pub fn fs(&self) -> StrictlyPositive {
        self.fs
    }

    /// Return Nyquist frequency
    pub fn fnyq(&self) -> Flt {
        *self.fs / 2.
    }

    /// Returns a single-sided frequency array corresponding to points in Power
    /// spectra computation.
    pub fn getFreq(&self) -> Array1<Flt> {
        getFreq(self.fs, self.nfft)
    }
}