Removed some unnecessary wrappers. Added interrupted noise.

This commit is contained in:
Anne de Jong 2024-10-28 19:24:23 +01:00
parent 9e4ce617ae
commit adc3db1be6
3 changed files with 119 additions and 49 deletions

View File

@ -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<InterruptState>,
}
impl WhiteNoise {
pub fn new() -> Self {
pub fn new(fs: Flt, interrupt_period: Option<Flt>) -> 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<Item = &mut Flt>) {
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<dyn SourceImpl> {
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<Flt>,
// 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<Flt>) -> 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,
}

View File

@ -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<Siggen> {
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<Self> {
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<Flt>) -> 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);

View File

@ -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<Source> {
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<Flt>) -> 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<Flt>) -> 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<Flt>) -> 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<Flt>) -> Source {
Self::newPinkNoise(DUMMY_SAMPLING_FREQ, interrupt_period)
}
#[staticmethod]
#[pyo3(name = "newSweep")]