use std::{
borrow::Borrow,
sync::{
- mpsc::{sync_channel, Receiver, SyncSender},
Arc, RwLock,
+ mpsc::{Receiver, SyncSender, sync_channel},
},
time::{Duration, Instant},
};
use cpal::{
- traits::{DeviceTrait, HostTrait, StreamTrait},
- BuildStreamError, DevicesError, PlayStreamError, SampleFormat, SampleRate, Stream, StreamError,
+ BuildStreamError, Device, DevicesError, InputCallbackInfo, OutputCallbackInfo, PlayStreamError,
+ SampleFormat, SampleRate, Stream, StreamError, SupportedStreamConfig,
SupportedStreamConfigRange, SupportedStreamConfigsError,
+ traits::{DeviceTrait, HostTrait, StreamTrait},
};
+use num_traits::{NumCast, WrappingNeg};
use thiserror::Error;
use crate::soundmodem::{
fn config_is_compatible<C: Borrow<SupportedStreamConfigRange>>(config: C) -> bool {
let config = config.borrow();
(config.channels() == 1 || config.channels() == 2)
- && config.sample_format() == SampleFormat::I16
+ && (config.sample_format() == SampleFormat::I16
+ || config.sample_format() == SampleFormat::I32)
&& config.min_sample_rate().0 <= 48000
&& config.max_sample_rate().0 >= 48000
}
}
}
+fn build_input_cb<T: NumCast + WrappingNeg + Clone>(
+ samples: SyncSender<SoundmodemEvent>,
+ channels: u16,
+ rx_inverted: bool,
+) -> impl Fn(&[T], &InputCallbackInfo) {
+ move |data: &[T], _info: &cpal::InputCallbackInfo| {
+ let mut out = vec![];
+ for d in data.chunks(channels as usize) {
+ // if we were given multi-channel input we'll pick the first channel
+ let mut sample = d[0].clone();
+ if rx_inverted {
+ sample = sample.wrapping_neg();
+ }
+ out.push(NumCast::from(sample).unwrap());
+ }
+ let _ = samples.try_send(SoundmodemEvent::BasebandInput(out.into()));
+ }
+}
+
+fn build_input_stream(
+ device: &Device,
+ input_config: SupportedStreamConfig,
+ errors: SoundmodemErrorSender,
+ samples: SyncSender<SoundmodemEvent>,
+ channels: u16,
+ rx_inverted: bool,
+) -> Result<Stream, BuildStreamError> {
+ if input_config.sample_format() == SampleFormat::I16 {
+ device.build_input_stream(
+ &input_config.into(),
+ build_input_cb::<i16>(samples, channels, rx_inverted),
+ move |e| {
+ errors.send_error(SoundcardError::Stream(e));
+ },
+ None,
+ )
+ } else {
+ device.build_input_stream(
+ &input_config.into(),
+ build_input_cb::<i32>(samples, channels, rx_inverted),
+ move |e| {
+ errors.send_error(SoundcardError::Stream(e));
+ },
+ None,
+ )
+ }
+}
+
+fn build_output_cb<T: NumCast + WrappingNeg + Clone>(
+ event_tx: SyncSender<SoundmodemEvent>,
+ channels: u16,
+ tx_inverted: bool,
+ buffer: Arc<RwLock<OutputBuffer>>,
+) -> impl Fn(&mut [T], &OutputCallbackInfo) {
+ move |data: &mut [T], info: &cpal::OutputCallbackInfo| {
+ let mut taken = 0;
+ let ts = info.timestamp();
+ let latency = ts
+ .playback
+ .duration_since(&ts.callback)
+ .unwrap_or(Duration::ZERO);
+ let mut buffer = buffer.write().unwrap();
+ buffer.latency = latency;
+ for out in data.chunks_mut(channels as usize) {
+ if let Some(s) = buffer.samples.pop_front() {
+ out.fill(NumCast::from(if tx_inverted { s.saturating_neg() } else { s }).unwrap());
+ taken += 1;
+ } else if buffer.idling {
+ out.fill(NumCast::from(0).unwrap());
+ } else {
+ let _ = event_tx.send(SoundmodemEvent::OutputUnderrun);
+ break;
+ }
+ }
+ let _ = event_tx.send(SoundmodemEvent::DidReadFromOutputBuffer {
+ len: taken,
+ timestamp: Instant::now(),
+ });
+ }
+}
+
+fn build_output_stream(
+ device: &Device,
+ output_config: SupportedStreamConfig,
+ errors: SoundmodemErrorSender,
+ event_tx: SyncSender<SoundmodemEvent>,
+ channels: u16,
+ tx_inverted: bool,
+ buffer: Arc<RwLock<OutputBuffer>>,
+) -> Result<Stream, BuildStreamError> {
+ if output_config.sample_format() == SampleFormat::I16 {
+ device.build_output_stream(
+ &output_config.into(),
+ build_output_cb::<i16>(event_tx, channels, tx_inverted, buffer),
+ move |e| {
+ errors.send_error(SoundcardError::Stream(e));
+ },
+ None,
+ )
+ } else {
+ device.build_output_stream(
+ &output_config.into(),
+ build_output_cb::<i32>(event_tx, channels, tx_inverted, buffer),
+ move |e| {
+ errors.send_error(SoundcardError::Stream(e));
+ },
+ None,
+ )
+ }
+}
+
fn spawn_soundcard_worker(
event_rx: Receiver<SoundcardEvent>,
setup_tx: SyncSender<Result<(), SoundcardError>>,
};
let input_config = input_config.with_sample_rate(SampleRate(48000));
let channels = input_config.channels();
- let errors_1 = errors.clone();
- let stream = match device.build_input_stream(
- &input_config.into(),
- move |data: &[i16], _info: &cpal::InputCallbackInfo| {
- let mut out = vec![];
- for d in data.chunks(channels as usize) {
- // if we were given multi-channel input we'll pick the first channel
- let mut sample = d[0];
- if rx_inverted {
- sample = sample.saturating_neg();
- }
- out.push(sample);
- }
- let _ = samples.try_send(SoundmodemEvent::BasebandInput(out.into()));
- },
- move |e| {
- errors_1.send_error(SoundcardError::Stream(e));
- },
- None,
+ let stream = match build_input_stream(
+ &device,
+ input_config,
+ errors.clone(),
+ samples,
+ channels,
+ rx_inverted,
) {
Ok(s) => s,
Err(e) => {
};
let output_config = output_config.with_sample_rate(SampleRate(48000));
let channels = output_config.channels();
- let errors_1 = errors.clone();
- let stream = match device.build_output_stream(
- &output_config.into(),
- move |data: &mut [i16], info: &cpal::OutputCallbackInfo| {
- let mut taken = 0;
- let ts = info.timestamp();
- let latency = ts
- .playback
- .duration_since(&ts.callback)
- .unwrap_or(Duration::ZERO);
- let mut buffer = buffer.write().unwrap();
- buffer.latency = latency;
- for out in data.chunks_mut(channels as usize) {
- if let Some(s) = buffer.samples.pop_front() {
- out.fill(if tx_inverted { s.saturating_neg() } else { s });
- taken += 1;
- } else if buffer.idling {
- out.fill(0);
- } else {
- let _ = event_tx.send(SoundmodemEvent::OutputUnderrun);
- break;
- }
- }
- let _ = event_tx.send(SoundmodemEvent::DidReadFromOutputBuffer {
- len: taken,
- timestamp: Instant::now(),
- });
- },
- move |e| {
- errors_1.send_error(SoundcardError::Stream(e));
- },
- None,
+ let stream = match build_output_stream(
+ &device,
+ output_config,
+ errors.clone(),
+ event_tx,
+ channels,
+ tx_inverted,
+ buffer,
) {
Ok(s) => s,
Err(e) => {