X-Git-Url: https://code.octet-stream.net/m17rt/blobdiff_plain/4cfda08117c4288a5408d45db1ef4be82f4facaa..bb98d1726f35a41236b66bcf9fa0a8fa36633342:/m17app/src/soundmodem.rs diff --git a/m17app/src/soundmodem.rs b/m17app/src/soundmodem.rs index 9c05ada..dced157 100644 --- a/m17app/src/soundmodem.rs +++ b/m17app/src/soundmodem.rs @@ -23,7 +23,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 +33,7 @@ impl Soundmodem { kiss_out_tx, Box::new(input), Box::new(output), + Box::new(ptt), ); Self { event_tx, @@ -127,6 +128,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 @@ -170,7 +172,10 @@ fn spawn_soundmodem_worker( input.start(event_tx.clone()); output.start(event_tx.clone(), out_buffer.clone()); } - SoundmodemEvent::Close => break, + SoundmodemEvent::Close => { + ptt_driver.ptt_off(); + break; + } SoundmodemEvent::DidReadFromOutputBuffer { len, timestamp } => { let (occupied, internal_latency) = { let out_buffer = out_buffer.read().unwrap(); @@ -194,9 +199,9 @@ fn spawn_soundmodem_worker( let new_ptt = tnc.ptt(); if new_ptt != ptt { if new_ptt { - // turn it on + ptt_driver.ptt_on(); } else { - // turn it off + ptt_driver.ptt_off(); } } ptt = new_ptt; @@ -547,3 +552,112 @@ 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(); + } +} + +pub trait Ptt: Send + 'static { + fn ptt_on(&mut self); + fn ptt_off(&mut self); +} + +/// 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 Ptt for NullPtt { + fn ptt_on(&mut self) {} + fn ptt_off(&mut self) {} +}