use super::timebuffer::TimeBuffer;
use super::CrossPowerSpecra;
use super::*;
use crate::config::*;
use anyhow::{bail, Result};
pub enum Overlap {
Percentage(Flt),
Number(usize),
NoOverlap,
}
impl Default for Overlap {
fn default() -> Self {
Overlap::Percentage(50.)
}
}
pub enum ApsResult<'a> {
AllIntermediateResults(Vec<CPS>),
OnlyLastResult(&'a CPS),
None,
}
pub enum ApsMode {
AllAveraging,
ExponentialWeighting {
fs: Flt,
tau: Flt,
},
Spectrogram,
}
impl Default for ApsMode {
fn default() -> Self {
ApsMode::AllAveraging
}
}
pub struct AvPowerSpectra {
ps: PowerSpectra,
mode: ApsMode,
overlap_keep: usize,
N: usize,
timebuf: TimeBuffer,
cur_est: CPS,
}
impl AvPowerSpectra {
pub fn nfft(&self) -> usize {
self.ps.nfft()
}
fn get_overlap_keep(nfft: usize, overlap: Overlap) -> Result<usize> {
let overlap_keep = match overlap {
Overlap::Number(i) if i >= nfft => {
bail!("Invalid overlap number of samples. Should be < nfft, which is {nfft}.")
}
Overlap::Number(i) if i < nfft => i,
Overlap::Percentage(p) if p >= 100. || p < 0.0 => {
bail!("Invalid overlap percentage. Should be >= 0. And < 100.")
}
Overlap::Percentage(p) => ((p * nfft as Flt) / 100.) as usize,
Overlap::NoOverlap => 0,
_ => unreachable!(),
};
if overlap_keep >= nfft {
bail!("Computed overlap results in invalid number of overlap samples. Please make sure the FFT length is large enough, when high overlap percentages are required.");
}
Ok(overlap_keep)
}
pub fn reset(&mut self) {
self.N = 0;
self.timebuf.reset();
self.cur_est = CPS::zeros((0,0,0));
}
pub fn new_simple(nfft: usize) -> AvPowerSpectra {
AvPowerSpectra::build(nfft, None, None, None).unwrap()
}
pub fn build(
nfft: usize,
windowtype: Option<WindowType>,
overlap: Option<Overlap>,
mode: Option<ApsMode>,
) -> Result<AvPowerSpectra> {
if nfft % 2 != 0 {
bail!("NFFT should be even")
}
if nfft == 0 {
bail!("Invalid NFFT")
}
let windowtype = windowtype.unwrap_or_default();
let window = Window::new(windowtype, nfft);
let mode = mode.unwrap_or_default();
let overlap = overlap.unwrap_or_default();
if let ApsMode::ExponentialWeighting { fs, tau } = mode {
if fs <= 0.0 {
bail!("Invalid sampling frequency given as parameter");
}
if tau <= 0.0 {
bail!("Invalid time weighting constant [s]. Should be > 0 if given.");
}
}
let ps = PowerSpectra::newFromWindow(window);
let overlap_keep = Self::get_overlap_keep(nfft, overlap)?;
Ok(AvPowerSpectra {
ps,
overlap_keep,
mode,
N: 0,
cur_est: CPS::default((0, 0, 0)),
timebuf: TimeBuffer::new(),
})
}
fn update_singleblock<'a, T>(&mut self, timedata: T)
where
T: AsArray<'a, Flt, Ix2>,
{
let timeblock = timedata.into();
let Cpsnew = self.ps.compute(&timeblock);
if self.cur_est.len() == 0 {
assert_eq!(self.N, 0);
self.cur_est = CPS::zeros(Cpsnew.raw_dim().f());
}
self.N += 1;
match self.mode {
ApsMode::AllAveraging => {
let Nf = Cflt {
re: self.N as Flt,
im: 0.,
};
self.cur_est = (Nf - 1.) / Nf * &self.cur_est + 1. / Nf * Cpsnew;
}
ApsMode::ExponentialWeighting { fs, tau } => {
debug_assert!(self.N > 0);
if self.N == 1 {
self.cur_est = Cpsnew;
} else {
let T = (self.nfft() - self.overlap_keep) as Flt / fs;
let alpha = Cflt::ONE * Flt::exp(-T / tau);
self.cur_est = alpha * &self.cur_est + (1. - alpha) * Cpsnew;
}
}
ApsMode::Spectrogram => {
self.cur_est = Cpsnew;
}
}
}
pub fn compute<'a, 'b, T>(
&'a mut self,
timedata: T,
giveInterMediateResults: bool,
) -> ApsResult<'a>
where
T: AsArray<'b, Flt, Ix2>,
{
self.timebuf.push(timedata);
let mut result = ApsResult::None;
let mut computed_single = false;
while let Some(timeblock) = self.timebuf.pop(self.nfft(), self.overlap_keep) {
self.update_singleblock(&timeblock);
computed_single = true;
if giveInterMediateResults {
if let ApsResult::None = result {
result = ApsResult::AllIntermediateResults(Vec::new())
}
}
if let ApsResult::AllIntermediateResults(v) = &mut result {
v.push(self.cur_est.clone());
}
}
if computed_single && !giveInterMediateResults {
return ApsResult::OnlyLastResult(&self.cur_est);
}
result
}
pub fn compute_last<'a, T>(&mut self, timedata: T) -> ApsResult
where
T: AsArray<'a, Flt, Ix2>,
{
return self.compute(timedata, false);
}
}
#[cfg(test)]
mod test {
use approx::assert_abs_diff_eq;
use ndarray_rand::rand_distr::Normal;
use ndarray_rand::RandomExt;
use super::CrossPowerSpecra;
use crate::{config::*, ps::ApsResult};
use super::{ApsMode, AvPowerSpectra, Overlap, WindowType, CPS};
#[test]
fn test_overlap_keep() {
let nfft = 10;
assert_eq!(
AvPowerSpectra::get_overlap_keep(nfft, Overlap::Percentage(50.)).unwrap(),
nfft / 2
);
let nfft = 11;
assert_eq!(
AvPowerSpectra::get_overlap_keep(nfft, Overlap::Percentage(50.)).unwrap(),
nfft / 2
);
let nfft = 1024;
assert_eq!(
AvPowerSpectra::get_overlap_keep(nfft, Overlap::Percentage(25.)).unwrap(),
nfft / 4
);
assert_eq!(
AvPowerSpectra::get_overlap_keep(nfft, Overlap::Number(10)).unwrap(),
10
);
}
#[test]
fn test_expweighting() {
let nfft = 48000;
let fs = nfft as Flt;
let tau = 2.;
let mut aps = AvPowerSpectra::build(
nfft,
None,
Some(Overlap::NoOverlap),
Some(ApsMode::ExponentialWeighting { fs, tau }),
)
.unwrap();
assert_eq!(aps.overlap_keep, 0);
let distr = Normal::new(1.0, 1.0).unwrap();
let timedata_some = Dmat::random((nfft, 1), distr);
let timedata_zeros = Dmat::zeros((nfft, 1));
let mut first_result = CPS::zeros((0, 0, 0));
if let ApsResult::OnlyLastResult(v) = aps.compute_last(timedata_some.view()) {
first_result = v.clone();
} else {
assert!(false, "Should return one value");
}
let overlap_keep = AvPowerSpectra::get_overlap_keep(nfft, Overlap::NoOverlap).unwrap();
if let ApsResult::OnlyLastResult(_) = aps.compute_last(&timedata_zeros) {
} else {
assert!(false, "Should return one value");
}
if let ApsResult::OnlyLastResult(v) = aps.compute_last(&timedata_zeros) {
let alpha = Flt::exp(-((nfft - overlap_keep) as Flt) / (fs * tau));
for i in 0..nfft / 2 + 1 {
assert_abs_diff_eq!(first_result.ap(0)[i] * alpha.powi(2), v.ap(0)[i]);
}
} else {
assert!(false, "Should return one value");
}
assert_eq!(aps.N, 3);
}
#[test]
fn test_tf1() {
let nfft = 4800;
let distr = Normal::new(1.0, 1.0).unwrap();
let mut timedata = Dmat::random((nfft, 1), distr);
timedata
.push_column(timedata.column(0).mapv(|a| 2. * a).view())
.unwrap();
let mut aps = AvPowerSpectra::build(nfft, None, None, None).unwrap();
if let ApsResult::OnlyLastResult(v) = aps.compute_last(&timedata) {
let tf = v.tf(0, 1, None);
assert_eq!((&tf - 2.0 * Cflt::ONE).sum().abs(), 0.0);
} else {
assert!(false);
}
}
#[test]
fn test_tf2() {
let nfft = 4800;
let distr = Normal::new(1.0, 1.0).unwrap();
let mut timedata = Dmat::random((nfft, 1), distr);
timedata
.push_column(timedata.column(0).mapv(|a| 2. * a).view())
.unwrap();
timedata
.push_column(timedata.column(0).mapv(|a| -1. * a).view())
.unwrap();
let mut aps = AvPowerSpectra::build(nfft, None, None, None).unwrap();
if let ApsResult::OnlyLastResult(v) = aps.compute_last(&timedata) {
let tf = v.tf(0, 1, Some(2));
assert_eq!((&tf - 2.0 * Cflt::ONE).sum().abs(), 0.0);
} else {
assert!(false);
}
}
#[test]
fn test_ap() {
let nfft = 1024;
let distr = Normal::new(1.0, 1.0).unwrap();
let timedata = Dmat::random((150 * nfft, 1), distr);
let timedata_mean_square = (&timedata * &timedata).sum() / (timedata.len() as Flt);
for wt in [
Some(WindowType::Rect),
Some(WindowType::Hann),
Some(WindowType::Bartlett),
Some(WindowType::Blackman),
None,
] {
let mut aps = AvPowerSpectra::build(nfft, wt, None, None).unwrap();
if let ApsResult::OnlyLastResult(v) = aps.compute_last(&timedata) {
let ap = v.ap(0);
assert_abs_diff_eq!((&ap).sum().abs(), timedata_mean_square, epsilon = 1e-2);
} else {
assert!(false);
}
}
}
}