lasprs 0.14.2

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

/// Settings for Cross Power Spectrum computation, compatible with lasp.CPSSettings
#[derive(Builder, Clone, Copy, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
#[builder(
    pattern = "owned",
    build_fn(validate = "Self::validate", error = "Error")
)]
#[cfg_attr(
    feature = "python-bindings",
    gen_stub_pyclass,
    pyclass(get_all, from_py_object)
)]
pub struct CPSSettings {
    /// FFT Length
    pub nfft: usize,
    /// Overlap percentage [0, 100)
    pub overlap: Overlap,
    /// Window applied to time segments. See [WindowType].
    #[builder(default)]
    pub window: WindowType,
    /// Start sample index (None for start of signal)
    #[builder(default)]
    pub istart: Option<usize>,
    /// Stop sample index (None for end of signal)
    #[builder(default)]
    pub istop: Option<usize>,
}
impl Default for CPSSettings {
    fn default() -> Self {
        Self {
            nfft: 2048,
            overlap: Default::default(),
            window: Default::default(),
            istart: Default::default(),
            istop: Default::default(),
        }
    }
}

impl CPSSettingsBuilder {
    fn validate(&self) -> Result<()> {
        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.")
        }

        if let Some(overlap) = self.overlap {
            overlap.validate(nfft)?;
        }

        if let (Some(istart), Some(istop)) = (self.istart, self.istop)
            && let (Some(start), Some(stop)) = (istart, istop)
            && start >= stop
        {
            bail!("istart should be less than istop")
        }

        Ok(())
    }
}

#[cfg(feature = "python-bindings")]
#[cfg_attr(feature = "python-bindings", gen_stub_pymethods, pymethods)]
impl CPSSettings {
    // I do not know why, but adding this signature results in compilation errors for the stub generation.
    // #[pyo3(signature = (nfft, overlap, window=WindowType::Hann, istart=None, istop=None))]
    #[new]
    fn new(
        nfft: usize,
        overlap: Overlap,
        window: WindowType,
        istart: Option<usize>,
        istop: Option<usize>,
    ) -> PyResult<CPSSettings> {
        let builder = CPSSettingsBuilder::default()
            .nfft(nfft)
            .overlap(overlap)
            .window(window)
            .istart(istart)
            .istop(istop);
        Ok(builder.build()?)
    }
    #[staticmethod]
    #[pyo3(name = "default")]
    fn default_py() -> CPSSettings {
        CPSSettings::default()
    }

    /// Get the number of samples to overlap
    fn get_overlap_samples_py(&self) -> usize {
        self.get_overlap_samples()
    }

    /// Get the hop size (number of samples to advance between FFT blocks)
    fn get_hop_size_py(&self) -> usize {
        self.get_hop_size()
    }
}

impl CPSSettings {
    /// Returns the amount of samples to overlap
    pub fn get_overlap_samples(&self) -> usize {
        self.overlap.get_overlap_samples(self.nfft) as usize
    }

    /// Returns the hop size (number of samples to advance between FFT blocks)
    pub fn get_hop_size(&self) -> usize {
        self.overlap.get_hop_size(self.nfft) as usize
    }

    /// Convert to ApsSettings given a sampling frequency
    pub fn to_aps_settings(&self, fs: StrictlyPositive) -> ApsSettings {
        ApsSettings {
            mode: ApsMode::AllAveraging {},
            overlap: self.overlap,
            windowType: self.window,
            freqWeightingType: FreqWeighting::Z, // Default to no weighting
            nfft: self.nfft,
            fs,
        }
    }
}