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/launch.json
.vscode .vscode
examples_py/.ipynb_checkpoints examples_py/.ipynb_checkpoints
.ipynb_checkpoints

View File

@ -1,10 +1,10 @@
use anyhow::Result; use anyhow::Result;
use crossbeam::channel::{ unbounded, Receiver, TryRecvError }; use crossbeam::channel::{unbounded, Receiver, TryRecvError};
use lasprs::daq::{ DaqConfig, StreamMgr, StreamStatus, StreamType }; use lasprs::daq::{DaqConfig, StreamMgr, StreamStatus, StreamType};
use lasprs::siggen::Siggen; use lasprs::siggen::Siggen;
use std::io; use std::io;
use std::time::Duration; use std::time::Duration;
use std::{ thread, time }; use std::{thread, time};
// use // use
/// Spawns a thread and waits for a single line, pushes it to the receiver and returns /// 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(); let stdin_channel = stdin_channel_wait_for_return();
println!("Creating signal generator..."); 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... // Reduce all gains a bit...
siggen.setAllGains(0.1); siggen.setAllGains(0.1);
// Apply signal generator // Apply signal generator
smgr.setSiggen(siggen); smgr.setSiggen(siggen)?;
println!("Starting stream..."); println!("Starting stream...");
let devs = smgr.getDeviceInfo(); let devs = smgr.getDeviceInfo();

View File

