Bugfixing on SLM. First tests. Code seems to work, but might still have subtle bugs (as timeweighting was wrong previous to this commit).

This commit is contained in:
Anne de Jong 2024-08-19 23:02:13 +02:00
parent 6208e97f8a
commit d8814e6c11
13 changed files with 257 additions and 84 deletions

View File

@ -14,7 +14,7 @@ mod zpkmodel;
mod butter; mod butter;
mod octave; mod octave;
pub use super::ps::FreqWeightingType; pub use super::ps::FreqWeighting;
pub use biquad::Biquad; pub use biquad::Biquad;
pub use biquadbank::BiquadBank; pub use biquadbank::BiquadBank;
pub use octave::{StandardFilterDescriptor, G, FREQ_REF}; pub use octave::{StandardFilterDescriptor, G, FREQ_REF};

View File

@ -1,6 +1,5 @@
use crate::{Flt, ZPKModel}; use crate::{Flt, ZPKModel};
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use clap::Error;
use num::{traits::float, Float}; use num::{traits::float, Float};
use rayon::iter::Filter; use rayon::iter::Filter;
use softfloat::F64; use softfloat::F64;
@ -67,11 +66,11 @@ pub const FREQ_REF: Flt = 1000.;
/// ///
/// ```rust /// ```rust
/// use lasprs::filter::*; /// use lasprs::filter::*;
/// fn main() -> anyhow::Result<()> { /// # fn main() -> anyhow::Result<()> {
/// let desc = StandardFilterDescriptor::Octave("16")?; /// let desc = StandardFilterDescriptor::Octave("16")?;
/// let filter = desc.genFilter().bilinear(48e3); /// let filter = desc.genFilter().bilinear(48e3);
/// Ok(()) /// # Ok(())
/// } /// # }
/// ``` /// ```
/// ///
/// ## Create a one-third octave band bandpass filter that has the frequency of 42 in its pass-band /// ## Create a one-third octave band bandpass filter that has the frequency of 42 in its pass-band
@ -79,11 +78,27 @@ pub const FREQ_REF: Flt = 1000.;
/// ```rust /// ```rust
/// ///
/// use lasprs::filter::*; /// use lasprs::filter::*;
/// fn main() -> anyhow::Result<()> { /// # fn main() -> anyhow::Result<()> {
/// let desc = StandardFilterDescriptor::filterForFreq(3, 42.)?; /// let desc = StandardFilterDescriptor::filterForFreq(3, 42.)?;
/// let filter = desc.genFilter().bilinear(48e3); /// let filter = desc.genFilter().bilinear(48e3);
/// Ok(()) /// # Ok(())
/// } /// # }
/// ```
///
/// ## Create a set of octave band filters
///
/// ```rust
/// use lasprs::filter::*;
/// # fn main() -> anyhow::Result<()> {
/// // Generate custom set..
/// let desc = StandardFilterDescriptor::genOctaveFilterSet("16", "16k").unwrap();
/// // Or, when lazy: just generate the full set
/// let desc = StandardFilterDescriptor::fullOctaveFilterSet();
/// let filters: Vec<SeriesBiquad> = desc.iter()
/// .map(|desc| desc.genFilter().bilinear(48e3))
/// .collect();
/// # Ok(())
/// # }
/// ``` /// ```
#[derive(PartialEq, Clone, Debug)] #[derive(PartialEq, Clone, Debug)]
pub struct StandardFilterDescriptor { pub struct StandardFilterDescriptor {
@ -222,38 +237,40 @@ impl StandardFilterDescriptor {
} }
/// Creates a set of octave filters. /// Creates a set of octave filters.
pub fn genOctaveFilterSet<T>(low_f: Option<T>, high_f: Option<T>) -> Result<Vec<Self>> pub fn genOctaveFilterSet<T>(low_f: T, high_f: T) -> Result<Vec<Self>>
where where
T: TryInto<OctaveBandDescriptor, Error = Error>, T: TryInto<OctaveBandDescriptor, Error = anyhow::Error>,
{ {
let xmin = if let Some(low_f) = low_f { let xmin = low_f.try_into()?.x;
low_f.try_into()?.x let xmax = high_f.try_into()?.x;
} else {
-OCTAVE_NAMES_OFFSET as i32
};
let xmax = if let Some(high_f) = high_f {
high_f.try_into()?.x
} else {
(OCTAVE_NOMINAL_MIDBAND_NAMES.len() - 1) as i32
};
Ok((xmin..=xmax).map(|x| Self::Octave(x).unwrap()).collect()) Ok((xmin..=xmax).map(|x| Self::Octave(x).unwrap()).collect())
} }
/// Generate a full third octave bandpass filter set
pub fn fullThirdOctaveFilterSet() -> Vec<Self> {
Self::genThirdOctaveFilterSet(
THIRDOCTAVE_NOMINAL_MIDBAND_NAMES[0],
*(THIRDOCTAVE_NOMINAL_MIDBAND_NAMES.last().unwrap()),
)
.unwrap()
}
/// Generate a full octave bandpass filter set
pub fn fullOctaveFilterSet() -> Vec<Self> {
Self::genOctaveFilterSet(
OCTAVE_NOMINAL_MIDBAND_NAMES[0],
*(OCTAVE_NOMINAL_MIDBAND_NAMES.last().unwrap()),
)
.unwrap()
}
/// Creates a set of one-third octave bandpass filters. /// Creates a set of one-third octave bandpass filters.
pub fn genThirdOctaveFilterSet<T>(low_f: Option<T>, high_f: Option<T>) -> Result<Vec<Self>> pub fn genThirdOctaveFilterSet<T>(low_f: T, high_f: T) -> Result<Vec<Self>>
where where
T: TryInto<ThirdOctaveBandDescriptor, Error = Error>, T: TryInto<ThirdOctaveBandDescriptor, Error = anyhow::Error>,
{ {
let xmin = if let Some(low_f) = low_f { let xmin = low_f.try_into()?.x;
low_f.try_into()?.x let xmax = high_f.try_into()?.x;
} else {
-THIRDOCTAVE_NAMES_OFFSET as i32
};
let xmax = if let Some(high_f) = high_f {
high_f.try_into()?.x
} else {
(THIRDOCTAVE_NOMINAL_MIDBAND_NAMES.len() - 1) as i32
};
Ok((xmin..=xmax) Ok((xmin..=xmax)
.map(|x| Self::ThirdOctave(x).unwrap()) .map(|x| Self::ThirdOctave(x).unwrap())
.collect()) .collect())

View File

@ -103,6 +103,7 @@ impl SeriesBiquad {
SeriesBiquad::new(filter_coefs).unwrap() SeriesBiquad::new(filter_coefs).unwrap()
} }
#[allow(dead_code)]
fn clone_dyn(&self) -> Box<dyn Filter> { fn clone_dyn(&self) -> Box<dyn Filter> {
Box::new(self.clone()) Box::new(self.clone())
} }

View File

@ -1,6 +1,6 @@
use super::butter::butter_lowpass_roots; use super::butter::butter_lowpass_roots;
use super::{Biquad, SeriesBiquad, TransferFunction}; use super::{Biquad, SeriesBiquad, TransferFunction};
use crate::{config::*, ps::FreqWeightingType}; use crate::{config::*, ps::FreqWeighting};
use itertools::{EitherOrBoth, Itertools}; use itertools::{EitherOrBoth, Itertools};
use num::{zero, Complex}; use num::{zero, Complex};
use std::cmp::{max, min}; use std::cmp::{max, min};
@ -73,7 +73,7 @@ pub enum FilterSpec {
/// poles should either be real, or come in complex conjugate pairs. This is /// poles should either be real, or come in complex conjugate pairs. This is
/// enforced by the way the poles and zero's are internally stored. /// enforced by the way the poles and zero's are internally stored.
/// ///
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug)]
#[cfg_attr(feature = "python-bindings", pyclass)] #[cfg_attr(feature = "python-bindings", pyclass)]
pub struct ZPKModel { pub struct ZPKModel {
// List of zeros // List of zeros
@ -87,6 +87,12 @@ pub struct ZPKModel {
// transform to create digital filter of this analogue one. // transform to create digital filter of this analogue one.
fwarp: Option<Flt>, fwarp: Option<Flt>,
} }
impl Default for ZPKModel {
fn default() -> Self {
ZPKModel{z:vec![], p:vec![], k: 1.0, fwarp: None}
}
}
impl<'a, T: AsArray<'a, Flt>> TransferFunction<'a, T> for ZPKModel { impl<'a, T: AsArray<'a, Flt>> TransferFunction<'a, T> for ZPKModel {
fn tf(&self, _fs: Flt, freq: T) -> Ccol { fn tf(&self, _fs: Flt, freq: T) -> Ccol {
let freq = freq.into(); let freq = freq.into();
@ -494,26 +500,26 @@ impl ZPKModel {
/// ///
/// # Args /// # Args
/// ///
/// - `wt` - `[FreqWeightingType]` to use. i.e. A-weighting. /// - `wt` - `[FreqWeighting]` to use. i.e. A-weighting.
/// ///
/// # Examples /// # Examples
/// ///
/// ## Get part of pulse response of digital A-filter at 48 kHz /// ## Get part of pulse response of digital A-filter at 48 kHz
/// ///
/// ``` /// ```
/// use lasprs::filter::{ZPKModel, FreqWeightingType, Filter}; /// use lasprs::filter::{ZPKModel, FreqWeighting, Filter};
/// ///
/// // Sampling frequency in Hz /// // Sampling frequency in Hz
/// let fs = 48000.; /// let fs = 48000.;
/// ///
/// let mut afilter = ZPKModel::freqWeightingFilter(FreqWeightingType::A).bilinear(fs); /// let mut afilter = ZPKModel::freqWeightingFilter(FreqWeighting::A).bilinear(fs);
/// let mut data = [0.; 1000]; /// let mut data = [0.; 1000];
/// data[0] = 1.0; /// data[0] = 1.0;
/// let out = afilter.filter(&data); /// let out = afilter.filter(&data);
/// ``` /// ```
/// ///
pub fn freqWeightingFilter(wt: FreqWeightingType) -> ZPKModel { pub fn freqWeightingFilter(wt: FreqWeighting) -> ZPKModel {
if let FreqWeightingType::Z = wt { if let FreqWeighting::Z = wt {
return ZPKModel { return ZPKModel {
z: vec![], z: vec![],
p: vec![], p: vec![],
@ -548,15 +554,15 @@ impl ZPKModel {
let p4 = 2. * pi * f4; let p4 = 2. * pi * f4;
let (zeros, poles) = match wt { let (zeros, poles) = match wt {
FreqWeightingType::Z => { FreqWeighting::Z => {
unreachable!() unreachable!()
} }
FreqWeightingType::C => { FreqWeighting::C => {
let zeros = vec![PoleOrZero::Real2(0., 0.)]; let zeros = vec![PoleOrZero::Real2(0., 0.)];
let poles = vec![PoleOrZero::Real2(-p1, -p1), PoleOrZero::Real2(-p4, -p4)]; let poles = vec![PoleOrZero::Real2(-p1, -p1), PoleOrZero::Real2(-p4, -p4)];
(zeros, poles) (zeros, poles)
} }
FreqWeightingType::A => { FreqWeighting::A => {
let poles = vec![ let poles = vec![
PoleOrZero::Real2(-p1, -p1), PoleOrZero::Real2(-p1, -p1),
PoleOrZero::Real2(-p2, -p3), PoleOrZero::Real2(-p2, -p3),

View File

@ -4,7 +4,7 @@ use super::*;
use crate::config::*; use crate::config::*;
use anyhow::{bail, Error, Result}; use anyhow::{bail, Error, Result};
use derive_builder::Builder; use derive_builder::Builder;
use freqweighting::FreqWeightingType; use freqweighting::FreqWeighting;
/// All settings used for computing averaged power spectra using Welch' method. /// All settings used for computing averaged power spectra using Welch' method.
#[derive(Builder, Clone)] #[derive(Builder, Clone)]
@ -21,7 +21,7 @@ pub struct ApsSettings {
windowType: WindowType, windowType: WindowType,
/// Kind of freqency weighting. Defaults to Z /// Kind of freqency weighting. Defaults to Z
#[builder(default)] #[builder(default)]
freqWeightingType: FreqWeightingType, freqWeightingType: FreqWeighting,
/// FFT Length /// FFT Length
nfft: usize, nfft: usize,
/// Sampling frequency /// Sampling frequency
@ -71,7 +71,7 @@ impl ApsSettings {
self.validate_get_overlap_keep().unwrap() self.validate_get_overlap_keep().unwrap()
} }
/// Unpack all, returns parts in tuple /// Unpack all, returns parts in tuple
pub fn get(self) -> (ApsMode, Overlap, WindowType, FreqWeightingType, usize, Flt) { pub fn get(self) -> (ApsMode, Overlap, WindowType, FreqWeighting, usize, Flt) {
( (
self.mode, self.mode,
self.overlap, self.overlap,
@ -134,7 +134,7 @@ impl ApsSettings {
nfft, nfft,
windowType: WindowType::default(), windowType: WindowType::default(),
overlap: Overlap::default(), overlap: Overlap::default(),
freqWeightingType: FreqWeightingType::default(), freqWeightingType: FreqWeighting::default(),
}) })
} }

View File

@ -11,11 +11,13 @@ pub struct FFT {
timescratch: Vec<Flt>, timescratch: Vec<Flt>,
// rounded down nfft/2 // rounded down nfft/2
half_nfft_rounded: usize, half_nfft_rounded: usize,
// nfft stored as float, this is how it is required most often
nfftF: Flt, nfftF: Flt,
} }
impl FFT { impl FFT {
/// Create new FFT from given nfft /// Create new FFT from given nfft
#[allow(dead_code)]
pub fn newFromNFFT(nfft: usize) -> FFT { pub fn newFromNFFT(nfft: usize) -> FFT {
let mut planner = RealFftPlanner::<Flt>::new(); let mut planner = RealFftPlanner::<Flt>::new();
let fft = planner.plan_fft_forward(nfft); let fft = planner.plan_fft_forward(nfft);

View File

@ -1,7 +1,7 @@
use strum_macros::{Display, EnumMessage}; use strum_macros::{Display, EnumMessage};
/// Sound level frequency weighting type (A, C, Z) /// Sound level frequency weighting type (A, C, Z)
#[derive(Display, Debug, EnumMessage, Default, Clone)] #[derive(Display, Debug, EnumMessage, Default, Clone)]
pub enum FreqWeightingType { pub enum FreqWeighting {
/// A-weighting /// A-weighting
A, A,
/// C-weighting /// C-weighting
@ -15,9 +15,9 @@ mod test {
use super::*; use super::*;
#[test] #[test]
fn test() { fn test() {
let a = FreqWeightingType::A; let a = FreqWeighting::A;
let c = FreqWeightingType::C; let c = FreqWeighting::C;
let z = FreqWeightingType::Z; let z = FreqWeighting::Z;
println!("A-weighting: {a}"); println!("A-weighting: {a}");
println!("C-weighting: {c}"); println!("C-weighting: {c}");
println!("Z-weighting: {z}"); println!("Z-weighting: {z}");

View File

@ -13,7 +13,7 @@ mod freqweighting;
use crate::config::*; use crate::config::*;
pub use freqweighting::FreqWeightingType; pub use freqweighting::FreqWeighting;
pub use aps::{ApsSettings, ApsSettingsBuilder,ApsMode, AvPowerSpectra, Overlap}; pub use aps::{ApsSettings, ApsSettingsBuilder,ApsMode, AvPowerSpectra, Overlap};
pub use ps::{CrossPowerSpecra, PowerSpectra, CPSResult}; pub use ps::{CrossPowerSpecra, PowerSpectra, CPSResult};
pub use window::{Window, WindowType}; pub use window::{Window, WindowType};

View File

@ -3,9 +3,69 @@
//! Provides structs and helpers (SLMBuilder) for creating configurated Sound //! Provides structs and helpers (SLMBuilder) for creating configurated Sound
//! Level Meters. //! Level Meters.
//! //!
//! # Usage examples
//!
//! ## Simple over-all level SLM
//!
//! This example creates a simple SLM that processes at 48kHz, uses fast time
//! weighting and A frequency weighting:
//!
//! ```
//! # use anyhow::Result;
//! use lasprs::slm::*;
//! use lasprs::filter::StandardFilterDescriptor;
//! use ndarray::Array1;
//! # fn main() -> Result<()> {
//!
//! // Generate an overall filter (no filter at all)
//! let desc = StandardFilterDescriptor::Overall().unwrap();
//!
//! // Generate settings
//! let settings = SLMSettingsBuilder::default()
//! .fs(48e3)
//! .freqWeighting(FreqWeighting::A)
//! .timeWeighting(TimeWeighting::Fast)
//! .filterDescriptors(&[desc]).build().unwrap();
//!
//! let mut slm = SLM::new(settings);
//! // Generate some data. Yes, this is not the most spectacular set
//! let mut data = Array1::zeros(48000);
//! data[0] = 1.;
//!
//! // Now apply some data. This is a kind of the SLM-s impulse response
//! let res = slm.run(&data.as_slice().unwrap()).unwrap();
//!
//! // Only one channel of result data
//! assert_eq!(res.len(), 1);
//!
//! let res = &res[0];
//! println!("Data is: {res:#?}");
//!
//! // Get the equivalent level:
//! let Leq = slm.Leq()[0];
//!
//! # Ok::<() , anyhow::Error>(())
//! # }
//! ```
//!
//! ## One-third octave band, plus overall
//!
//! ```
//! use lasprs::filter::StandardFilterDescriptor;
//! // Generate the default set of one-third octave band filters
//! let mut desc = StandardFilterDescriptor::fullThirdOctaveFilterSet();
//! desc.push(StandardFilterDescriptor::Overall().unwrap());
//!
//! // Rest of code is the same as in previous example
//!
//! ```
//!
mod settings; mod settings;
mod tw; mod tw;
mod slm; mod slm;
pub use slm::SLM; pub use slm::SLM;
pub use settings::SLMSettings; pub use settings::{SLMSettings, SLMSettingsBuilder};
pub use tw::TimeWeightingType; pub use tw::TimeWeighting;
pub use crate::ps::FreqWeighting;
const SLM_MAX_CHANNELS: usize = 64;

View File

@ -1,19 +1,46 @@
use super::{TimeWeighting, SLM_MAX_CHANNELS};
use crate::{filter::StandardFilterDescriptor, Flt, FreqWeighting};
use anyhow::Result;
use clap::builder;
use derive_builder::Builder; use derive_builder::Builder;
use smallvec::SmallVec; use smallvec::{smallvec, SmallVec};
use crate::{Flt, FreqWeightingType, filter::StandardFilterDescriptor}; /// Settings used to create a Sound Level Meter.
use super::TimeWeightingType;
#[derive(Builder, Clone)] #[derive(Builder, Clone)]
#[builder(setter(into))]
pub struct SLMSettings { pub struct SLMSettings {
/// Sampling frequency in \[Hz\]
pub fs: Flt, pub fs: Flt,
/// Reference level, in units of measured quantity. For sound pressure in
/// air, this is typically 20 μPa. This is also the default value.
#[builder(default = "2e-5")]
pub Lref: Flt, pub Lref: Flt,
pub freqWeighting: FreqWeightingType, /// Frequency weightin A/C/Z applied to data. Defaults to [FreqWeighting::default()].
pub timeWeighting: TimeWeightingType, #[builder(default)]
pub filterDescriptors: SmallVec<[StandardFilterDescriptor; 64]>, pub freqWeighting: FreqWeighting,
/// For time-dependent output, the time weighthing applied (Fast / Slow)
pub timeWeighting: TimeWeighting,
/// Descriptors for the filters, maximum of 64, which is a reasonable amount
/// and - if all used - might already choke a computer.
pub filterDescriptors: Vec<StandardFilterDescriptor>,
} }
impl SLMSettings { #[cfg(test)]
mod test {
use super::*;
use anyhow::Result;
#[test]
fn test_slmsettings1() -> Result<()> {
let desc = StandardFilterDescriptor::genFilterSetInRange(1, 100., 5e3, true).unwrap();
let _ = SLMSettingsBuilder::default()
.fs(1e3)
.freqWeighting(FreqWeighting::A)
.timeWeighting(TimeWeighting::Slow)
.filterDescriptors(desc)
.build()
.unwrap();
Ok(())
}
} }

View File

@ -5,23 +5,30 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use rayon::prelude::*; use rayon::prelude::*;
use smallvec::SmallVec; use smallvec::SmallVec;
use super::settings::SLMSettings; use super::{settings::SLMSettings, SLM_MAX_CHANNELS};
use crate::{config::*, filter::Filter}; use crate::{config::*, filter::Filter};
use crate::{Biquad, Dcol, Flt, FreqWeightingType, PoleOrZero, SeriesBiquad, ZPKModel}; use crate::{Biquad, Dcol, Flt, FreqWeighting, PoleOrZero, SeriesBiquad, ZPKModel};
#[derive(Debug, Clone)]
struct SLMChannel { struct SLMChannel {
// Statistics to store
stat: SLMStat, stat: SLMStat,
// The bandpass filter for this channel
bp: SeriesBiquad, bp: SeriesBiquad,
// The rectifier filter
rect_lowpass_up: Biquad, rect_lowpass_up: Biquad,
// The rectifier filter for decreasing values. Only for asymmetric time
// weighting (case of impulse weighting: [TimeWeighting::Impulse])
rect_lowpass_down: Option<Biquad>, rect_lowpass_down: Option<Biquad>,
} }
/// Sound Level Meter /// Sound Level Meter
#[derive(Debug, Clone)]
pub struct SLM { pub struct SLM {
// Number of samples processed after last run() is called. // Number of samples processed after last run() is called.
N: usize, N: usize,
Lrefsq: Flt, Lrefsq: Flt,
prefilter: SeriesBiquad, prefilter: SeriesBiquad,
channels: SmallVec<[SLMChannel; 64]>, channels: SmallVec<[SLMChannel; SLM_MAX_CHANNELS]>,
} }
impl SLM { impl SLM {
@ -179,7 +186,7 @@ impl SLM {
} }
} }
#[derive(Clone, Default)] #[derive(Debug, Clone, Default)]
/// Quantities defined as powers, i.e. square of amplitude /// Quantities defined as powers, i.e. square of amplitude
struct SLMStat { struct SLMStat {
// Max signal power // Max signal power
@ -192,3 +199,45 @@ struct SLMStat {
// Last obtained signal power, after last time run() is called. // Last obtained signal power, after last time run() is called.
Pt_last: 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, 1000.);
siggen.setAllMute(false);
siggen.reset(fs);
let mut data = vec![0.; N];
siggen.genSignal(&mut data);
// println!("{:#?}", data);
let mut slm = SLM::new(settings);
// println!("{slm:#?}");
let res = slm.run(&data).unwrap();
let res = &res[0];
println!("{slm:#?}");
println!("{:#?}", &res[res.len()- 100..]);
}
}

View File

@ -1,14 +1,16 @@
use crate::Flt; use crate::Flt;
/// Time weighting to use in level detection of Sound Level Meter.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum TimeWeightingType { pub enum TimeWeighting {
/// Slow time weighting /// Slow time weighting ~ 1 s
Slow, Slow,
/// Fast time weighting /// Fast time weighting ~ 1/8 s
Fast, Fast,
/// Impulse time weighting /// Impulse time weighting ~ 30 ms
Impulse, Impulse,
/// A custom symmetric time weighting /// A custom symmetric time weighting
CustomSymmetric { CustomSymmetric {
/// Custom time constant [s]
t: Flt, t: Flt,
}, },
/// A custom symmetric time weighting /// A custom symmetric time weighting
@ -19,14 +21,23 @@ pub enum TimeWeightingType {
tdown: Flt, tdown: Flt,
}, },
} }
impl TimeWeightingType { impl Default for TimeWeighting {
fn default() -> Self {
TimeWeighting::Fast
}
}
impl TimeWeighting {
/// get the analog poles of the single pole lowpass filter required for /// get the analog poles of the single pole lowpass filter required for
/// getting the 'rectified' level (detector phase of SLM). /// getting the 'rectified' level (detection phase of SLM).
pub fn getLowpassPoles(&self) -> (Flt, Option<Flt>) { pub fn getLowpassPoles(&self) -> (Flt, Option<Flt>) {
use TimeWeightingType::*; use TimeWeighting::*;
match self { match self {
Slow => (-1.0, None), Slow => (-1.0, None),
Fast => (-1. / 8., None), Fast =>
// Time constant is 1/8 s, pole is at -8 rad/s
{
(-8., None)
}
Impulse => { Impulse => {
// For the impulse time weighting, some source says ~ 2.9 dB/s // For the impulse time weighting, some source says ~ 2.9 dB/s
// drop for the decay // drop for the decay
@ -41,7 +52,7 @@ impl TimeWeightingType {
// with 10*log10(exp(-1.0/tau)) = -10/ln(10)/tau ≅ -4.34/tau // with 10*log10(exp(-1.0/tau)) = -10/ln(10)/tau ≅ -4.34/tau
// dB/s where ln denotes the natural logarithm. So suppose we // dB/s where ln denotes the natural logarithm. So suppose we
// have 1.5 s, we indeed get a decay rate of 2.9 dB/s // have 1.5 s, we indeed get a decay rate of 2.9 dB/s
(-35e-3, Some(-1.5)) (-1. / 35e-3, Some(-1. / 1.5))
} }
CustomSymmetric { t } => { CustomSymmetric { t } => {
assert!(*t > 0.); assert!(*t > 0.);

View File

@ -6,6 +6,6 @@
# $ cargo install cargo-watch cargo-docserver` # $ cargo install cargo-watch cargo-docserver`
# ``` # ```
# #
cargo watch -s "clear && cargo rustdoc -p lasprs --lib && cargo docserve" cargo watch -s "clear && cargo doc --no-deps -p lasprs --lib && cargo docserve"
# Then open: ${browser} http://localhost:4000 # Then open: ${browser} http://localhost:4000