use std::ops::Deref;
use super::{source::SourceImpl, *};
use crate::*;
use serde::{Deserialize, Serialize};
use snafu::prelude::*;
use strum::{EnumMessage, IntoEnumIterator};
use strum_macros::{Display, EnumIter, EnumMessage};
use crate::*;
const NITER_NEWTON: usize = 20;
const SWEEP_MAX_LENGTH_S: i32 = 61;
type Result<T> = std::result::Result<T, SiggenError>;
#[cfg_attr(
feature = "python-bindings",
gen_stub_pyclass_enum,
pyclass(eq, eq_int, from_py_object)
)]
#[derive(Debug, PartialEq, Clone, Display, EnumMessage, Serialize, Deserialize, EnumIter)]
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", gen_stub_pymethods, pymethods)]
impl SweepType {
#[staticmethod]
fn all() -> Vec<SweepType> {
Self::iter().collect()
}
fn __str__(&self) -> String {
self.get_message().unwrap().into()
}
}
#[cfg_attr(feature = "python-bindings", gen_stub_pyclass, pyclass(from_py_object))]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SweepSettings {
sweep_type: SweepType,
fl: Bounded<0, 100_0001>,
fu: StrictBounded<0, 100_0000>,
sweep_time: StrictBounded<0, SWEEP_MAX_LENGTH_S>,
quiet_time: Option<StrictBounded<0, 101>>,
amplitude_modulation: Option<Vec<(Positive, Positive)>>,
}
impl SweepSettings {
pub fn new(
fl: Flt,
fu: Flt,
sweep_time: Flt,
sweep_type: SweepType,
quiet_time: Option<Flt>,
amplitude_modulation: Option<Vec<(Positive, Positive)>>,
) -> Result<Self> {
ensure!(
fl <= fu,
InvalidParameterSnafu {
param: "fl",
criterion: "Lower frequency should be smaller than upper frequency"
}
);
let quiet_time = if let Some(quiet_time) = quiet_time {
Some(quiet_time.try_into().context(ParameterOutOfRangeSnafu {
parameter: "Quiet time",
})?)
} else {
None
};
Ok(SweepSettings {
sweep_type,
fl: fl.try_into().context(ParameterOutOfRangeSnafu {
parameter: "Lower frequency",
})?,
fu: fu.try_into().context(ParameterOutOfRangeSnafu {
parameter: "Upper frequency",
})?,
sweep_time: sweep_time.try_into().context(ParameterOutOfRangeSnafu {
parameter: "Sweep time",
})?,
quiet_time,
amplitude_modulation,
})
}
fn getSignal(&self, fs: StrictlyPositive) -> Result<Dcol> {
let Ns = (*self.sweep_time * *fs) as usize;
let phase = self.getPhase(Ns, fs);
let fs = *fs;
let Nq = if let Some(qt) = &self.quiet_time {
(**qt * fs) as usize
} else {
0
};
let N = Ns + Nq;
Ok(Dcol::from_iter(
(0..N).map(|i| if i < Ns { Flt::sin(phase[i]) } else { 0. }),
))
}
fn getPhase(&self, Ns: usize, fs: StrictlyPositive) -> Dcol {
match self.sweep_type {
SweepType::BackwardLin | SweepType::ForwardLin => self.getLinSweepFBPhase(Ns, fs),
SweepType::BackwardLog | SweepType::ForwardLog => self.getLogSweepFBPhase(Ns, fs),
SweepType::ContinuousLin => self.getLinSweepContPhase(Ns, fs),
SweepType::ContinuousLog => self.getLogSweepContPhase(Ns, fs),
}
}
fn getLinSweepFBPhase(&self, Ns: usize, fs: StrictlyPositive) -> Dcol {
let sweep_type = &self.sweep_type;
assert!(matches!(
sweep_type,
SweepType::BackwardLin | SweepType::ForwardLin
));
let (fl, fu) = (*self.fl, *self.fu);
let (fl, fu) = if matches!(sweep_type, SweepType::BackwardLin | SweepType::BackwardLog) {
(fu, fl)
} else {
(fl, fu)
};
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, Ns: usize, fs: StrictlyPositive) -> Dcol {
let sweep_type = &self.sweep_type;
assert!(matches!(
sweep_type,
SweepType::BackwardLog | SweepType::ForwardLog
));
let fs = *fs;
let (fl, fu) = (*self.fl, *self.fu);
let (fl, fu) = if matches!(sweep_type, SweepType::BackwardLin | SweepType::BackwardLog) {
(fu, fl)
} else {
(fl, fu)
};
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, Ns: usize, fs: StrictlyPositive) -> Dcol {
let sweep_type = &self.sweep_type;
assert!(matches!(sweep_type, SweepType::ContinuousLog));
let fs = *fs;
let (fl, fu) = (*self.fl, *self.fu);
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, Ns: usize, fs: StrictlyPositive) -> Dcol {
assert!(matches!(self.sweep_type, SweepType::ContinuousLin));
let fs = *fs;
let (fl, fu) = (*self.fl, *self.fu);
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(feature = "python-bindings")]
#[cfg_attr(feature = "python-bindings", gen_stub_pymethods, pymethods)]
impl SweepSettings {
#[new]
#[pyo3(signature=(fl, fu, sweep_time, sweep_type, quiet_time=None, amplitude_modulation=None))]
fn py_new(
fl: Flt,
fu: Flt,
sweep_time: Flt,
sweep_type: SweepType,
quiet_time: Option<Flt>,
amplitude_modulation: Option<Vec<(Positive, Positive)>>,
) -> PyResult<Self> {
Ok(Self::new(
fl,
fu,
sweep_time,
sweep_type,
quiet_time,
amplitude_modulation,
)?)
}
}
#[derive(Debug, Clone)]
pub struct Sweep {
settings: SweepSettings,
fs: StrictlyPositive,
N: usize,
periodic_buf: Dcol,
}
impl Deref for Sweep {
type Target = SweepSettings;
fn deref(&self) -> &Self::Target {
&self.settings
}
}
impl Sweep {
pub fn new(settings: SweepSettings, fs: StrictlyPositive) -> Result<Self> {
ensure!(
1. / *settings.sweep_time < *fs / 2.,
InvalidParameterSnafu {
param: "Sweep time",
criterion: "Must be less than Nyquist frequency"
}
);
ensure!(*fs / 2. >= *settings.fu, SampleRateTooLowSnafu { fs: *fs });
let periodic_buf = settings.getSignal(fs)?;
Ok(Sweep {
N: 0,
settings,
fs,
periodic_buf,
})
}
}
impl SourceImpl for Sweep {
fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator<Item = &mut Flt>) {
let Nnew = self.N + sig.len();
let sweep_iter = self
.periodic_buf
.as_slice()
.unwrap()
.iter()
.cycle()
.skip(self.N);
for (sig, sweep_sample) in sig.zip(sweep_iter) {
*sig = *sweep_sample;
}
self.N = Nnew % self.periodic_buf.len();
}
fn fs(&self) -> StrictlyPositive {
self.fs
}
}
#[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 sweep_settings =
SweepSettings::new(fl, fu, 10., SweepType::ForwardLin, None, None).unwrap();
let Ns = (*sweep_settings.sweep_time * fs) as usize;
let phase = sweep_settings.getPhase(Ns, fs.try_into().unwrap());
assert_abs_diff_eq!(phase[10], &(twopi));
}
}