X-Git-Url: https://code.octet-stream.net/m17rt/blobdiff_plain/4cfda08117c4288a5408d45db1ef4be82f4facaa..2fb25de49daca6ddff6f5af13bcf7c314aafafb3:/m17app/src/soundmodem.rs diff --git a/m17app/src/soundmodem.rs b/m17app/src/soundmodem.rs index 9c05ada..1cb871a 100644 --- a/m17app/src/soundmodem.rs +++ b/m17app/src/soundmodem.rs @@ -1,8 +1,5 @@ +use crate::error::{M17Error, SoundmodemError}; use crate::tnc::{Tnc, TncError}; -use cpal::traits::DeviceTrait; -use cpal::traits::HostTrait; -use cpal::traits::StreamTrait; -use cpal::{SampleFormat, SampleRate}; use log::debug; use m17core::kiss::MAX_FRAME_LEN; use m17core::modem::{Demodulator, Modulator, ModulatorAction, SoftDemodulator, SoftModulator}; @@ -23,7 +20,7 @@ pub struct Soundmodem { } impl Soundmodem { - pub fn new_with_input_and_output(input: I, output: O) -> Self { + pub fn new(input: I, output: O, ptt: P) -> Self { // must create TNC here let (event_tx, event_rx) = sync_channel(128); let (kiss_out_tx, kiss_out_rx) = sync_channel(128); @@ -33,6 +30,7 @@ impl Soundmodem { kiss_out_tx, Box::new(input), Box::new(output), + Box::new(ptt), ); Self { event_tx, @@ -127,6 +125,7 @@ fn spawn_soundmodem_worker( kiss_out_tx: SyncSender>, input: Box, output: Box, + mut ptt_driver: Box, ) { std::thread::spawn(move || { // TODO: should be able to provide a custom Demodulator for a soundmodem @@ -140,8 +139,12 @@ fn spawn_soundmodem_worker( let mut ptt = false; while let Ok(ev) = event_rx.recv() { // Update clock on TNC before we do anything - let sample_time = (start.elapsed().as_nanos() / 48000) as u64; - tnc.set_now(sample_time); + let sample_time = start.elapsed(); + let secs = sample_time.as_secs(); + let nanos = sample_time.subsec_nanos(); + // Accurate to within approx 1 sample + let now_samples = 48000 * secs + (nanos as u64 / 20833); + tnc.set_now(now_samples); // Handle event match ev { @@ -167,10 +170,14 @@ fn spawn_soundmodem_worker( tnc.set_data_carrier_detect(demodulator.data_carrier_detect()); } SoundmodemEvent::Start => { - input.start(event_tx.clone()); - output.start(event_tx.clone(), out_buffer.clone()); + // TODO: runtime event handling + input.start(event_tx.clone()).unwrap(); + output.start(event_tx.clone(), out_buffer.clone()).unwrap(); + } + SoundmodemEvent::Close => { + ptt_driver.ptt_off().unwrap(); + break; } - SoundmodemEvent::Close => break, SoundmodemEvent::DidReadFromOutputBuffer { len, timestamp } => { let (occupied, internal_latency) = { let out_buffer = out_buffer.read().unwrap(); @@ -194,9 +201,9 @@ fn spawn_soundmodem_worker( let new_ptt = tnc.ptt(); if new_ptt != ptt { if new_ptt { - // turn it on + ptt_driver.ptt_on().unwrap(); } else { - // turn it off + ptt_driver.ptt_off().unwrap(); } } ptt = new_ptt; @@ -230,100 +237,33 @@ fn spawn_soundmodem_worker( } pub trait InputSource: Send + Sync + 'static { - fn start(&self, samples: SyncSender); - fn close(&self); -} - -pub struct InputSoundcard { - // TODO: allow for inversion both here and in output - cpal_name: Option, - end_tx: Mutex>>, -} - -impl InputSoundcard { - 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 InputSource for InputSoundcard { - fn start(&self, samples: SyncSender) { - 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.input_devices() - .unwrap() - .find(|d| d.name().unwrap() == name) - .unwrap() - } else { - host.default_input_device().unwrap() - }; - let mut configs = device.supported_input_configs().unwrap(); - let config = configs - .find(|c| c.channels() == 1 && c.sample_format() == SampleFormat::I16) - .unwrap() - .with_sample_rate(SampleRate(48000)); - let stream = device - .build_input_stream( - &config.into(), - move |data: &[i16], _info: &cpal::InputCallbackInfo| { - let out: Vec = data.iter().map(|s| *s).collect(); - let _ = samples.try_send(SoundmodemEvent::BasebandInput(out.into())); - }, - |e| { - // TODO: abort? - debug!("error occurred in soundcard input: {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(); - } + fn start(&self, samples: SyncSender) -> Result<(), SoundmodemError>; + fn close(&self) -> Result<(), SoundmodemError>; } pub struct InputRrcFile { - path: PathBuf, + baseband: Arc<[u8]>, end_tx: Mutex>>, } impl InputRrcFile { - pub fn new(path: PathBuf) -> Self { - Self { - path, + pub fn new(path: PathBuf) -> Result { + let mut file = File::open(&path).map_err(|_| M17Error::InvalidRrcPath(path.clone()))?; + let mut baseband = vec![]; + file.read_to_end(&mut baseband) + .map_err(|_| M17Error::RrcReadFailed(path))?; + Ok(Self { + baseband: baseband.into(), end_tx: Mutex::new(None), - } + }) } } impl InputSource for InputRrcFile { - fn start(&self, samples: SyncSender) { + fn start(&self, samples: SyncSender) -> Result<(), SoundmodemError> { let (end_tx, end_rx) = channel(); - let path = self.path.clone(); + let baseband = self.baseband.clone(); std::thread::spawn(move || { - // TODO: error handling - let mut file = File::open(path).unwrap(); - let mut baseband = vec![]; - file.read_to_end(&mut baseband).unwrap(); - // assuming 48 kHz for now const TICK: Duration = Duration::from_millis(25); const SAMPLES_PER_TICK: usize = 1200; @@ -342,7 +282,7 @@ impl InputSource for InputRrcFile { if let Err(e) = samples.try_send(SoundmodemEvent::BasebandInput(buf.into())) { debug!("overflow feeding soundmodem: {e:?}"); } - next_tick = next_tick + TICK; + next_tick += TICK; idx = 0; std::thread::sleep(next_tick.duration_since(Instant::now())); } @@ -352,10 +292,12 @@ impl InputSource for InputRrcFile { } }); *self.end_tx.lock().unwrap() = Some(end_tx); + Ok(()) } - fn close(&self) { + fn close(&self) -> Result<(), SoundmodemError> { let _ = self.end_tx.lock().unwrap().take(); + Ok(()) } } @@ -372,7 +314,7 @@ impl NullInputSource { } impl InputSource for NullInputSource { - fn start(&self, samples: SyncSender) { + fn start(&self, samples: SyncSender) -> Result<(), SoundmodemError> { let (end_tx, end_rx) = channel(); std::thread::spawn(move || { // assuming 48 kHz for now @@ -382,7 +324,7 @@ impl InputSource for NullInputSource { loop { std::thread::sleep(next_tick.duration_since(Instant::now())); - next_tick = next_tick + TICK; + next_tick += TICK; if end_rx.try_recv() != Err(TryRecvError::Empty) { break; } @@ -394,18 +336,26 @@ impl InputSource for NullInputSource { } }); *self.end_tx.lock().unwrap() = Some(end_tx); + Ok(()) } - fn close(&self) { + fn close(&self) -> Result<(), SoundmodemError> { let _ = self.end_tx.lock().unwrap().take(); + Ok(()) + } +} + +impl Default for NullInputSource { + fn default() -> Self { + Self::new() } } pub struct OutputBuffer { - idling: bool, + pub idling: bool, // TODO: something more efficient - samples: VecDeque, - latency: Duration, + pub samples: VecDeque, + pub latency: Duration, } impl OutputBuffer { @@ -418,9 +368,19 @@ impl OutputBuffer { } } +impl Default for OutputBuffer { + fn default() -> Self { + Self::new() + } +} + pub trait OutputSink: Send + Sync + 'static { - fn start(&self, event_tx: SyncSender, buffer: Arc>); - fn close(&self); + fn start( + &self, + event_tx: SyncSender, + buffer: Arc>, + ) -> Result<(), SoundmodemError>; + fn close(&self) -> Result<(), SoundmodemError>; } pub struct OutputRrcFile { @@ -438,13 +398,14 @@ impl OutputRrcFile { } impl OutputSink for OutputRrcFile { - fn start(&self, event_tx: SyncSender, buffer: Arc>) { + fn start( + &self, + event_tx: SyncSender, + buffer: Arc>, + ) -> Result<(), SoundmodemError> { let (end_tx, end_rx) = channel(); - let path = self.path.clone(); + let mut file = File::create(self.path.clone())?; std::thread::spawn(move || { - // TODO: error handling - let mut file = File::create(path).unwrap(); - // assuming 48 kHz for now const TICK: Duration = Duration::from_millis(25); const SAMPLES_PER_TICK: usize = 1200; @@ -455,7 +416,7 @@ impl OutputSink for OutputRrcFile { loop { std::thread::sleep(next_tick.duration_since(Instant::now())); - next_tick = next_tick + TICK; + next_tick += TICK; if end_rx.try_recv() != Err(TryRecvError::Empty) { break; } @@ -486,10 +447,12 @@ impl OutputSink for OutputRrcFile { } }); *self.end_tx.lock().unwrap() = Some(end_tx); + Ok(()) } - fn close(&self) { + fn close(&self) -> Result<(), SoundmodemError> { let _ = self.end_tx.lock().unwrap().take(); + Ok(()) } } @@ -505,8 +468,18 @@ impl NullOutputSink { } } +impl Default for NullOutputSink { + fn default() -> Self { + Self::new() + } +} + impl OutputSink for NullOutputSink { - fn start(&self, event_tx: SyncSender, buffer: Arc>) { + fn start( + &self, + event_tx: SyncSender, + buffer: Arc>, + ) -> Result<(), SoundmodemError> { let (end_tx, end_rx) = channel(); std::thread::spawn(move || { // assuming 48 kHz for now @@ -516,7 +489,7 @@ impl OutputSink for NullOutputSink { loop { std::thread::sleep(next_tick.duration_since(Instant::now())); - next_tick = next_tick + TICK; + next_tick += TICK; if end_rx.try_recv() != Err(TryRecvError::Empty) { break; } @@ -524,7 +497,7 @@ impl OutputSink for NullOutputSink { let mut buffer = buffer.write().unwrap(); let mut taken = 0; for _ in 0..SAMPLES_PER_TICK { - if !buffer.samples.pop_front().is_some() { + if buffer.samples.pop_front().is_none() { if !buffer.idling { debug!("null output had underrun"); let _ = event_tx.send(SoundmodemEvent::OutputUnderrun); @@ -541,9 +514,41 @@ impl OutputSink for NullOutputSink { } }); *self.end_tx.lock().unwrap() = Some(end_tx); + Ok(()) } - fn close(&self) { + fn close(&self) -> Result<(), SoundmodemError> { let _ = self.end_tx.lock().unwrap().take(); + Ok(()) + } +} + +pub trait Ptt: Send + 'static { + fn ptt_on(&mut self) -> Result<(), SoundmodemError>; + fn ptt_off(&mut self) -> Result<(), SoundmodemError>; +} + +/// There is no PTT because this TNC will never make transmissions on a real radio. +pub struct NullPtt; + +impl NullPtt { + pub fn new() -> Self { + Self + } +} + +impl Default for NullPtt { + fn default() -> Self { + Self::new() + } +} + +impl Ptt for NullPtt { + fn ptt_on(&mut self) -> Result<(), SoundmodemError> { + Ok(()) + } + + fn ptt_off(&mut self) -> Result<(), SoundmodemError> { + Ok(()) } }