From 0567e7fb9215e1820370e60aab65d2758f8e31d8 Mon Sep 17 00:00:00 2001 From: "J.A. de Jong - Redu-Sone B.V., ASCEE V.O.F." Date: Sat, 26 Oct 2024 11:53:34 +0200 Subject: [PATCH] Added siggencommand. Exported sweep types. Added debug derives on some more classes. StreamMgr now returns result on stream commands, by polling back on a channel for result values. --- src/daq/streamcmd.rs | 1 + src/daq/streammgr.rs | 47 ++++++++++++++++++++++++++++++++++--- src/filter/biquadbank.rs | 2 +- src/filter/mod.rs | 5 ++-- src/lib.rs | 2 ++ src/siggen/siggen.rs | 2 +- src/siggen/siggenchannel.rs | 2 +- src/siggen/siggencmd.rs | 5 ++-- src/siggen/source.rs | 45 +++++++++++++++++++++++++++++++---- src/siggen/sweep.rs | 30 ++++++++++++++++++++++- 10 files changed, 125 insertions(+), 16 deletions(-) diff --git a/src/daq/streamcmd.rs b/src/daq/streamcmd.rs index b928de1..a6064b8 100644 --- a/src/daq/streamcmd.rs +++ b/src/daq/streamcmd.rs @@ -3,6 +3,7 @@ use super::streammgr::SharedInQueue; /// Commands that can be sent to a running stream +#[derive(Debug)] pub enum StreamCommand { /// Add a new queue to a running INPUT stream AddInQueue(SharedInQueue), diff --git a/src/daq/streammgr.rs b/src/daq/streammgr.rs index b5f2324..0ca703d 100644 --- a/src/daq/streammgr.rs +++ b/src/daq/streammgr.rs @@ -2,7 +2,7 @@ use super::*; use crate::{ config::*, - siggen::{self, Siggen}, + siggen::{self, Siggen, SiggenCommand}, }; use anyhow::{anyhow, bail, Error, Result}; use api::StreamApiDescr; @@ -122,7 +122,13 @@ impl StreamMgr { // value (not the Arc) has to be cloned. self.getStreamMetaData(st).map(|b| (*b).clone()) } + #[pyo3(name = "siggenCommand")] + fn siggenCommand_py(&mut self, cmd: SiggenCommand) -> PyResult<()> { + self.siggenCommand(cmd)?; + Ok(()) + } } + impl Default for StreamMgr { fn default() -> Self { Self::new() @@ -148,7 +154,7 @@ impl StreamMgr { devs: vec![], input_stream: None, output_stream: None, - siggen: None, + siggen: Some(Siggen::newSilence(1., 1)), #[cfg(feature = "cpal-api")] cpal_api: CpalApi::new(), @@ -354,6 +360,7 @@ impl StreamMgr { .take() .unwrap_or_else(|| Siggen::newSilence(meta.samplerate, nchannels)); + siggen.setAllMute(true); if siggen.nchannels() != nchannels { // Updating number of channels siggen.setNChannels(nchannels); @@ -398,7 +405,7 @@ impl StreamMgr { } } } - while tx.len() < 2 { + if tx.len() < 1 { // Obtain signal from signal generator siggen.genSignal(&mut floatbuf); @@ -663,6 +670,40 @@ impl StreamMgr { StreamType::Output => self.stopOutputStream(), } } + + /// Apply a signal generator command to control the output stream's signal + /// generator. see [SiggenCommand] for types of commands. Muting, setting + /// gain etc. A result code is given back and should be checked for errors. + pub fn siggenCommand(&mut self, cmd: SiggenCommand) -> Result<()> { + if let Some(stream) = self.output_stream.as_ref() { + stream + .commtx + .send(StreamCommand::SiggenCommand(cmd)) + .unwrap(); + return stream.commrx.recv().unwrap(); + } else if let Some(stream) = self.input_stream.as_ref() { + // When its duplex, it should have a signal generator + if matches!(stream.streamtype, StreamType::Duplex) { + stream + .commtx + .send(StreamCommand::SiggenCommand(cmd)) + .unwrap(); + stream.commrx.recv().unwrap() + } else { + return self + .siggen + .as_mut() + .expect("siggen should be in rest pos") + .applyCommand(cmd); + } + } else { + return self + .siggen + .as_mut() + .expect("siggen should be in rest pos") + .applyCommand(cmd); + } + } } // impl StreamMgr impl Drop for StreamMgr { fn drop(&mut self) { diff --git a/src/filter/biquadbank.rs b/src/filter/biquadbank.rs index c2fa116..6765f1b 100644 --- a/src/filter/biquadbank.rs +++ b/src/filter/biquadbank.rs @@ -2,7 +2,7 @@ use super::*; use super::seriesbiquad::*; use rayon::prelude::*; #[cfg_attr(feature = "python-bindings", pyclass)] -#[derive(Clone)] +#[derive(Clone, Debug)] /// Multiple biquad filter that operate in parallel on a signal, and can apply a gain value to each /// of the returned values. The BiquadBank can be used to decompose a signal by running it through /// parallel filters, or it can directly be used to eq a signal. For the latter process, also a diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 3903ca1..83807e3 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -4,6 +4,8 @@ //! Contains [Biquad], [SeriesBiquad], and [BiquadBank]. These are all constructs that work on //! blocks of input data, and apply filters on it. TODO: implement FIR filter. #![allow(non_snake_case)] +use std::fmt::Debug; + use super::config::*; mod biquad; @@ -23,7 +25,7 @@ pub use seriesbiquad::SeriesBiquad; pub use zpkmodel::{PoleOrZero, ZPKModel, FilterSpec}; /// Implementations of this trait are able to DSP-filter input data. -pub trait Filter: Send { +pub trait Filter: Send + Debug { //! The filter trait is implemented by, for example, [Biquad], [SeriesBiquad], and [BiquadBank]. /// Filter input to generate output. A vector of output floats is generated with the same @@ -39,7 +41,6 @@ pub trait Filter: Send { } /// Implementations are able to generate transfer functions of itself - pub trait TransferFunction<'a, T>: Send where T: AsArray<'a, Flt>, diff --git a/src/lib.rs b/src/lib.rs index dd05fcc..15ee0c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,6 +63,8 @@ fn lasprs(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; // SLM m.add_class::()?; diff --git a/src/siggen/siggen.rs b/src/siggen/siggen.rs index f7fcee3..ee1e8d8 100644 --- a/src/siggen/siggen.rs +++ b/src/siggen/siggen.rs @@ -18,7 +18,7 @@ use std::slice::IterMut; /// * [Siggen::newSine] /// * [Siggen::newSilence] /// -#[derive(Clone)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "python-bindings", pyclass)] pub struct Siggen { // The source dynamic signal. Noise, a sine wave, sweep, etc diff --git a/src/siggen/siggenchannel.rs b/src/siggen/siggenchannel.rs index 64c03ef..bdca565 100644 --- a/src/siggen/siggenchannel.rs +++ b/src/siggen/siggenchannel.rs @@ -1,7 +1,7 @@ use crate::config::*; use crate::filter::Filter; /// Signal generator config for a certain channel -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct SiggenChannelConfig { muted: bool, prefilter: Option>, diff --git a/src/siggen/siggencmd.rs b/src/siggen/siggencmd.rs index 8d74287..9c55d9b 100644 --- a/src/siggen/siggencmd.rs +++ b/src/siggen/siggencmd.rs @@ -4,6 +4,7 @@ use crate::config::*; /// Messages that can be send to a given signal generator [Siggen], to change its behaviour #[cfg_attr(feature = "python-bindings", pyclass)] +#[derive(Clone, Debug)] pub enum SiggenCommand { /// Change the source to a sine wave with given frequency. ChangeSource{ @@ -13,13 +14,13 @@ pub enum SiggenCommand { /// Reset the signal generator state ResetSiggen { - /// New sampling frequency \[Hz\] + /// Sampling frequency \[Hz\] fs: Flt, }, /// Set all gains to value g SetAllGains { - /// Linear gain level to apply + /// Linear gain level to apply to all channels g: Flt, }, diff --git a/src/siggen/source.rs b/src/siggen/source.rs index 3917e62..6101250 100644 --- a/src/siggen/source.rs +++ b/src/siggen/source.rs @@ -1,6 +1,7 @@ //! All sources for a signal generator. Sine waves, sweeps, noise, etc. use super::sweep::{SweepParams, SweepType}; use crate::config::*; +use std::fmt::Debug; use std::ops::{Deref, DerefMut}; /// Ratio between circumference and radius of a circle @@ -14,7 +15,7 @@ use rand_distr::StandardNormal; /// Signal source for a signal generator. A signal source is capable of creating /// new signal data. #[cfg_attr(feature = "python-bindings", pyclass)] -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Source { src: Box, } @@ -69,7 +70,41 @@ impl Source { } } -#[derive(Clone)] +#[cfg(feature = "python-bindings")] +#[cfg_attr(feature = "python-bindings", pymethods)] +impl Source { + #[staticmethod] + #[pyo3(name = "newSine")] + fn newSine_py(fs: Flt, freq: Flt) -> PyResult { + Ok(Self::newSine(fs, freq)?) + } + #[pyo3(name = "newSilence")] + #[staticmethod] + fn newSilence_py() -> Source { + Self::newSilence() + } + #[staticmethod] + #[pyo3(name = "newWhiteNoise")] + fn newWhiteNoise_py() -> Source { + Self::newWhiteNoise() + } + #[staticmethod] + #[pyo3(name = "newSweep")] + fn newSweep_py( + fs: Flt, + fl: Flt, + fu: Flt, + sweep_time: Flt, + quiet_time: Flt, + sweep_type: SweepType, + ) -> PyResult { + Ok(Self::newSweep( + fs, fl, fu, sweep_time, quiet_time, sweep_type, + )?) + } +} + +#[derive(Clone, Debug)] /// Silence source. Most simple one does only send out a 0. struct Silence {} @@ -85,7 +120,7 @@ impl SourceImpl for Silence { } } /// White noise source. Can be colored by applying a color filter to the source -#[derive(Clone)] +#[derive(Clone, Debug)] struct WhiteNoise {} impl SourceImpl for WhiteNoise { fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator) { @@ -100,7 +135,7 @@ impl SourceImpl for WhiteNoise { } /// Sine wave, with configurable frequency -#[derive(Clone)] +#[derive(Clone, Debug)] struct Sine { // Sampling freq \[Hz\] fs: Flt, @@ -212,7 +247,7 @@ impl DerefMut for Source { } /// Source for the signal generator. Implementations are sine waves, sweeps, noise. -pub trait SourceImpl: Send { +pub trait SourceImpl: Send + Debug { /// Generate the 'pure' source signal. Output is placed inside the `sig` argument. fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator); /// Reset the source state, i.e. set phase to 0, etc diff --git a/src/siggen/sweep.rs b/src/siggen/sweep.rs index ef8e310..b7fa9c3 100644 --- a/src/siggen/sweep.rs +++ b/src/siggen/sweep.rs @@ -1,4 +1,6 @@ //! Sweep signal generation code +use strum_macros::{Display, EnumMessage}; +use strum::EnumMessage; use { crate::config::*, anyhow::{bail, Result}, @@ -9,23 +11,49 @@ const twopi: Flt = 2. * pi; /// Enumerator representing the type of sweep source to create. Used as /// parameter in [Siggen::newSweep]. #[cfg_attr(feature = "python-bindings", pyclass)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Display, EnumMessage)] pub enum SweepType { /// Forward only logarithmic sweep, repeats itself + #[strum(message = "Forward logarithmic")] ForwardLog, /// Reverse only logarithmic sweep, repeats itself + #[strum(message = "Backward logarithmic")] BackwardLog, /// Continuous logarithmic sweep, repeats itself + #[strum(message = "Continuous logarithmic")] ContinuousLog, /// Forward only linear sweep, repeats itself + #[strum(message = "Forward linear")] ForwardLin, /// Reverse only linear sweep, repeats itself + #[strum(message = "Backward linear")] BackwardLin, /// Continuous linear sweep, repeats itself + #[strum(message = "Continuous linear")] ContinuousLin, } +#[cfg(feature = "python-bindings")] +#[cfg_attr(feature = "python-bindings", pymethods)] +impl SweepType { + #[staticmethod] + fn all() -> Vec { + use SweepType::*; + vec![ + ForwardLin, + ForwardLog, + BackwardLin, + BackwardLog, + ContinuousLin, + ContinuousLog, + ] + } + fn __str__(&self) -> String { + self.get_message().unwrap().into() + } +} + #[derive(Debug, Clone)] pub struct SweepParams { // These parameters are described at [Source::newSweep]