From: Thomas Karpiniec Date: Sat, 18 Jan 2025 03:25:58 +0000 (+1100) Subject: Support for baseband output to a soundcard X-Git-Url: https://code.octet-stream.net/m17rt/commitdiff_plain/refs/heads/master?ds=sidebyside;hp=ac1b1b703bd012b82404951be0637dabe4606660 Support for baseband output to a soundcard --- diff --git a/m17app/src/soundmodem.rs b/m17app/src/soundmodem.rs index 9c05ada..74c176b 100644 --- a/m17app/src/soundmodem.rs +++ b/m17app/src/soundmodem.rs @@ -547,3 +547,93 @@ impl OutputSink for NullOutputSink { let _ = self.end_tx.lock().unwrap().take(); } } + +pub struct OutputSoundcard { + // TODO: allow for inversion both here and in output + cpal_name: Option, + end_tx: Mutex>>, +} + +impl OutputSoundcard { + pub fn new() -> Self { + Self { + cpal_name: None, + end_tx: Mutex::new(None), + } + } + + pub fn new_with_card(card_name: String) -> Self { + Self { + cpal_name: Some(card_name), + end_tx: Mutex::new(None), + } + } +} + +impl OutputSink for OutputSoundcard { + fn start(&self, event_tx: SyncSender, buffer: Arc>) { + let (end_tx, end_rx) = channel(); + let cpal_name = self.cpal_name.clone(); + std::thread::spawn(move || { + let host = cpal::default_host(); + let device = if let Some(name) = cpal_name.as_deref() { + host.output_devices() + .unwrap() + .find(|d| d.name().unwrap() == name) + .unwrap() + } else { + host.default_output_device().unwrap() + }; + let mut configs = device.supported_output_configs().unwrap(); + // TODO: more error handling + let config = configs + .find(|c| c.channels() == 1 && c.sample_format() == SampleFormat::I16) + .unwrap() + .with_sample_rate(SampleRate(48000)); + let stream = device + .build_output_stream( + &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.iter_mut() { + if let Some(s) = buffer.samples.pop_front() { + *out = s; + taken += 1; + } else if buffer.idling { + *out = 0; + } else { + debug!("output soundcard had underrun"); + let _ = event_tx.send(SoundmodemEvent::OutputUnderrun); + break; + } + } + //debug!("latency is {} ms, taken {taken}", latency.as_millis()); + let _ = event_tx.send(SoundmodemEvent::DidReadFromOutputBuffer { + len: taken, + timestamp: Instant::now(), + }); + }, + |e| { + // TODO: abort? + debug!("error occurred in soundcard output: {e:?}"); + }, + None, + ) + .unwrap(); + stream.play().unwrap(); + let _ = end_rx.recv(); + }); + *self.end_tx.lock().unwrap() = Some(end_tx); + } + + fn close(&self) { + let _ = self.end_tx.lock().unwrap().take(); + } +} diff --git a/m17core/src/modem.rs b/m17core/src/modem.rs index 35a23a7..fe57188 100644 --- a/m17core/src/modem.rs +++ b/m17core/src/modem.rs @@ -419,7 +419,7 @@ impl Modulator for SoftModulator { // if we have pre-TX padding to accommodate TxDelay then expend that first if self.tx_delay_padding > 0 { - let len = out.len().max(self.tx_delay_padding); + let len = out.len().min(self.tx_delay_padding); self.tx_delay_padding -= len; for x in 0..len { out[x] = 0; diff --git a/m17core/src/tnc.rs b/m17core/src/tnc.rs index ee0fa30..7851948 100644 --- a/m17core/src/tnc.rs +++ b/m17core/src/tnc.rs @@ -212,6 +212,7 @@ impl SoftTnc { } pub fn set_tx_end_time(&mut self, in_samples: usize) { + log::debug!("tnc has been told that tx will complete in {in_samples} samples"); match self.state { State::TxEnding => { self.state = State::TxEndingAtTime(self.now + in_samples as u64); diff --git a/tools/m17rt-demod/src/main.rs b/tools/m17rt-demod/src/main.rs index 1125c3a..40ab3db 100755 --- a/tools/m17rt-demod/src/main.rs +++ b/tools/m17rt-demod/src/main.rs @@ -6,6 +6,7 @@ use std::path::PathBuf; pub fn m17app_test() { //let path = PathBuf::from("../../../Data/test_vk7xt.rrc"); let path = PathBuf::from("../../../Data/mymod.rrc"); + //let path = PathBuf::from("../../../Data/mymod-noisy.raw"); let source = InputRrcFile::new(path); //let source = InputSoundcard::new(); let soundmodem = Soundmodem::new_with_input_and_output(source, NullOutputSink::new()); diff --git a/tools/m17rt-mod/src/main.rs b/tools/m17rt-mod/src/main.rs index 08973cb..968c22a 100644 --- a/tools/m17rt-mod/src/main.rs +++ b/tools/m17rt-mod/src/main.rs @@ -1,15 +1,17 @@ use m17app::app::M17App; use m17app::soundmodem::{ - InputRrcFile, InputSoundcard, NullInputSource, NullOutputSink, OutputRrcFile, Soundmodem, + InputRrcFile, InputSoundcard, NullInputSource, NullOutputSink, OutputRrcFile, OutputSoundcard, + Soundmodem, }; use m17codec2::{Codec2Adapter, WavePlayer}; use std::path::PathBuf; pub fn mod_test() { let in_path = PathBuf::from("../../../Data/test_vk7xt_8k.wav"); - let out_path = PathBuf::from("../../../Data/mymod.rrc"); - let soundmodem = - Soundmodem::new_with_input_and_output(NullInputSource::new(), OutputRrcFile::new(out_path)); + //let out_path = PathBuf::from("../../../Data/mymod.rrc"); + //let output = OutputRrcFile::new(out_path); + let output = OutputSoundcard::new(); + let soundmodem = Soundmodem::new_with_input_and_output(NullInputSource::new(), output); let app = M17App::new(soundmodem); app.start(); std::thread::sleep(std::time::Duration::from_secs(1));