]> code.octet-stream.net Git - m17rt/blobdiff - m17app/src/soundcard.rs
Support i16 and i32
[m17rt] / m17app / src / soundcard.rs
index 9209de2a09c705642d46158b03476f4ff9a4524e..a61c962b417cdc12cbf2c4abe15f2403aa2d5e88 100644 (file)
@@ -1,17 +1,19 @@
 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::{
@@ -113,7 +115,8 @@ impl Soundcard {
 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
 }
@@ -173,6 +176,117 @@ impl OutputSink for SoundcardOutputSink {
     }
 }
 
+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>>,
@@ -216,25 +330,13 @@ fn spawn_soundcard_worker(
                     };
                     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) => {
@@ -272,38 +374,14 @@ fn spawn_soundcard_worker(
                     };
                     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) => {