lasprs/src/filter/biquadbank.rs

175 lines
6.4 KiB
Rust

use super::*;
use super::seriesbiquad::*;
use rayon::prelude::*;
#[cfg_attr(feature = "python-bindings", pyclass)]
#[derive(Clone)]
/// Multiple biquad filter that operate in parallel on a signal, and can apply a gain value to each
/// of the returned values. The BiquadBank can be used to decompose a signal by running it through
/// parallel filters, or it can directly be used to eq a signal. For the latter process, also a
/// gain can be applied when the output is made as the sum of the filtered inputs for each biquad.
///
/// # Detailed description
///
/// Below is an example of the signal flow is for the case of three SeriesBiquad filters, `h1`,
/// `h2` and `h3`:
///
/// ```markdown
///
/// analysis() gain application sum()
///
/// +------+ +-----+ +------------+
/// +-----+ h1 |---+ g1 +-------------+ | |
/// | +------+ +-----+ ++ | +------ |
/// | +-----| ++ |
/// Input | +------+ +-----+ | ++ | Output of filter()
///--------|>--+-----+ h2 |---+ g2 |--------------------| +-+ +----------------|>
/// | ---+---+ +-----+ | +-+ |
/// | +-----| ++ |
/// | +------+ +-----+ ++ | ++ |
/// +-----+ h3 |---+ g3 |-------------+ | +------ |
/// +------+ +-----+ | |
/// | +------------+
/// |
/// | Output of analysis() method (optional)
/// +-------------|>
/// ```
pub struct BiquadBank {
biqs: Vec<Box<dyn Filter>>,
gains: Vec<Flt>,
}
#[cfg(feature = "python-bindings")]
#[cfg_attr(feature = "python-bindings", pymethods)]
/// Methods to wrap it in Python
impl BiquadBank {
#[new]
/// Create new biquadbank filter set. See [BiquadBank::new()]
///
pub fn new_py<'py>(coefs: PyReadonlyArrayDyn<Flt>) -> PyResult<Self> {
let mut filts = vec![];
for col in coefs.as_array().columns() {
match col.as_slice() {
Some(colslice) => {
let new_ser = SeriesBiquad::new(colslice)?;
filts.push(new_ser.clone_dyn());
}
None => {
return Err(PyValueError::new_err("Error generating column"));
}
}
}
Ok(BiquadBank::new(filts))
}
#[pyo3(name = "filter")]
/// See: [BiquadBank::filter()]
pub fn filter_py<'py>(
&mut self,
py: Python<'py>,
input: PyArrayLike1<Flt>,
) -> PyResult<&'py PyArray1<Flt>> {
Ok(self.filter(input.as_slice()?).into_pyarray(py))
}
#[pyo3(name = "reset")]
/// See: [BiquadBank::reset()]
pub fn reset_py(&mut self) {
self.reset();
}
#[pyo3(name = "set_gains")]
/// See: [BiquadBank::set_gains()]
pub fn set_gains_py<'py>(&mut self, gains: PyArrayLike1<Flt>) -> PyResult<()> {
if gains.len()? != self.len() {
return Err(PyValueError::new_err("Invalid number of provided gains"));
}
self.set_gains(gains.as_slice()?);
Ok(())
}
#[pyo3(name = "set_gains_dB")]
/// See: [BiquadBank::set_gains_dB()]
pub fn set_gains_dB_py<'py>(&mut self, gains_dB: PyArrayLike1<Flt>) -> PyResult<()> {
if gains_dB.len()? != self.len() {
return Err(PyValueError::new_err("Invalid number of provided gains"));
}
self.set_gains_dB(gains_dB.as_slice()?);
Ok(())
}
#[pyo3(name = "len")]
/// See: [BiquadBank::len()]
pub fn len_py(&self) -> usize {
self.len()
}
}
impl BiquadBank {
/// Create new biquad bank. Initialized from given vector of series biquads.
pub fn new(biqs: Vec<Box<dyn Filter>>) -> BiquadBank {
let gains = vec![1.0; biqs.len()];
BiquadBank { biqs, gains }
}
/// Return the number of parallel filters installed.
pub fn len(&self) -> usize {
self.biqs.len()
}
/// Set the gains for each of the biquad. The gains are not used in the analyisis phase, but in
/// the reconstruction phase, so when BiquadBank::filter() is run on an input signal.
///
/// # Panics
///
/// When gains_dB.len() != to the number of filters.
pub fn set_gains_dB(&mut self, gains_dB: &[Flt]) {
if gains_dB.len() != self.gains.len() {
panic!("Invalid gains size!");
}
self.gains
.iter_mut()
.zip(gains_dB)
.for_each(|(g, gdB)| *g = Flt::powf(10., gdB / 20.));
}
/// Set linear gain values for each biquad. Same comments hold as for
/// [BiquadBank::set_gains_dB()].
pub fn set_gains(&mut self, gains: &[Flt]) {
if gains.len() != self.gains.len() {
panic!("Invalid gains size!");
}
// This could be done more efficient, but it does not matter. How often would you change
// the gain values?
self.gains.clone_from(&gains.to_vec());
}
/// Analysis step. Runs input signal through all filters. Outputs a vector of output results,
/// one for each filter in the bank.
pub fn analysis(&mut self, input: &[Flt]) -> Vec<Vd> {
// Filtered output for each filter in biquad bank
let filtered_out: Vec<Vd> = self
.biqs
.par_iter_mut()
// .iter_mut()
.map(|biq| biq.filter(input))
.collect();
filtered_out
}
}
impl Filter for BiquadBank {
fn filter(&mut self, input: &[Flt]) -> Vd {
// Sum of filter output times gains
let filtered_out = self.analysis(input);
let mut out: Vd = vec![0.; input.len()];
for (f, g) in filtered_out.iter().zip(&self.gains) {
for (outi, fi) in out.iter_mut().zip(f) {
// Sum and multiply by gain value
*outi += g * fi;
}
}
out
}
fn reset(&mut self) {
self.biqs.iter_mut().for_each(|b| b.reset());
}
fn clone_dyn(&self) -> Box<dyn Filter> {
Box::new(self.clone())
}
}