lasprs 0.14.2

Library for Acoustic Signal Processing (Rust edition, with optional Python bindings via pyo3)
//! Graphic equalizer implementation details
use std::convert::Infallible;

use super::FilterGenerator;
use crate::{
    filter::{
        BiquadBank, FilterError, InvalidFilterOrderSnafu, Result, SeriesBiquad,
        StandardFilterDescriptor, error::ChannelIndexOutOfBoundsSnafu,
    },
    *,
};
use serde::{Deserialize, Serialize};
use snafu::prelude::*;

#[cfg_attr(
    feature = "python-bindings",
    gen_stub_pyclass_enum,
    pyclass(from_py_object)
)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[non_exhaustive] // Maybe add more in the future
/// Type of graphic equalizer
pub enum GraphicEqualizerType {
    /// Octave bank
    OctaveBank = 0,
    /// Third octave bank
    ThirdOctaveBank = 1,
}

#[cfg_attr(feature = "python-bindings", gen_stub_pyclass, pyclass(from_py_object))]
#[derive(Debug, Clone, Serialize, Deserialize)]
/// Graphic equalizer configuration. This uses parallell band pass filters, each
/// with a different amount of gain.
pub struct GraphicEqualizer {
    /// .0: Whether the band is active (unmuted)
    /// .1: Band name
    /// .2: Gain in dB
    channels: Vec<(bool, String, Flt)>,

    /// Filter order for used filters
    filter_order: u32,

    /// Type of graphic equalizer
    graphic_type: GraphicEqualizerType,
}

impl GraphicEqualizer {
    /// Get the names of the bands in the graphic equalizer
    pub fn names(&self) -> Vec<String> {
        self.channels
            .iter()
            .map(|(_active, name, _gain)| name.clone())
            .collect()
    }
    /// Get the gains of the bands in the graphic equalizer
    pub fn gains_dB(&self) -> Vec<Flt> {
        self.channels
            .iter()
            .map(|(_active, _name, gain)| *gain)
            .collect()
    }
    /// Get the number of bands in the graphic equalizer
    pub fn len(&self) -> usize {
        self.channels.len()
    }
    /// True when number of bands is zero
    pub fn is_empty(&self) -> bool {
        self.channels.is_empty()
    }
    /// Set the gain of a specific band in the graphic equalizer
    pub fn setGain_dB(&mut self, index: usize, gain: Flt) -> Result<()> {
        if index < self.channels.len() {
            self.channels[index].2 = gain;
            Ok(())
        } else {
            ChannelIndexOutOfBoundsSnafu.fail()
        }
    }

    /// Create a new graphic equalizer with the given parameters, for an octave
    /// filter bank
    pub fn newOctave(filter_order: u32) -> Result<Self> {
        let xs = StandardFilterDescriptor::fullOctaveFilterSet();
        Self::new(xs, filter_order)
    }
    /// Create a new graphic equalizer with the given parameters, for a third
    /// octave filter bank
    pub fn newThirdOctave(filter_order: u32) -> Result<Self> {
        let xs = StandardFilterDescriptor::fullThirdOctaveFilterSet();
        Self::new(xs, filter_order)
    }
    /// Get the type of graphic equalizer (octave or third octave)
    pub fn graphicType(&self) -> GraphicEqualizerType {
        self.graphic_type
    }
    fn new(
        xs: Vec<StandardFilterDescriptor>,
        filter_order: u32,
    ) -> std::result::Result<Self, FilterError> {
        ensure!(filter_order > 0, InvalidFilterOrderSnafu);
        let gains_dB = xs
            .iter()
            .map(|x| {
                let name = x.name();
                (true, name.into(), 0.)
            })
            .collect();

        Ok(Self {
            channels: gains_dB,
            filter_order,
            graphic_type: GraphicEqualizerType::OctaveBank,
        })
    }
}

impl Default for GraphicEqualizer {
    fn default() -> Self {
        Self::newOctave(2).unwrap()
    }
}
impl FilterGenerator for GraphicEqualizer {
    fn genFilter(&self, fs: StrictlyPositive) -> Result<filter::Filter> {
        let desc = match self.graphic_type {
            GraphicEqualizerType::OctaveBank => StandardFilterDescriptor::fullOctaveFilterSet(),
            GraphicEqualizerType::ThirdOctaveBank => {
                StandardFilterDescriptor::fullThirdOctaveFilterSet()
            }
        };
        let mut filters: Vec<SeriesBiquad> = Vec::with_capacity(self.len());
        for (desc, cfg) in desc.iter().zip(self.channels.iter()) {
            // Check that the channel name matches the filter name
            debug_assert!(cfg.1 == desc.name());
            let filter_analog = desc.genFilter_custom_order(self.filter_order);
            let filter = {
                let mut f = filter_analog.bilinear(fs);
                f.setGain(cfg.2);
                f
            };
            filters.push(filter);
        }
        Ok(BiquadBank::new(filters).into())
    }
}

#[cfg(feature = "python-bindings")]
#[cfg_attr(feature = "python-bindings", gen_stub_pymethods, pymethods)]
impl GraphicEqualizer {
    #[staticmethod]
    #[pyo3(name = "newOctave")]
    fn newOctave_py(filter_order: u32) -> PyResult<Self> {
        Ok(Self::newOctave(filter_order)?)
    }

    #[staticmethod]
    #[pyo3(name = "newThirdOctave")]
    fn newThirdOctave_py(filter_order: u32) -> PyResult<Self> {
        Ok(Self::newThirdOctave(filter_order)?)
    }

    #[pyo3(name = "names")]
    fn names_py(&self) -> Vec<String> {
        self.names()
    }

    #[pyo3(name = "graphicType")]
    fn graphicType_py(&self) -> GraphicEqualizerType {
        self.graphic_type
    }

    #[pyo3(name = "gains_dB")]
    fn gains_dB_py(&self) -> Vec<Flt> {
        self.gains_dB()
    }

    #[pyo3(name = "len")]
    fn len_py(&self) -> usize {
        self.len()
    }

    #[pyo3(name = "setGain_dB")]
    fn setGain_dB_py(&mut self, index: usize, gain: Flt) -> PyResult<()> {
        Ok(self.setGain_dB(index, gain)?)
    }
}