use crate::config::*;
use derive_builder::Builder;
use itertools::Itertools;
use ndarray::ArrayView1;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use rayon::prelude::*;
use smallvec::SmallVec;
use super::{slmsettings::SLMSettings, SLM_MAX_CHANNELS};
use crate::{config::*, filter::Filter};
use crate::{Biquad, Dcol, Flt, FreqWeighting, PoleOrZero, SeriesBiquad, ZPKModel};
#[derive(Debug, Clone)]
struct SLMChannel {
stat: SLMStat,
bp: SeriesBiquad,
rect_lowpass_up: Biquad,
rect_lowpass_down: Option<Biquad>,
}
#[cfg_attr(feature = "python-bindings", pyclass)]
#[derive(Debug, Clone)]
pub struct SLM {
N: usize,
Lrefsq: Flt,
prefilter: SeriesBiquad,
channels: SmallVec<[SLMChannel; SLM_MAX_CHANNELS]>,
}
impl SLM {
fn lpfilter_from_pole(fs: Flt, p: PoleOrZero) -> Biquad {
Biquad::bilinear_zpk(fs, None, Some(p), Some(1.0), None).setGainAt(0., 1.)
}
pub fn new(settings: SLMSettings) -> Self {
let fs = settings.fs;
let prefilter = ZPKModel::freqWeightingFilter(settings.freqWeighting).bilinear(fs);
let channels = settings
.filterDescriptors
.iter()
.map(|descriptor| {
let bp = descriptor.genFilter().bilinear(fs);
let stat = SLMStat::default();
let poles = settings.timeWeighting.getLowpassPoles();
let rect_lowpass_up = Self::lpfilter_from_pole(fs, PoleOrZero::Real1(poles.0));
let rect_lowpass_down = if let Some(p) = poles.1 {
Some(Self::lpfilter_from_pole(fs, PoleOrZero::Real1(p)))
} else {
None
};
SLMChannel {
stat,
bp,
rect_lowpass_up,
rect_lowpass_down,
}
})
.collect();
SLM {
prefilter,
channels,
Lrefsq: settings.Lref.powi(2),
N: 0,
}
}
pub fn run(&mut self, td: &[Flt], provide_output: bool) -> Option<Vec<Vec<Flt>>> {
if td.len() == 0 {
return None;
}
let prefiltered = self.prefilter.filter(td);
let level_fun = |a| 10. * Flt::log10(a) / self.Lrefsq;
let Lt_iter = self.channels.par_iter_mut().map(|ch| {
let mut tmp = ch.bp.filter(&prefiltered);
let mut N = self.N;
let mut filtered_squared = {
let mut tmp_view = ArrayViewMut1::from(&mut tmp);
tmp_view.mapv_inplace(|a| a * a);
tmp_view
};
filtered_squared.for_each(|sample_pwr| {
let new_pk = sample_pwr.abs();
if new_pk > ch.stat.Ppk {
ch.stat.Ppk = new_pk;
}
ch.stat.Peq = (ch.stat.Peq * N as Flt + sample_pwr) / (N as Flt + 1.);
N += 1;
});
if let Some(rectifier_down) = &mut ch.rect_lowpass_down {
filtered_squared.mapv_inplace(|sample_sq| {
let rectifier_up = &mut ch.rect_lowpass_up;
let mut fup = sample_sq;
let mut fdown = sample_sq;
rectifier_up.filter_inout_single(&mut fup);
rectifier_down.filter_inout_single(&mut fdown);
if fup >= fdown {
rectifier_down.setToDCValue(fup);
fup
} else {
rectifier_up.setToDCValue(fdown);
fdown
}
});
} else {
let rectifier = &mut ch.rect_lowpass_up;
rectifier.filter_inout(filtered_squared.as_slice_mut().unwrap());
}
let rectified = &mut filtered_squared;
rectified.for_each(|val| {
if *val > ch.stat.Pmax {
ch.stat.Pmax = *val;
}
});
ch.stat.Pt_last = *filtered_squared.last().unwrap();
filtered_squared.mapv_inplace(level_fun);
tmp
});
if provide_output {
let Lt: Vec<_> = Lt_iter.collect();
self.N += td.len();
Some(Lt)
} else {
Lt_iter.for_each(|_| {});
self.N += td.len();
None
}
}
pub fn nch(&self) -> usize {
self.channels.len()
}
fn levels_from<T>(&self, stat_returner: T) -> Dcol
where
T: Fn(&SLMChannel) -> Flt,
{
Dcol::from_iter(
self.channels
.iter()
.map(|ch| 10. * Flt::log10(stat_returner(ch) / self.Lrefsq)),
)
}
pub fn Lmax(&self) -> Dcol {
self.levels_from(|ch| ch.stat.Pmax)
}
pub fn Lpk(&self) -> Dcol {
self.levels_from(|ch| ch.stat.Ppk)
}
pub fn Leq(&self) -> Dcol {
self.levels_from(|ch| ch.stat.Peq)
}
pub fn Ltlast(&self) -> Dcol {
self.levels_from(|ch| ch.stat.Pt_last)
}
}
#[cfg(feature = "python-bindings")]
#[cfg_attr(feature = "python-bindings", pymethods)]
impl SLM {
#[new]
fn new_py(settings: SLMSettings) -> SLM {
SLM::new(settings)
}
#[pyo3(name = "run", signature=(dat, provide_output=true))]
fn run_py<'py>(
&mut self,
py: Python<'py>,
dat: PyArrayLike1<Flt>,
provide_output: bool,
) -> Option<Vec<Bound<'py, PyArray1<Flt>>>> {
if let Some(res) = self.run(dat.as_array().as_slice()?, provide_output) {
let vec_py_iter = res.into_iter().map(|v| PyArray1::from_vec_bound(py, v));
let vec_py = vec_py_iter.collect::<Vec<Bound<'py, PyArray1<Flt>>>>();
return Some(vec_py);
}
None
}
#[pyo3(name = "Lmax")]
fn Lmax_py<'py>(&self, py: Python<'py>) -> PyArr1Flt<'py> {
PyArray1::from_array_bound(py, &self.Lmax())
}
#[pyo3(name = "Leq")]
fn Leq_py<'py>(&self, py: Python<'py>) -> PyArr1Flt<'py> {
PyArray1::from_array_bound(py, &self.Leq())
}
#[pyo3(name = "Lpk")]
fn Lpk_py<'py>(&self, py: Python<'py>) -> PyArr1Flt<'py> {
PyArray1::from_array_bound(py, &self.Lpk())
}
#[pyo3(name = "Ltlast")]
fn Ltlast_py<'py>(&self, py: Python<'py>) -> PyArr1Flt<'py> {
PyArray1::from_array_bound(py, &self.Ltlast())
}
}
#[derive(Debug, Clone, Default)]
struct SLMStat {
Pmax: Flt,
Ppk: Flt,
Peq: Flt,
Pt_last: Flt,
}
#[cfg(test)]
mod test {
use crate::{
siggen::Siggen,
slm::{SLMSettingsBuilder, TimeWeighting},
Flt, FreqWeighting, StandardFilterDescriptor,
};
use super::SLM;
#[test]
fn test_slm1() {
const fs: Flt = 48e3;
const N: usize = (fs / 8.) as usize;
let desc = StandardFilterDescriptor::Overall().unwrap();
let settings = SLMSettingsBuilder::default()
.fs(fs)
.timeWeighting(TimeWeighting::Fast {})
.freqWeighting(FreqWeighting::Z)
.filterDescriptors(&[desc])
.build()
.unwrap();
let mut siggen = Siggen::newSine(1., 1, 1000.).unwrap();
siggen.setAllMute(false);
siggen.reset(fs);
let mut data = vec![0.; N];
siggen.genSignal(&mut data);
let mut slm = SLM::new(settings);
let res = slm.run(&data, true).unwrap();
let res = &res[0];
println!("{slm:#?}");
println!("{:#?}", &res[res.len() - 100..]);
}
}