Signal generator working, even with Python bindings. startOutputStream implementend for CPAL
This commit is contained in:
parent
22a9c9f850
commit
158ea77c40
102
src/bin/lasp_output.rs
Normal file
102
src/bin/lasp_output.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use crossbeam::channel::{ unbounded, Receiver, TryRecvError };
|
||||||
|
use lasprs::daq::{ DaqConfig, StreamMgr, StreamStatus, StreamType };
|
||||||
|
use lasprs::siggen::Siggen;
|
||||||
|
use std::io;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{ thread, time };
|
||||||
|
// use
|
||||||
|
|
||||||
|
/// Spawns a thread and waits for a single line, pushes it to the receiver and returns
|
||||||
|
fn stdin_channel_wait_for_return() -> Receiver<String> {
|
||||||
|
let (tx, rx) = unbounded();
|
||||||
|
thread::spawn(move || {
|
||||||
|
loop {
|
||||||
|
let mut buffer = String::new();
|
||||||
|
io::stdin().read_line(&mut buffer).unwrap();
|
||||||
|
// Do not care whether we succeed here.
|
||||||
|
let _ = tx.send(buffer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
fn sleep(millis: u64) {
|
||||||
|
let duration = time::Duration::from_millis(millis);
|
||||||
|
thread::sleep(duration);
|
||||||
|
}
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let mut smgr = StreamMgr::new();
|
||||||
|
|
||||||
|
let stdin_channel = stdin_channel_wait_for_return();
|
||||||
|
|
||||||
|
println!("Creating signal generator...");
|
||||||
|
let mut siggen = Siggen::newSine(2, 432.0);
|
||||||
|
|
||||||
|
// Reduce all gains a bit...
|
||||||
|
siggen.setAllGains(0.1);
|
||||||
|
|
||||||
|
// Apply signal generator
|
||||||
|
smgr.setSiggen(siggen);
|
||||||
|
|
||||||
|
println!("Starting stream...");
|
||||||
|
let devs = smgr.getDeviceInfo();
|
||||||
|
for (i, dev) in devs.iter().enumerate() {
|
||||||
|
println!("No: {}, name: {}", i, dev.device_name);
|
||||||
|
}
|
||||||
|
print!("Please choose device by number [0-{}]: ", devs.len());
|
||||||
|
let dev = loop {
|
||||||
|
match stdin_channel.try_recv() {
|
||||||
|
Ok(nostr) => {
|
||||||
|
if let Ok(val) = nostr.trim().parse::<i32>() {
|
||||||
|
if (val as usize) > devs.len() - 1 {
|
||||||
|
println!(
|
||||||
|
"Invalid device number. Expected a value between 0 and {}. Please try again.",
|
||||||
|
devs.len()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break &devs[val as usize];
|
||||||
|
} else {
|
||||||
|
println!("Invalid value. Please fill in a number. ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Empty) => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Disconnected) => panic!("Channel disconnected"),
|
||||||
|
}
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cfg = DaqConfig::newFromDeviceInfo(dev);
|
||||||
|
cfg.outchannel_config[0].enabled = true;
|
||||||
|
cfg.outchannel_config[1].enabled = true;
|
||||||
|
cfg.outchannel_config[2].enabled = true;
|
||||||
|
|
||||||
|
smgr.startStream(StreamType::Output, &cfg)?;
|
||||||
|
|
||||||
|
println!("Press <enter> key to quit...");
|
||||||
|
'infy: loop {
|
||||||
|
match stdin_channel.try_recv() {
|
||||||
|
Ok(_key) => {
|
||||||
|
break 'infy;
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Empty) => {}
|
||||||
|
Err(TryRecvError::Disconnected) => panic!("Channel disconnected"),
|
||||||
|
}
|
||||||
|
sleep(100);
|
||||||
|
match smgr.getStatus(StreamType::Output) {
|
||||||
|
StreamStatus::NotRunning {} => {
|
||||||
|
println!("Stream is not running?");
|
||||||
|
break 'infy;
|
||||||
|
}
|
||||||
|
StreamStatus::Running {} => {}
|
||||||
|
StreamStatus::Error { e } => {
|
||||||
|
println!("Stream error: {}", e);
|
||||||
|
break 'infy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -28,7 +28,7 @@ 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::newSineWave(2, 432.);
|
let mut siggen = Siggen::newSine(2, 432.);
|
||||||
|
|
||||||
// Some things that can be done
|
// Some things that can be done
|
||||||
// siggen.setDCOffset(&[0.1, 0.]);
|
// siggen.setDCOffset(&[0.1, 0.]);
|
||||||
@ -36,11 +36,12 @@ fn main() -> Result<()> {
|
|||||||
// Reduce all gains a bit...
|
// Reduce all gains a bit...
|
||||||
siggen.setAllGains(0.1);
|
siggen.setAllGains(0.1);
|
||||||
|
|
||||||
// Apply signal generator
|
|
||||||
smgr.setSiggen(siggen)?;
|
|
||||||
|
|
||||||
println!("Starting stream...");
|
println!("Starting stream...");
|
||||||
smgr.startDefaultOutputStream()?;
|
smgr.startDefaultOutputStream()?;
|
||||||
|
|
||||||
|
// Apply signal generator
|
||||||
|
smgr.setSiggen(siggen);
|
||||||
|
|
||||||
println!("Press <enter> key to quit...");
|
println!("Press <enter> key to quit...");
|
||||||
'infy: loop {
|
'infy: loop {
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
use super::Stream;
|
use super::Stream;
|
||||||
use super::StreamMetaData;
|
use super::StreamMetaData;
|
||||||
use crate::daq::{streamdata::*, StreamApiDescr};
|
use crate::config::{ self, * };
|
||||||
use crate::config::{self, *};
|
use crate::daq::{ self, * };
|
||||||
use crate::daq::{self, *};
|
use crate::daq::{ streamdata::*, StreamApiDescr };
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{ bail, Result };
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{ DeviceTrait, HostTrait, StreamTrait };
|
||||||
use cpal::{Device, Host, Sample, SampleFormat, SupportedBufferSize};
|
use cpal::SampleRate;
|
||||||
|
use cpal::SupportedStreamConfig;
|
||||||
|
use cpal::{ Device, Host, Sample, SampleFormat, SupportedBufferSize };
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use crossbeam::channel::{Receiver, Sender};
|
use crossbeam::channel::{ Receiver, Sender };
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use num::ToPrimitive;
|
use num::ToPrimitive;
|
||||||
use reinterpret::reinterpret_slice;
|
use reinterpret::reinterpret_slice;
|
||||||
use std::any::{Any, TypeId};
|
use std::any;
|
||||||
|
use std::any::{ Any, TypeId };
|
||||||
|
use std::collections::btree_map::OccupiedEntry;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -78,19 +82,22 @@ impl CpalApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn getDeviceInfo(&self) -> Result<Vec<DeviceInfo>> {
|
pub fn getDeviceInfo(&self) -> Result<Vec<DeviceInfo>> {
|
||||||
let srs_1 = [
|
let srs_1 = [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 srs_2 = [11025, 22050, 44100, 88200];
|
||||||
|
|
||||||
let mut srs_tot = Vec::from_iter(srs_1.iter().chain(srs_2.iter()));
|
let mut srs_tot = Vec::from_iter(srs_1.iter().chain(srs_2.iter()));
|
||||||
srs_tot.sort();
|
srs_tot.sort();
|
||||||
let srs_tot = Vec::from_iter(srs_tot.iter().copied().map(|i| *i as Flt));
|
let srs_tot = Vec::from_iter(
|
||||||
|
srs_tot
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.map(|i| *i as Flt)
|
||||||
|
);
|
||||||
|
|
||||||
// srs_tot.sort();
|
// srs_tot.sort();
|
||||||
|
|
||||||
let mut devs = vec![];
|
let mut devs = vec![];
|
||||||
for dev in self.host.devices()? {
|
'devloop: for dev in self.host.devices()? {
|
||||||
// println!("{:?}", dev.name());
|
// println!("{:?}", dev.name());
|
||||||
let mut iChannelCount = 0;
|
let mut iChannelCount = 0;
|
||||||
let mut oChannelCount = 0;
|
let mut oChannelCount = 0;
|
||||||
@ -107,8 +114,8 @@ impl CpalApi {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sample_formats.push(icfg.sample_format());
|
sample_formats.push(icfg.sample_format());
|
||||||
avSampleRates.retain(|sr| *sr >= icfg.min_sample_rate().0 as Flt);
|
avSampleRates.retain(|sr| *sr >= (icfg.min_sample_rate().0 as Flt));
|
||||||
avSampleRates.retain(|sr| *sr <= icfg.max_sample_rate().0 as Flt);
|
avSampleRates.retain(|sr| *sr <= (icfg.max_sample_rate().0 as Flt));
|
||||||
if let SupportedBufferSize::Range { min, max } = icfg.buffer_size() {
|
if let SupportedBufferSize::Range { min, max } = icfg.buffer_size() {
|
||||||
avFramesPerBlock.retain(|i| i >= &(*min as usize));
|
avFramesPerBlock.retain(|i| i >= &(*min as usize));
|
||||||
avFramesPerBlock.retain(|i| i <= &(*max as usize));
|
avFramesPerBlock.retain(|i| i <= &(*max as usize));
|
||||||
@ -117,15 +124,15 @@ impl CpalApi {
|
|||||||
// avFramesPerBlock.retain(|i| i >= icfg.buffer_size().)
|
// avFramesPerBlock.retain(|i| i >= icfg.buffer_size().)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Ok(ocfg) = dev.supported_input_configs() {
|
if let Ok(ocfg) = dev.supported_output_configs() {
|
||||||
for ocfg in ocfg {
|
for ocfg in ocfg {
|
||||||
let thissf = ocfg.sample_format();
|
let thissf = ocfg.sample_format();
|
||||||
if thissf.is_uint() {
|
if thissf.is_uint() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sample_formats.push(thissf);
|
sample_formats.push(thissf);
|
||||||
avSampleRates.retain(|sr| *sr >= ocfg.min_sample_rate().0 as Flt);
|
avSampleRates.retain(|sr| *sr >= (ocfg.min_sample_rate().0 as Flt));
|
||||||
avSampleRates.retain(|sr| *sr <= ocfg.max_sample_rate().0 as Flt);
|
avSampleRates.retain(|sr| *sr <= (ocfg.max_sample_rate().0 as Flt));
|
||||||
if let SupportedBufferSize::Range { min, max } = ocfg.buffer_size() {
|
if let SupportedBufferSize::Range { min, max } = ocfg.buffer_size() {
|
||||||
avFramesPerBlock.retain(|i| i >= &(*min as usize));
|
avFramesPerBlock.retain(|i| i >= &(*min as usize));
|
||||||
avFramesPerBlock.retain(|i| i <= &(*max as usize));
|
avFramesPerBlock.retain(|i| i <= &(*max as usize));
|
||||||
@ -138,14 +145,22 @@ impl CpalApi {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let dtypes: Vec<DataType> =
|
let dtypes: Vec<DataType> = sample_formats
|
||||||
sample_formats.iter().dedup().map(|i| (*i).into()).collect();
|
.iter()
|
||||||
|
.dedup()
|
||||||
|
.map(|i| (*i).into())
|
||||||
|
.collect();
|
||||||
|
|
||||||
let prefDataType = match dtypes.iter().position(|d| d == &DataType::F32) {
|
let prefDataType = match dtypes.iter().position(|d| d == &DataType::F32) {
|
||||||
Some(idx) => dtypes[idx],
|
Some(idx) => dtypes[idx],
|
||||||
None => dtypes[dtypes.len() - 1],
|
None => dtypes[dtypes.len() - 1],
|
||||||
};
|
};
|
||||||
let prefSampleRate = *avSampleRates.last().unwrap_or(&48000.);
|
let prefSampleRate = *avSampleRates.last().unwrap_or(&48000.0);
|
||||||
|
|
||||||
|
// Do not add device if it does not have any channels at all.
|
||||||
|
if iChannelCount == oChannelCount && oChannelCount == 0 {
|
||||||
|
break 'devloop;
|
||||||
|
}
|
||||||
devs.push(DeviceInfo {
|
devs.push(DeviceInfo {
|
||||||
api: StreamApiDescr::Cpal,
|
api: StreamApiDescr::Cpal,
|
||||||
device_name: dev.name()?,
|
device_name: dev.name()?,
|
||||||
@ -166,7 +181,7 @@ impl CpalApi {
|
|||||||
hasInternalOutputMonitor: false,
|
hasInternalOutputMonitor: false,
|
||||||
duplexModeForced: false,
|
duplexModeForced: false,
|
||||||
physicalIOQty: Qty::Number,
|
physicalIOQty: Qty::Number,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(devs)
|
Ok(devs)
|
||||||
@ -175,9 +190,8 @@ impl CpalApi {
|
|||||||
// Create the error function closure, that capture the send channel on which error messages from the stream are sent
|
// Create the error function closure, that capture the send channel on which error messages from the stream are sent
|
||||||
fn create_errfcn(
|
fn create_errfcn(
|
||||||
send_ch: Option<Sender<RawStreamData>>,
|
send_ch: Option<Sender<RawStreamData>>,
|
||||||
status: Arc<AtomicCell<StreamStatus>>,
|
status: Arc<AtomicCell<StreamStatus>>
|
||||||
) -> impl FnMut(cpal::StreamError) {
|
) -> impl FnMut(cpal::StreamError) {
|
||||||
|
|
||||||
move |err: cpal::StreamError| {
|
move |err: cpal::StreamError| {
|
||||||
let serr = match err {
|
let serr = match err {
|
||||||
cpal::StreamError::DeviceNotAvailable => StreamError::DeviceNotAvailable,
|
cpal::StreamError::DeviceNotAvailable => StreamError::DeviceNotAvailable,
|
||||||
@ -186,7 +200,7 @@ impl CpalApi {
|
|||||||
if let Some(sender) = &send_ch {
|
if let Some(sender) = &send_ch {
|
||||||
sender.send(RawStreamData::StreamError(serr)).unwrap();
|
sender.send(RawStreamData::StreamError(serr)).unwrap();
|
||||||
}
|
}
|
||||||
status.store(StreamStatus::Error{e: serr});
|
status.store(StreamStatus::Error { e: serr });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,10 +208,9 @@ impl CpalApi {
|
|||||||
config: &cpal::StreamConfig,
|
config: &cpal::StreamConfig,
|
||||||
sender: Sender<RawStreamData>,
|
sender: Sender<RawStreamData>,
|
||||||
framesPerBlock: usize,
|
framesPerBlock: usize,
|
||||||
en_inchannels: Vec<usize>,
|
en_inchannels: Vec<usize>
|
||||||
) -> impl FnMut(&[T], &cpal::InputCallbackInfo)
|
) -> impl FnMut(&[T], &cpal::InputCallbackInfo)
|
||||||
where
|
where T: 'static + Sample + ToPrimitive
|
||||||
T: 'static + Sample + ToPrimitive,
|
|
||||||
{
|
{
|
||||||
let tot_inch = config.channels as usize;
|
let tot_inch = config.channels as usize;
|
||||||
|
|
||||||
@ -233,7 +246,7 @@ impl CpalApi {
|
|||||||
|
|
||||||
// Send over data
|
// Send over data
|
||||||
let msg = RawStreamData::from(enabled_ch_data.clone());
|
let msg = RawStreamData::from(enabled_ch_data.clone());
|
||||||
sender.send(msg).unwrap()
|
sender.send(msg).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,13 +262,13 @@ impl CpalApi {
|
|||||||
device: &cpal::Device,
|
device: &cpal::Device,
|
||||||
sender: Sender<RawStreamData>,
|
sender: Sender<RawStreamData>,
|
||||||
en_inchannels: Vec<usize>,
|
en_inchannels: Vec<usize>,
|
||||||
framesPerBlock: usize,
|
framesPerBlock: usize
|
||||||
) -> Result<(cpal::Stream, Arc<AtomicCell<StreamStatus>>)> {
|
) -> Result<(cpal::Stream, Arc<AtomicCell<StreamStatus>>)> {
|
||||||
let status = Arc::new(AtomicCell::new(StreamStatus::NotRunning{}));
|
let status = Arc::new(AtomicCell::new(StreamStatus::NotRunning {}));
|
||||||
|
|
||||||
let errfcn = CpalApi::create_errfcn(Some(sender.clone()), status.clone());
|
let errfcn = CpalApi::create_errfcn(Some(sender.clone()), status.clone());
|
||||||
|
|
||||||
macro_rules! build_stream{
|
macro_rules! build_stream {
|
||||||
($($cpaltype:pat => $rtype:ty),*) => {
|
($($cpaltype:pat => $rtype:ty),*) => {
|
||||||
match sf {
|
match sf {
|
||||||
$(
|
$(
|
||||||
@ -269,9 +282,10 @@ impl CpalApi {
|
|||||||
}),*,
|
}),*,
|
||||||
_ => bail!("Unsupported sample format '{}'", sf)
|
_ => bail!("Unsupported sample format '{}'", sf)
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
let stream: cpal::Stream = build_stream!(
|
let stream: cpal::Stream =
|
||||||
|
build_stream!(
|
||||||
SampleFormat::I8 => i8,
|
SampleFormat::I8 => i8,
|
||||||
SampleFormat::I16 => i16,
|
SampleFormat::I16 => i16,
|
||||||
SampleFormat::I32 => i32,
|
SampleFormat::I32 => i32,
|
||||||
@ -284,24 +298,43 @@ impl CpalApi {
|
|||||||
config: &cpal::StreamConfig,
|
config: &cpal::StreamConfig,
|
||||||
streamstatus: Arc<AtomicCell<StreamStatus>>,
|
streamstatus: Arc<AtomicCell<StreamStatus>>,
|
||||||
receiver: Receiver<RawStreamData>,
|
receiver: Receiver<RawStreamData>,
|
||||||
framesPerBlock: usize,
|
ch_config: &[DaqChannel],
|
||||||
|
framesPerBlock: usize
|
||||||
) -> impl FnMut(&mut [T], &cpal::OutputCallbackInfo)
|
) -> impl FnMut(&mut [T], &cpal::OutputCallbackInfo)
|
||||||
where
|
where T: 'static + Sample + Debug
|
||||||
T: 'static + Sample + Debug,
|
|
||||||
{
|
{
|
||||||
let tot_outch: usize = config.channels as usize;
|
let number_total_out_channels: usize = config.channels as usize;
|
||||||
// println!("Numer of channels: {:?}", tot_outch);
|
let number_enabled_out_channels = ch_config
|
||||||
let mut callback_ctr: usize = 0;
|
.iter()
|
||||||
let mut q = VecDeque::<T>::with_capacity(2 * tot_outch * framesPerBlock);
|
.filter(|ch| ch.enabled)
|
||||||
|
.count();
|
||||||
|
|
||||||
move |data, _info: &_| {
|
let disabled_ch = DaqChannel::default();
|
||||||
let nsamples_asked = data.len();
|
let disabled_repeater = std::iter::repeat(&disabled_ch);
|
||||||
|
let enabled_outch = ch_config.iter().chain(disabled_repeater);
|
||||||
|
|
||||||
|
// Vector of enabled output channells, with length of number_total_out_channels
|
||||||
|
let enabled_outch: Vec<bool> = (0..number_total_out_channels)
|
||||||
|
.zip(enabled_outch)
|
||||||
|
.map(|(_, b)| b.enabled)
|
||||||
|
.collect();
|
||||||
|
assert_eq!(enabled_outch.len(), number_total_out_channels);
|
||||||
|
|
||||||
|
let mut callback_ctr: usize = 0;
|
||||||
|
let mut q = VecDeque::<T>::with_capacity(2 * number_total_out_channels * framesPerBlock);
|
||||||
|
|
||||||
|
move |outdata, _info: &_| {
|
||||||
|
let nsamples_asked =
|
||||||
|
(outdata.len() / number_total_out_channels) * number_enabled_out_channels;
|
||||||
let status = streamstatus.load();
|
let status = streamstatus.load();
|
||||||
callback_ctr += 1;
|
callback_ctr += 1;
|
||||||
|
|
||||||
let mut setToEquilibrium = || data.iter_mut().for_each(|v| *v = Sample::EQUILIBRIUM);
|
let mut setToEquilibrium = ||
|
||||||
|
outdata.iter_mut().for_each(|v| {
|
||||||
|
*v = Sample::EQUILIBRIUM;
|
||||||
|
});
|
||||||
match status {
|
match status {
|
||||||
StreamStatus::NotRunning{} | StreamStatus::Error{..} => {
|
StreamStatus::NotRunning {} | StreamStatus::Error { .. } => {
|
||||||
setToEquilibrium();
|
setToEquilibrium();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -312,7 +345,7 @@ impl CpalApi {
|
|||||||
// Obtain new samples from the generator
|
// Obtain new samples from the generator
|
||||||
for dat in receiver.try_iter() {
|
for dat in receiver.try_iter() {
|
||||||
let slice = dat.getRef::<T>();
|
let slice = dat.getRef::<T>();
|
||||||
if let StreamStatus::Running{} = status {
|
if let StreamStatus::Running {} = status {
|
||||||
q.extend(slice);
|
q.extend(slice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,16 +354,37 @@ impl CpalApi {
|
|||||||
if q.len() >= nsamples_asked {
|
if q.len() >= nsamples_asked {
|
||||||
// All right, we have enough samples to send out! They are
|
// All right, we have enough samples to send out! They are
|
||||||
// drained from the queue
|
// drained from the queue
|
||||||
data.iter_mut()
|
let out_chunks = outdata.iter_mut().chunks(number_total_out_channels);
|
||||||
.zip(q.drain(..nsamples_asked))
|
let siggen_chunks = q.drain(..nsamples_asked).chunks(number_enabled_out_channels);
|
||||||
.for_each(|(o, i)| *o = i);
|
for (och, ich) in out_chunks.into_iter().zip(siggen_chunks.into_iter()) {
|
||||||
|
|
||||||
|
let mut sig_frame_iter = ich.into_iter();
|
||||||
|
och.into_iter()
|
||||||
|
.zip(&enabled_outch)
|
||||||
|
.for_each(|(o, en)| (
|
||||||
|
if *en {
|
||||||
|
*o = sig_frame_iter.next().unwrap();
|
||||||
|
} else {
|
||||||
|
*o = Sample::EQUILIBRIUM;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// outdata
|
||||||
|
// .iter_mut()
|
||||||
|
// .zip(q.drain(..nsamples_asked))
|
||||||
|
// .for_each(|(o, i)| {
|
||||||
|
// *o = i;
|
||||||
|
// });
|
||||||
} else if callback_ctr <= 2 {
|
} else if callback_ctr <= 2 {
|
||||||
// For the first two blocks, we allow dat the data is not yet
|
// For the first two blocks, we allow dat the data is not yet
|
||||||
// ready, without complaining on underruns
|
// ready, without complaining on underruns
|
||||||
setToEquilibrium();
|
setToEquilibrium();
|
||||||
} else {
|
} else {
|
||||||
// Output buffer underrun
|
// Output buffer underrun
|
||||||
streamstatus.store(StreamStatus::Error{e:StreamError::OutputUnderrunError});
|
streamstatus.store(StreamStatus::Error {
|
||||||
|
e: StreamError::OutputUnderrunError,
|
||||||
|
});
|
||||||
setToEquilibrium();
|
setToEquilibrium();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -341,19 +395,18 @@ impl CpalApi {
|
|||||||
config: &cpal::StreamConfig,
|
config: &cpal::StreamConfig,
|
||||||
device: &cpal::Device,
|
device: &cpal::Device,
|
||||||
receiver: Receiver<RawStreamData>,
|
receiver: Receiver<RawStreamData>,
|
||||||
framesPerBlock: usize,
|
ch_config: &[DaqChannel],
|
||||||
|
framesPerBlock: usize
|
||||||
) -> Result<(cpal::Stream, Arc<AtomicCell<StreamStatus>>)> {
|
) -> Result<(cpal::Stream, Arc<AtomicCell<StreamStatus>>)> {
|
||||||
// let tot_ch = config.channels as usize;
|
let status = Arc::new(AtomicCell::new(StreamStatus::NotRunning {}));
|
||||||
|
|
||||||
let status = Arc::new(AtomicCell::new(StreamStatus::NotRunning{}));
|
|
||||||
|
|
||||||
let err_cb = CpalApi::create_errfcn(None, status.clone());
|
let err_cb = CpalApi::create_errfcn(None, status.clone());
|
||||||
macro_rules! build_stream{
|
macro_rules! build_stream {
|
||||||
($($cpaltype:pat => $rtype:ty),*) => {
|
($($cpaltype:pat => $rtype:ty),*) => {
|
||||||
match sf {
|
match sf {
|
||||||
$(
|
$(
|
||||||
$cpaltype => {
|
$cpaltype => {
|
||||||
let outcallback = CpalApi::create_outcallback::<$rtype>(config, status.clone(), receiver, framesPerBlock);
|
let outcallback = CpalApi::create_outcallback::<$rtype>(config, status.clone(), receiver, ch_config, framesPerBlock);
|
||||||
device.build_output_stream(
|
device.build_output_stream(
|
||||||
&config,
|
&config,
|
||||||
outcallback,
|
outcallback,
|
||||||
@ -362,9 +415,10 @@ impl CpalApi {
|
|||||||
}),*,
|
}),*,
|
||||||
_ => bail!("Unsupported sample format '{}'", sf)
|
_ => bail!("Unsupported sample format '{}'", sf)
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
let stream: cpal::Stream = build_stream!(
|
let stream: cpal::Stream =
|
||||||
|
build_stream!(
|
||||||
SampleFormat::I8 => i8,
|
SampleFormat::I8 => i8,
|
||||||
SampleFormat::I16 => i16,
|
SampleFormat::I16 => i16,
|
||||||
SampleFormat::I32 => i32,
|
SampleFormat::I32 => i32,
|
||||||
@ -380,10 +434,9 @@ impl CpalApi {
|
|||||||
devinfo: &DeviceInfo,
|
devinfo: &DeviceInfo,
|
||||||
conf: &DaqConfig,
|
conf: &DaqConfig,
|
||||||
_dev: &cpal::Device,
|
_dev: &cpal::Device,
|
||||||
conf_iterator: T,
|
conf_iterator: T
|
||||||
) -> Result<cpal::SupportedStreamConfig>
|
) -> Result<cpal::SupportedStreamConfig>
|
||||||
where
|
where T: Iterator<Item = cpal::SupportedStreamConfigRange>
|
||||||
T: Iterator<Item = cpal::SupportedStreamConfigRange>,
|
|
||||||
{
|
{
|
||||||
let nchannels = match st {
|
let nchannels = match st {
|
||||||
StreamType::Input => devinfo.iChannelCount,
|
StreamType::Input => devinfo.iChannelCount,
|
||||||
@ -393,10 +446,11 @@ impl CpalApi {
|
|||||||
for cpalconf in conf_iterator {
|
for cpalconf in conf_iterator {
|
||||||
if cpalconf.sample_format() == conf.dtype.into() {
|
if cpalconf.sample_format() == conf.dtype.into() {
|
||||||
// Specified sample format is available
|
// Specified sample format is available
|
||||||
if cpalconf.channels() == nchannels as u16 {
|
if cpalconf.channels() == (nchannels as u16) {
|
||||||
let requested_sr = conf.sampleRate(devinfo);
|
let requested_sr = conf.sampleRate(devinfo);
|
||||||
if cpalconf.min_sample_rate().0 as Flt <= requested_sr
|
if
|
||||||
&& cpalconf.max_sample_rate().0 as Flt >= requested_sr
|
(cpalconf.min_sample_rate().0 as Flt) <= requested_sr &&
|
||||||
|
(cpalconf.max_sample_rate().0 as Flt) >= requested_sr
|
||||||
{
|
{
|
||||||
// Sample rate falls within range.
|
// Sample rate falls within range.
|
||||||
let requested_fpb = conf.framesPerBlock(devinfo) as u32;
|
let requested_fpb = conf.framesPerBlock(devinfo) as u32;
|
||||||
@ -409,7 +463,7 @@ impl CpalApi {
|
|||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
requested_fpb
|
requested_fpb
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -428,27 +482,29 @@ impl CpalApi {
|
|||||||
stype: StreamType,
|
stype: StreamType,
|
||||||
devinfo: &DeviceInfo,
|
devinfo: &DeviceInfo,
|
||||||
conf: &DaqConfig,
|
conf: &DaqConfig,
|
||||||
sender: Sender<RawStreamData>,
|
sender: Sender<RawStreamData>
|
||||||
) -> Result<Box<dyn Stream>> {
|
) -> Result<Box<dyn Stream>> {
|
||||||
for cpaldev in self.host.devices()? {
|
for cpaldev in self.host.devices()? {
|
||||||
// See if we can create a supported stream config.
|
// See if we can create a supported stream config.
|
||||||
let supported_config = match stype {
|
let supported_config = (match stype {
|
||||||
StreamType::Duplex => bail!("Duplex stream not supported for CPAL"),
|
StreamType::Duplex => bail!("Duplex stream not supported for CPAL"),
|
||||||
StreamType::Input => CpalApi::create_cpal_config(
|
StreamType::Input =>
|
||||||
stype,
|
CpalApi::create_cpal_config(
|
||||||
devinfo,
|
stype,
|
||||||
conf,
|
devinfo,
|
||||||
&cpaldev,
|
conf,
|
||||||
cpaldev.supported_input_configs()?,
|
&cpaldev,
|
||||||
),
|
cpaldev.supported_input_configs()?
|
||||||
StreamType::Output => CpalApi::create_cpal_config(
|
),
|
||||||
stype,
|
StreamType::Output =>
|
||||||
devinfo,
|
CpalApi::create_cpal_config(
|
||||||
conf,
|
stype,
|
||||||
&cpaldev,
|
devinfo,
|
||||||
cpaldev.supported_output_configs()?,
|
conf,
|
||||||
),
|
&cpaldev,
|
||||||
}?;
|
cpaldev.supported_output_configs()?
|
||||||
|
),
|
||||||
|
})?;
|
||||||
let framesPerBlock = conf.framesPerBlock(devinfo);
|
let framesPerBlock = conf.framesPerBlock(devinfo);
|
||||||
|
|
||||||
let sf = supported_config.sample_format();
|
let sf = supported_config.sample_format();
|
||||||
@ -458,7 +514,7 @@ impl CpalApi {
|
|||||||
&conf.enabledInchannelConfig(),
|
&conf.enabledInchannelConfig(),
|
||||||
conf.dtype,
|
conf.dtype,
|
||||||
supported_config.sample_rate().0 as Flt,
|
supported_config.sample_rate().0 as Flt,
|
||||||
framesPerBlock,
|
framesPerBlock
|
||||||
)?;
|
)?;
|
||||||
let meta = Arc::new(meta);
|
let meta = Arc::new(meta);
|
||||||
|
|
||||||
@ -468,23 +524,27 @@ impl CpalApi {
|
|||||||
&cpaldev,
|
&cpaldev,
|
||||||
sender,
|
sender,
|
||||||
conf.enabledInchannelsList(),
|
conf.enabledInchannelsList(),
|
||||||
framesPerBlock,
|
framesPerBlock
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
stream.play()?;
|
stream.play()?;
|
||||||
status.store(StreamStatus::Running{});
|
status.store(StreamStatus::Running {});
|
||||||
|
|
||||||
return Ok(Box::new(CpalStream {
|
return Ok(
|
||||||
stream,
|
Box::new(CpalStream {
|
||||||
md: meta,
|
stream,
|
||||||
noutchannels: 0,
|
md: meta,
|
||||||
status,
|
noutchannels: 0,
|
||||||
}));
|
status,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
bail!(format!(
|
bail!(
|
||||||
"Error: requested device {} not found. Please make sure the device is available.",
|
format!(
|
||||||
devinfo.device_name
|
"Error: requested device {} not found. Please make sure the device is available.",
|
||||||
))
|
devinfo.device_name
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start a default input stream.
|
/// Start a default input stream.
|
||||||
@ -492,7 +552,7 @@ impl CpalApi {
|
|||||||
///
|
///
|
||||||
pub fn startDefaultInputStream(
|
pub fn startDefaultInputStream(
|
||||||
&mut self,
|
&mut self,
|
||||||
sender: Sender<RawStreamData>,
|
sender: Sender<RawStreamData>
|
||||||
) -> Result<Box<dyn Stream>> {
|
) -> Result<Box<dyn Stream>> {
|
||||||
if let Some(device) = self.host.default_input_device() {
|
if let Some(device) = self.host.default_input_device() {
|
||||||
if let Ok(config) = device.default_input_config() {
|
if let Ok(config) = device.default_input_config() {
|
||||||
@ -511,15 +571,16 @@ impl CpalApi {
|
|||||||
&device,
|
&device,
|
||||||
sender,
|
sender,
|
||||||
en_inchannels,
|
en_inchannels,
|
||||||
framesPerBlock,
|
framesPerBlock
|
||||||
)?;
|
)?;
|
||||||
stream.play()?;
|
stream.play()?;
|
||||||
status.store(StreamStatus::Running{});
|
status.store(StreamStatus::Running {});
|
||||||
|
|
||||||
// Daq: default channel config
|
// Daq: default channel config
|
||||||
let daqchannels = Vec::from_iter(
|
let daqchannels = Vec::from_iter(
|
||||||
(0..final_config.channels)
|
(0..final_config.channels).map(|i|
|
||||||
.map(|i| DaqChannel::defaultAudio(format!("Unnamed input channel {}", i))),
|
DaqChannel::defaultAudio(format!("Unnamed input channel {}", i))
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Specify data tape
|
// Specify data tape
|
||||||
@ -530,15 +591,17 @@ impl CpalApi {
|
|||||||
&daqchannels,
|
&daqchannels,
|
||||||
dtype,
|
dtype,
|
||||||
config.sample_rate().0 as Flt,
|
config.sample_rate().0 as Flt,
|
||||||
framesPerBlock,
|
framesPerBlock
|
||||||
)?;
|
)?;
|
||||||
let md = Arc::new(md);
|
let md = Arc::new(md);
|
||||||
Ok(Box::new(CpalStream {
|
Ok(
|
||||||
stream,
|
Box::new(CpalStream {
|
||||||
md,
|
stream,
|
||||||
noutchannels: 0,
|
md,
|
||||||
status,
|
noutchannels: 0,
|
||||||
}))
|
status,
|
||||||
|
})
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
bail!("Could not obtain default input configuration")
|
bail!("Could not obtain default input configuration")
|
||||||
}
|
}
|
||||||
@ -547,70 +610,147 @@ impl CpalApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn getDefaultOutputConfig(&self) -> Result<(Device, cpal::StreamConfig, SampleFormat, usize)> {
|
||||||
|
if let Some(dev) = self.host.default_output_device() {
|
||||||
|
let cfg = dev.default_output_config()?;
|
||||||
|
// let framesPerBlock: usize = 256;
|
||||||
|
// let framesPerBlock: usize = 8192;
|
||||||
|
let framesPerBlock: usize = cfg.sample_rate().0 as usize;
|
||||||
|
// let framesPerBlock: usize = 256;
|
||||||
|
let final_config = cpal::StreamConfig {
|
||||||
|
channels: cfg.channels(),
|
||||||
|
sample_rate: cfg.sample_rate(),
|
||||||
|
buffer_size: cpal::BufferSize::Fixed(framesPerBlock as u32),
|
||||||
|
};
|
||||||
|
return Ok((dev, final_config, cfg.sample_format(), framesPerBlock));
|
||||||
|
}
|
||||||
|
bail!("Could not find default output device!");
|
||||||
|
}
|
||||||
|
|
||||||
pub fn startDefaultOutputStream(
|
pub fn startDefaultOutputStream(
|
||||||
&self,
|
&self,
|
||||||
receiver: Receiver<RawStreamData>,
|
receiver: Receiver<RawStreamData>
|
||||||
) -> Result<Box<dyn Stream>> {
|
) -> Result<Box<dyn Stream>> {
|
||||||
if let Some(device) = self.host.default_output_device() {
|
let (device, config, sampleformat, framesPerBlock) = self.getDefaultOutputConfig()?;
|
||||||
if let Ok(config) = device.default_output_config() {
|
|
||||||
// let framesPerBlock: usize = 256;
|
|
||||||
// let framesPerBlock: usize = 8192;
|
|
||||||
let framesPerBlock: usize = config.sample_rate().0 as usize;
|
|
||||||
// let framesPerBlock: usize = 256;
|
|
||||||
let final_config = cpal::StreamConfig {
|
|
||||||
channels: config.channels(),
|
|
||||||
sample_rate: config.sample_rate(),
|
|
||||||
buffer_size: cpal::BufferSize::Fixed(framesPerBlock as u32),
|
|
||||||
};
|
|
||||||
// let en_outchannels = Vec::from_iter((0..config.channels()).map(|i| i as usize));
|
|
||||||
|
|
||||||
let sampleformat = config.sample_format();
|
// Daq: default channel config
|
||||||
let (stream, status) = CpalApi::build_output_stream(
|
let daqchannels = Vec::from_iter(
|
||||||
sampleformat,
|
(0..config.channels).map(|i|
|
||||||
&final_config,
|
DaqChannel::defaultAudio(format!("Unnamed output channel {}", i))
|
||||||
&device,
|
)
|
||||||
receiver,
|
);
|
||||||
framesPerBlock,
|
let (stream, status) = CpalApi::build_output_stream(
|
||||||
)?;
|
sampleformat,
|
||||||
|
&config,
|
||||||
|
&device,
|
||||||
|
receiver,
|
||||||
|
&daqchannels,
|
||||||
|
framesPerBlock
|
||||||
|
)?;
|
||||||
|
|
||||||
stream.play()?;
|
stream.play()?;
|
||||||
status.store(StreamStatus::Running{});
|
status.store(StreamStatus::Running {});
|
||||||
|
|
||||||
// Daq: default channel config
|
// // Specify data tape
|
||||||
let daqchannels =
|
let dtype = DataType::from(sampleformat);
|
||||||
Vec::from_iter((0..final_config.channels).map(|i| {
|
|
||||||
DaqChannel::defaultAudio(format!("Unnamed output channel {}", i))
|
|
||||||
}));
|
|
||||||
|
|
||||||
// // Specify data tape
|
// // Create stream metadata
|
||||||
let dtype = DataType::from(sampleformat);
|
let md = StreamMetaData::new(
|
||||||
|
&daqchannels,
|
||||||
// // Create stream metadata
|
dtype,
|
||||||
let md = StreamMetaData::new(
|
config.sample_rate.0 as Flt,
|
||||||
&daqchannels,
|
framesPerBlock
|
||||||
dtype,
|
)?;
|
||||||
config.sample_rate().0 as Flt,
|
let md = Arc::new(md);
|
||||||
framesPerBlock,
|
let str = Box::new(CpalStream {
|
||||||
)?;
|
stream,
|
||||||
let md = Arc::new(md);
|
md,
|
||||||
let str = Box::new(CpalStream {
|
noutchannels: daqchannels.len(),
|
||||||
stream,
|
status,
|
||||||
md,
|
});
|
||||||
noutchannels: daqchannels.len(),
|
Ok(str)
|
||||||
status,
|
|
||||||
});
|
|
||||||
Ok(str)
|
|
||||||
} else {
|
|
||||||
bail!("Could not obtain default output configuration")
|
|
||||||
} // Default output config is OK
|
|
||||||
} else {
|
|
||||||
bail!("Could not open output device")
|
|
||||||
} // Could not
|
|
||||||
}
|
}
|
||||||
// Create an output stream, using given signal generators for each channel.
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn startOutputStream(&self, _rx: Receiver<RawStreamData>) -> Result<Box<dyn Stream>> {
|
fn getCPALOutputConfig(
|
||||||
bail!("Not implemented");
|
&self,
|
||||||
|
dev: &DeviceInfo,
|
||||||
|
daqconfig: &DaqConfig
|
||||||
|
) -> Result<(Device, cpal::StreamConfig, SampleFormat, usize)> {
|
||||||
|
let samplerate = dev.avSampleRates[daqconfig.sampleRateIndex] as u32;
|
||||||
|
let framesPerBlock = dev.avFramesPerBlock[daqconfig.framesPerBlockIndex];
|
||||||
|
|
||||||
|
let highest_ch: Result<usize, anyhow::Error> = daqconfig
|
||||||
|
.highestEnabledOutChannel()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("No output channels enabled."));
|
||||||
|
let highest_ch = highest_ch? as u16;
|
||||||
|
|
||||||
|
for cpaldev in self.host.devices()? {
|
||||||
|
if cpaldev.name()? == dev.device_name {
|
||||||
|
// Check, device name matches required device name
|
||||||
|
for cpalcfg in cpaldev.supported_output_configs()? {
|
||||||
|
let sf = cpalcfg.sample_format();
|
||||||
|
if sf == daqconfig.dtype.into() {
|
||||||
|
let max_sr = cpalcfg.max_sample_rate().0;
|
||||||
|
let min_sr = cpalcfg.min_sample_rate().0;
|
||||||
|
if samplerate <= max_sr && samplerate >= min_sr {
|
||||||
|
let cfg = cpalcfg.with_sample_rate(SampleRate(samplerate as u32));
|
||||||
|
|
||||||
|
let mut cfg = cfg.config();
|
||||||
|
cfg.channels = highest_ch + 1;
|
||||||
|
|
||||||
|
// Overwrite buffer size to required buffer size
|
||||||
|
cfg.buffer_size = cpal::BufferSize::Fixed(framesPerBlock as u32);
|
||||||
|
|
||||||
|
// Return tuple of device, config, sample format and
|
||||||
|
// frames per block
|
||||||
|
return Ok((cpaldev, cfg, sf, framesPerBlock));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bail!("Could not find device with name '{}'", dev.device_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn startOutputStream(
|
||||||
|
&self,
|
||||||
|
dev: &DeviceInfo,
|
||||||
|
cfg: &DaqConfig,
|
||||||
|
receiver: Receiver<RawStreamData>
|
||||||
|
) -> Result<Box<dyn Stream>> {
|
||||||
|
let (device, cpalconfig, sampleformat, framesPerBlock) = self.getCPALOutputConfig(
|
||||||
|
dev,
|
||||||
|
cfg
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let (stream, status) = Self::build_output_stream(
|
||||||
|
sampleformat,
|
||||||
|
&cpalconfig,
|
||||||
|
&device,
|
||||||
|
receiver,
|
||||||
|
&cfg.outchannel_config,
|
||||||
|
framesPerBlock
|
||||||
|
)?;
|
||||||
|
|
||||||
|
stream.play()?;
|
||||||
|
status.store(StreamStatus::Running {});
|
||||||
|
|
||||||
|
// // Specify data tape
|
||||||
|
let dtype = DataType::from(sampleformat);
|
||||||
|
|
||||||
|
let md = StreamMetaData::new(
|
||||||
|
&cfg.enabledOutchannelConfig(),
|
||||||
|
dtype,
|
||||||
|
cpalconfig.sample_rate.0 as Flt,
|
||||||
|
framesPerBlock
|
||||||
|
)?;
|
||||||
|
let md = Arc::new(md);
|
||||||
|
let str = Box::new(CpalStream {
|
||||||
|
stream,
|
||||||
|
md,
|
||||||
|
noutchannels: cpalconfig.channels as usize,
|
||||||
|
status,
|
||||||
|
});
|
||||||
|
Ok(str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ pub trait Stream {
|
|||||||
|
|
||||||
/// Stream API descriptor: type and corresponding text
|
/// Stream API descriptor: type and corresponding text
|
||||||
#[cfg_attr(feature = "python-bindings", pyclass)]
|
#[cfg_attr(feature = "python-bindings", pyclass)]
|
||||||
#[derive(strum_macros::EnumMessage, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[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
|
||||||
|
@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
/// DAQ Configuration for a single channel
|
/// DAQ Configuration for a single channel
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "python-bindings", pyclass(get_all, set_all))]
|
||||||
pub struct DaqChannel {
|
pub struct DaqChannel {
|
||||||
/// Whether the channel is enabled
|
/// Whether the channel is enabled
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
@ -53,6 +54,7 @@ impl DaqChannel {
|
|||||||
|
|
||||||
/// Configuration of a device.
|
/// Configuration of a device.
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "python-bindings", pyclass(get_all, set_all))]
|
||||||
pub struct DaqConfig {
|
pub struct DaqConfig {
|
||||||
/// The API
|
/// The API
|
||||||
pub api: StreamApiDescr,
|
pub api: StreamApiDescr,
|
||||||
@ -65,6 +67,7 @@ pub struct DaqConfig {
|
|||||||
|
|
||||||
/// 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,
|
||||||
|
|
||||||
@ -72,15 +75,28 @@ pub struct DaqConfig {
|
|||||||
pub digitalHighPassCutOn: Flt,
|
pub digitalHighPassCutOn: Flt,
|
||||||
|
|
||||||
/// The index to use in the list of possible sample rates
|
/// The index to use in the list of possible sample rates
|
||||||
sampleRateIndex: usize,
|
pub sampleRateIndex: usize,
|
||||||
|
|
||||||
/// The index to use in the list of possible frames per block
|
/// The index to use in the list of possible frames per block
|
||||||
framesPerBlockIndex: usize,
|
pub framesPerBlockIndex: usize,
|
||||||
|
|
||||||
/// Used when output channels should be monitored, i.e. reverse-looped back as input channels.
|
/// Used when output channels should be monitored, i.e. reverse-looped back as input channels.
|
||||||
monitorOutput: bool,
|
pub monitorOutput: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python-bindings")]
|
||||||
|
#[cfg_attr(feature = "python-bindings", pymethods)]
|
||||||
|
impl DaqConfig {
|
||||||
|
#[pyo3(name = "newFromDeviceInfo")]
|
||||||
|
#[staticmethod]
|
||||||
|
fn newFromDeviceInfo_py(d: &DeviceInfo) -> PyResult<DaqConfig> {
|
||||||
|
Ok(DaqConfig::newFromDeviceInfo(d))
|
||||||
|
}
|
||||||
|
fn __repr__(&self) -> String {
|
||||||
|
format!("{:#?}", self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
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
|
||||||
/// the DeviceInfo descriptor.
|
/// the DeviceInfo descriptor.
|
||||||
@ -216,4 +232,24 @@ impl DaqConfig {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.collect()
|
.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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,8 @@ pub fn add_py_classses(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|||||||
m.add_class::<StreamError>()?;
|
m.add_class::<StreamError>()?;
|
||||||
m.add_class::<StreamStatus>()?;
|
m.add_class::<StreamStatus>()?;
|
||||||
m.add_class::<StreamError>()?;
|
m.add_class::<StreamError>()?;
|
||||||
|
m.add_class::<DaqChannel>()?;
|
||||||
|
m.add_class::<DaqConfig>()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -275,7 +275,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::newSineWave(Nch, 1.);
|
let mut siggen = Siggen::newSine(Nch, 1.);
|
||||||
|
|
||||||
siggen.reset(fs);
|
siggen.reset(fs);
|
||||||
siggen.setMute(&[false, true]);
|
siggen.setMute(&[false, true]);
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
//! Data acquisition model. Provides abstract layers around DAQ devices.
|
//! Data acquisition model. Provides abstract layers around DAQ devices.
|
||||||
use super::*;
|
|
||||||
use super::config::*;
|
use super::config::*;
|
||||||
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
config::*,
|
config::*,
|
||||||
siggen::{self, Siggen},
|
siggen::{self, Siggen},
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Error, Result};
|
use anyhow::{bail, Error, Result};
|
||||||
|
use api::StreamApiDescr;
|
||||||
use array_init::from_iter;
|
use array_init::from_iter;
|
||||||
use core::time;
|
use core::time;
|
||||||
use cpal::Sample;
|
use cpal::Sample;
|
||||||
@ -15,11 +16,9 @@ use crossbeam::{
|
|||||||
};
|
};
|
||||||
use std::sync::{atomic::AtomicBool, Arc, Mutex};
|
use std::sync::{atomic::AtomicBool, Arc, Mutex};
|
||||||
use std::thread::{JoinHandle, Thread};
|
use std::thread::{JoinHandle, Thread};
|
||||||
use streamdata::*;
|
|
||||||
use streamcmd::StreamCommand;
|
use streamcmd::StreamCommand;
|
||||||
|
use streamdata::*;
|
||||||
use streammsg::*;
|
use streammsg::*;
|
||||||
use api::StreamApiDescr;
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature = "cpal-api")]
|
#[cfg(feature = "cpal-api")]
|
||||||
use super::api::{api_cpal::CpalApi, Stream};
|
use super::api::{api_cpal::CpalApi, Stream};
|
||||||
@ -68,7 +67,7 @@ pub struct StreamMgr {
|
|||||||
siggen: Option<crate::siggen::Siggen>,
|
siggen: Option<crate::siggen::Siggen>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "python-bindings")]
|
#[cfg(feature="python-bindings")]
|
||||||
#[cfg_attr(feature = "python-bindings", pymethods)]
|
#[cfg_attr(feature = "python-bindings", pymethods)]
|
||||||
impl StreamMgr {
|
impl StreamMgr {
|
||||||
#[new]
|
#[new]
|
||||||
@ -85,6 +84,14 @@ impl StreamMgr {
|
|||||||
fn startDefaultOutputStream_py(&mut self) -> PyResult<()> {
|
fn startDefaultOutputStream_py(&mut self) -> PyResult<()> {
|
||||||
Ok(self.startDefaultOutputStream()?)
|
Ok(self.startDefaultOutputStream()?)
|
||||||
}
|
}
|
||||||
|
#[pyo3(name = "startStream")]
|
||||||
|
fn startStream_py(&mut self, st: StreamType, d: &DaqConfig) -> PyResult<()> {
|
||||||
|
Ok(self.startStream(st, d)?)
|
||||||
|
}
|
||||||
|
#[pyo3(name = "stopStream")]
|
||||||
|
fn stopStream_py(&mut self, st: StreamType) -> PyResult<()> {
|
||||||
|
Ok(self.stopStream(st)?)
|
||||||
|
}
|
||||||
#[pyo3(name = "getDeviceInfo")]
|
#[pyo3(name = "getDeviceInfo")]
|
||||||
fn getDeviceInfo_py(&mut self) -> PyResult<Vec<DeviceInfo>> {
|
fn getDeviceInfo_py(&mut self) -> PyResult<Vec<DeviceInfo>> {
|
||||||
Ok(self.getDeviceInfo())
|
Ok(self.getDeviceInfo())
|
||||||
@ -93,7 +100,10 @@ impl StreamMgr {
|
|||||||
fn getStatus_py(&self, st: StreamType) -> StreamStatus {
|
fn getStatus_py(&self, st: StreamType) -> StreamStatus {
|
||||||
self.getStatus(st)
|
self.getStatus(st)
|
||||||
}
|
}
|
||||||
|
#[pyo3(name = "setSiggen")]
|
||||||
|
fn setSiggen_py(&mut self, siggen: Siggen) {
|
||||||
|
self.setSiggen(siggen)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl Default for StreamMgr {
|
impl Default for StreamMgr {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@ -135,14 +145,14 @@ impl StreamMgr {
|
|||||||
if let Some(s) = &self.input_stream {
|
if let Some(s) = &self.input_stream {
|
||||||
s.stream.status()
|
s.stream.status()
|
||||||
} else {
|
} else {
|
||||||
StreamStatus::NotRunning{}
|
StreamStatus::NotRunning {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StreamType::Output => {
|
StreamType::Output => {
|
||||||
if let Some(s) = &self.output_stream {
|
if let Some(s) = &self.output_stream {
|
||||||
s.stream.status()
|
s.stream.status()
|
||||||
} else {
|
} else {
|
||||||
StreamStatus::NotRunning{}
|
StreamStatus::NotRunning {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,29 +160,19 @@ 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) -> Result<()> {
|
pub fn setSiggen(&mut self, siggen: Siggen) {
|
||||||
// Current signal generator. Where to place it?
|
// Current signal generator. Where to place it?
|
||||||
if let Some(istream) = &self.input_stream {
|
if let Some(istream) = &self.input_stream {
|
||||||
if let StreamType::Duplex = istream.streamtype {
|
if let StreamType::Duplex = istream.streamtype {
|
||||||
if siggen.nchannels() != istream.stream.noutchannels() {
|
|
||||||
bail!("Invalid number of channels configured in signal generator")
|
|
||||||
}
|
|
||||||
assert!(self.siggen.is_none());
|
assert!(self.siggen.is_none());
|
||||||
istream.comm.send(StreamCommand::NewSiggen(siggen)).unwrap();
|
istream.comm.send(StreamCommand::NewSiggen(siggen)).unwrap();
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
} else if let Some(os) = &self.output_stream {
|
} else if let Some(os) = &self.output_stream {
|
||||||
assert!(self.siggen.is_none());
|
assert!(self.siggen.is_none());
|
||||||
if siggen.nchannels() != os.stream.noutchannels() {
|
|
||||||
bail!("Invalid number of channels configured in signal generator")
|
|
||||||
}
|
|
||||||
os.comm.send(StreamCommand::NewSiggen(siggen)).unwrap();
|
os.comm.send(StreamCommand::NewSiggen(siggen)).unwrap();
|
||||||
return Ok(());
|
|
||||||
} else {
|
} else {
|
||||||
self.siggen = Some(siggen);
|
self.siggen = Some(siggen);
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
unreachable!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain a list of devices that are available for each available API
|
/// Obtain a list of devices that are available for each available API
|
||||||
@ -194,7 +194,7 @@ impl StreamMgr {
|
|||||||
|
|
||||||
/// Add a new queue to the lists of queues. On the queue, input data is
|
/// Add a new queue to the lists of queues. On the queue, input data is
|
||||||
/// added.
|
/// added.
|
||||||
///
|
///
|
||||||
/// If the stream is unable to write data on the queue (which might
|
/// If the stream is unable to write data on the queue (which might
|
||||||
/// happen when the handler is dropped), the queue is removed from the list
|
/// happen when the handler is dropped), the queue is removed from the list
|
||||||
/// of queues that get data from the stream.
|
/// of queues that get data from the stream.
|
||||||
@ -234,7 +234,10 @@ impl StreamMgr {
|
|||||||
|
|
||||||
// Stop this thread. Returns the queue
|
// Stop this thread. Returns the queue
|
||||||
StreamCommand::StopThread => {
|
StreamCommand::StopThread => {
|
||||||
sendMsgToAllQueuesRemoveUnused(&mut iqueues, InStreamMsg::StreamStopped);
|
sendMsgToAllQueuesRemoveUnused(
|
||||||
|
&mut iqueues,
|
||||||
|
InStreamMsg::StreamStopped,
|
||||||
|
);
|
||||||
break 'infy;
|
break 'infy;
|
||||||
}
|
}
|
||||||
StreamCommand::NewSiggen(_) => {
|
StreamCommand::NewSiggen(_) => {
|
||||||
@ -259,8 +262,15 @@ impl StreamMgr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Match device info struct on given daq config.
|
// Match device info struct on given daq config.
|
||||||
fn match_devinfo(&self, cfg: &DaqConfig) -> Option<&DeviceInfo> {
|
fn find_device(&self, cfg: &DaqConfig) -> Result<&DeviceInfo> {
|
||||||
self.devs.iter().find(|&d| d.device_name == cfg.device_name)
|
if let Some(matching_dev) = self
|
||||||
|
.devs
|
||||||
|
.iter()
|
||||||
|
.find(|&d| d.device_name == cfg.device_name && d.api == cfg.api)
|
||||||
|
{
|
||||||
|
return Ok(matching_dev);
|
||||||
|
}
|
||||||
|
bail!("Could not find device with name {}.", cfg.device_name);
|
||||||
}
|
}
|
||||||
fn startOuputStreamThread(
|
fn startOuputStreamThread(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -359,27 +369,41 @@ impl StreamMgr {
|
|||||||
self.startInputOrDuplexStream(stype, cfg)?;
|
self.startInputOrDuplexStream(stype, cfg)?;
|
||||||
}
|
}
|
||||||
StreamType::Output => {
|
StreamType::Output => {
|
||||||
// self.startOutputStream(cfg)?;
|
self.startOutputStream(cfg)?;
|
||||||
bail!("No output stream defined yet");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn startOutputStream(&mut self, cfg: &DaqConfig) -> Result<()> {
|
/// Start a stream for output only, using only the output channel
|
||||||
// let (tx, rx): (Sender<RawStreamData>, Receiver<RawStreamData>) = unbounded();
|
/// configuration as given in the `cfg`.
|
||||||
// let stream = match cfg.api {
|
fn startOutputStream(&mut self, cfg: &DaqConfig) -> Result<()> {
|
||||||
// StreamApiDescr::Cpal => {
|
let (tx, rx): (Sender<RawStreamData>, Receiver<RawStreamData>) = unbounded();
|
||||||
// let devinfo = self
|
let stream = match cfg.api {
|
||||||
// .match_devinfo(cfg)
|
StreamApiDescr::Cpal => {
|
||||||
// .ok_or(anyhow::anyhow!("Unable to find device {}", cfg.device_name))?;
|
let devinfo = self.find_device(cfg)?;
|
||||||
// self.cpal_api.startOutputStream(devinfo, cfg, tx)?
|
cfg_if::cfg_if! {
|
||||||
// }
|
if #[cfg(feature="cpal-api")] {
|
||||||
// _ => bail!("Unimplemented api!"),
|
self.cpal_api.startOutputStream(devinfo, cfg, rx)?
|
||||||
// };
|
} else {
|
||||||
|
bail!("API {} not available", cfg.api)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => bail!("API {} not implemented!", cfg.api),
|
||||||
|
};
|
||||||
|
let meta = stream.metadata();
|
||||||
|
let (threadhandle, commtx) = self.startOuputStreamThread(meta, tx);
|
||||||
|
|
||||||
// Ok(())
|
self.output_stream = Some(StreamInfo {
|
||||||
// }
|
streamtype: StreamType::Input,
|
||||||
|
stream,
|
||||||
|
threadhandle,
|
||||||
|
comm: commtx,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// Start an input or duplex stream
|
// Start an input or duplex stream
|
||||||
fn startInputOrDuplexStream(&mut self, stype: StreamType, cfg: &DaqConfig) -> Result<()> {
|
fn startInputOrDuplexStream(&mut self, stype: StreamType, cfg: &DaqConfig) -> Result<()> {
|
||||||
@ -404,12 +428,16 @@ impl StreamMgr {
|
|||||||
if stype == StreamType::Duplex {
|
if stype == StreamType::Duplex {
|
||||||
bail!("Duplex mode not supported for CPAL api");
|
bail!("Duplex mode not supported for CPAL api");
|
||||||
}
|
}
|
||||||
let devinfo = self
|
let devinfo = self.find_device(cfg)?;
|
||||||
.match_devinfo(cfg)
|
cfg_if::cfg_if! {
|
||||||
.ok_or(anyhow::anyhow!("Unable to find device {}", cfg.device_name))?;
|
if #[cfg(feature="cpal-api")] {
|
||||||
self.cpal_api.startInputStream(stype, devinfo, cfg, tx)?
|
self.cpal_api.startInputStream(stype, devinfo, cfg, tx)?
|
||||||
|
} else {
|
||||||
|
bail!("API {} not available", cfg.api)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => bail!("Unimplemented api!"),
|
_ => bail!("API {} not implemented!", cfg.api),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Input queues should be available, otherwise panic bug.
|
// Input queues should be available, otherwise panic bug.
|
||||||
|
@ -21,6 +21,7 @@ pub struct Biquad {
|
|||||||
a1: Flt,
|
a1: Flt,
|
||||||
a2: Flt,
|
a2: Flt,
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "python-bindings")]
|
||||||
#[cfg_attr(feature = "python-bindings", pymethods)]
|
#[cfg_attr(feature = "python-bindings", pymethods)]
|
||||||
impl Biquad {
|
impl Biquad {
|
||||||
#[new]
|
#[new]
|
||||||
|
@ -34,6 +34,7 @@ fn lasprs(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|||||||
m.add_class::<filter::Biquad>()?;
|
m.add_class::<filter::Biquad>()?;
|
||||||
m.add_class::<filter::SeriesBiquad>()?;
|
m.add_class::<filter::SeriesBiquad>()?;
|
||||||
m.add_class::<filter::BiquadBank>()?;
|
m.add_class::<filter::BiquadBank>()?;
|
||||||
|
m.add_class::<siggen::Siggen>()?;
|
||||||
|
|
||||||
daq::add_py_classses(m)?;
|
daq::add_py_classses(m)?;
|
||||||
|
|
||||||
|
146
src/siggen.rs
146
src/siggen.rs
@ -17,7 +17,7 @@
|
|||||||
//! ```
|
//! ```
|
||||||
use super::config::*;
|
use super::config::*;
|
||||||
use super::filter::Filter;
|
use super::filter::Filter;
|
||||||
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;
|
||||||
@ -27,7 +27,7 @@ use rand::prelude::*;
|
|||||||
use rand::rngs::ThreadRng;
|
use rand::rngs::ThreadRng;
|
||||||
use rand_distr::StandardNormal;
|
use rand_distr::StandardNormal;
|
||||||
|
|
||||||
const twopi: Flt = 2. * pi;
|
const twopi: Flt = 2.0 * pi;
|
||||||
|
|
||||||
/// Source for the signal generator. Implementations are sine waves, sweeps, noise.
|
/// Source for the signal generator. Implementations are sine waves, sweeps, noise.
|
||||||
pub trait Source: Send {
|
pub trait Source: Send {
|
||||||
@ -49,7 +49,9 @@ struct Silence {}
|
|||||||
|
|
||||||
impl Source for Silence {
|
impl Source 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| *s = 0.0);
|
sig.for_each(|s| {
|
||||||
|
*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 Source> {
|
||||||
@ -68,7 +70,9 @@ impl WhiteNoise {
|
|||||||
}
|
}
|
||||||
impl Source for 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| *s = thread_rng().sample(StandardNormal));
|
sig.for_each(|s| {
|
||||||
|
*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 Source> {
|
||||||
@ -95,17 +99,17 @@ impl Sine {
|
|||||||
/// *
|
/// *
|
||||||
fn new(freq: Flt) -> Sine {
|
fn new(freq: Flt) -> Sine {
|
||||||
Sine {
|
Sine {
|
||||||
fs: -1.,
|
fs: -1.0,
|
||||||
phase: 0.,
|
phase: 0.0,
|
||||||
omg: 2. * pi * freq,
|
omg: 2.0 * pi * freq,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Source for Sine {
|
impl Source 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. {
|
if self.fs <= 0.0 {
|
||||||
sig.for_each(|s| {
|
sig.for_each(|s| {
|
||||||
*s = 0.;
|
*s = 0.0;
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -117,7 +121,7 @@ impl Source for Sine {
|
|||||||
}
|
}
|
||||||
fn reset(&mut self, fs: Flt) {
|
fn reset(&mut self, fs: Flt) {
|
||||||
self.fs = fs;
|
self.fs = fs;
|
||||||
self.phase = 0.;
|
self.phase = 0.0;
|
||||||
}
|
}
|
||||||
fn clone_dyn(&self) -> Box<dyn Source> {
|
fn clone_dyn(&self) -> Box<dyn Source> {
|
||||||
Box::new(self.clone())
|
Box::new(self.clone())
|
||||||
@ -131,6 +135,7 @@ impl Source for Sine {
|
|||||||
/// * (Siggen::newSine)
|
/// * (Siggen::newSine)
|
||||||
///
|
///
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
#[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: Box<dyn Source>,
|
||||||
@ -143,10 +148,22 @@ pub struct Siggen {
|
|||||||
// Output buffers (for filtered source signal)
|
// Output buffers (for filtered source signal)
|
||||||
chout_buf: Vec<Vec<Flt>>,
|
chout_buf: Vec<Vec<Flt>>,
|
||||||
}
|
}
|
||||||
|
#[cfg_attr(feature = "python-bindings", pymethods)]
|
||||||
|
impl Siggen {
|
||||||
|
#[pyo3(name = "newWhiteNoise")]
|
||||||
|
#[staticmethod]
|
||||||
|
fn newWhiteNoise_py() -> Siggen {
|
||||||
|
Siggen::newWhiteNoise(0)
|
||||||
|
}
|
||||||
|
#[pyo3(name = "newSine")]
|
||||||
|
#[staticmethod]
|
||||||
|
fn newSine_py(freq: Flt) -> Siggen {
|
||||||
|
Siggen::newSine(0, freq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Multiple channel signal generator. Can use a single source (coherent) to provide multiple signals
|
/// Multiple channel signal generator. Can use a single source (coherent) to provide multiple signals
|
||||||
/// that can be sent out through different EQ's
|
/// that can be sent out through different EQ's
|
||||||
|
|
||||||
/// A struct that implements the Siggen trait is able to generate a signal.
|
|
||||||
impl Siggen {
|
impl Siggen {
|
||||||
/// 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 {
|
||||||
@ -192,17 +209,19 @@ impl Siggen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set the DC offset for all channels
|
/// Set the DC offset for all channels
|
||||||
pub fn setDCOffset(&mut self,dc: &[Flt]) {
|
pub fn setDCOffset(&mut self, dc: &[Flt]) {
|
||||||
self.channels.iter_mut().zip(dc).for_each(
|
self.channels
|
||||||
|(ch, dc)| {ch.DCOffset = *dc;});
|
.iter_mut()
|
||||||
|
.zip(dc)
|
||||||
|
.for_each(|(ch, dc)| {
|
||||||
|
ch.DCOffset = *dc;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a sine wave signal generator
|
/// Create a sine wave signal generator
|
||||||
///
|
///
|
||||||
/// * freq: Frequency of the sine wave in \[Hz\]
|
/// * freq: Frequency of the sine wave in \[Hz\]
|
||||||
pub fn newSineWave(nchannels: usize, freq: Flt) -> Siggen {
|
pub fn newSine(nchannels: usize, freq: Flt) -> Siggen {
|
||||||
Siggen::new(nchannels, Box::new(Sine::new(freq)))
|
Siggen::new(nchannels, Box::new(Sine::new(freq)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,40 +237,35 @@ impl Siggen {
|
|||||||
|
|
||||||
/// 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 T: Sample + FromSample<Flt> + Debug, Flt: Sample
|
||||||
T: Sample + FromSample<Flt> + Debug,
|
|
||||||
Flt: Sample,
|
|
||||||
{
|
{
|
||||||
let nch = self.nchannels();
|
let nch = self.nchannels();
|
||||||
let nsamples: usize = out.len() / nch;
|
let nsamples: usize = out.len() / nch;
|
||||||
assert!(out.len() % self.nchannels() == 0);
|
assert!(out.len() % self.nchannels() == 0);
|
||||||
|
|
||||||
// Create source signal
|
// Create source signal
|
||||||
self.source_buf.resize(nsamples, 0.);
|
self.source_buf.resize(nsamples, 0.0);
|
||||||
self.source
|
self.source.genSignal_unscaled(&mut self.source_buf.iter_mut());
|
||||||
.genSignal_unscaled(&mut self.source_buf.iter_mut());
|
|
||||||
// println!("Source signal: {:?}", self.source_buf);
|
// println!("Source signal: {:?}", self.source_buf);
|
||||||
|
|
||||||
// Write output while casted to the correct type
|
// Write output while casted to the correct type
|
||||||
// Iterate over each channel, and counter
|
// Iterate over each channel, and counter
|
||||||
self.chout_buf.resize(nch, vec![]);
|
self.chout_buf.resize(nch, vec![]);
|
||||||
|
|
||||||
for (channelno, (channel, chout)) in self
|
for (channelno, (channel, chout)) in self.channels
|
||||||
.channels
|
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(self.chout_buf.iter_mut())
|
.zip(self.chout_buf.iter_mut())
|
||||||
.enumerate()
|
.enumerate() {
|
||||||
{
|
chout.resize(nsamples, 0.0);
|
||||||
chout.resize(nsamples, 0.);
|
|
||||||
|
|
||||||
// Create output signal, overwrite chout
|
// Create output signal, overwrite chout
|
||||||
channel.genSignal(&self.source_buf, chout);
|
channel.genSignal(&self.source_buf, chout);
|
||||||
// println!("Channel: {}, {:?}", channelno, chout);
|
// println!("Channel: {}, {:?}", channelno, chout);
|
||||||
|
|
||||||
let out_iterator = out.iter_mut().skip(channelno).step_by(nch);
|
let out_iterator = out.iter_mut().skip(channelno).step_by(nch);
|
||||||
out_iterator
|
out_iterator.zip(chout).for_each(|(out, chin)| {
|
||||||
.zip(chout)
|
*out = chin.to_sample();
|
||||||
.for_each(|(out, chin)| *out = chin.to_sample());
|
});
|
||||||
}
|
}
|
||||||
// println!("{:?}", out);
|
// println!("{:?}", out);
|
||||||
}
|
}
|
||||||
@ -277,9 +291,12 @@ impl Siggen {
|
|||||||
/// as number of channels in signal generator.
|
/// as number of channels in signal generator.
|
||||||
pub fn setMute(&mut self, mute: &[bool]) {
|
pub fn setMute(&mut self, mute: &[bool]) {
|
||||||
assert!(mute.len() == self.nchannels());
|
assert!(mute.len() == self.nchannels());
|
||||||
self.channels.iter_mut().zip(mute).for_each(|(s, m)| {
|
self.channels
|
||||||
s.setMute(*m);
|
.iter_mut()
|
||||||
});
|
.zip(mute)
|
||||||
|
.for_each(|(s, m)| {
|
||||||
|
s.setMute(*m);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,13 +335,13 @@ impl SiggenChannelConfig {
|
|||||||
muted: false,
|
muted: false,
|
||||||
prefilter: None,
|
prefilter: None,
|
||||||
gain: 1.0,
|
gain: 1.0,
|
||||||
DCOffset: 0.,
|
DCOffset: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set mute on channel. If true, only DC signal offset is outputed from (SiggenChannelConfig::transform).
|
/// Set mute on channel. If true, only DC signal offset is outputed from (SiggenChannelConfig::transform).
|
||||||
pub fn setMute(&mut self, mute: bool) {
|
pub fn setMute(&mut self, mute: bool) {
|
||||||
self.muted = mute
|
self.muted = mute;
|
||||||
}
|
}
|
||||||
/// Generate new signal data, given input source data.
|
/// Generate new signal data, given input source data.
|
||||||
///
|
///
|
||||||
@ -367,7 +384,7 @@ mod test {
|
|||||||
#[test]
|
#[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.; 10];
|
let mut t = [0.0; 10];
|
||||||
Siggen::newWhiteNoise(1).genSignal(&mut t);
|
Siggen::newWhiteNoise(1).genSignal(&mut t);
|
||||||
// println!("{:?}", &t);
|
// println!("{:?}", &t);
|
||||||
}
|
}
|
||||||
@ -377,55 +394,70 @@ mod test {
|
|||||||
// This code is just to check syntax. We should really be listening to
|
// This code is just to check syntax. We should really be listening to
|
||||||
// these outputs.
|
// these outputs.
|
||||||
const N: usize = 10000;
|
const N: usize = 10000;
|
||||||
let mut s1 = [0.; N];
|
let mut s1 = [0.0; N];
|
||||||
let mut s2 = [0.; N];
|
let mut s2 = [0.0; N];
|
||||||
let mut siggen = Siggen::newSineWave(1, 1.);
|
let mut siggen = Siggen::newSine(1, 1.0);
|
||||||
|
|
||||||
siggen.reset(10.);
|
siggen.reset(10.0);
|
||||||
siggen.setAllMute(false);
|
siggen.setAllMute(false);
|
||||||
siggen.genSignal(&mut s1);
|
siggen.genSignal(&mut s1);
|
||||||
siggen.genSignal(&mut s2);
|
siggen.genSignal(&mut s2);
|
||||||
|
|
||||||
let absdiff = s1.iter().zip(s2.iter()).map(|(s1, s2)| {Flt::abs(*s1-*s2)}).sum::<Flt>();
|
let absdiff = s1
|
||||||
assert!(absdiff< 1e-10);
|
.iter()
|
||||||
|
.zip(s2.iter())
|
||||||
|
.map(|(s1, s2)| { Flt::abs(*s1 - *s2) })
|
||||||
|
.sum::<Flt>();
|
||||||
|
assert!(absdiff < 1e-10);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sine2() {
|
fn test_sine2() {
|
||||||
// Test if channels are properly separated etc. Check if RMS is correct
|
// Test if channels are properly separated etc. Check if RMS is correct
|
||||||
// for amplitude = 1.0.
|
// for amplitude = 1.0.
|
||||||
const fs: Flt = 10.;
|
const fs: Flt = 10.0;
|
||||||
// Number of samples per channel
|
// Number of samples per channel
|
||||||
const Nframes: usize = 10000;
|
const Nframes: usize = 10000;
|
||||||
const Nch: usize = 2;
|
const Nch: usize = 2;
|
||||||
let mut signal = [0.; Nch*Nframes];
|
let mut signal = [0.0; Nch * Nframes];
|
||||||
let mut siggen = Siggen::newSineWave(Nch, 1.);
|
let mut siggen = Siggen::newSine(Nch, 1.0);
|
||||||
|
|
||||||
siggen.reset(fs);
|
siggen.reset(fs);
|
||||||
siggen.setMute(&[false, true]);
|
siggen.setMute(&[false, true]);
|
||||||
// siggen.channels[0].DCOffset = 0.1;
|
// siggen.channels[0].DCOffset = 0.1;
|
||||||
|
|
||||||
// Split off in two terms, see if this works properly
|
// Split off in two terms, see if this works properly
|
||||||
siggen.genSignal(&mut signal[..Nframes/2]);
|
siggen.genSignal(&mut signal[..Nframes / 2]);
|
||||||
siggen.genSignal(&mut signal[Nframes/2..]);
|
siggen.genSignal(&mut signal[Nframes / 2..]);
|
||||||
|
|
||||||
// Mean square of the signal
|
// Mean square of the signal
|
||||||
let ms1 = signal.iter().step_by(2).map(|s1| {*s1 * *s1}).sum::<Flt>() / Nframes as Flt;
|
let ms1 =
|
||||||
println!("ms1: {}",ms1);
|
signal
|
||||||
|
.iter()
|
||||||
|
.step_by(2)
|
||||||
|
.map(|s1| { *s1 * *s1 })
|
||||||
|
.sum::<Flt>() / (Nframes as Flt);
|
||||||
|
println!("ms1: {}", ms1);
|
||||||
|
|
||||||
let ms2 = signal.iter().skip(1).step_by(2).map(|s1| {*s1 * *s1}).sum::<Flt>() / Nframes as Flt;
|
let ms2 =
|
||||||
|
signal
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.step_by(2)
|
||||||
|
.map(|s1| { *s1 * *s1 })
|
||||||
|
.sum::<Flt>() / (Nframes as Flt);
|
||||||
|
|
||||||
assert!(Flt::abs(ms1 - 0.5) < 1e-12);
|
assert!(Flt::abs(ms1 - 0.5) < 1e-12);
|
||||||
assert_eq!(ms2 , 0.);
|
assert_eq!(ms2, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A small test to learn a bit about sample types and conversion. This
|
// A small test to learn a bit about sample types and conversion. This
|
||||||
// is the thing we want.
|
// is the thing we want.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sample() {
|
fn test_sample() {
|
||||||
assert_eq!(0.5f32.to_sample::<i8>(), 64);
|
assert_eq!((0.5f32).to_sample::<i8>(), 64);
|
||||||
assert_eq!(1.0f32.to_sample::<i8>(), 127);
|
assert_eq!((1.0f32).to_sample::<i8>(), 127);
|
||||||
assert_eq!(-(1.0f32.to_sample::<i8>()), -127);
|
assert_eq!(-(1.0f32).to_sample::<i8>(), -127);
|
||||||
assert_eq!(1.0f32.to_sample::<i16>(), i16::MAX);
|
assert_eq!((1.0f32).to_sample::<i16>(), i16::MAX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user