diff --git a/src/siggen/noise.rs b/src/siggen/noise.rs index 2349b84..ea6f32b 100644 --- a/src/siggen/noise.rs +++ b/src/siggen/noise.rs @@ -14,21 +14,72 @@ const PINKNOISE_ANALOG_ORDER: usize = 10; pub struct WhiteNoise { // SmallRng is a cheap random number generator rng: SmallRng, + // Interruption state (whether to output, number of samples, number of samples after which a switch need to be performed) + interrupt_state: Option, } impl WhiteNoise { - pub fn new() -> Self { + pub fn new(fs: Flt, interrupt_period: Option) -> Self { + let interrupt_state = if let Some(period) = interrupt_period { + if period > 0. { + Some(InterruptState { + period, + cur_idx: 0, + max_idx: (period * fs) as usize, + silence: false, + }) + } else { + None + } + } else { + None + }; WhiteNoise { rng: SmallRng::from_entropy(), + interrupt_state, } } } impl SourceImpl for WhiteNoise { fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator) { - sig.for_each(|s| { - *s = self.rng.sample(StandardNormal); - }); + let mut output = true; + + // Look at whether we should do interruption of the played noise. + if let Some(InterruptState { + cur_idx, + max_idx, + silence, + .. + }) = &mut self.interrupt_state + { + if cur_idx > max_idx { + // Swap flag + *cur_idx = 0; + *silence = !*silence; + } + output = !*silence; + *cur_idx += sig.len(); + } + // If output is true, send new random noise. Otherwise, just silence + if output { + sig.for_each(|s| { + *s = self.rng.sample(StandardNormal); + }); + } else { + sig.for_each(|s| { + *s = 0.; + }); + } + } + + fn reset(&mut self, fs: Flt) { + if let Some(state) = &mut self.interrupt_state { + // Restore to first start with output + state.silence = false; + state.cur_idx = 0; + state.max_idx = (state.period * fs) as usize; + // state.period = untouched + } } - fn reset(&mut self, _fs: Flt) {} fn clone_dyn(&self) -> Box { Box::new(self.clone()) } @@ -38,14 +89,18 @@ impl SourceImpl for WhiteNoise { pub struct ColoredNoise { // White noise generator wn: WhiteNoise, + // Temporary storage for the generated signal. Needs to be able to slice, + // which is not guaranteed by the input iterator. tmp: Vec, + // Analog filter used to generate the digital filter below analogue_blueprint: ZPKModel, + // The digital filter that colors the white noise filter: SeriesBiquad, } impl ColoredNoise { /// Generate a colored noise signal source that outputs pink noise (-3 dB / /// octave ) from 20 Hz to 20 kHz. - pub fn newPinkNoise() -> Self { + pub fn newPinkNoise(fs: Flt, interrupt_period: Option) -> Self { let twopi = 2. * pi; let fl = 10.; let fu = 20e3; @@ -72,7 +127,7 @@ impl ColoredNoise { let analogue_blueprint = ZPKModel::new(zeros, poles, gain); let filter = analogue_blueprint.bilinear(480000.); Self { - wn: WhiteNoise::new(), + wn: WhiteNoise::new(fs, interrupt_period), tmp: vec![], analogue_blueprint, filter, @@ -128,3 +183,11 @@ impl SourceImpl for ColoredNoise { Box::new(self.clone()) } } + +#[derive(Clone, Copy, Debug)] +struct InterruptState { + period: Flt, + cur_idx: usize, + max_idx: usize, + silence: bool, +} diff --git a/src/siggen/siggen.rs b/src/siggen/siggen.rs index e028332..c3633e9 100644 --- a/src/siggen/siggen.rs +++ b/src/siggen/siggen.rs @@ -11,6 +11,10 @@ use std::fmt::Debug; use std::iter::ExactSizeIterator; use std::slice::IterMut; +/// Dummy sampling frequency to be filled in when the sampling frequency is +/// still unknown at the point in time. +pub const DUMMY_SAMPLING_FREQ: Flt = 48000.; + /// Multiple channel signal generator. Able to create (acoustic) output signals. See above example on how to use. /// Typical signal that can be created are: /// @@ -39,30 +43,9 @@ pub struct Siggen { #[cfg(feature = "python-bindings")] #[cfg_attr(feature = "python-bindings", pymethods)] impl Siggen { - #[pyo3(name = "newWhiteNoise")] - #[staticmethod] - fn newWhiteNoise_py(fs: Flt) -> Siggen { - Siggen::newWhiteNoise(fs, 0) - } - #[pyo3(name = "newSine")] - #[staticmethod] - fn newSine_py(fs: Flt, freq: Flt, nchannels: usize) -> PyResult { - Ok(Siggen::newSine(fs, nchannels, freq)?) - } - #[pyo3(name = "newSweep")] - #[staticmethod] - fn newSweep_py( - fs: Flt, - nchannels: usize, - fl: Flt, - fu: Flt, - sweep_time: Flt, - quiet_time: Flt, - sweep_type: SweepType, - ) -> Result { - Ok(Siggen::newSweep( - fs, nchannels, fl, fu, sweep_time, quiet_time, sweep_type, - )?) + #[new] + fn new_py() -> Self { + Siggen::new(1, Source::newSilence()) } } @@ -71,7 +54,7 @@ impl Siggen { /// # Args /// /// - `nchannels` - The number of channels to output - /// - `source` - Source function + /// - `source` - Source that generates the signal pub fn new(nchannels: usize, source: Source) -> Siggen { Siggen { fs: None, @@ -131,8 +114,8 @@ impl Siggen { /// /// - `fs` - Sampling frequency \[Hz\] /// - `nchannels` - The number of channels to output - pub fn newWhiteNoise(_fs: Flt, nchannels: usize) -> Siggen { - Siggen::new(nchannels, Source::newWhiteNoise()) + pub fn newWhiteNoise(fs: Flt, nchannels: usize, interrupt_period: Option) -> Siggen { + Siggen::new(nchannels, Source::newWhiteNoise(fs, interrupt_period)) } /// Returns the number of channels this signal generator is generating for. @@ -278,7 +261,7 @@ mod test { fn test_whitenoise() { // This code is just to check syntax. We should really be listening to these outputs. let mut t = [0.0; 10]; - Siggen::newWhiteNoise(1., 1).genSignal(&mut t); + Siggen::newWhiteNoise(1., 1, None).genSignal(&mut t); // println!("{:?}", &t); } @@ -289,7 +272,7 @@ mod test { const N: usize = 10000; let mut s1 = [0.0; N]; let mut s2 = [0.0; N]; - let mut siggen = Siggen::newSine(1., 1, 1.0).unwrap(); + let mut siggen = Siggen::newSine(10., 1, 1.0).unwrap(); siggen.reset(10.0); siggen.setAllMute(false); diff --git a/src/siggen/source.rs b/src/siggen/source.rs index 0bce2a3..d6bc3f8 100644 --- a/src/siggen/source.rs +++ b/src/siggen/source.rs @@ -1,6 +1,7 @@ //! All sources for a signal generator. Sine waves, sweeps, noise, etc. -use super::sweep::{SweepParams, SweepType}; use super::noise::{ColoredNoise, WhiteNoise}; +use super::siggen::DUMMY_SAMPLING_FREQ; +use super::sweep::{SweepParams, SweepType}; use crate::config::*; use std::fmt::Debug; use std::ops::{Deref, DerefMut}; @@ -23,7 +24,10 @@ impl Source { /// /// # Args /// - /// - `fs` - Sampling frequency \[Hz\] + /// - `fs`: Sampling frequency \[Hz\]. When not known at the point in time, + /// just fill in something sensible. If the [Siggen] runs in a + /// [StreamMgr], the [StreamMgr] cals [Siggen::reset] to set the right + /// sampling frequency. /// * `freq` - Frequency of the sine wave in \[Hz\] pub fn newSine(fs: Flt, freq: Flt) -> Result { Ok(Source { @@ -39,15 +43,32 @@ impl Source { } /// Create a white noise signal source - pub fn newWhiteNoise() -> Source { + /// + /// # Args + /// + /// - `fs`: Sampling frequency \[Hz\]. When not known at the point in time, + /// just fill in something sensible. If the [Siggen] runs in a + /// [StreamMgr], the [StreamMgr] cals [Siggen::reset] to set the right + /// sampling frequency. + /// - `interrupt_period` - when given AND > 0, this turns on and off the + /// noise source with periods given by the value, in \[s\]. + pub fn newWhiteNoise(fs: Flt, interrupt_period: Option) -> Source { Source { - src: Box::new(WhiteNoise::new()), + src: Box::new(WhiteNoise::new(fs, interrupt_period)), } } /// Create a pink noise signal source - pub fn newPinkNoise() -> Source { + /// # Args + /// + /// - `fs`: Sampling frequency \[Hz\]. When not known at the point in time, + /// just fill in something sensible. If the [Siggen] runs in a + /// [StreamMgr], the [StreamMgr] cals [Siggen::reset] to set the right + /// sampling frequency. + /// - `interrupt_period` - when given AND > 0, this turns on and off the + /// noise source with periods given by the value, in \[s\]. + pub fn newPinkNoise(fs: Flt, interrupt_period: Option) -> Source { Source { - src: Box::new(ColoredNoise::newPinkNoise()), + src: Box::new(ColoredNoise::newPinkNoise(fs, interrupt_period)), } } @@ -55,7 +76,10 @@ impl Source { /// /// # Args /// - /// - `fs` - Sample rate \[Hz\] + /// - `fs`: Sampling frequency \[Hz\]. When not known at the point in time, + /// just fill in something sensible. If the [Siggen] runs in a + /// [StreamMgr], the [StreamMgr] cals [Siggen::reset] to set the right + /// sampling frequency. /// - `fl` - Lower frequency \[Hz\] /// - `fu` - Upper frequency \[Hz\] /// - `sweep_time` - The duration of a single sweep \[s\] @@ -89,14 +113,14 @@ impl Source { Self::newSilence() } #[staticmethod] - #[pyo3(name = "newWhiteNoise")] - fn newWhiteNoise_py() -> Source { - Self::newWhiteNoise() + #[pyo3(name = "newWhiteNoise", signature=(interrupt_period=None))] + fn newWhiteNoise_py(interrupt_period: Option) -> Source { + Self::newWhiteNoise(DUMMY_SAMPLING_FREQ, interrupt_period) } #[staticmethod] - #[pyo3(name = "newPinkNoise")] - fn newPinkNoise_py() -> Source { - Self::newPinkNoise() + #[pyo3(name = "newPinkNoise", signature=(interrupt_period=None))] + fn newPinkNoise_py(interrupt_period: Option) -> Source { + Self::newPinkNoise(DUMMY_SAMPLING_FREQ, interrupt_period) } #[staticmethod] #[pyo3(name = "newSweep")]