From ec010945c627bbcd067ef01d67ccea8ffce98c21 Mon Sep 17 00:00:00 2001 From: "J.A. de Jong - Redu-Sone B.V., ASCEE V.O.F" Date: Wed, 29 Nov 2023 07:20:13 +0100 Subject: [PATCH] Added first parts signal generator. Not yet for sweeps, not yet filters. --- Cargo.toml | 11 +-- src/config.rs | 3 + src/lib.rs | 3 + src/siggen.rs | 181 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 src/siggen.rs diff --git a/Cargo.toml b/Cargo.toml index 6422c8d..e80e094 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,24 +14,27 @@ categories = [ "mathematics"] [lib] name = "lasprs" -crate-type = ["cdylib", "lib"] +crate-type = ["cdylib", "rlib"] [dependencies] anyhow = "1.0.75" -pyo3 = { version = "0.20", features=["anyhow", "extension-module"]} # Optional future feature for ndarray: blas ndarray = { version = "0.15.3", features = ["rayon"] } num = "0.4.1" rayon = "1.8.0" numpy = { version = "0.20" } +strum_macros = "0.25.3" +pyo3 = { version = "0.20", features=["anyhow", "extension-module"]} +rand = "0.8.5" +rand_distr = "0.4.3" # blas-src = { version = "0.8", features = ["openblas"] } # openblas-src = { version = "0.10", features = ["cblas", "system"] } [features] -# default = ["f64"] +default = ["f64"] # Use this for debugging extension -default = ["f64", "extension-module", "pyo3/extension-module"] +# default = ["f64", "extension-module", "pyo3/extension-module"] f64 = [] f32 = [] extension-module = ["pyo3/extension-module"] diff --git a/src/config.rs b/src/config.rs index 3fcc978..2832ba0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,6 +16,9 @@ use numpy::ndarray::{Array1, Array2}; pub type Vd = Vec; pub type Vc = Vec; +pub type Dcol = Array1; +pub type Ccol = Array1; + pub type Dmat = Array2; pub type Cmat = Array2; diff --git a/src/lib.rs b/src/lib.rs index 7296f08..45852ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,9 @@ mod config; pub mod filter; + // pub mod window; + // pub mod ps; + pub mod siggen; extern crate pyo3; #[cfg(feature = "extension-module")] diff --git a/src/siggen.rs b/src/siggen.rs new file mode 100644 index 0000000..369f1ef --- /dev/null +++ b/src/siggen.rs @@ -0,0 +1,181 @@ +use super::config::*; +use super::filter::Filter; +use pyo3::prelude::*; +use rand::prelude::*; +use rand::rngs::ThreadRng; +use rand_distr::StandardNormal; + +pub trait Source: Send { + fn genSignal_unscaled(&mut self, sig: &mut [Flt]); + fn reset(&mut self, fs: Flt); + fn clone_dyn(&self) -> Box; +} +impl Clone for Box { + fn clone(&self) -> Self { + self.clone_dyn() + } +} + +/// White noise source +#[derive(Clone)] +struct WhiteNoise {} +impl WhiteNoise { + /// Generate new WhiteNoise generator + fn new() -> WhiteNoise { + WhiteNoise {} + } +} +impl Source for WhiteNoise { + fn genSignal_unscaled(&mut self, sig: &mut [Flt]) { + sig.iter_mut() + .for_each(|s| *s = thread_rng().sample(StandardNormal)); + } + fn reset(&mut self,_fs: Flt) {} + fn clone_dyn(&self) -> Box { + Box::new(self.clone()) + } +} + +/// Sine wave, with configurable frequency +#[derive(Clone)] +struct Sine { + // Sampling freq [Hz] + fs: Flt, + // current stored phase + phase: Flt, + // Signal frequency [rad/s] + omg: Flt, +} +impl Sine { + /// Create new sine source signal + /// + /// Args: + /// + /// * fs: Sampling freq [Hz] + /// * + fn new(freq: Flt) -> Sine { + Sine { + fs: -1., + phase: 0., + omg: 2. * pi * freq, + } + } +} +impl Source for Sine { + fn genSignal_unscaled(&mut self, sig: &mut [Flt]) { + if self.fs < 0. { + sig.iter_mut().for_each(|s| { + *s = 0.; + }); + return; + } + sig.iter_mut().for_each(|s| { + *s = Flt::sin(self.phase); + self.phase += self.omg / self.fs; + }); + while self.phase > 2. * pi { + self.phase -= 2. * pi; + } + } + fn reset(&mut self, fs: Flt) { + self.fs = fs; + self.phase = 0.; + } + fn clone_dyn(&self) -> Box { + Box::new(self.clone()) + } +} + +/// Sweep signal + + +#[derive(Clone)] +/// Signal generator. Able to create acoustic output signals +pub struct Siggen { + source: Box, + prefilter: Option>, + muted: bool, + gain: Flt, + DCOffset: Flt, +} + +/// A struct that implements the Siggen trait is able to generate a signal. +impl Siggen { + /// Set new pre-filter that filters the source signal + pub fn setPreFilter(&mut self, pref: Option>) { + self.prefilter = pref.clone(); + } + pub fn newWhiteNoise() -> Siggen { + Siggen::new(Box::new(WhiteNoise::new())) + } + pub fn newSineWave(freq: Flt) -> Siggen { + Siggen::new(Box::new(Sine::new(freq))) + } + + /// Create a new signal generator wiht an arbitrary source. + pub fn new(source: Box) -> Siggen { + Siggen { + source, + prefilter: None, + muted: false, + gain: 1.0, + DCOffset: 0.0, + } + } + + /// Generate new signal data. + /// + /// # Args + /// + /// sig: Reference of array of float values to be filled with signal data. + /// + /// # Details + /// + /// - When muted, the DC offset is still applied + /// - The order of the generation is: + /// - First, the source is generated. + /// - If a prefilter is installed, this pre-filter is applied to the source signal. + /// - Gain is applied. + /// - Offset is applied (thus, no gain is applied to the DC offset). + /// + fn genSignal(&mut self, sig: &mut [Flt]) { + if self.muted { + sig.iter_mut().for_each(|x| { + *x = 0.0; + }); + } else { + self.source.genSignal_unscaled(sig); + if let Some(f) = &mut self.prefilter { + f.filter(sig); + } + } + sig.iter_mut().for_each(|x| { + // First apply gain, then offset + *x *= self.gain; + *x += self.DCOffset; + }); + } + + /// Reset signal generator. Applies any kind of cleanup necessary. + /// + /// Args + /// + /// * fs: (New) Sampling frequency [Hz] + /// + fn reset(&mut self, fs: Flt) { + self.source.reset(fs); + if let Some(f) = self.prefilter { + f.reset(); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_whitenoise() { + println!("{:?}", WhiteNoise::new().genSignal(10)); + } +}