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.

This commit is contained in:
Anne de Jong 2024-10-26 11:53:34 +02:00
parent 45da6370ec
commit 0567e7fb92
10 changed files with 125 additions and 16 deletions

View File

@ -3,6 +3,7 @@ use super::streammgr::SharedInQueue;
/// Commands that can be sent to a running stream /// Commands that can be sent to a running stream
#[derive(Debug)]
pub enum StreamCommand { pub enum StreamCommand {
/// Add a new queue to a running INPUT stream /// Add a new queue to a running INPUT stream
AddInQueue(SharedInQueue), AddInQueue(SharedInQueue),

View File

@ -2,7 +2,7 @@
use super::*; use super::*;
use crate::{ use crate::{
config::*, config::*,
siggen::{self, Siggen}, siggen::{self, Siggen, SiggenCommand},
}; };
use anyhow::{anyhow, bail, Error, Result}; use anyhow::{anyhow, bail, Error, Result};
use api::StreamApiDescr; use api::StreamApiDescr;
@ -122,7 +122,13 @@ impl StreamMgr {
// value (not the Arc) has to be cloned. // value (not the Arc) has to be cloned.
self.getStreamMetaData(st).map(|b| (*b).clone()) 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 { impl Default for StreamMgr {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
@ -148,7 +154,7 @@ impl StreamMgr {
devs: vec![], devs: vec![],
input_stream: None, input_stream: None,
output_stream: None, output_stream: None,
siggen: None, siggen: Some(Siggen::newSilence(1., 1)),
#[cfg(feature = "cpal-api")] #[cfg(feature = "cpal-api")]
cpal_api: CpalApi::new(), cpal_api: CpalApi::new(),
@ -354,6 +360,7 @@ impl StreamMgr {
.take() .take()
.unwrap_or_else(|| Siggen::newSilence(meta.samplerate, nchannels)); .unwrap_or_else(|| Siggen::newSilence(meta.samplerate, nchannels));
siggen.setAllMute(true);
if siggen.nchannels() != nchannels { if siggen.nchannels() != nchannels {
// Updating number of channels // Updating number of channels
siggen.setNChannels(nchannels); siggen.setNChannels(nchannels);
@ -398,7 +405,7 @@ impl StreamMgr {
} }
} }
} }
while tx.len() < 2 { if tx.len() < 1 {
// Obtain signal from signal generator // Obtain signal from signal generator
siggen.genSignal(&mut floatbuf); siggen.genSignal(&mut floatbuf);
@ -663,6 +670,40 @@ impl StreamMgr {
StreamType::Output => self.stopOutputStream(), 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 StreamMgr
impl Drop for StreamMgr { impl Drop for StreamMgr {
fn drop(&mut self) { fn drop(&mut self) {

View File

@ -2,7 +2,7 @@ use super::*;
use super::seriesbiquad::*; use super::seriesbiquad::*;
use rayon::prelude::*; use rayon::prelude::*;
#[cfg_attr(feature = "python-bindings", pyclass)] #[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 /// 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 /// 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 /// parallel filters, or it can directly be used to eq a signal. For the latter process, also a

View File

@ -4,6 +4,8 @@
//! Contains [Biquad], [SeriesBiquad], and [BiquadBank]. These are all constructs that work on //! 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. //! blocks of input data, and apply filters on it. TODO: implement FIR filter.
#![allow(non_snake_case)] #![allow(non_snake_case)]
use std::fmt::Debug;
use super::config::*; use super::config::*;
mod biquad; mod biquad;
@ -23,7 +25,7 @@ pub use seriesbiquad::SeriesBiquad;
pub use zpkmodel::{PoleOrZero, ZPKModel, FilterSpec}; pub use zpkmodel::{PoleOrZero, ZPKModel, FilterSpec};
/// Implementations of this trait are able to DSP-filter input data. /// 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]. //! 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 /// 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 /// Implementations are able to generate transfer functions of itself
pub trait TransferFunction<'a, T>: Send pub trait TransferFunction<'a, T>: Send
where where
T: AsArray<'a, Flt>, T: AsArray<'a, Flt>,

View File

@ -63,6 +63,8 @@ fn lasprs(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<siggen::Siggen>()?; m.add_class::<siggen::Siggen>()?;
m.add_class::<siggen::SiggenCommand>()?; m.add_class::<siggen::SiggenCommand>()?;
m.add_class::<siggen::SweepType>()?; m.add_class::<siggen::SweepType>()?;
m.add_class::<siggen::SiggenCommand>()?;
m.add_class::<siggen::Source>()?;
// SLM // SLM
m.add_class::<slm::TimeWeighting>()?; m.add_class::<slm::TimeWeighting>()?;

View File

@ -18,7 +18,7 @@ use std::slice::IterMut;
/// * [Siggen::newSine] /// * [Siggen::newSine]
/// * [Siggen::newSilence] /// * [Siggen::newSilence]
/// ///
#[derive(Clone)] #[derive(Clone, Debug)]
#[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

View File

@ -1,7 +1,7 @@
use crate::config::*; use crate::config::*;
use crate::filter::Filter; use crate::filter::Filter;
/// Signal generator config for a certain channel /// Signal generator config for a certain channel
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct SiggenChannelConfig { pub struct SiggenChannelConfig {
muted: bool, muted: bool,
prefilter: Option<Box<dyn Filter>>, prefilter: Option<Box<dyn Filter>>,

View File

@ -4,6 +4,7 @@ use crate::config::*;
/// Messages that can be send to a given signal generator [Siggen], to change its behaviour /// Messages that can be send to a given signal generator [Siggen], to change its behaviour
#[cfg_attr(feature = "python-bindings", pyclass)] #[cfg_attr(feature = "python-bindings", pyclass)]
#[derive(Clone, Debug)]
pub enum SiggenCommand { pub enum SiggenCommand {
/// Change the source to a sine wave with given frequency. /// Change the source to a sine wave with given frequency.
ChangeSource{ ChangeSource{
@ -13,13 +14,13 @@ pub enum SiggenCommand {
/// Reset the signal generator state /// Reset the signal generator state
ResetSiggen { ResetSiggen {
/// New sampling frequency \[Hz\] /// Sampling frequency \[Hz\]
fs: Flt, fs: Flt,
}, },
/// Set all gains to value g /// Set all gains to value g
SetAllGains { SetAllGains {
/// Linear gain level to apply /// Linear gain level to apply to all channels
g: Flt, g: Flt,
}, },

View File

@ -1,6 +1,7 @@
//! All sources for a signal generator. Sine waves, sweeps, noise, etc. //! All sources for a signal generator. Sine waves, sweeps, noise, etc.
use super::sweep::{SweepParams, SweepType}; use super::sweep::{SweepParams, SweepType};
use crate::config::*; use crate::config::*;
use std::fmt::Debug;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
/// Ratio between circumference and radius of a circle /// 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 /// Signal source for a signal generator. A signal source is capable of creating
/// new signal data. /// new signal data.
#[cfg_attr(feature = "python-bindings", pyclass)] #[cfg_attr(feature = "python-bindings", pyclass)]
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct Source { pub struct Source {
src: Box<dyn SourceImpl>, src: Box<dyn SourceImpl>,
} }
@ -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<Source> {
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<Source> {
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. /// Silence source. Most simple one does only send out a 0.
struct Silence {} struct Silence {}
@ -85,7 +120,7 @@ impl SourceImpl for Silence {
} }
} }
/// White noise source. Can be colored by applying a color filter to the source /// White noise source. Can be colored by applying a color filter to the source
#[derive(Clone)] #[derive(Clone, Debug)]
struct WhiteNoise {} struct WhiteNoise {}
impl SourceImpl for WhiteNoise { impl SourceImpl 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>) {
@ -100,7 +135,7 @@ impl SourceImpl for WhiteNoise {
} }
/// Sine wave, with configurable frequency /// Sine wave, with configurable frequency
#[derive(Clone)] #[derive(Clone, Debug)]
struct Sine { struct Sine {
// Sampling freq \[Hz\] // Sampling freq \[Hz\]
fs: Flt, fs: Flt,
@ -212,7 +247,7 @@ impl DerefMut for Source {
} }
/// Source for the signal generator. Implementations are sine waves, sweeps, noise. /// 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. /// Generate the 'pure' source signal. Output is placed inside the `sig` argument.
fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator<Item = &mut Flt>); fn genSignal_unscaled(&mut self, sig: &mut dyn ExactSizeIterator<Item = &mut Flt>);
/// Reset the source state, i.e. set phase to 0, etc /// Reset the source state, i.e. set phase to 0, etc

View File

@ -1,4 +1,6 @@
//! Sweep signal generation code //! Sweep signal generation code
use strum_macros::{Display, EnumMessage};
use strum::EnumMessage;
use { use {
crate::config::*, crate::config::*,
anyhow::{bail, Result}, anyhow::{bail, Result},
@ -9,23 +11,49 @@ const twopi: Flt = 2. * pi;
/// Enumerator representing the type of sweep source to create. Used as /// Enumerator representing the type of sweep source to create. Used as
/// parameter in [Siggen::newSweep]. /// parameter in [Siggen::newSweep].
#[cfg_attr(feature = "python-bindings", pyclass)] #[cfg_attr(feature = "python-bindings", pyclass)]
#[derive(Debug, Clone)] #[derive(Debug, Clone, Display, EnumMessage)]
pub enum SweepType { pub enum SweepType {
/// Forward only logarithmic sweep, repeats itself /// Forward only logarithmic sweep, repeats itself
#[strum(message = "Forward logarithmic")]
ForwardLog, ForwardLog,
/// Reverse only logarithmic sweep, repeats itself /// Reverse only logarithmic sweep, repeats itself
#[strum(message = "Backward logarithmic")]
BackwardLog, BackwardLog,
/// Continuous logarithmic sweep, repeats itself /// Continuous logarithmic sweep, repeats itself
#[strum(message = "Continuous logarithmic")]
ContinuousLog, ContinuousLog,
/// Forward only linear sweep, repeats itself /// Forward only linear sweep, repeats itself
#[strum(message = "Forward linear")]
ForwardLin, ForwardLin,
/// Reverse only linear sweep, repeats itself /// Reverse only linear sweep, repeats itself
#[strum(message = "Backward linear")]
BackwardLin, BackwardLin,
/// Continuous linear sweep, repeats itself /// Continuous linear sweep, repeats itself
#[strum(message = "Continuous linear")]
ContinuousLin, ContinuousLin,
} }
#[cfg(feature = "python-bindings")]
#[cfg_attr(feature = "python-bindings", pymethods)]
impl SweepType {
#[staticmethod]
fn all() -> Vec<SweepType> {
use SweepType::*;
vec![
ForwardLin,
ForwardLog,
BackwardLin,
BackwardLog,
ContinuousLin,
ContinuousLog,
]
}
fn __str__(&self) -> String {
self.get_message().unwrap().into()
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SweepParams { pub struct SweepParams {
// These parameters are described at [Source::newSweep] // These parameters are described at [Source::newSweep]