use strum::EnumMessage;
use strum_macros::{Display, EnumMessage};
use {
crate::config::*,
anyhow::{bail, Result},
};
const NITER_NEWTON: usize = 20;
const twopi: Flt = 2. * pi;
#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
#[derive(Debug, PartialEq, Clone, Display, EnumMessage)]
pub enum SweepType {
#[strum(message = "Forward logarithmic")]
ForwardLog,
#[strum(message = "Backward logarithmic")]
BackwardLog,
#[strum(message = "Continuous logarithmic")]
ContinuousLog,
#[strum(message = "Forward linear")]
ForwardLin,
#[strum(message = "Backward linear")]
BackwardLin,
#[strum(message = "Continuous linear")]
ContinuousLin,
}
#[cfg(feature = "python-bindings")]
#[cfg_attr(feature = "python-bindings", pymethods)]
impl SweepType {
#[staticmethod]
fn all() -> Vec<SweepType> {
use SweepType::*;
vec![
ForwardLin,
ForwardLog,
BackwardLin,
BackwardLog,
ContinuousLin,
ContinuousLog,
]
}
fn __str__(&self) -> String {
self.get_message().unwrap().into()
}
}
#[derive(Debug, Clone)]
pub struct SweepParams {
fs: Flt,
fl: Flt,
fu: Flt,
sweep_time: Flt,
quiet_time: Flt,
sweeptype: SweepType,
}
impl SweepParams {
pub fn new(
fs: Flt,
fl: Flt,
fu: Flt,
sweep_time: Flt,
quiet_time: Flt,
sweeptype: SweepType,
) -> Result<Self> {
if fs <= 0. {
bail!("Invalid sampling frequency: {} Hz", fs);
}
if fl > fu {
bail!("Lower frequency should be smaller than upper frequency");
}
if fu >= fs / 2. {
bail!("Upper frequency should be smaller than sampling frequency");
}
if sweep_time <= 0. {
bail!("Invalid sweep time, should be > 0.");
}
if 1. / sweep_time > fs / 2. {
bail!("Invalid sweep time: too short");
}
let (fl, fu) = if matches!(sweeptype, SweepType::BackwardLin | SweepType::BackwardLog) {
(fu, fl)
} else {
(fl, fu)
};
Ok(SweepParams {
fs,
fl,
fu,
sweep_time,
quiet_time,
sweeptype,
})
}
pub fn reset(&mut self, fs: Flt) -> Dcol {
self.fs = fs;
self.getSignal()
}
fn Ns(&self) -> usize {
(self.sweep_time * self.fs) as usize
}
fn getPhase(&self) -> Dcol {
match self.sweeptype {
SweepType::BackwardLin | SweepType::ForwardLin => self.getLinSweepFBPhase(),
SweepType::BackwardLog | SweepType::ForwardLog => self.getLogSweepFBPhase(),
SweepType::ContinuousLin => self.getLinSweepContPhase(),
SweepType::ContinuousLog => self.getLogSweepContPhase(),
}
}
pub fn getSignal(&self) -> Dcol {
let fs = self.fs;
let Ns = (self.sweep_time * fs) as usize;
let Nq = (self.quiet_time * fs) as usize;
let N = Ns + Nq;
let phase = self.getPhase();
Dcol::from_iter((0..N).map(|i| if i < Ns { Flt::sin(phase[i]) } else { 0. }))
}
fn getLinSweepFBPhase(&self) -> Dcol {
assert!(matches!(
self.sweeptype,
SweepType::BackwardLin | SweepType::ForwardLin
));
let (Ns, fl, fu, fs) = (self.Ns(), self.fl, self.fu, self.fs);
let Dt = 1. / fs;
let Nsf = Ns as Flt;
let K = (Dt * (fl * Nsf + 0.5 * (Nsf - 1.) * (fu - fl))).floor();
let eps_num = K / Dt - fl * Nsf - 0.5 * (Nsf - 1.) * (fu - fl);
let eps = eps_num / (0.5 * (Nsf - 1.));
let mut phase = 0.;
Dcol::from_iter((0..Ns).map(|n| {
let freq = fl + (n as Flt - 1.) / (Ns as Flt) * (fu + eps - fl);
let phase_out = phase;
phase += twopi * Dt * freq;
phase_out
}))
}
fn getLogSweepFBPhase(&self) -> Dcol {
assert!(matches!(
self.sweeptype,
SweepType::BackwardLog | SweepType::ForwardLog
));
let (Ns, fl, fu, fs) = (self.Ns(), self.fl, self.fu, self.fs);
let Dt = 1. / fs;
let Nsf = Ns as Flt;
let mut k = fu / fl;
let K = (Dt * fl * (k - 1.) / ((k.powf(1.0 / Nsf)) - 1.)).floor();
(0..10).for_each(|_| {
let E = 1. + K / (Dt * fl) * (k.powf(1.0 / Nsf) - 1.) - k;
let dEdk = K / (Dt * fl) * k.powf(1.0 / Nsf) / (Nsf * k) - 1.;
k -= E / dEdk;
});
let mut phase = 0.;
Dcol::from_iter((0..Ns).map(|n| {
let nf = n as Flt;
let fnn = fl * k.powf(nf / Nsf);
let phase_old = phase;
phase += twopi * Dt * fnn;
phase_old
}))
}
fn getLogSweepContPhase(&self) -> Dcol {
assert!(matches!(self.sweeptype, SweepType::ContinuousLog));
let (Ns, fl, fu, fs) = (self.Ns(), self.fl, self.fu, self.fs);
let Dt = 1. / fs;
let Nf = Ns / 2;
let Nff = Nf as Flt;
let Nb = Ns - Nf;
let Nbf = Nb as Flt;
let k1 = fu / fl;
let phif1 = twopi * Dt * fl * (k1 - 1.) / (k1.powf(1.0 / Nff) - 1.);
let K =
(phif1 / twopi + Dt * fu * (1. / k1 - 1.) / ((1. / k1).powf(1.0 / Nbf) - 1.)).floor();
let mut k = k1;
(0..NITER_NEWTON).for_each(|_| {
let E = (k - 1.) / (k.powf(1.0 / Nff) - 1.) + (k - 1.) / (1. - k.powf(-1.0 / Nbf))
- K / Dt / fl;
let dEdk1 = 1. / (k.powf(1.0 / Nff) - 1.);
let dEdk2 = (1. / k - 1.) / (k.powf(-1.0 / Nbf) - 1.);
let dEdk3 = -1. / (k * (k.powf(-1.0 / Nbf) - 1.));
let dEdk4 = k.powf(-1.0 / Nbf) * (1. / k - 1.)
/ (Nbf * Flt::powi(Flt::powf(k, -1.0 / Nbf) - 1., 2));
let dEdk5 = -Flt::powf(k, 1.0 / Nff) * (k - 1.)
/ (Nff * k * Flt::powi(Flt::powf(k, 1.0 / Nff) - 1., 2));
let dEdk = dEdk1 + dEdk2 + dEdk3 + dEdk4 + dEdk5;
k -= E / dEdk;
});
let mut phase = 0.;
Dcol::from_iter((0..Ns).map(|n| {
let nf = n as Flt;
let fnn = if n <= Nf {
fl * k.powf(nf / Nff)
} else {
fl * k * (1. / k).powf((nf - Nff) / Nbf)
};
let phase_old = phase;
phase += twopi * Dt * fnn;
phase_old
}))
}
fn getLinSweepContPhase(&self) -> Dcol {
assert!(matches!(self.sweeptype, SweepType::ContinuousLin));
let (Ns, fl, fu, fs) = (self.Ns(), self.fl, self.fu, self.fs);
let Dt = 1. / fs;
let Nf = Ns / 2;
let Nb = Ns - Nf;
let Nff = Nf as Flt;
let Nbf = Nb as Flt;
let phih = twopi * Dt * (fl * Nff + 0.5 * (Nff - 1.) * (fu - fl));
let K = (phih / twopi + Dt * (fu * Nbf - (Nb as Flt - 1.) * (fu - fl))).floor();
let eps_num1 = (K - phih / twopi) / Dt;
let eps_num2 = -fu * Nbf + (Nbf - 1.) * (fu - fl);
let eps = (eps_num1 + eps_num2) / (0.5 * (Nbf + 1.));
let mut phase = 0.;
Dcol::from_iter((0..Ns).map(|n| {
let nf = n as Flt;
let freq = if n < Nf {
fl + nf / Nff * (fu - fl)
} else {
fu - (nf - Nff) / Nbf * (fu + eps - fl)
};
let phase_out = phase;
phase += twopi * Dt * freq;
phase_out
}))
}
}
#[cfg(test)]
mod test {
use approx::assert_abs_diff_eq;
use super::*;
#[test]
fn test_phase_linsweep1() {
let fs = 10.;
let fl = 1.;
let fu = 1.;
let phase = SweepParams::new(fs, fl, fu, 10., 0., SweepType::ForwardLin)
.unwrap()
.getLinSweepFBPhase();
assert_abs_diff_eq!(phase[10], &(twopi));
}
}