Implemented sweep signal generator. Some sweep implementations can error when something goes wrong. Changing params on running signal generator give results back that need to be handled

This commit is contained in:
Anne de Jong 2024-10-13 13:02:49 +02:00
parent 8ee5fcbf02
commit cde2c74467
14 changed files with 792 additions and 231 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ python/lasprs/_lasprs*
.vscode/launch.json
.vscode
examples_py/.ipynb_checkpoints
.ipynb_checkpoints

View File

@ -30,13 +30,13 @@ fn main() -> Result<()> {
let stdin_channel = stdin_channel_wait_for_return();
println!("Creating signal generator...");
let mut siggen = Siggen::newSine(2, 432.0);
let mut siggen = Siggen::newSine(1., 2, 432.0).unwrap();
// Reduce all gains a bit...
siggen.setAllGains(0.1);
// Apply signal generator
smgr.setSiggen(siggen);
smgr.setSiggen(siggen)?;
println!("Starting stream...");
let devs = smgr.getDeviceInfo();

View File

@ -1,14 +1,13 @@
use anyhow::Result;
use crossbeam::channel::{unbounded, Receiver, TryRecvError};
use lasprs::daq::{StreamMgr, StreamStatus, StreamType};
use lasprs::siggen::Siggen;
use lasprs::siggen::{Siggen, SweepType};
use std::io;
use std::{thread, time};
// use
/// Spawns a thread and waits for a single line, pushes it to the receiver and returns
fn stdin_channel_wait_for_return() -> Receiver<String> {
let (tx, rx) = unbounded();
thread::spawn(move || {
let mut buffer = String::new();
@ -28,7 +27,19 @@ fn main() -> Result<()> {
let stdin_channel = stdin_channel_wait_for_return();
println!("Creating signal generator...");
let mut siggen = Siggen::newSine(2, 432.);
// let mut siggen = Siggen::newSine(44100., 2, 432.)?;
let mut siggen = Siggen::newSweep(
44100.,
1, // nchannels: usize,
100., // fl: Flt,
10000., //fu: Flt,
1.0, // sweep_time: Flt,
// 1.0, //quiet_time: Flt,
0., //quiet_time: Flt,
// SweepType::ForwardLin//sweep_type: SweepType,
// SweepType::ForwardLog, //sweep_type: SweepType,
SweepType::ContinuousLog, //sweep_type: SweepType,
)?;
// Some things that can be done
// siggen.setDCOffset(&[0.1, 0.]);
@ -36,12 +47,11 @@ fn main() -> Result<()> {
// Reduce all gains a bit...
siggen.setAllGains(0.1);
println!("Starting stream...");
smgr.startDefaultOutputStream()?;
// Apply signal generator
smgr.setSiggen(siggen);
smgr.setSiggen(siggen)?;
println!("Press <enter> key to quit...");
'infy: loop {

View File

@ -10,6 +10,9 @@ pub enum StreamCommand {
/// New signal generator config to be used in OUTPUT stream
NewSiggen(Siggen),
/// Apply command to the signal generator.
SiggenCommand(SiggenCommand),
/// Stop the thread, do not listen for data anymore.
StopThread,

View File

@ -307,7 +307,7 @@ mod test {
const Nframes: usize = 20;
const Nch: usize = 2;
let mut signal = [0.; Nch * Nframes];
let mut siggen = Siggen::newSine(Nch, 1.);
let mut siggen = Siggen::newSine(fs, Nch, 1.).unwrap();
siggen.reset(fs);
siggen.setMute(&[false, true]);

View File

@ -4,7 +4,7 @@ use crate::{
config::*,
siggen::{self, Siggen},
};
use anyhow::{bail, Error, Result};
use anyhow::{anyhow, bail, Error, Result};
use api::StreamApiDescr;
use array_init::from_iter;
use core::time;
@ -13,8 +13,11 @@ use crossbeam::{
channel::{unbounded, Receiver, Sender, TrySendError},
thread,
};
use std::sync::{atomic::AtomicBool, Arc, Mutex};
use std::thread::{JoinHandle, Thread};
use std::{
sync::{atomic::AtomicBool, Arc, Mutex},
time::Duration,
};
use streamcmd::StreamCommand;
use streamdata::*;
use streammetadata::*;
@ -33,7 +36,8 @@ struct StreamInfo<T> {
streamtype: StreamType,
stream: Box<dyn Stream>,
threadhandle: JoinHandle<T>,
comm: Sender<StreamCommand>,
commtx: Sender<StreamCommand>,
commrx: Receiver<Result<()>>,
}
/// Keep track of whether the stream has been created. To ensure singleton behaviour.
@ -108,8 +112,9 @@ impl StreamMgr {
self.getStatus(st)
}
#[pyo3(name = "setSiggen")]
fn setSiggen_py(&mut self, siggen: Siggen) {
self.setSiggen(siggen)
fn setSiggen_py(&mut self, siggen: Siggen) -> PyResult<()> {
self.setSiggen(siggen)?;
Ok(())
}
#[pyo3(name = "getStreamMetaData")]
fn getStreamMetaData_py(&self, st: StreamType) -> Option<StreamMetaData> {
@ -195,18 +200,27 @@ impl StreamMgr {
/// Set a new signal generator. Returns an error if it is unapplicable.
/// It is unapplicable if the number of channels of output does not match the
/// number of output channels in a running stream.
pub fn setSiggen(&mut self, siggen: Siggen) {
pub fn setSiggen(&mut self, siggen: Siggen) -> Result<()> {
// Current signal generator. Where to place it?
if let Some(istream) = &self.input_stream {
if let Some(os) = &self.output_stream {
assert!(self.siggen.is_none());
os.commtx.send(StreamCommand::NewSiggen(siggen)).unwrap();
os.commrx.recv().unwrap()
} else if let Some(istream) = &self.input_stream {
if let StreamType::Duplex = istream.streamtype {
assert!(self.siggen.is_none());
istream.comm.send(StreamCommand::NewSiggen(siggen)).unwrap();
}
} else if let Some(os) = &self.output_stream {
assert!(self.siggen.is_none());
os.comm.send(StreamCommand::NewSiggen(siggen)).unwrap();
istream
.commtx
.send(StreamCommand::NewSiggen(siggen))
.unwrap();
istream.commrx.recv().unwrap()
} else {
self.siggen = Some(siggen);
Ok(())
}
} else {
self.siggen = Some(siggen);
Ok(())
}
}
@ -235,7 +249,7 @@ impl StreamMgr {
/// of queues that get data from the stream.
pub fn addInQueue(&mut self, tx: Sender<InStreamMsg>) {
if let Some(is) = &self.input_stream {
is.comm.send(StreamCommand::AddInQueue(tx)).unwrap()
is.commtx.send(StreamCommand::AddInQueue(tx)).unwrap()
} else {
self.instreamqueues.as_mut().unwrap().push(tx);
}
@ -245,8 +259,14 @@ impl StreamMgr {
&mut self,
meta: Arc<StreamMetaData>,
rx: Receiver<InStreamMsg>,
) -> (JoinHandle<InQueues>, Sender<StreamCommand>) {
let (commtx, commrx) = unbounded();
) -> (
JoinHandle<InQueues>,
Sender<StreamCommand>,
Receiver<Result<()>>,
) {
// Bi-directional communication between input stream thread and stream manager
let (commtx_ret, commrx) = unbounded();
let (commtx, commrx_ret) = unbounded();
// Unwrap here, as the queues should be free to grab
let mut iqueues = self
@ -261,8 +281,17 @@ impl StreamMgr {
// New queue added
StreamCommand::AddInQueue(queue) => {
match queue.send(InStreamMsg::StreamStarted(meta.clone())) {
Ok(()) => iqueues.push(queue),
Err(_) => {}
Ok(()) => {
iqueues.push(queue);
commtx.send(Ok(())).unwrap();
}
Err(e) => {
commtx
.send(Err(anyhow!(
"Cannot push to queue: {e}. Object destructed?"
)))
.unwrap();
}
}
}
@ -272,11 +301,15 @@ impl StreamMgr {
&mut iqueues,
InStreamMsg::StreamStopped,
);
commtx.send(Ok(())).unwrap();
break 'infy;
}
StreamCommand::NewSiggen(_) => {
panic!("Error: signal generator send to input-only stream.");
}
StreamCommand::SiggenCommand(_) => {
panic!("Error: signal generator command send to input-only stream.");
}
}
}
if let Ok(msg) = rx.recv_timeout(time::Duration::from_millis(10)) {
@ -285,7 +318,7 @@ impl StreamMgr {
}
iqueues
});
(threadhandle, commtx)
(threadhandle, commtx_ret, commrx_ret)
}
// Match device info struct on given daq config.
@ -303,8 +336,13 @@ impl StreamMgr {
&mut self,
meta: Arc<StreamMetaData>,
tx: Sender<RawStreamData>,
) -> (JoinHandle<Siggen>, Sender<StreamCommand>) {
let (commtx, commrx) = unbounded();
) -> (
JoinHandle<Siggen>,
Sender<StreamCommand>,
Receiver<Result<()>>,
) {
let (commtx_res, commrx) = unbounded();
let (commtx, commrx_res) = unbounded();
// Number of channels to output for
let nchannels = meta.nchannels();
@ -314,7 +352,7 @@ impl StreamMgr {
let mut siggen = self
.siggen
.take()
.unwrap_or_else(|| Siggen::newSilence(nchannels));
.unwrap_or_else(|| Siggen::newSilence(meta.samplerate, nchannels));
if siggen.nchannels() != nchannels {
// Updating number of channels
@ -323,9 +361,15 @@ impl StreamMgr {
siggen.reset(meta.samplerate);
let threadhandle = std::thread::spawn(move || {
let mut floatbuf: Vec<Flt> = Vec::with_capacity(nchannels * meta.framesPerBlock);
// What is a good sleep time? We have made sure that there are
// two buffers available for the output stream. We choose to wake up twice per frame.
let sleep_time_us = Duration::from_micros(
(0.5 * 1e6 * meta.framesPerBlock as Flt / meta.samplerate) as u64,
);
let mut floatbuf: Vec<Flt> = vec![0.; nchannels * meta.framesPerBlock];
'infy: loop {
if let Ok(comm_msg) = commrx.try_recv() {
if let Ok(comm_msg) = commrx.recv_timeout(sleep_time_us) {
match comm_msg {
// New queue added
StreamCommand::AddInQueue(_) => {
@ -334,6 +378,7 @@ impl StreamMgr {
// Stop this thread. Returns the queue
StreamCommand::StopThread => {
commtx.send(Ok(())).unwrap();
break 'infy;
}
StreamCommand::NewSiggen(new_siggen) => {
@ -344,16 +389,20 @@ impl StreamMgr {
// println!("Updating channels");
siggen.setNChannels(nchannels);
}
commtx.send(Ok(())).unwrap();
}
StreamCommand::SiggenCommand(cmd) => {
// Apply command to signal generator.
let res = siggen.applyCommand(cmd);
commtx.send(res).unwrap();
}
}
}
while tx.len() < 2 {
unsafe {
floatbuf.set_len(nchannels * meta.framesPerBlock);
}
// Obtain signal
// Obtain signal from signal generator
siggen.genSignal(&mut floatbuf);
// println!("level: {}", floatbuf.iter().sum::<Flt>());
// Convert signal generator data to raw data and push to the stream thread
let msg = match meta.rawDatatype {
DataType::I8 => {
let v = Vec::<i8>::from_iter(floatbuf.iter().map(|f| f.to_sample()));
@ -377,14 +426,17 @@ impl StreamMgr {
}
};
if let Err(_e) = tx.send(msg) {
// println!("Error sending raw stream data to output stream!");
break 'infy;
// An error occured while trying to send the raw data to
// the stream. This might be because the stream has
// stopped or has an error.
// There is nothing we can do here, but we should not stop the thread.
}
}
}
siggen
});
(threadhandle, commtx)
(threadhandle, commtx_res, commrx_res)
}
/// Start a stream of certain type, using given configuration
@ -418,13 +470,14 @@ impl StreamMgr {
_ => bail!("API {} not implemented!", cfg.api),
};
let meta = stream.metadata();
let (threadhandle, commtx) = self.startOuputStreamThread(meta, tx);
let (threadhandle, commtx, commrx) = self.startOuputStreamThread(meta, tx);
self.output_stream = Some(StreamInfo {
streamtype: StreamType::Input,
stream,
threadhandle,
comm: commtx,
commtx,
commrx,
});
Ok(())
@ -472,13 +525,14 @@ impl StreamMgr {
sendMsgToAllQueuesRemoveUnused(iqueues, InStreamMsg::StreamStarted(meta.clone()));
let (threadhandle, commtx) = self.startInputStreamThread(meta, rx);
let (threadhandle, commtx, commrx) = self.startInputStreamThread(meta, rx);
self.input_stream = Some(StreamInfo {
streamtype: stype,
stream,
threadhandle,
comm: commtx,
commtx,
commrx,
});
Ok(())
@ -503,13 +557,14 @@ impl StreamMgr {
let meta = stream.metadata();
sendMsgToAllQueuesRemoveUnused(iqueues, InStreamMsg::StreamStarted(meta.clone()));
let (threadhandle, commtx) = self.startInputStreamThread(meta, rx);
let (threadhandle, commtx, commrx) = self.startInputStreamThread(meta, rx);
self.input_stream = Some(StreamInfo {
streamtype: StreamType::Input,
stream,
threadhandle,
comm: commtx,
commtx,
commrx,
});
Ok(())
@ -537,15 +592,14 @@ impl StreamMgr {
let (tx, rx)= unbounded();
let stream = self.cpal_api.startDefaultOutputStream(rx)?;
let meta = stream.metadata();
let (threadhandle, commtx) = self.startOuputStreamThread(meta, tx);
// Inform all listeners of new stream data
let (threadhandle, commtx, commrx) = self.startOuputStreamThread(meta, tx);
self.output_stream = Some(StreamInfo {
streamtype: StreamType::Input,
stream,
threadhandle,
comm: commtx,
commtx,
commrx,
});
Ok(())
@ -563,19 +617,22 @@ impl StreamMgr {
streamtype: _, // Ignored here
stream: _,
threadhandle,
comm,
commtx,
commrx,
}) = self.input_stream.take()
{
// println!("Stopping existing stream..");
// Send thread to stop
comm.send(StreamCommand::StopThread).unwrap();
commtx.send(StreamCommand::StopThread).unwrap();
// Store stream queues back into StreamMgr
self.instreamqueues = Some(threadhandle.join().expect("Stream thread panicked!"));
let res = commrx.recv().unwrap();
return res;
} else {
bail!("Stream is not running.")
}
Ok(())
}
/// Stop existing output stream
pub fn stopOutputStream(&mut self) -> Result<()> {
@ -583,21 +640,17 @@ impl StreamMgr {
streamtype: _, // Ignored here
stream: _,
threadhandle,
comm,
commtx,
commrx,
}) = self.output_stream.take()
{
if comm.send(StreamCommand::StopThread).is_err() {
// Failed to send command over channel. This means the thread is
// already finished due to some other reason.
assert!(threadhandle.is_finished());
}
// println!("Wainting for threadhandle to join...");
commtx.send(StreamCommand::StopThread).unwrap();
// eprintln!("Wainting for threadhandle to join...");
self.siggen = Some(threadhandle.join().expect("Output thread panicked!"));
// println!("Threadhandle joined!");
commrx.recv().unwrap()
} else {
bail!("Stream is not running.");
}
Ok(())
}
/// Stop existing running stream.
///

View File

@ -8,7 +8,7 @@ use std::collections::VecDeque;
/// TimeBuffer, storage to add blocks of data in a ring buffer, that can be
/// extracted by blocks of other size. Also, we can keep samples in a buffer to
/// create, for example, overlapping windows of time data.
#[derive(Default, Debug)]
#[derive(Default, Debug, Clone)]
pub struct TimeBuffer {
data: Vec<VecDeque<Flt>>,
}

View File

@ -21,6 +21,11 @@
//!
//! ```
mod siggen;
mod siggenchannel;
mod source;
pub use source::{Silence, Sine, WhiteNoise};
mod siggencmd;
mod sweep;
pub use source::Source;
pub use siggen::Siggen;
pub use sweep::SweepType;
pub use siggencmd::SiggenCommand;

View File

@ -1,26 +1,30 @@
use super::siggenchannel::SiggenChannelConfig;
use super::SiggenCommand;
use super::source::{self, *};
use super::sweep::SweepType;
use crate::config::*;
use crate::filter::Filter;
use super::source::*;
use anyhow::{bail, Result};
use dasp_sample::{FromSample, Sample};
use rayon::prelude::*;
use std::fmt::Debug;
use std::iter::ExactSizeIterator;
use std::slice::IterMut;
/// Signal generator. Able to create acoustic output signals. See above example on how to use.
/// Multiple channel signal generator. Able to create (acoustic) output signals. See above example on how to use.
/// Typical signal that can be created are:
///
/// * (Siggen::newWhiteNoise)
/// * (Siggen::newSine)
/// * [Siggen::newWhiteNoise]
/// * [Siggen::newSine]
/// * [Siggen::newSilence]
///
#[derive(Clone)]
#[cfg_attr(feature = "python-bindings", pyclass)]
pub struct Siggen {
// The source dynamic signal. Noise, a sine wave, sweep, etc
source: Box<dyn Source>,
// Filter applied to the source signal
source: Source,
// Channel configuration for each output channel
channels: Vec<SiggenChannelConfig>,
// Temporary source signal buffer
@ -34,40 +38,117 @@ pub struct Siggen {
impl Siggen {
#[pyo3(name = "newWhiteNoise")]
#[staticmethod]
fn newWhiteNoise_py() -> Siggen {
Siggen::newWhiteNoise(0)
fn newWhiteNoise_py(fs: Flt) -> Siggen {
Siggen::newWhiteNoise(fs, 0)
}
#[pyo3(name = "newSine")]
#[staticmethod]
fn newSine_py(freq: Flt) -> Siggen {
Siggen::newSine(0, freq)
fn newSine_py(fs: Flt, freq: Flt, nchannels: usize) -> PyResult<Siggen> {
Ok(Siggen::newSine(fs, nchannels, freq)?)
}
}
/// Multiple channel signal generator. Can use a single source (coherent) to provide multiple signals
/// that can be sent out through different EQ's
impl Siggen {
/// Create a new signal generator with an arbitrary source.
/// # Args
///
/// - `nchannels` - The number of channels to output
/// - `source` - Source function
pub fn new(nchannels: usize, source: Source) -> Siggen {
Siggen {
source,
channels: vec![SiggenChannelConfig::new(); nchannels],
source_buf: vec![],
chout_buf: vec![],
}
}
/// Create sine sweep signal generator
///
/// # Args
///
/// - `fs` - Sample rate \[Hz\]
/// - `nchannels`: The number of channels to output
/// - `fl` - Lower frequency \[Hz\]
/// - `fu` - Upper frequency \[Hz\]
/// - `sweep_time` - The duration of a single sweep \[s\]
/// - `quiet_time` - Time of silence after one sweep and start of the next \[s\]
/// - `sweep_type` - The type of the sweep, see [SweepType].
pub fn newSweep(
fs: Flt,
nchannels: usize,
fl: Flt,
fu: Flt,
sweep_time: Flt,
quiet_time: Flt,
sweep_type: SweepType,
) -> Result<Self> {
let source = Source::newSweep(fs, fl, fu, sweep_time, quiet_time, sweep_type)?;
Ok(Self::new(nchannels, source))
}
/// Create a sine wave signal generator
///
/// # Args
///
/// - `fs` - Sampling frequency \[Hz\]
/// - `nchannels`: The number of channels to output
/// * `freq` - Frequency of the sine wave in \[Hz\]
pub fn newSine(fs: Flt, nchannels: usize, freq: Flt) -> Result<Siggen> {
Ok(Siggen::new(nchannels, Source::newSine(fs, freq)?))
}
/// Silence: create a signal generator that does not output any dynamic
/// signal at all.
/// # Args
///
/// - `fs` - Sampling frequency \[Hz\]
/// - `nchannels` - The number of channels to output
pub fn newSilence(_fs: Flt, nchannels: usize) -> Siggen {
Siggen::new(nchannels, Source::newSilence())
}
/// Create a white noise signal generator.
///
/// # Args
///
/// - `fs` - Sampling frequency \[Hz\]
/// - `nchannels` - The number of channels to output
pub fn newWhiteNoise(_fs: Flt, nchannels: usize) -> Siggen {
Siggen::new(nchannels, Source::newWhiteNoise())
}
/// Returns the number of channels this signal generator is generating for.
pub fn nchannels(&self) -> usize {
self.channels.len()
}
/// Silence: create a signal generator that does not output any dynamic
/// signal at all.
pub fn newSilence(nchannels: usize) -> Siggen {
Siggen {
channels: vec![SiggenChannelConfig::new(); nchannels],
source: Box::new(Silence {}),
source_buf: vec![],
chout_buf: vec![],
/// Apply command to current signal generator to change its state.
pub fn applyCommand(&mut self, msg: SiggenCommand) -> Result<()> {
match msg {
SiggenCommand::ChangeSource { src } => {
self.source = src;
Ok(())
}
SiggenCommand::ResetSiggen { fs } => {
self.reset(fs);
Ok(())
}
SiggenCommand::SetMuteAllChannels { mute } => {
self.setAllMute(mute);
Ok(())
}
SiggenCommand::SetMuteChannel { ch, mute } => {
if ch > self.channels.len() {
bail!("Invalid channel index: {ch}");
}
self.channels[ch].setMute(mute);
Ok(())
}
SiggenCommand::SetAllGains { g } => {
self.setAllGains(g);
Ok(())
}
}
/// Create a white noise signal generator.
pub fn newWhiteNoise(nchannels: usize) -> Siggen {
Siggen::new(nchannels, Box::new(WhiteNoise::new()))
}
/// Set gains of all channels in signal generator to the same value
///
/// # Args
@ -97,23 +178,6 @@ impl Siggen {
});
}
/// Create a sine wave signal generator
///
/// * freq: Frequency of the sine wave in \[Hz\]
pub fn newSine(nchannels: usize, freq: Flt) -> Siggen {
Siggen::new(nchannels, Box::new(Sine::new(freq)))
}
/// Create a new signal generator wiht an arbitrary source.
pub fn new(nchannels: usize, source: Box<dyn Source>) -> Siggen {
Siggen {
source,
channels: vec![SiggenChannelConfig::new(); nchannels],
source_buf: vec![],
chout_buf: vec![],
}
}
/// Creates *interleaved* output signal
pub fn genSignal<T>(&mut self, out: &mut [T])
where
@ -181,83 +245,6 @@ impl Siggen {
}
}
/// Signal generator config for a certain channel
#[derive(Clone)]
struct SiggenChannelConfig {
muted: bool,
prefilter: Option<Box<dyn Filter>>,
gain: Flt,
DCOffset: Flt,
}
unsafe impl Send for SiggenChannelConfig {}
impl SiggenChannelConfig {
/// Set new pre-filter that filters the source signal
pub fn setPreFilter(&mut self, pref: Option<Box<dyn Filter>>) {
self.prefilter = pref;
}
/// Set the gain applied to the source signal
///
/// * g: Gain value. Can be any float. If set to 0.0, the source is effectively muted. Only
/// using (setMute) is a more efficient way to do this.
pub fn setGain(&mut self, g: Flt) {
self.gain = g;
}
/// Reset signal channel config. Only resets the prefilter state
pub fn reset(&mut self, _fs: Flt) {
if let Some(f) = &mut self.prefilter {
f.reset()
}
}
/// Generate new channel configuration using 'arbitrary' initial config: muted false, gain 1.0, DC offset 0.
/// and no prefilter
pub fn new() -> SiggenChannelConfig {
SiggenChannelConfig {
muted: false,
prefilter: None,
gain: 1.0,
DCOffset: 0.0,
}
}
/// Set mute on channel. If true, only DC signal offset is outputed from (SiggenChannelConfig::transform).
pub fn setMute(&mut self, mute: bool) {
self.muted = mute;
}
/// Generate new signal data, given input source data.
///
/// # Args
///
/// source: Input source signal.
/// result: 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:
/// - 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).
///
pub fn genSignal(&mut self, source: &[Flt], result: &mut [Flt]) {
if self.muted {
result.iter_mut().for_each(|x| {
*x = 0.0;
});
} else {
result.copy_from_slice(source);
if let Some(f) = &mut self.prefilter {
f.filter(result);
}
}
result.iter_mut().for_each(|x| {
// First apply gain, then offset
*x *= self.gain;
*x += self.DCOffset;
});
}
}
#[cfg(test)]
mod test {
use approx::assert_abs_diff_eq;
@ -269,7 +256,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).genSignal(&mut t);
Siggen::newWhiteNoise(1., 1).genSignal(&mut t);
// println!("{:?}", &t);
}
@ -280,7 +267,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.0);
let mut siggen = Siggen::newSine(1., 1, 1.0).unwrap();
siggen.reset(10.0);
siggen.setAllMute(false);
@ -305,7 +292,7 @@ mod test {
const Nframes: usize = 10000;
const Nch: usize = 2;
let mut signal = [0.0; Nch * Nframes];
let mut siggen = Siggen::newSine(Nch, 1.0);
let mut siggen = Siggen::newSine(fs, Nch, 1.0).unwrap();
siggen.reset(fs);
siggen.setMute(&[false, true]);

View File

@ -0,0 +1,78 @@
use crate::config::*;
use crate::filter::Filter;
/// Signal generator config for a certain channel
#[derive(Clone)]
pub struct SiggenChannelConfig {
muted: bool,
prefilter: Option<Box<dyn Filter>>,
gain: Flt,
pub DCOffset: Flt,
}
unsafe impl Send for SiggenChannelConfig {}
impl SiggenChannelConfig {
/// Set new pre-filter that filters the source signal
pub fn setPreFilter(&mut self, pref: Option<Box<dyn Filter>>) {
self.prefilter = pref;
}
/// Set the gain applied to the source signal
///
/// * g: Gain value. Can be any float. If set to 0.0, the source is effectively muted. Only
/// using (setMute) is a more efficient way to do this.
pub fn setGain(&mut self, g: Flt) {
self.gain = g;
}
/// Reset signal channel config. Only resets the prefilter state
pub fn reset(&mut self, _fs: Flt) {
if let Some(f) = &mut self.prefilter {
f.reset()
}
}
/// Generate new channel configuration using 'arbitrary' initial config: muted false, gain 1.0, DC offset 0.
/// and no prefilter
pub fn new() -> SiggenChannelConfig {
SiggenChannelConfig {
muted: false,
prefilter: None,
gain: 1.0,
DCOffset: 0.0,
}
}
/// Set mute on channel. If true, only DC signal offset is outputed from (SiggenChannelConfig::transform).
pub fn setMute(&mut self, mute: bool) {
self.muted = mute;
}
/// Generate new signal data, given input source data.
///
/// # Args
///
/// source: Input source signal.
/// result: 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:
/// - 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).
///
pub fn genSignal(&mut self, source: &[Flt], result: &mut [Flt]) {
if self.muted {
result.iter_mut().for_each(|x| {
*x = 0.0;
});
} else {
result.copy_from_slice(source);
if let Some(f) = &mut self.prefilter {
f.filter(result);
}
}
result.iter_mut().for_each(|x| {
// First apply gain, then offset
*x *= self.gain;
*x += self.DCOffset;
});
}
}

38
src/siggen/siggencmd.rs Normal file
View File

@ -0,0 +1,38 @@
use super::source::*;
use crate::config::*;
/// Messages that can be send to a given signal generator [Siggen], to change its behaviour
#[cfg_attr(feature = "python-bindings", pyclass)]
pub enum SiggenCommand {
/// Change the source to a sine wave with given frequency.
ChangeSource{
/// New signal source to apply for signal generator
src: Source,
},
/// Reset the signal generator state
ResetSiggen {
/// New sampling frequency \[Hz\]
fs: Flt,
},
/// Set all gains to value g
SetAllGains {
/// Linear gain level to apply
g: Flt,
},
/// Change the mute state for a certain channel
SetMuteChannel {
/// channel index
ch: usize,
/// mute state
mute: bool,
},
/// Change the mute state for all channels
SetMuteAllChannels {
/// mute state
mute: bool,
},
}

View File

@ -1,64 +1,107 @@
//! All sources for a signal generator. Sine waves, sweeps, noise, etc.
use super::sweep::{SweepParams, SweepType};
use crate::config::*;
use std::ops::{Deref, DerefMut};
/// Ratio between circumference and radius of a circle
const twopi: Flt = 2.0 * pi;
use crate::config::*;
use anyhow::{bail, Result};
use rand::prelude::*;
use rand::rngs::ThreadRng;
use rand_distr::StandardNormal;
/// Ratio between circumference and radius of a circle
const twopi: Flt = 2.0 * pi;
/// Source for the signal generator. Implementations are sine waves, sweeps, noise.
pub trait Source: Send {
/// Generate the 'pure' source signal. Output is placed inside the `sig` argument.
fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator<Item = &mut Flt>);
/// Reset the source state, i.e. set phase to 0, etc
fn reset(&mut self, fs: Flt);
/// Used to make the Siggen struct cloneable
fn clone_dyn(&self) -> Box<dyn Source>;
/// Signal source for a signal generator. A signal source is capable of creating
/// new signal data.
#[cfg_attr(feature = "python-bindings", pyclass)]
#[derive(Clone)]
pub struct Source {
src: Box<dyn SourceImpl>,
}
impl Clone for Box<dyn Source> {
fn clone(&self) -> Self {
self.clone_dyn()
impl Source {
/// Create a sine wave signal source
///
/// # Args
///
/// - `fs` - Sampling frequency \[Hz\]
/// * `freq` - Frequency of the sine wave in \[Hz\]
pub fn newSine(fs: Flt, freq: Flt) -> Result<Source> {
Ok(Source {
src: Box::new(Sine::new(fs, freq)?),
})
}
/// Silence: create a signal source that does not output any dynamic
/// signal at all.
pub fn newSilence() -> Source {
Source {
src: Box::new(Silence {}),
}
}
/// Create a white noise signal source
pub fn newWhiteNoise() -> Source {
Source {
src: Box::new(WhiteNoise {}),
}
}
/// Sine sweep source
///
/// # Args
///
/// - `fs` - Sample rate \[Hz\]
/// - `fl` - Lower frequency \[Hz\]
/// - `fu` - Upper frequency \[Hz\]
/// - `sweep_time` - The duration of a single sweep \[s\]
/// - `quiet_time` - Time of silence after one sweep and start of the next \[s\]
/// - `sweep_type` - The type of the sweep, see [SweepType].
pub fn newSweep(
fs: Flt,
fl: Flt,
fu: Flt,
sweep_time: Flt,
quiet_time: Flt,
sweep_type: SweepType,
) -> Result<Source> {
Ok(Source {
src: Box::new(Sweep::new(fs, fl, fu, sweep_time, quiet_time, sweep_type)?),
})
}
}
#[derive(Clone)]
pub struct Silence {}
/// Silence source. Most simple one does only send out a 0.
struct Silence {}
impl Source for Silence {
impl SourceImpl for Silence {
fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator<Item = &mut Flt>) {
sig.for_each(|s| {
*s = 0.0;
});
}
fn reset(&mut self, _fs: Flt) {}
fn clone_dyn(&self) -> Box<dyn Source> {
fn clone_dyn(&self) -> Box<dyn SourceImpl> {
Box::new(self.clone())
}
}
/// White noise source
/// White noise source. Can be colored by applying a color filter to the source
#[derive(Clone)]
pub struct WhiteNoise {}
impl WhiteNoise {
/// Generate new WhiteNoise generator
pub fn new() -> WhiteNoise {
WhiteNoise {}
}
}
impl Source for WhiteNoise {
struct WhiteNoise {}
impl SourceImpl for WhiteNoise {
fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator<Item = &mut Flt>) {
sig.for_each(|s| {
*s = thread_rng().sample(StandardNormal);
});
}
fn reset(&mut self, _fs: Flt) {}
fn clone_dyn(&self) -> Box<dyn Source> {
fn clone_dyn(&self) -> Box<dyn SourceImpl> {
Box::new(self.clone())
}
}
/// Sine wave, with configurable frequency
#[derive(Clone)]
pub struct Sine {
struct Sine {
// Sampling freq \[Hz\]
fs: Flt,
// current stored phase
@ -73,15 +116,22 @@ impl Sine {
///
/// * fs: Sampling freq [Hz]
/// *
pub fn new(freq: Flt) -> Sine {
Sine {
fs: -1.0,
pub fn new(fs: Flt, freq: Flt) -> Result<Sine> {
if fs <= 0. {
bail!("Invalid sampling frequency");
}
if freq >= fs / 2. {
bail!("Frequency of sine wave should be smaller than Nyquist frequency");
}
Ok(Sine {
fs,
phase: 0.0,
omg: 2.0 * pi * freq,
})
}
}
}
impl Source for Sine {
impl SourceImpl for Sine {
fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator<Item = &mut Flt>) {
if self.fs <= 0.0 {
sig.for_each(|s| {
@ -99,7 +149,79 @@ impl Source for Sine {
self.fs = fs;
self.phase = 0.0;
}
fn clone_dyn(&self) -> Box<dyn Source> {
fn clone_dyn(&self) -> Box<dyn SourceImpl> {
Box::new(self.clone())
}
}
#[cfg_attr(feature = "python-bindings", pyclass)]
#[derive(Debug, Clone)]
struct Sweep {
params: SweepParams,
// Generated time-periodic buffer
gen: Dcol,
N: usize,
}
impl Sweep {
fn new(
fs: Flt,
fl_: Flt,
fu_: Flt,
sweep_time: Flt,
quiet_time: Flt,
sweeptype: SweepType,
) -> Result<Self> {
let params = SweepParams::new(fs, fl_, fu_, sweep_time, quiet_time, sweeptype)?;
let gen = params.getSignal();
Ok(Sweep { params, gen, N: 0 })
}
}
// Linear forward or backward sweep phase
impl SourceImpl for Sweep {
fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator<Item = &mut Flt>) {
let sweep_iter = self.gen.as_slice().unwrap().iter().cycle().skip(self.N);
for (sig, sweep_sample) in sig.zip(sweep_iter) {
*sig = *sweep_sample;
self.N += 1;
}
// Modulo number of samples in generator
self.N %= self.gen.len();
}
fn reset(&mut self, fs: Flt) {
self.gen = self.params.reset(fs);
self.N = 0;
}
fn clone_dyn(&self) -> Box<dyn SourceImpl> {
Box::new(self.clone())
}
}
impl Deref for Source {
type Target = Box<dyn SourceImpl>;
fn deref(&self) -> &Self::Target {
&self.src
}
}
impl DerefMut for Source {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.src
}
}
/// Source for the signal generator. Implementations are sine waves, sweeps, noise.
pub trait SourceImpl: Send {
/// Generate the 'pure' source signal. Output is placed inside the `sig` argument.
fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator<Item = &mut Flt>);
/// Reset the source state, i.e. set phase to 0, etc
fn reset(&mut self, fs: Flt);
/// Used to make the Siggen struct cloneable
fn clone_dyn(&self) -> Box<dyn SourceImpl>;
}
impl Clone for Box<dyn SourceImpl> {
fn clone(&self) -> Self {
self.clone_dyn()
}
}

264
src/siggen/sweep.rs Normal file
View File

@ -0,0 +1,264 @@
//! Sweep signal generation code
use {
crate::config::*,
anyhow::{bail, Result},
};
const NITER_NEWTON: usize = 20;
const twopi: Flt = 2. * pi;
#[cfg_attr(feature = "python-bindings", pyclass)]
#[derive(Debug, Clone)]
pub enum SweepType {
/// Forward only logarithmic sweep, repeats itself
ForwardLog,
/// Reverse only logarithmic sweep, repeats itself
BackwardLog,
/// Continuous logarithmic sweep, repeats itself
ContinuousLog,
/// Forward only linear sweep, repeats itself
ForwardLin,
/// Reverse only linear sweep, repeats itself
BackwardLin,
/// Continuous linear sweep, repeats itself
ContinuousLin,
}
#[derive(Debug, Clone)]
pub struct SweepParams {
// These parameters are described at [Source::newSweep]
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
}
/// Returns the phase as a function of time
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;
// Number of samples in sweep
let Ns = (self.sweep_time * fs) as usize;
// Number of samples in quiet time
let Nq = (self.quiet_time * fs) as usize;
// Total number of samples
let N = Ns + Nq;
let phase = self.getPhase();
Dcol::from_iter((0..N).map(|i| if i < Ns { Flt::sin(phase[i]) } else { 0. }))
}
// Linear forward or backward sweep phase
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);
// Time step
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
}))
}
// Logarithmic forward or backward sweep phase
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);
// // Time step
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();
/* Iterate k to the right solution */
(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
}))
}
// Continuous log sweep phase
fn getLogSweepContPhase(&self) -> Dcol {
assert!(matches!(self.sweeptype, SweepType::ContinuousLog));
let (Ns, fl, fu, fs) = (self.Ns(), self.fl, self.fu, self.fs);
// // Time step
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;
/* Newton iterations to converge k to the value such that the sweep is
* continuous */
(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;
// /* All parts of the derivative of above error E to k */
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
}))
}
// Continuous linear sweep phase
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;
/* Phi halfway */
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));
}
}

View File

@ -285,7 +285,7 @@ mod test {
.build()
.unwrap();
let mut siggen = Siggen::newSine(1, 1000.);
let mut siggen = Siggen::newSine(1., 1, 1000.).unwrap();
siggen.setAllMute(false);
siggen.reset(fs);
let mut data = vec![0.; N];