@ -1,16 +1,15 @@
use anyhow::Result; use anyhow::Result;
use crossbeam::channel::{unbounded, Receiver, TryRecvError}; use crossbeam::channel::{unbounded, Receiver, TryRecvError};
use lasprs::daq::{StreamMgr, StreamStatus, StreamType}; use lasprs::daq::{StreamMgr, StreamStatus, StreamType};
use lasprs::siggen::Siggen; use lasprs::siggen::{Siggen, SweepType};
use std::io; use std::io;
use std::{thread, time}; use std::{thread, time};
// use // use
/// Spawns a thread and waits for a single line, pushes it to the receiver and returns /// Spawns a thread and waits for a single line, pushes it to the receiver and returns
fn stdin_channel_wait_for_return() -> Receiver<String> { fn stdin_channel_wait_for_return() -> Receiver<String> {
let (tx, rx) = unbounded(); let (tx, rx) = unbounded();
thread::spawn(move || { thread::spawn(move || {
let mut buffer = String::new(); let mut buffer = String::new();
io::stdin().read_line(&mut buffer).unwrap(); io::stdin().read_line(&mut buffer).unwrap();
// Do not care whether we succeed here. // Do not care whether we succeed here.
@ -28,7 +27,19 @@ fn main() -> Result<()> {
let stdin_channel = stdin_channel_wait_for_return(); let stdin_channel = stdin_channel_wait_for_return();
println!("Creating signal generator..."); 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 // Some things that can be done
// siggen.setDCOffset(&[0.1, 0.]); // siggen.setDCOffset(&[0.1, 0.]);
@ -36,12 +47,11 @@ fn main() -> Result<()> {
// Reduce all gains a bit... // Reduce all gains a bit...
siggen.setAllGains(0.1); siggen.setAllGains(0.1);
println!("Starting stream..."); println!("Starting stream...");
smgr.startDefaultOutputStream()?; smgr.startDefaultOutputStream()?;
// Apply signal generator // Apply signal generator
smgr.setSiggen(siggen); smgr.setSiggen(siggen)?;
println!("Press <enter> key to quit..."); println!("Press <enter> key to quit...");
'infy: loop { 'infy: loop {
@ -52,12 +62,12 @@ fn main() -> Result<()> {
} }
sleep(100); sleep(100);
match smgr.getStatus(StreamType::Output) { match smgr.getStatus(StreamType::Output) {
StreamStatus::NotRunning{} => { StreamStatus::NotRunning {} => {
println!("Stream is not running?"); println!("Stream is not running?");
break 'infy; break 'infy;
} }
StreamStatus::Running{} => {} StreamStatus::Running {} => {}
StreamStatus::Error{e} => { StreamStatus::Error { e } => {
println!("Stream error: {}", e); println!("Stream error: {}", e);
break 'infy; break 'infy;
} }

View File

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

View File

@ -307,7 +307,7 @@ mod test {
const Nframes: usize = 20; const Nframes: usize = 20;
const Nch: usize = 2; const Nch: usize = 2;
let mut signal = [0.; Nch * Nframes]; 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.reset(fs);
siggen.setMute(&[false, true]); siggen.setMute(&[false, true]);

View File

@ -4,7 +4,7 @@ use crate::{
config::*, config::*,
siggen::{self, Siggen}, siggen::{self, Siggen},
}; };
use anyhow::{bail, Error, Result}; use anyhow::{anyhow, bail, Error, Result};
use api::StreamApiDescr; use api::StreamApiDescr;
use array_init::from_iter; use array_init::from_iter;
use core::time; use core::time;
@ -13,8 +13,11 @@ use crossbeam::{
channel::{unbounded, Receiver, Sender, TrySendError}, channel::{unbounded, Receiver, Sender, TrySendError},
thread, thread,
}; };
use std::sync::{atomic::AtomicBool, Arc, Mutex};
use std::thread::{JoinHandle, Thread}; use std::thread::{JoinHandle, Thread};
use std::{
sync::{atomic::AtomicBool, Arc, Mutex},
time::Duration,
};
use streamcmd::StreamCommand; use streamcmd::StreamCommand;
use streamdata::*; use streamdata::*;
use streammetadata::*; use streammetadata::*;
@ -33,7 +36,8 @@ struct StreamInfo<T> {
streamtype: StreamType, streamtype: StreamType,
stream: Box<dyn Stream>, stream: Box<dyn Stream>,
threadhandle: JoinHandle<T>, 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. /// Keep track of whether the stream has been created. To ensure singleton behaviour.
@ -108,8 +112,9 @@ impl StreamMgr {
self.getStatus(st) self.getStatus(st)
} }
#[pyo3(name = "setSiggen")] #[pyo3(name = "setSiggen")]
fn setSiggen_py(&mut self, siggen: Siggen) { fn setSiggen_py(&mut self, siggen: Siggen) -> PyResult<()> {
self.setSiggen(siggen) self.setSiggen(siggen)?;
Ok(())
} }
#[pyo3(name = "getStreamMetaData")] #[pyo3(name = "getStreamMetaData")]
fn getStreamMetaData_py(&self, st: StreamType) -> Option<StreamMetaData> { 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. /// 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 /// It is unapplicable if the number of channels of output does not match the
/// number of output channels in a running stream. /// 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? // 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 { if let StreamType::Duplex = istream.streamtype {
assert!(self.siggen.is_none()); 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 { } else {
self.siggen = Some(siggen); self.siggen = Some(siggen);
Ok(())
} }
} }
@ -235,7 +249,7 @@ impl StreamMgr {
/// of queues that get data from the stream. /// of queues that get data from the stream.
pub fn addInQueue(&mut self, tx: Sender<InStreamMsg>) { pub fn addInQueue(&mut self, tx: Sender<InStreamMsg>) {
if let Some(is) = &self.input_stream { if let Some(is) = &self.input_stream {
is.comm.send(StreamCommand::AddInQueue(tx)).unwrap() is.commtx.send(StreamCommand::AddInQueue(tx)).unwrap()
} else { } else {
self.instreamqueues.as_mut().unwrap().push(tx); self.instreamqueues.as_mut().unwrap().push(tx);
} }
@ -245,8 +259,14 @@ impl StreamMgr {
&mut self, &mut self,
meta: Arc<StreamMetaData>, meta: Arc<StreamMetaData>,
rx: Receiver<InStreamMsg>, 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 // Unwrap here, as the queues should be free to grab
let mut iqueues = self let mut iqueues = self
@ -261,8 +281,17 @@ impl StreamMgr {
// New queue added // New queue added
StreamCommand::AddInQueue(queue) => { StreamCommand::AddInQueue(queue) => {
match queue.send(InStreamMsg::StreamStarted(meta.clone())) { match queue.send(InStreamMsg::StreamStarted(meta.clone())) {
Ok(()) => iqueues.push(queue), Ok(()) => {
Err(_) => {} 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, &mut iqueues,
InStreamMsg::StreamStopped, InStreamMsg::StreamStopped,
); );
commtx.send(Ok(())).unwrap();
break 'infy; break 'infy;
} }
StreamCommand::NewSiggen(_) => { StreamCommand::NewSiggen(_) => {
panic!("Error: signal generator send to input-only stream."); 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)) { if let Ok(msg) = rx.recv_timeout(time::Duration::from_millis(10)) {
@ -285,7 +318,7 @@ impl StreamMgr {
} }
iqueues iqueues
}); });
(threadhandle, commtx) (threadhandle, commtx_ret, commrx_ret)
} }
// Match device info struct on given daq config. // Match device info struct on given daq config.
@ -303,8 +336,13 @@ impl StreamMgr {
&mut self, &mut self,
meta: Arc<StreamMetaData>, meta: Arc<StreamMetaData>,
tx: Sender<RawStreamData>, 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 // Number of channels to output for
let nchannels = meta.nchannels(); let nchannels = meta.nchannels();
@ -314,7 +352,7 @@ impl StreamMgr {
let mut siggen = self let mut siggen = self
.siggen .siggen
.take() .take()
.unwrap_or_else(|| Siggen::newSilence(nchannels)); .unwrap_or_else(|| Siggen::newSilence(meta.samplerate, nchannels));
if siggen.nchannels() != nchannels { if siggen.nchannels() != nchannels {
// Updating number of channels // Updating number of channels
@ -323,9 +361,15 @@ impl StreamMgr {
siggen.reset(meta.samplerate); siggen.reset(meta.samplerate);
let threadhandle = std::thread::spawn(move || { 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 { 'infy: loop {
if let Ok(comm_msg) = commrx.try_recv() { if let Ok(comm_msg) = commrx.recv_timeout(sleep_time_us) {
match comm_msg { match comm_msg {
// New queue added // New queue added
StreamCommand::AddInQueue(_) => { StreamCommand::AddInQueue(_) => {
@ -334,6 +378,7 @@ impl StreamMgr {
// Stop this thread. Returns the queue // Stop this thread. Returns the queue
StreamCommand::StopThread => { StreamCommand::StopThread => {
commtx.send(Ok(())).unwrap();
break 'infy; break 'infy;
} }
StreamCommand::NewSiggen(new_siggen) => { StreamCommand::NewSiggen(new_siggen) => {
@ -344,16 +389,20 @@ impl StreamMgr {
// println!("Updating channels"); // println!("Updating channels");
siggen.setNChannels(nchannels); 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 { while tx.len() < 2 {
unsafe { // Obtain signal from signal generator
floatbuf.set_len(nchannels * meta.framesPerBlock);
}
// Obtain signal
siggen.genSignal(&mut floatbuf); 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 { let msg = match meta.rawDatatype {
DataType::I8 => { DataType::I8 => {
let v = Vec::<i8>::from_iter(floatbuf.iter().map(|f| f.to_sample())); 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) { if let Err(_e) = tx.send(msg) {
// println!("Error sending raw stream data to output stream!"); // An error occured while trying to send the raw data to
break 'infy; // 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 siggen
}); });
(threadhandle, commtx) (threadhandle, commtx_res, commrx_res)
} }
/// Start a stream of certain type, using given configuration /// Start a stream of certain type, using given configuration
@ -418,13 +470,14 @@ impl StreamMgr {
_ => bail!("API {} not implemented!", cfg.api), _ => bail!("API {} not implemented!", cfg.api),
}; };
let meta = stream.metadata(); let meta = stream.metadata();
let (threadhandle, commtx) = self.startOuputStreamThread(meta, tx); let (threadhandle, commtx, commrx) = self.startOuputStreamThread(meta, tx);
self.output_stream = Some(StreamInfo { self.output_stream = Some(StreamInfo {
streamtype: StreamType::Input, streamtype: StreamType::Input,
stream, stream,
threadhandle, threadhandle,
comm: commtx, commtx,
commrx,
}); });
Ok(()) Ok(())
@ -472,13 +525,14 @@ impl StreamMgr {
sendMsgToAllQueuesRemoveUnused(iqueues, InStreamMsg::StreamStarted(meta.clone())); 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 { self.input_stream = Some(StreamInfo {
streamtype: stype, streamtype: stype,
stream, stream,
threadhandle, threadhandle,
comm: commtx, commtx,
commrx,
}); });
Ok(()) Ok(())
@ -503,13 +557,14 @@ impl StreamMgr {
let meta = stream.metadata(); let meta = stream.metadata();
sendMsgToAllQueuesRemoveUnused(iqueues, InStreamMsg::StreamStarted(meta.clone())); 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 { self.input_stream = Some(StreamInfo {
streamtype: StreamType::Input, streamtype: StreamType::Input,
stream, stream,
threadhandle, threadhandle,
comm: commtx, commtx,
commrx,
}); });
Ok(()) Ok(())
@ -537,15 +592,14 @@ impl StreamMgr {
let (tx, rx)= unbounded(); let (tx, rx)= unbounded();
let stream = self.cpal_api.startDefaultOutputStream(rx)?; let stream = self.cpal_api.startDefaultOutputStream(rx)?;
let meta = stream.metadata(); let meta = stream.metadata();
let (threadhandle, commtx) = self.startOuputStreamThread(meta, tx); let (threadhandle, commtx, commrx) = self.startOuputStreamThread(meta, tx);
// Inform all listeners of new stream data
self.output_stream = Some(StreamInfo { self.output_stream = Some(StreamInfo {
streamtype: StreamType::Input, streamtype: StreamType::Input,
stream, stream,
threadhandle, threadhandle,
comm: commtx, commtx,
commrx,
}); });
Ok(()) Ok(())
@ -563,19 +617,22 @@ impl StreamMgr {
streamtype: _, // Ignored here streamtype: _, // Ignored here
stream: _, stream: _,
threadhandle, threadhandle,
comm, commtx,
commrx,
}) = self.input_stream.take() }) = self.input_stream.take()
{ {
// println!("Stopping existing stream.."); // println!("Stopping existing stream..");
// Send thread to stop // Send thread to stop
comm.send(StreamCommand::StopThread).unwrap(); commtx.send(StreamCommand::StopThread).unwrap();
// Store stream queues back into StreamMgr // Store stream queues back into StreamMgr
self.instreamqueues = Some(threadhandle.join().expect("Stream thread panicked!")); self.instreamqueues = Some(threadhandle.join().expect("Stream thread panicked!"));
let res = commrx.recv().unwrap();
return res;
} else { } else {
bail!("Stream is not running.") bail!("Stream is not running.")
} }
Ok(())
} }
/// Stop existing output stream /// Stop existing output stream
pub fn stopOutputStream(&mut self) -> Result<()> { pub fn stopOutputStream(&mut self) -> Result<()> {
@ -583,21 +640,17 @@ impl StreamMgr {
streamtype: _, // Ignored here streamtype: _, // Ignored here
stream: _, stream: _,
threadhandle, threadhandle,
comm, commtx,
commrx,
}) = self.output_stream.take() }) = self.output_stream.take()
{ {
if comm.send(StreamCommand::StopThread).is_err() { commtx.send(StreamCommand::StopThread).unwrap();
// Failed to send command over channel. This means the thread is // eprintln!("Wainting for threadhandle to join...");
// already finished due to some other reason.
assert!(threadhandle.is_finished());
}
// println!("Wainting for threadhandle to join...");
self.siggen = Some(threadhandle.join().expect("Output thread panicked!")); self.siggen = Some(threadhandle.join().expect("Output thread panicked!"));
// println!("Threadhandle joined!"); commrx.recv().unwrap()
} else { } else {
bail!("Stream is not running."); bail!("Stream is not running.");
} }
Ok(())
} }
/// Stop existing running stream. /// 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 /// 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 /// extracted by blocks of other size. Also, we can keep samples in a buffer to
/// create, for example, overlapping windows of time data. /// create, for example, overlapping windows of time data.
#[derive(Default, Debug)] #[derive(Default, Debug, Clone)]
pub struct TimeBuffer { pub struct TimeBuffer {
data: Vec<VecDeque<Flt>>, data: Vec<VecDeque<Flt>>,
} }

View File

@ -21,6 +21,11 @@
//! //!
//! ``` //! ```
mod siggen; mod siggen;
mod siggenchannel;
mod source; mod source;
pub use source::{Silence, Sine, WhiteNoise}; mod siggencmd;
mod sweep;
pub use source::Source;
pub use siggen::Siggen; 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::config::*;
use crate::filter::Filter; use crate::filter::Filter;
use super::source::*; use anyhow::{bail, Result};
use dasp_sample::{FromSample, Sample}; use dasp_sample::{FromSample, Sample};
use rayon::prelude::*; use rayon::prelude::*;
use std::fmt::Debug; use std::fmt::Debug;
use std::iter::ExactSizeIterator; use std::iter::ExactSizeIterator;
use std::slice::IterMut; use std::slice::IterMut;
/// Multiple channel signal generator. Able to create (acoustic) output signals. See above example on how to use.
/// Signal generator. Able to create acoustic output signals. See above example on how to use.
/// Typical signal that can be created are: /// Typical signal that can be created are:
/// ///
/// * (Siggen::newWhiteNoise) /// * [Siggen::newWhiteNoise]
/// * (Siggen::newSine) /// * [Siggen::newSine]
/// * [Siggen::newSilence]
/// ///
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "python-bindings", pyclass)] #[cfg_attr(feature = "python-bindings", pyclass)]
pub struct Siggen { pub struct Siggen {
// The source dynamic signal. Noise, a sine wave, sweep, etc // The source dynamic signal. Noise, a sine wave, sweep, etc
source: Box<dyn Source>, source: Source,
// Filter applied to the source signal
// Channel configuration for each output channel
channels: Vec<SiggenChannelConfig>, channels: Vec<SiggenChannelConfig>,
// Temporary source signal buffer // Temporary source signal buffer
@ -34,40 +38,117 @@ pub struct Siggen {
impl Siggen { impl Siggen {
#[pyo3(name = "newWhiteNoise")] #[pyo3(name = "newWhiteNoise")]
#[staticmethod] #[staticmethod]
fn newWhiteNoise_py() -> Siggen { fn newWhiteNoise_py(fs: Flt) -> Siggen {
Siggen::newWhiteNoise(0) Siggen::newWhiteNoise(fs, 0)
} }
#[pyo3(name = "newSine")] #[pyo3(name = "newSine")]
#[staticmethod] #[staticmethod]
fn newSine_py(freq: Flt) -> Siggen { fn newSine_py(fs: Flt, freq: Flt, nchannels: usize) -> PyResult<Siggen> {
Siggen::newSine(0, freq) 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 { 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. /// Returns the number of channels this signal generator is generating for.
pub fn nchannels(&self) -> usize { pub fn nchannels(&self) -> usize {
self.channels.len() self.channels.len()
} }
/// Silence: create a signal generator that does not output any dynamic /// Apply command to current signal generator to change its state.
/// signal at all. pub fn applyCommand(&mut self, msg: SiggenCommand) -> Result<()> {
pub fn newSilence(nchannels: usize) -> Siggen { match msg {
Siggen { SiggenCommand::ChangeSource { src } => {
channels: vec![SiggenChannelConfig::new(); nchannels], self.source = src;
source: Box::new(Silence {}), Ok(())
source_buf: vec![], }
chout_buf: vec![], 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 /// Set gains of all channels in signal generator to the same value
/// ///
/// # Args /// # 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 /// Creates *interleaved* output signal
pub fn genSignal<T>(&mut self, out: &mut [T]) pub fn genSignal<T>(&mut self, out: &mut [T])
where 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)] #[cfg(test)]
mod test { mod test {
use approx::assert_abs_diff_eq; use approx::assert_abs_diff_eq;
@ -269,7 +256,7 @@ mod test {
fn test_whitenoise() { fn test_whitenoise() {
// This code is just to check syntax. We should really be listening to these outputs. // This code is just to check syntax. We should really be listening to these outputs.
let mut t = [0.0; 10]; let mut t = [0.0; 10];
Siggen::newWhiteNoise(1).genSignal(&mut t); Siggen::newWhiteNoise(1., 1).genSignal(&mut t);
// println!("{:?}", &t); // println!("{:?}", &t);
} }
@ -280,7 +267,7 @@ mod test {
const N: usize = 10000; const N: usize = 10000;
let mut s1 = [0.0; N]; let mut s1 = [0.0; N];
let mut s2 = [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.reset(10.0);
siggen.setAllMute(false); siggen.setAllMute(false);
@ -305,7 +292,7 @@ mod test {
const Nframes: usize = 10000; const Nframes: usize = 10000;
const Nch: usize = 2; const Nch: usize = 2;
let mut signal = [0.0; Nch * Nframes]; 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.reset(fs);
siggen.setMute(&[false, true]); siggen.setMute(&[false, true]);
@ -327,7 +314,7 @@ mod test {
.sum::<Flt>() .sum::<Flt>()
/ (Nframes as 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); assert_eq!(ms2, 0.0);
} }

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 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::prelude::*;
use rand::rngs::ThreadRng; use rand::rngs::ThreadRng;
use rand_distr::StandardNormal; use rand_distr::StandardNormal;
/// Ratio between circumference and radius of a circle /// Signal source for a signal generator. A signal source is capable of creating
const twopi: Flt = 2.0 * pi; /// new signal data.
#[cfg_attr(feature = "python-bindings", pyclass)]
/// Source for the signal generator. Implementations are sine waves, sweeps, noise. #[derive(Clone)]
pub trait Source: Send { pub struct Source {
/// Generate the 'pure' source signal. Output is placed inside the `sig` argument. src: Box<dyn SourceImpl>,
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>;
} }
impl Clone for Box<dyn Source> { impl Source {
fn clone(&self) -> Self { /// Create a sine wave signal source
self.clone_dyn() ///
/// # 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)] #[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>) { fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator<Item = &mut Flt>) {
sig.for_each(|s| { sig.for_each(|s| {
*s = 0.0; *s = 0.0;
}); });
} }
fn reset(&mut self, _fs: Flt) {} fn reset(&mut self, _fs: Flt) {}
fn clone_dyn(&self) -> Box<dyn Source> { fn clone_dyn(&self) -> Box<dyn SourceImpl> {
Box::new(self.clone()) Box::new(self.clone())
} }
} }
/// White noise source /// White noise source. Can be colored by applying a color filter to the source
#[derive(Clone)] #[derive(Clone)]
pub struct WhiteNoise {} struct WhiteNoise {}
impl WhiteNoise { impl SourceImpl for WhiteNoise {
/// Generate new WhiteNoise generator
pub fn new() -> WhiteNoise {
WhiteNoise {}
}
}
impl Source for WhiteNoise {
fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator<Item = &mut Flt>) { fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator<Item = &mut Flt>) {
sig.for_each(|s| { sig.for_each(|s| {
*s = thread_rng().sample(StandardNormal); *s = thread_rng().sample(StandardNormal);
}); });
} }
fn reset(&mut self, _fs: Flt) {} fn reset(&mut self, _fs: Flt) {}
fn clone_dyn(&self) -> Box<dyn Source> { fn clone_dyn(&self) -> Box<dyn SourceImpl> {
Box::new(self.clone()) Box::new(self.clone())
} }
} }
/// Sine wave, with configurable frequency /// Sine wave, with configurable frequency
#[derive(Clone)] #[derive(Clone)]
pub struct Sine { struct Sine {
// Sampling freq \[Hz\] // Sampling freq \[Hz\]
fs: Flt, fs: Flt,
// current stored phase // current stored phase
@ -73,15 +116,22 @@ impl Sine {
/// ///
/// * fs: Sampling freq [Hz] /// * fs: Sampling freq [Hz]
/// * /// *
pub fn new(freq: Flt) -> Sine { pub fn new(fs: Flt, freq: Flt) -> Result<Sine> {
Sine { if fs <= 0. {
fs: -1.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, phase: 0.0,
omg: 2.0 * pi * freq, 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>) { fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator<Item = &mut Flt>) {
if self.fs <= 0.0 { if self.fs <= 0.0 {
sig.for_each(|s| { sig.for_each(|s| {
@ -99,7 +149,79 @@ impl Source for Sine {
self.fs = fs; self.fs = fs;
self.phase = 0.0; self.phase = 0.0;
} }
fn clone_dyn(&self) -> Box<dyn Source> { fn clone_dyn(&self) -> Box<dyn SourceImpl> {
Box::new(self.clone()) 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() .build()
.unwrap(); .unwrap();
let mut siggen = Siggen::newSine(1, 1000.); let mut siggen = Siggen::newSine(1., 1, 1000.).unwrap();
siggen.setAllMute(false); siggen.setAllMute(false);
siggen.reset(fs); siggen.reset(fs);
let mut data = vec![0.; N]; let mut data = vec![0.; N];