Signal generator working, even with Python bindings. startOutputStream implementend for CPAL

This commit is contained in:
Anne de Jong 2024-05-05 15:01:50 +02:00
parent 22a9c9f850
commit 158ea77c40
11 changed files with 614 additions and 271 deletions

102
src/bin/lasp_output.rs Normal file
View 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(())
}

View File

@ -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,12 +36,13 @@ 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 {
match stdin_channel.try_recv() { match stdin_channel.try_recv() {

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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
}
} }

View File

@ -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(())
} }

View File

@ -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]);

View File

@ -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
@ -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.

View File

@ -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]

View File

@ -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)?;

View File

@ -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);
} }
} }