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 {
N: usize,
Lrefsq: Flt,
stat: SLMStat,
bp: SeriesBiquad,
twfilter: TwFilter,
}
impl SLMChannel {
fn run(&mut self, prefiltered: &[Flt], provide_output: bool) -> Option<Vec<Flt>> {
if prefiltered.is_empty() {
if provide_output {
return Some(vec![]);
} else {
return None;
}
}
assert!(!prefiltered.is_empty());
let level_fun = |a| 10. * Flt::log10(a / self.Lrefsq);
let mut tmp = self.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 > self.stat.Ppk {
self.stat.Ppk = new_pk;
}
self.stat.Peq = (self.stat.Peq * N as Flt + sample_pwr) / (N as Flt + 1.);
N += 1;
});
filtered_squared.mapv_inplace(|s| self.twfilter.filter(s));
let time_weighted = &mut filtered_squared;
time_weighted.for_each(|val| {
if *val > self.stat.Pmax {
self.stat.Pmax = *val;
}
});
self.stat.Pt_last = *filtered_squared.last().unwrap();
filtered_squared.mapv_inplace(level_fun);
self.N += prefiltered.len();
if provide_output {
Some(tmp)
} else {
None
}
}
}
#[cfg_attr(feature = "python-bindings", pyclass)]
#[derive(Debug, Clone)]
pub struct SLM {
prefilter: SeriesBiquad,
channels: SmallVec<[SLMChannel; SLM_MAX_CHANNELS]>,
}
impl SLM {
pub fn new(settings: SLMSettings) -> Self {
let fs = settings.fs;
let poles = settings.timeWeighting.getLowpassPoles();
let alpha_up = Flt::exp(poles.0 / fs);
let alpha_down = poles.1.map(|p| Flt::exp(p / fs));
let twfilter = TwFilter::new(alpha_up, alpha_down);
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();
SLMChannel {
stat,
bp,
twfilter,
Lrefsq: settings.Lref.powi(2),
N: 0,
}
})
.collect();
SLM {
prefilter,
channels,
}
}
pub fn run(&mut self, td: &[Flt], provide_output: bool) -> Option<Vec<Vec<Flt>>> {
if td.is_empty() {
return None;
}
let prefiltered = self.prefilter.filter(td);
let ch_iter = self.channels.par_iter_mut().map(|ch| {
ch.run(&prefiltered, provide_output)
});
if provide_output {
let Lt: Vec<_> = ch_iter
.map(|a| a.expect("No output provided for ch"))
.collect();
Some(Lt)
} else {
ch_iter.for_each(|_| {});
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) / ch.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(py, v));
let vec_py = vec_py_iter.collect::<Vec<Bound<'py, PyArray1<Flt>>>>();
Some(vec_py)
} else {
None
}
}
#[pyo3(name = "Lmax")]
fn Lmax_py<'py>(&self, py: Python<'py>) -> PyArr1Flt<'py> {
PyArray1::from_array(py, &self.Lmax())
}
#[pyo3(name = "Leq")]
fn Leq_py<'py>(&self, py: Python<'py>) -> PyArr1Flt<'py> {
PyArray1::from_array(py, &self.Leq())
}
#[pyo3(name = "Lpk")]
fn Lpk_py<'py>(&self, py: Python<'py>) -> PyArr1Flt<'py> {
PyArray1::from_array(py, &self.Lpk())
}
#[pyo3(name = "Ltlast")]
fn Ltlast_py<'py>(&self, py: Python<'py>) -> PyArr1Flt<'py> {
PyArray1::from_array(py, &self.Ltlast())
}
}
#[derive(Debug, Clone, Default)]
struct SLMStat {
Pmax: Flt,
Ppk: Flt,
Peq: Flt,
Pt_last: Flt,
}
#[derive(Clone, Copy, Debug)]
struct F {
alpha: Flt,
state: Flt,
}
#[derive(Clone, Copy, Debug)]
struct TwFilter {
filter_state_up: F,
filter_state_down: Option<F>,
}
impl TwFilter {
fn new(alpha_up: Flt, alpha_down: Option<Flt>) -> TwFilter {
let filter_state_down = alpha_down.map(|alpha_down| F {
alpha: alpha_down,
state: 0.,
});
TwFilter {
filter_state_up: F {
alpha: alpha_up,
state: 0.,
},
filter_state_down,
}
}
#[inline]
fn filter(&mut self, input: Flt) -> Flt {
if self.filter_state_down.is_some() {
self.filter_asymmetric(input)
} else {
self.filter_symmetric(input)
}
}
#[inline]
fn filter_symmetric(&mut self, input: Flt) -> Flt {
assert!(
self.filter_state_down.is_none(),
"Call this only for symmetric filters"
);
let F { alpha, state } = &mut self.filter_state_up;
let alpha = *alpha;
let output = (1. - alpha) * input + alpha * *state;
*state = output;
output
}
#[inline]
fn filter_asymmetric(&mut self, input: Flt) -> Flt {
let F {
alpha: alpha_up,
state: state_up,
} = &mut self.filter_state_up;
let alpha_up = *alpha_up;
let filter_state_down = self
.filter_state_down
.as_mut()
.expect("Call this only for asymmetric filters");
let F {
alpha: alpha_down,
state: state_down,
} = filter_state_down;
let alpha_down = *alpha_down;
let pu = (1. - alpha_up) * input + alpha_up * *state_up;
*state_up = pu;
let pIn = alpha_down * *state_down;
if pu > pIn {
*state_down = pu;
pu
} else {
*state_down = pIn;
pIn
}
}
}
#[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..]);
}
}