]> code.octet-stream.net Git - m17rt/commitdiff
Support i16 and i32 master
authorThomas Karpiniec <tom.karpiniec@outlook.com>
Thu, 10 Jul 2025 10:29:12 +0000 (20:29 +1000)
committerThomas Karpiniec <tom.karpiniec@outlook.com>
Thu, 10 Jul 2025 10:29:12 +0000 (20:29 +1000)
Cargo.lock
m17app/Cargo.toml
m17app/src/soundcard.rs

index 4800984ee50ae4400ec7c6415a37ab349c912985..0a4cb5003f71bccca860ffe72622b31ecfd7c5de 100644 (file)
@@ -509,6 +509,7 @@ dependencies = [
  "cpal",
  "log",
  "m17core",
  "cpal",
  "log",
  "m17core",
+ "num-traits",
  "serialport",
  "thiserror 2.0.11",
 ]
  "serialport",
  "thiserror 2.0.11",
 ]
index 5a0e621afe534e91a2849f3a1a248cdf72a0e983..8ef9c208505c50627c5e53a204a046fa24b3978c 100644 (file)
@@ -16,5 +16,6 @@ readme = "README.md"
 cpal = "0.15.3"
 m17core = { version = "0.1", path = "../m17core" }
 log = "0.4.22"
 cpal = "0.15.3"
 m17core = { version = "0.1", path = "../m17core" }
 log = "0.4.22"
+num-traits = "0.2.19"
 serialport = { version = "4.7.0", default-features = false }
 thiserror = "2.0.11"
 serialport = { version = "4.7.0", default-features = false }
 thiserror = "2.0.11"
index fb292827acfe764ec891830bcdd676dacc1a8f16..a61c962b417cdc12cbf2c4abe15f2403aa2d5e88 100644 (file)
@@ -8,10 +8,12 @@ use std::{
 };
 
 use cpal::{
 };
 
 use cpal::{
-    BuildStreamError, DevicesError, PlayStreamError, SampleFormat, SampleRate, Stream, StreamError,
+    BuildStreamError, Device, DevicesError, InputCallbackInfo, OutputCallbackInfo, PlayStreamError,
+    SampleFormat, SampleRate, Stream, StreamError, SupportedStreamConfig,
     SupportedStreamConfigRange, SupportedStreamConfigsError,
     traits::{DeviceTrait, HostTrait, StreamTrait},
 };
     SupportedStreamConfigRange, SupportedStreamConfigsError,
     traits::{DeviceTrait, HostTrait, StreamTrait},
 };
+use num_traits::{NumCast, WrappingNeg};
 use thiserror::Error;
 
 use crate::soundmodem::{
 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)
 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
 }
         && 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>>,
 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 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) => {
                     ) {
                         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 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) => {
                     ) {
                         Ok(s) => s,
                         Err(e) => {