Updated clip code to include data range from channel. Changed DaqChannel and DaqConfig struct to match expected API
This commit is contained in:
parent
b275591ad5
commit
f58c6f8839
@ -82,16 +82,16 @@ impl CpalApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn getDeviceInfo(&self) -> Result<Vec<DeviceInfo>> {
|
pub fn getDeviceInfo(&self) -> Result<Vec<DeviceInfo>> {
|
||||||
let srs_1 = [
|
let samplerates_set1 = [
|
||||||
1000, 2000, 4000, 8000, 12000, 16000, 24000, 48000, 96000, 192000, 384000,
|
1000, 2000, 4000, 8000, 12000, 16000, 24000, 48000, 96000, 192000, 384000,
|
||||||
];
|
];
|
||||||
let srs_2 = [11025, 22050, 44100, 88200];
|
let samplerates_set2 = [11025, 22050, 44100, 88200];
|
||||||
|
|
||||||
let mut srs_tot = Vec::from_iter(srs_1.iter().chain(srs_2.iter()));
|
let mut samplerates_set =
|
||||||
srs_tot.sort();
|
Vec::from_iter(samplerates_set1.iter().chain(samplerates_set2.iter()));
|
||||||
let srs_tot = Vec::from_iter(srs_tot.iter().copied().map(|i| *i as Flt));
|
samplerates_set.sort();
|
||||||
|
// Convert to floating point
|
||||||
// srs_tot.sort();
|
let samplerates_set = Vec::from_iter(samplerates_set.iter().copied().map(|i| *i as Flt));
|
||||||
|
|
||||||
let mut devs = vec![];
|
let mut devs = vec![];
|
||||||
'devloop: for dev in self.host.devices()? {
|
'devloop: for dev in self.host.devices()? {
|
||||||
@ -99,7 +99,7 @@ impl CpalApi {
|
|||||||
let mut iChannelCount = 0;
|
let mut iChannelCount = 0;
|
||||||
let mut oChannelCount = 0;
|
let mut oChannelCount = 0;
|
||||||
|
|
||||||
let mut avSampleRates = srs_tot.clone();
|
let mut avSampleRates = samplerates_set.clone();
|
||||||
let mut avFramesPerBlock = vec![256_usize, 512, 1024, 2048, 8192];
|
let mut avFramesPerBlock = vec![256_usize, 512, 1024, 2048, 8192];
|
||||||
|
|
||||||
let mut sample_formats = vec![];
|
let mut sample_formats = vec![];
|
||||||
@ -155,6 +155,9 @@ impl CpalApi {
|
|||||||
if iChannelCount == oChannelCount && oChannelCount == 0 {
|
if iChannelCount == oChannelCount && oChannelCount == 0 {
|
||||||
break 'devloop;
|
break 'devloop;
|
||||||
}
|
}
|
||||||
|
if avSampleRates.len() == 0 {
|
||||||
|
break 'devloop;
|
||||||
|
}
|
||||||
devs.push(DeviceInfo {
|
devs.push(DeviceInfo {
|
||||||
api: StreamApiDescr::Cpal,
|
api: StreamApiDescr::Cpal,
|
||||||
device_name: dev.name()?,
|
device_name: dev.name()?,
|
||||||
@ -168,8 +171,10 @@ impl CpalApi {
|
|||||||
|
|
||||||
iChannelCount,
|
iChannelCount,
|
||||||
oChannelCount,
|
oChannelCount,
|
||||||
|
avInputRanges: vec![(-1.,1.)],
|
||||||
|
avOutputRanges: vec![(-1.,1.)],
|
||||||
hasInputIEPE: false,
|
hasInputIEPE: false,
|
||||||
|
hasDuplexMode: false,
|
||||||
hasInputACCouplingSwitch: false,
|
hasInputACCouplingSwitch: false,
|
||||||
hasInputTrigger: false,
|
hasInputTrigger: false,
|
||||||
hasInternalOutputMonitor: false,
|
hasInternalOutputMonitor: false,
|
||||||
@ -512,10 +517,11 @@ impl CpalApi {
|
|||||||
let config: cpal::StreamConfig = supported_config.config();
|
let config: cpal::StreamConfig = supported_config.config();
|
||||||
|
|
||||||
let meta = StreamMetaData::new(
|
let meta = StreamMetaData::new(
|
||||||
&conf.enabledInchannelConfig(),
|
&conf.enabledInChannels(),
|
||||||
conf.dtype,
|
conf.dtype,
|
||||||
supported_config.sample_rate().0 as Flt,
|
supported_config.sample_rate().0 as Flt,
|
||||||
framesPerBlock,
|
framesPerBlock,
|
||||||
|
Qty::Number
|
||||||
);
|
);
|
||||||
let meta = Arc::new(meta);
|
let meta = Arc::new(meta);
|
||||||
|
|
||||||
@ -578,6 +584,7 @@ impl CpalApi {
|
|||||||
dtype,
|
dtype,
|
||||||
config.sample_rate().0 as Flt,
|
config.sample_rate().0 as Flt,
|
||||||
framesPerBlock,
|
framesPerBlock,
|
||||||
|
Qty::Number
|
||||||
);
|
);
|
||||||
let metadata = Arc::new(metadata);
|
let metadata = Arc::new(metadata);
|
||||||
|
|
||||||
@ -656,6 +663,7 @@ impl CpalApi {
|
|||||||
dtype,
|
dtype,
|
||||||
config.sample_rate.0 as Flt,
|
config.sample_rate.0 as Flt,
|
||||||
framesPerBlock,
|
framesPerBlock,
|
||||||
|
Qty::Number
|
||||||
);
|
);
|
||||||
let md = Arc::new(md);
|
let md = Arc::new(md);
|
||||||
let str = Box::new(CpalStream {
|
let str = Box::new(CpalStream {
|
||||||
@ -733,10 +741,11 @@ impl CpalApi {
|
|||||||
let dtype = DataType::from(sampleformat);
|
let dtype = DataType::from(sampleformat);
|
||||||
|
|
||||||
let md = StreamMetaData::new(
|
let md = StreamMetaData::new(
|
||||||
&cfg.enabledOutchannelConfig(),
|
&cfg.enabledOutChannels(),
|
||||||
dtype,
|
dtype,
|
||||||
cpalconfig.sample_rate.0 as Flt,
|
cpalconfig.sample_rate.0 as Flt,
|
||||||
framesPerBlock,
|
framesPerBlock,
|
||||||
|
Qty::Number
|
||||||
);
|
);
|
||||||
let md = Arc::new(md);
|
let md = Arc::new(md);
|
||||||
let str = Box::new(CpalStream {
|
let str = Box::new(CpalStream {
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
|
use crate::config::*;
|
||||||
/// Daq apis that are optionally compiled in. Examples:
|
/// Daq apis that are optionally compiled in. Examples:
|
||||||
///
|
///
|
||||||
/// - CPAL (Cross-Platform Audio Library)
|
/// - CPAL (Cross-Platform Audio Library)
|
||||||
/// - ...
|
/// - ...
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::Arc;
|
use std::{fmt::format, sync::Arc};
|
||||||
use strum::EnumMessage;
|
use strum::EnumMessage;
|
||||||
use strum_macros;
|
use strum_macros;
|
||||||
use crate::config::*;
|
|
||||||
|
|
||||||
use super::{StreamStatus, StreamMetaData};
|
use super::{StreamMetaData, StreamStatus};
|
||||||
|
|
||||||
#[cfg(feature = "cpal-api")]
|
#[cfg(feature = "cpal-api")]
|
||||||
pub mod api_cpal;
|
pub mod api_cpal;
|
||||||
@ -33,7 +33,15 @@ pub trait Stream {
|
|||||||
|
|
||||||
/// Stream API descriptor: type and corresponding text
|
/// Stream API descriptor: type and corresponding text
|
||||||
#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||||
#[derive(strum_macros::EnumMessage, Debug, Clone, PartialEq, Serialize, Deserialize, strum_macros::Display)]
|
#[derive(
|
||||||
|
strum_macros::EnumMessage,
|
||||||
|
Debug,
|
||||||
|
Clone,
|
||||||
|
PartialEq,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
strum_macros::Display,
|
||||||
|
)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub enum StreamApiDescr {
|
pub enum StreamApiDescr {
|
||||||
/// CPAL api
|
/// CPAL api
|
||||||
@ -42,4 +50,10 @@ pub enum StreamApiDescr {
|
|||||||
/// PulseAudio api
|
/// PulseAudio api
|
||||||
#[strum(message = "pulse", detailed_message = "Pulseaudio")]
|
#[strum(message = "pulse", detailed_message = "Pulseaudio")]
|
||||||
Pulse = 1,
|
Pulse = 1,
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "python-bindings")]
|
||||||
|
impl StreamApiDescr {
|
||||||
|
fn __str__(&self) -> String {
|
||||||
|
format!("{}", self.get_detailed_message().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ use std::{ops::Index, path::PathBuf};
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::config::*;
|
use crate::config::*;
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// DAQ Configuration for a single channel
|
/// DAQ Configuration for a single channel
|
||||||
@ -19,21 +19,25 @@ pub struct DaqChannel {
|
|||||||
pub IEPEEnabled: bool,
|
pub IEPEEnabled: bool,
|
||||||
/// Enabled hardware AC coupling (if)
|
/// Enabled hardware AC coupling (if)
|
||||||
pub ACCouplingMode: bool,
|
pub ACCouplingMode: bool,
|
||||||
/// If supporting multiple input ranges: select the right index
|
/// The configured range (minumum, maximum) value
|
||||||
pub rangeIndex: usize,
|
pub range: (Flt, Flt),
|
||||||
/// Physical quantity
|
/// Physical quantity
|
||||||
pub qty: Qty,
|
pub qty: Qty,
|
||||||
|
/// Apply digital highpass filter to remove D.C. before processing
|
||||||
|
/// Value is in Hz. A value <= 0 means it is disabled.
|
||||||
|
pub digitalHighpassCutOn: Flt,
|
||||||
}
|
}
|
||||||
impl Default for DaqChannel {
|
impl Default for DaqChannel {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
DaqChannel {
|
DaqChannel {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
name: "".into(),
|
name: "".into(),
|
||||||
sensitivity: -1.0,
|
sensitivity: 1.0,
|
||||||
IEPEEnabled: false,
|
IEPEEnabled: false,
|
||||||
ACCouplingMode: false,
|
ACCouplingMode: false,
|
||||||
rangeIndex: 0,
|
range: (-1.0, 1.0),
|
||||||
qty: Qty::Number,
|
qty: Qty::Number,
|
||||||
|
digitalHighpassCutOn: -1.,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,11 +50,20 @@ impl DaqChannel {
|
|||||||
sensitivity: 1.0,
|
sensitivity: 1.0,
|
||||||
IEPEEnabled: false,
|
IEPEEnabled: false,
|
||||||
ACCouplingMode: false,
|
ACCouplingMode: false,
|
||||||
rangeIndex: 0,
|
range: (-1.0, 1.0),
|
||||||
qty: Qty::Number,
|
qty: Qty::Number,
|
||||||
|
digitalHighpassCutOn: -1.,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg_attr(feature = "python-bindings", pymethods)]
|
||||||
|
impl DaqChannel {
|
||||||
|
#[cfg(feature = "python-bindings")]
|
||||||
|
#[new]
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Configuration of a device.
|
/// Configuration of a device.
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
@ -64,10 +77,10 @@ pub struct DaqConfig {
|
|||||||
|
|
||||||
/// Configuration of the input channels
|
/// Configuration of the input channels
|
||||||
pub inchannel_config: Vec<DaqChannel>,
|
pub inchannel_config: Vec<DaqChannel>,
|
||||||
|
|
||||||
/// Configuration of the output channels
|
/// Configuration of the output channels
|
||||||
pub outchannel_config: Vec<DaqChannel>,
|
pub outchannel_config: Vec<DaqChannel>,
|
||||||
|
|
||||||
/// The data type to use
|
/// The data type to use
|
||||||
pub dtype: DataType,
|
pub dtype: DataType,
|
||||||
|
|
||||||
@ -84,18 +97,104 @@ pub struct DaqConfig {
|
|||||||
pub monitorOutput: bool,
|
pub monitorOutput: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "python-bindings")]
|
|
||||||
#[cfg_attr(feature = "python-bindings", pymethods)]
|
#[cfg_attr(feature = "python-bindings", pymethods)]
|
||||||
impl DaqConfig {
|
impl DaqConfig {
|
||||||
|
#[cfg(feature = "python-bindings")]
|
||||||
#[pyo3(name = "newFromDeviceInfo")]
|
#[pyo3(name = "newFromDeviceInfo")]
|
||||||
#[staticmethod]
|
#[staticmethod]
|
||||||
fn newFromDeviceInfo_py(d: &DeviceInfo) -> PyResult<DaqConfig> {
|
fn newFromDeviceInfo_py(d: &DeviceInfo) -> PyResult<DaqConfig> {
|
||||||
Ok(DaqConfig::newFromDeviceInfo(d))
|
Ok(DaqConfig::newFromDeviceInfo(d))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python-bindings")]
|
||||||
fn __repr__(&self) -> String {
|
fn __repr__(&self) -> String {
|
||||||
format!("{:#?}", self)
|
format!("{:#?}", self)
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "python-bindings")]
|
||||||
|
#[staticmethod]
|
||||||
|
fn fromTOML(toml: &str) -> PyResult<DaqConfig> {
|
||||||
|
let res = toml::from_str::<DaqConfig>(toml).map_err(|e| anyhow!(format!("{e}")))?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "python-bindings")]
|
||||||
|
fn toTOML(&self) -> PyResult<String> {
|
||||||
|
Ok(toml::to_string(&self).map_err(|e| anyhow!(format!("{e}")))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the total number of channels that appear in a running input stream.
|
||||||
|
pub fn numberEnabledInChannels(&self) -> usize {
|
||||||
|
self.inchannel_config.iter().filter(|ch| ch.enabled).count()
|
||||||
|
}
|
||||||
|
/// Returns the total number of channels that appear in a running output stream.
|
||||||
|
pub fn numberEnabledOutChannels(&self) -> usize {
|
||||||
|
self.outchannel_config
|
||||||
|
.iter()
|
||||||
|
.filter(|ch| ch.enabled)
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
/// Provide samplerate, based on device and specified sample rate index
|
||||||
|
pub fn sampleRate(&self, dev: &DeviceInfo) -> Flt {
|
||||||
|
*dev.avSampleRates.get(self.sampleRateIndex).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide samplerate, based on device and specified sample rate index
|
||||||
|
pub fn framesPerBlock(&self, dev: &DeviceInfo) -> usize {
|
||||||
|
dev.avFramesPerBlock[self.framesPerBlockIndex]
|
||||||
|
}
|
||||||
|
/// Returns vec of channel configuration for enabled input channels only
|
||||||
|
pub fn enabledInChannels(&self) -> Vec<DaqChannel> {
|
||||||
|
self.inchannel_config
|
||||||
|
.iter()
|
||||||
|
.filter(|ch| ch.enabled)
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
/// Returns a list of enabled input channel numbers as indices
|
||||||
|
/// in the list of all input channels (enabled and not)
|
||||||
|
pub fn enabledInchannelsList(&self) -> Vec<usize> {
|
||||||
|
self.inchannel_config
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, ch)| ch.enabled)
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
/// Returns vec of channel configuration for enabled output channels only
|
||||||
|
pub fn enabledOutChannels(&self) -> Vec<DaqChannel> {
|
||||||
|
self.outchannel_config
|
||||||
|
.iter()
|
||||||
|
.filter(|ch| ch.enabled)
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the channel number of the highest enabled input channel, if any.
|
||||||
|
pub fn highestEnabledInChannel(&self) -> Option<usize> {
|
||||||
|
let mut highest = None;
|
||||||
|
|
||||||
|
self.inchannel_config.iter().enumerate().for_each(|(i, c)| {
|
||||||
|
if c.enabled {
|
||||||
|
highest = Some(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
highest
|
||||||
|
}
|
||||||
|
/// Returns the channel number of the highest enabled output channel, if any.
|
||||||
|
pub fn highestEnabledOutChannel(&self) -> Option<usize> {
|
||||||
|
let mut highest = None;
|
||||||
|
|
||||||
|
self.outchannel_config
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, c)| {
|
||||||
|
if c.enabled {
|
||||||
|
highest = Some(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
highest
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl DaqConfig {
|
impl DaqConfig {
|
||||||
/// Creates a new default device configuration for a given device as specified with
|
/// Creates a new default device configuration for a given device as specified with
|
||||||
@ -182,74 +281,4 @@ impl DaqConfig {
|
|||||||
self.serialize_TOML(&mut file)?;
|
self.serialize_TOML(&mut file)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of enabled input channel numbers as indices
|
|
||||||
/// in the list of all input channels (enabled and not)
|
|
||||||
pub fn enabledInchannelsList(&self) -> Vec<usize> {
|
|
||||||
self.inchannel_config
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, ch)| ch.enabled)
|
|
||||||
.map(|(i, _)| i)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the total number of channels that appear in a running input stream.
|
|
||||||
pub fn numberEnabledInChannels(&self) -> usize {
|
|
||||||
self.inchannel_config.iter().filter(|ch| ch.enabled).count()
|
|
||||||
}
|
|
||||||
/// Returns the total number of channels that appear in a running output stream.
|
|
||||||
pub fn numberEnabledOutChannels(&self) -> usize {
|
|
||||||
self.outchannel_config
|
|
||||||
.iter()
|
|
||||||
.filter(|ch| ch.enabled)
|
|
||||||
.count()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provide samplerate, based on device and specified sample rate index
|
|
||||||
pub fn sampleRate(&self, dev: &DeviceInfo) -> Flt {
|
|
||||||
dev.avSampleRates[self.sampleRateIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provide samplerate, based on device and specified sample rate index
|
|
||||||
pub fn framesPerBlock(&self, dev: &DeviceInfo) -> usize {
|
|
||||||
dev.avFramesPerBlock[self.framesPerBlockIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns vec of channel configuration for enabled input channels only
|
|
||||||
pub fn enabledInchannelConfig(&self) -> Vec<DaqChannel> {
|
|
||||||
self.inchannel_config
|
|
||||||
.iter()
|
|
||||||
.filter(|ch| ch.enabled)
|
|
||||||
.cloned()
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
/// Returns vec of channel configuration for enabled output channels only
|
|
||||||
pub fn enabledOutchannelConfig(&self) -> Vec<DaqChannel> {
|
|
||||||
self.outchannel_config
|
|
||||||
.iter()
|
|
||||||
.filter(|ch| ch.enabled)
|
|
||||||
.cloned()
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the channel number of the highest enabled input channel, if any.
|
|
||||||
pub fn highestEnabledInChannel(&self) -> Option<usize> {
|
|
||||||
let mut highest = None;
|
|
||||||
|
|
||||||
self.inchannel_config.iter().enumerate().for_each(|(i,c)| if c.enabled {highest = Some(i);});
|
|
||||||
|
|
||||||
highest
|
|
||||||
}
|
|
||||||
/// Returns the channel number of the highest enabled output channel, if any.
|
|
||||||
pub fn highestEnabledOutChannel(&self) -> Option<usize> {
|
|
||||||
let mut highest = None;
|
|
||||||
|
|
||||||
self.outchannel_config.iter().enumerate().for_each(|(i,c)| if c.enabled {highest = Some(i);});
|
|
||||||
println!("{:?}", highest);
|
|
||||||
|
|
||||||
highest
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -58,18 +58,27 @@ pub struct DeviceInfo {
|
|||||||
/// Daq's that are able to run in full duplex mode.
|
/// Daq's that are able to run in full duplex mode.
|
||||||
pub hasInternalOutputMonitor: bool,
|
pub hasInternalOutputMonitor: bool,
|
||||||
|
|
||||||
|
/// Whether the device can run in duplex mode Y/N. Duplex mode means that a
|
||||||
|
/// stream provides both input and output at the same time.
|
||||||
|
pub hasDuplexMode: bool,
|
||||||
|
|
||||||
/// This flag is used to be able to indicate that the device cannot run
|
/// This flag is used to be able to indicate that the device cannot run
|
||||||
/// input and output streams independently, without opening the device in
|
/// input and output streams independently, without opening the device in
|
||||||
/// duplex mode. This is for example true for the UlDaq: only one handle to
|
/// duplex mode. This is for example true for the UlDaq: only one handle to
|
||||||
/// the device can be given at the same time.
|
/// the device can be given at the same time.
|
||||||
pub duplexModeForced: bool,
|
pub duplexModeForced: bool,
|
||||||
|
|
||||||
/// The physical quantity of the output signal. For 'normal' audio
|
/// The physical quantity of the input / output signal. For 'normal' audio
|
||||||
/// devices, this is typically a 'number' between +/- full scale. For some
|
/// devices, this is typically a 'number' between +/- full scale. For some
|
||||||
/// devices however, the output quantity corresponds to a physical signal,
|
/// devices however, the output quantity corresponds to a physical signal,
|
||||||
/// such a Volts.
|
/// such a Volts. Same holds for inputs.
|
||||||
// #[pyo3(get)]
|
|
||||||
pub physicalIOQty: Qty,
|
pub physicalIOQty: Qty,
|
||||||
|
|
||||||
|
/// Data range for input signal (minimum, maximum)
|
||||||
|
pub avInputRanges: Vec<(Flt, Flt)>,
|
||||||
|
|
||||||
|
/// Data range for output signal (minimum, maximum)
|
||||||
|
pub avOutputRanges: Vec<(Flt, Flt)>,
|
||||||
}
|
}
|
||||||
#[cfg_attr(feature = "python-bindings", pymethods)]
|
#[cfg_attr(feature = "python-bindings", pymethods)]
|
||||||
impl DeviceInfo {
|
impl DeviceInfo {
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
//!
|
//!
|
||||||
|
|
||||||
use crate::config::*;
|
use crate::config::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::EnumMessage;
|
use strum::EnumMessage;
|
||||||
use strum_macros;
|
use strum_macros;
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
/// Physical quantities that are I/O of a Daq device.
|
/// Physical quantities that are I/O of a Daq device.
|
||||||
#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||||
@ -15,7 +15,10 @@ pub enum Qty {
|
|||||||
#[strum(message = "number", detailed_message = "Unitless number")]
|
#[strum(message = "number", detailed_message = "Unitless number")]
|
||||||
Number = 0,
|
Number = 0,
|
||||||
/// Acoustic pressure
|
/// Acoustic pressure
|
||||||
#[strum(message = "acousticpressure", detailed_message = "Acoustic Pressure [Pa]")]
|
#[strum(
|
||||||
|
message = "acousticpressure",
|
||||||
|
detailed_message = "Acoustic Pressure [Pa]"
|
||||||
|
)]
|
||||||
AcousticPressure = 1,
|
AcousticPressure = 1,
|
||||||
/// Voltage
|
/// Voltage
|
||||||
#[strum(message = "voltage", detailed_message = "Voltage [V]")]
|
#[strum(message = "voltage", detailed_message = "Voltage [V]")]
|
||||||
@ -24,3 +27,46 @@ pub enum Qty {
|
|||||||
/// User defined
|
/// User defined
|
||||||
UserDefined = 3,
|
UserDefined = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "python-bindings", pymethods)]
|
||||||
|
impl Qty {
|
||||||
|
#[cfg(feature = "python-bindings")]
|
||||||
|
#[staticmethod]
|
||||||
|
fn all() -> Vec<Qty> {
|
||||||
|
use Qty::*;
|
||||||
|
vec![Number, AcousticPressure, Voltage]
|
||||||
|
}
|
||||||
|
fn __str__(&self) -> String {
|
||||||
|
self.get_detailed_message().unwrap().into()
|
||||||
|
}
|
||||||
|
/// Return a unit symbol for the current quantity
|
||||||
|
fn unit_symb(&self) -> String {
|
||||||
|
use Qty::*;
|
||||||
|
match self {
|
||||||
|
Number => "1".into(),
|
||||||
|
AcousticPressure => "Pa".into(),
|
||||||
|
Voltage => "V".into(),
|
||||||
|
UserDefined => "?".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Reference level for computing dB's
|
||||||
|
fn level_ref_value(&self) -> Flt {
|
||||||
|
use Qty::*;
|
||||||
|
match self {
|
||||||
|
Number => 1.,
|
||||||
|
AcousticPressure => 2e-5,
|
||||||
|
Voltage => 1.,
|
||||||
|
UserDefined => 1.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Level units (dB re..)
|
||||||
|
fn level_unit(&self) -> String {
|
||||||
|
use Qty::*;
|
||||||
|
match self {
|
||||||
|
Number => "dB re FS".into(),
|
||||||
|
AcousticPressure => "dB SPL".into(),
|
||||||
|
Voltage => "dBV".into(),
|
||||||
|
UserDefined => "?".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||||||
|
|
||||||
/// Stream metadata. All information required for properly interpreting the raw
|
/// Stream metadata. All information required for properly interpreting the raw
|
||||||
/// data that is coming from the stream.
|
/// data that is coming from the stream.
|
||||||
#[cfg_attr(feature = "python-bindings", pyclass)]
|
#[cfg_attr(feature = "python-bindings", pyclass(get_all))]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct StreamMetaData {
|
pub struct StreamMetaData {
|
||||||
/// Information for each channel in the stream
|
/// Information for each channel in the stream
|
||||||
@ -20,18 +20,27 @@ pub struct StreamMetaData {
|
|||||||
/// channelInfo.len() we get the total number of samples that come in at
|
/// channelInfo.len() we get the total number of samples that come in at
|
||||||
/// each callback.
|
/// each callback.
|
||||||
pub framesPerBlock: usize,
|
pub framesPerBlock: usize,
|
||||||
|
|
||||||
|
/// The quantity of input / output
|
||||||
|
pub physicalIOQty: Qty,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StreamMetaData {
|
impl StreamMetaData {
|
||||||
/// Create new metadata object.
|
/// Create new metadata object.
|
||||||
/// ///
|
|
||||||
/// # Args
|
/// # Args
|
||||||
///
|
///
|
||||||
|
/// - `channelInfo`: DaqChannel configuraion for each channel in the stream
|
||||||
|
/// - `rawdtype`: Datatype of raw stream data
|
||||||
|
/// - `samplerate`: Sampling frequency \[Hz\]
|
||||||
|
/// - `framesPerBlock`: Number of frames per callback
|
||||||
|
/// - `ioqty` - Physical quantity of i/o
|
||||||
pub fn new<'a, T>(
|
pub fn new<'a, T>(
|
||||||
channelInfo: T,
|
channelInfo: T,
|
||||||
rawdtype: DataType,
|
rawdtype: DataType,
|
||||||
sr: Flt,
|
samplerate: Flt,
|
||||||
framesPerBlock: usize,
|
framesPerBlock: usize,
|
||||||
|
ioqty: Qty,
|
||||||
) -> StreamMetaData
|
) -> StreamMetaData
|
||||||
where
|
where
|
||||||
T: IntoIterator<Item = &'a DaqChannel>,
|
T: IntoIterator<Item = &'a DaqChannel>,
|
||||||
@ -49,8 +58,9 @@ impl StreamMetaData {
|
|||||||
StreamMetaData {
|
StreamMetaData {
|
||||||
channelInfo,
|
channelInfo,
|
||||||
rawDatatype: rawdtype,
|
rawDatatype: rawdtype,
|
||||||
samplerate: sr,
|
samplerate,
|
||||||
framesPerBlock,
|
framesPerBlock,
|
||||||
|
physicalIOQty: ioqty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,21 +75,7 @@ impl StreamMetaData {
|
|||||||
#[cfg(feature = "python-bindings")]
|
#[cfg(feature = "python-bindings")]
|
||||||
#[cfg_attr(feature = "python-bindings", pymethods)]
|
#[cfg_attr(feature = "python-bindings", pymethods)]
|
||||||
impl StreamMetaData {
|
impl StreamMetaData {
|
||||||
#[getter]
|
fn __repr__(&self) -> String {
|
||||||
fn channelInfo(&self) -> Vec<DaqChannel> {
|
format!("{:#?}", self)
|
||||||
self.channelInfo.clone()
|
|
||||||
}
|
|
||||||
#[getter]
|
|
||||||
fn rawDatatype(&self) -> DataType {
|
|
||||||
self.rawDatatype
|
|
||||||
}
|
|
||||||
|
|
||||||
#[getter]
|
|
||||||
fn samplerate(&self) -> Flt {
|
|
||||||
self.samplerate
|
|
||||||
}
|
|
||||||
#[getter]
|
|
||||||
fn framesPerBlock(&self) -> usize {
|
|
||||||
self.framesPerBlock
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::daq::InStreamMsg;
|
use crate::daq::{InStreamMsg, StreamMetaData};
|
||||||
use crate::math::maxabs;
|
use crate::math::{max, maxabs, min};
|
||||||
use crate::slm::{self, SLMSettingsBuilder, TimeWeighting, SLM};
|
use crate::slm::{self, SLMSettingsBuilder, TimeWeighting, SLM};
|
||||||
use crate::{config::*, FreqWeighting, StandardFilterDescriptor};
|
use crate::{config::*, FreqWeighting, StandardFilterDescriptor};
|
||||||
use crate::{daq::StreamMgr, Dcol};
|
use crate::{daq::StreamMgr, Dcol};
|
||||||
@ -26,6 +26,10 @@ const LEVEL_THRESHOLD_FOR_HIGH_LEVEL: Flt = -10.;
|
|||||||
|
|
||||||
type SharedPPMStatus = Arc<Mutex<Vec<PPMChannelStatus>>>;
|
type SharedPPMStatus = Arc<Mutex<Vec<PPMChannelStatus>>>;
|
||||||
|
|
||||||
|
fn level(lin: Flt) -> Flt {
|
||||||
|
20. * lin.log10()
|
||||||
|
}
|
||||||
|
|
||||||
/// Peak programme meter implementation, including clip detector. Effectively uses a realtime SLM on all
|
/// Peak programme meter implementation, including clip detector. Effectively uses a realtime SLM on all
|
||||||
/// input channels. Also includes a clipping detector.
|
/// input channels. Also includes a clipping detector.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -62,6 +66,7 @@ impl PPM {
|
|||||||
|
|
||||||
rayon::spawn(move || {
|
rayon::spawn(move || {
|
||||||
let mut slms: Vec<SLM> = vec![];
|
let mut slms: Vec<SLM> = vec![];
|
||||||
|
let mut streammeta: Option<Arc<StreamMetaData>> = None;
|
||||||
|
|
||||||
let resetall = |slms: &mut Vec<SLM>| {
|
let resetall = |slms: &mut Vec<SLM>| {
|
||||||
let mut status = status.lock();
|
let mut status = status.lock();
|
||||||
@ -77,12 +82,19 @@ impl PPM {
|
|||||||
InStreamMsg::InStreamData(d) => {
|
InStreamMsg::InStreamData(d) => {
|
||||||
let mut status = status.lock();
|
let mut status = status.lock();
|
||||||
let floatdata = d.getFloatData();
|
let floatdata = d.getFloatData();
|
||||||
|
let meta = streammeta.as_ref().expect("Stream metadata not available");
|
||||||
|
let channels = &meta.channelInfo;
|
||||||
|
|
||||||
'channel: for (chno, (slm, ppmstatus)) in
|
'channel: for (chno, ((slm, ppmstatus), ch)) in slms
|
||||||
slms.iter_mut().zip(status.iter_mut()).enumerate()
|
.iter_mut()
|
||||||
|
.zip(status.iter_mut())
|
||||||
|
.zip(channels)
|
||||||
|
.enumerate()
|
||||||
{
|
{
|
||||||
let chdata = floatdata.slice(s![.., chno]);
|
let chdata = floatdata.slice(s![.., chno]);
|
||||||
let maxabs_new = maxabs(chdata);
|
let min_val = min(chdata);
|
||||||
|
let max_val = max(chdata);
|
||||||
|
|
||||||
let chdata = chdata
|
let chdata = chdata
|
||||||
.as_slice()
|
.as_slice()
|
||||||
.expect("Data not contiguous on sample axis");
|
.expect("Data not contiguous on sample axis");
|
||||||
@ -104,13 +116,27 @@ impl PPM {
|
|||||||
continue 'channel;
|
continue 'channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let clip = min_val <= ALMOST_CLIPPED_REL_AMP * ch.range.0
|
||||||
|
|| max_val >= ALMOST_CLIPPED_REL_AMP * ch.range.1;
|
||||||
|
|
||||||
|
let abs_range = if ch.range.0.abs() > ch.range.1.abs() {
|
||||||
|
ch.range.0.abs()
|
||||||
|
} else {
|
||||||
|
ch.range.1.abs()
|
||||||
|
};
|
||||||
|
let high_level_threshold =
|
||||||
|
level(abs_range) + LEVEL_THRESHOLD_FOR_HIGH_LEVEL;
|
||||||
|
let low_level_threshold =
|
||||||
|
level(abs_range) + LEVEL_THRESHOLD_FOR_LOW_LEVEL;
|
||||||
|
let high_level = last_level > high_level_threshold;
|
||||||
|
let low_level = last_level < low_level_threshold;
|
||||||
// Update clip status, if we were not clipping
|
// Update clip status, if we were not clipping
|
||||||
ppmstatus.clip = if maxabs_new > ALMOST_CLIPPED_REL_AMP {
|
ppmstatus.clip = if clip {
|
||||||
ppmstatus.clip_time = Some(Instant::now());
|
ppmstatus.clip_time = Some(Instant::now());
|
||||||
ClipState::Clipped
|
ClipState::Clipped
|
||||||
} else if last_level > LEVEL_THRESHOLD_FOR_HIGH_LEVEL {
|
} else if high_level {
|
||||||
ClipState::HighLevel
|
ClipState::HighLevel
|
||||||
} else if last_level < LEVEL_THRESHOLD_FOR_LOW_LEVEL {
|
} else if low_level {
|
||||||
ClipState::LowLevel
|
ClipState::LowLevel
|
||||||
} else {
|
} else {
|
||||||
ClipState::LevelFine
|
ClipState::LevelFine
|
||||||
@ -149,6 +175,7 @@ impl PPM {
|
|||||||
clip_time: None,
|
clip_time: None,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
streammeta = Some(meta);
|
||||||
}
|
}
|
||||||
InStreamMsg::StreamStopped => {}
|
InStreamMsg::StreamStopped => {}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use crate::daq::InStreamMsg;
|
use crate::daq::InStreamMsg;
|
||||||
|
use crate::daq::StreamMetaData;
|
||||||
use crate::daq::StreamMgr;
|
use crate::daq::StreamMgr;
|
||||||
|
use crate::math::max;
|
||||||
use crate::math::maxabs;
|
use crate::math::maxabs;
|
||||||
|
use crate::math::min;
|
||||||
use crate::Flt;
|
use crate::Flt;
|
||||||
use crossbeam::channel::bounded;
|
use crossbeam::channel::bounded;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@ -10,8 +13,9 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// If signal is above this value, we indicate that the signal has clipped.
|
/// If signal is below / above the range times the value below, we indicate that
|
||||||
const CLIP_STRONG_LIMIT: Flt = 0.999;
|
/// the signal has clipped.
|
||||||
|
const CLIP_REL_LIMIT: Flt = 0.999;
|
||||||
|
|
||||||
/// Very simple clip detector. Used to detect cliping in a recording. Stores one
|
/// Very simple clip detector. Used to detect cliping in a recording. Stores one
|
||||||
/// clip value if just something happened between time of new and moment of drop().
|
/// clip value if just something happened between time of new and moment of drop().
|
||||||
@ -38,12 +42,40 @@ impl SimpleClipDetector {
|
|||||||
|
|
||||||
smgr.addInQueue(tx);
|
smgr.addInQueue(tx);
|
||||||
rayon::spawn(move || loop {
|
rayon::spawn(move || loop {
|
||||||
|
let mut streammeta: Option<Arc<StreamMetaData>> = None;
|
||||||
if let Ok(msg) = rx.recv_timeout(Duration::from_millis(1500)) {
|
if let Ok(msg) = rx.recv_timeout(Duration::from_millis(1500)) {
|
||||||
match msg {
|
match msg {
|
||||||
InStreamMsg::InStreamData(dat) => {
|
InStreamMsg::InStreamData(dat) => {
|
||||||
|
let meta = streammeta
|
||||||
|
.expect("If we are here, stream metadata should be available");
|
||||||
let flt = dat.getFloatData();
|
let flt = dat.getFloatData();
|
||||||
let maxabs = maxabs(flt.view());
|
let maxs = flt
|
||||||
if maxabs >= CLIP_STRONG_LIMIT {
|
.columns()
|
||||||
|
.into_iter()
|
||||||
|
.map(|col| max(col))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let mins = flt
|
||||||
|
.columns()
|
||||||
|
.into_iter()
|
||||||
|
.map(|col| min(col))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut clip = false;
|
||||||
|
|
||||||
|
maxs.into_iter().zip(mins).zip(&meta.channelInfo).for_each(
|
||||||
|
|((max, min), ch)| {
|
||||||
|
let min_for_clip = CLIP_REL_LIMIT * ch.range.0;
|
||||||
|
let max_for_clip = CLIP_REL_LIMIT * ch.range.1;
|
||||||
|
if max >= max_for_clip {
|
||||||
|
clip = true;
|
||||||
|
}
|
||||||
|
if min <= min_for_clip {
|
||||||
|
clip = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if clip {
|
||||||
clipstate.store(true, Relaxed);
|
clipstate.store(true, Relaxed);
|
||||||
// We do not have to do anything anymore. The signal
|
// We do not have to do anything anymore. The signal
|
||||||
// has clipped so we do not have to check any new
|
// has clipped so we do not have to check any new
|
||||||
@ -51,6 +83,9 @@ impl SimpleClipDetector {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
InStreamMsg::StreamStarted(meta) => {
|
||||||
|
streammeta = Some(meta);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user