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:
parent
8ee5fcbf02
commit
cde2c74467
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ python/lasprs/_lasprs*
|
||||
.vscode/launch.json
|
||||
.vscode
|
||||
examples_py/.ipynb_checkpoints
|
||||
.ipynb_checkpoints
|
||||
|
@ -1,10 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use crossbeam::channel::{ unbounded, Receiver, TryRecvError };
|
||||
use lasprs::daq::{ DaqConfig, StreamMgr, StreamStatus, StreamType };
|
||||
use crossbeam::channel::{unbounded, Receiver, TryRecvError};
|
||||
use lasprs::daq::{DaqConfig, StreamMgr, StreamStatus, StreamType};
|
||||
use lasprs::siggen::Siggen;
|
||||
use std::io;
|
||||
use std::time::Duration;
|
||||
use std::{ thread, time };
|
||||
use std::{thread, time};
|
||||
// use
|
||||
|
||||
/// Spawns a thread and waits for a single line, pushes it to the receiver and returns
|
||||
@ -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();
|
||||
|
@ -1,16 +1,15 @@
|
||||
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 || {
|
||||
thread::spawn(move || {
|
||||
let mut buffer = String::new();
|
||||
io::stdin().read_line(&mut buffer).unwrap();
|
||||
// Do not care whether we succeed here.
|
||||
@ -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 {
|
||||
@ -52,12 +62,12 @@ fn main() -> Result<()> {
|
||||
}
|
||||
sleep(100);
|
||||
match smgr.getStatus(StreamType::Output) {
|
||||
StreamStatus::NotRunning{} => {
|
||||
StreamStatus::NotRunning {} => {
|
||||
println!("Stream is not running?");
|
||||
break 'infy;
|
||||
}
|
||||
StreamStatus::Running{} => {}
|
||||
StreamStatus::Error{e} => {
|
||||
StreamStatus::Running {} => {}
|
||||
StreamStatus::Error { e } => {
|
||||
println!("Stream error: {}", e);
|
||||
break 'infy;
|
||||
}
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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]);
|
||||
|
@ -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();
|
||||
istream
|
||||
.commtx
|
||||
.send(StreamCommand::NewSiggen(siggen))
|
||||
.unwrap();
|
||||
istream.commrx.recv().unwrap()
|
||||
} else {
|
||||
self.siggen = Some(siggen);
|
||||
Ok(())
|
||||
}
|
||||
} else if let Some(os) = &self.output_stream {
|
||||
assert!(self.siggen.is_none());
|
||||
os.comm.send(StreamCommand::NewSiggen(siggen)).unwrap();
|
||||
} 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.
|
||||
///
|
||||
|
@ -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>>,
|
||||
}
|
||||
|
@ -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;
|
@ -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]);
|
||||
@ -327,7 +314,7 @@ mod test {
|
||||
.sum::<Flt>()
|
||||
/ (Nframes as Flt);
|
||||
|
||||
assert_abs_diff_eq!(Flt::abs(ms1 - 0.5) , 0., epsilon= Flt::EPSILON * 1e3);
|
||||
assert_abs_diff_eq!(Flt::abs(ms1 - 0.5), 0., epsilon = Flt::EPSILON * 1e3);
|
||||
assert_eq!(ms2, 0.0);
|
||||
}
|
||||
|
||||
|
78
src/siggen/siggenchannel.rs
Normal file
78
src/siggen/siggenchannel.rs
Normal 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
38
src/siggen/siggencmd.rs
Normal 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,
|
||||
},
|
||||
}
|
@ -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
264
src/siggen/sweep.rs
Normal 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));
|
||||
}
|
||||
}
|
@ -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];
|
||||
|
Loading…
Reference in New Issue
Block a user