]>
code.octet-stream.net Git - m17rt/blob - m17app/src/soundmodem.rs
   1 use crate::tnc
::{Tnc
, TncError
}; 
   2 use cpal
::traits
::DeviceTrait
; 
   3 use cpal
::traits
::HostTrait
; 
   4 use cpal
::traits
::StreamTrait
; 
   5 use cpal
::{SampleFormat
, SampleRate
}; 
   7 use m17core
::kiss
::MAX_FRAME_LEN
; 
   8 use m17core
::modem
::{Demodulator
, Modulator
, ModulatorAction
, SoftDemodulator
, SoftModulator
}; 
   9 use m17core
::tnc
::SoftTnc
; 
  10 use std
::collections
::VecDeque
; 
  12 use std
::io
::{self, ErrorKind
, Read
, Write
}; 
  13 use std
::path
::PathBuf
; 
  14 use std
::sync
::mpsc
::{channel
, sync_channel
, Receiver
, Sender
, SyncSender
, TryRecvError
}; 
  15 use std
::sync
::RwLock
; 
  16 use std
::sync
::{Arc
, Mutex
}; 
  17 use std
::time
::{Duration
, Instant
}; 
  19 pub struct Soundmodem 
{ 
  20     event_tx
: SyncSender
<SoundmodemEvent
>, 
  21     kiss_out_rx
: Arc
<Mutex
<Receiver
<Arc
<[u8]>>>>, 
  22     partial_kiss_out
: Arc
<Mutex
<Option
<PartialKissOut
>>>, 
  26     pub fn new
<I
: InputSource
, O
: OutputSink
, P
: Ptt
>(input
: I
, output
: O
, ptt
: P
) -> Self { 
  27         // must create TNC here 
  28         let (event_tx
, event_rx
) = sync_channel(128); 
  29         let (kiss_out_tx
, kiss_out_rx
) = sync_channel(128); 
  30         spawn_soundmodem_worker( 
  40             kiss_out_rx
: Arc
::new(Mutex
::new(kiss_out_rx
)), 
  41             partial_kiss_out
: Arc
::new(Mutex
::new(None
)), 
  46 struct PartialKissOut 
{ 
  51 impl Read 
for Soundmodem 
{ 
  52     fn read(&mut self, buf
: &mut [u8]) -> io
::Result
<usize> { 
  54             let mut partial_kiss_out 
= self.partial_kiss_out
.lock().unwrap
(); 
  55             if let Some(partial
) = partial_kiss_out
.as_mut() { 
  56                 let remaining 
= partial
.output
.len() - partial
.idx
; 
  57                 let to_write 
= remaining
.min(buf
.len()); 
  59                     .copy_from_slice(&partial
.output
[partial
.idx
..(partial
.idx 
+ to_write
)]); 
  60                 if to_write 
== remaining 
{ 
  61                     *partial_kiss_out 
= None
; 
  63                     partial
.idx 
+= to_write
; 
  69             let rx 
= self.kiss_out_rx
.lock().unwrap
(); 
  71                 .map_err(|s
| io
::Error
::new(ErrorKind
::Other
, format
!("{:?}", s
)))?
 
  73         let to_write 
= output
.len().min(buf
.len()); 
  74         buf
[0..to_write
].copy_from_slice(&output
[0..to_write
]); 
  75         if to_write 
!= output
.len() { 
  76             *self.partial_kiss_out
.lock().unwrap
() = Some(PartialKissOut 
{ 
  85 impl Write 
for Soundmodem 
{ 
  86     fn write(&mut self, buf
: &[u8]) -> std
::io
::Result
<usize> { 
  87         let _ 
= self.event_tx
.try_send(SoundmodemEvent
::Kiss(buf
.into
())); 
  91     fn flush(&mut self) -> std
::io
::Result
<()> { 
  96 impl Tnc 
for Soundmodem 
{ 
  97     fn try_clone(&mut self) -> Result
<Self, TncError
> { 
  99             event_tx
: self.event_tx
.clone(), 
 100             kiss_out_rx
: self.kiss_out_rx
.clone(), 
 101             partial_kiss_out
: self.partial_kiss_out
.clone(), 
 105     fn start(&mut self) -> Result
<(), TncError
> { 
 106         let _ 
= self.event_tx
.send(SoundmodemEvent
::Start
); 
 110     fn close(&mut self) -> Result
<(), TncError
> { 
 111         let _ 
= self.event_tx
.send(SoundmodemEvent
::Close
); 
 116 pub enum SoundmodemEvent 
{ 
 118     BasebandInput(Arc
<[i16]>), 
 121     DidReadFromOutputBuffer 
{ len
: usize, timestamp
: Instant 
}, 
 125 fn spawn_soundmodem_worker( 
 126     event_tx
: SyncSender
<SoundmodemEvent
>, 
 127     event_rx
: Receiver
<SoundmodemEvent
>, 
 128     kiss_out_tx
: SyncSender
<Arc
<[u8]>>, 
 129     input
: Box
<dyn InputSource
>, 
 130     output
: Box
<dyn OutputSink
>, 
 131     mut ptt_driver
: Box
<dyn Ptt
>, 
 133     std
::thread
::spawn(move || { 
 134         // TODO: should be able to provide a custom Demodulator for a soundmodem 
 135         let mut demodulator 
= SoftDemodulator
::new(); 
 136         let mut modulator 
= SoftModulator
::new(); 
 137         let mut tnc 
= SoftTnc
::new(); 
 138         let mut buf 
= [0u8; MAX_FRAME_LEN
]; 
 139         let out_buffer 
= Arc
::new(RwLock
::new(OutputBuffer
::new())); 
 140         let mut out_samples 
= [0i16; 1024]; 
 141         let start 
= Instant
::now(); 
 143         while let Ok(ev
) = event_rx
.recv() { 
 144             // Update clock on TNC before we do anything 
 145             let sample_time 
= (start
.elapsed().as_nanos() / 48000) as u64; 
 146             tnc
.set_now(sample_time
); 
 150                 SoundmodemEvent
::Kiss(k
) => { 
 151                     let _n 
= tnc
.write_kiss(&k
); 
 152                     // TODO: what does it mean if we fail to write it all? 
 153                     // Probably we have to read frames for tx first - revisit this during tx 
 155                 SoundmodemEvent
::BasebandInput(b
) => { 
 157                         if let Some(frame
) = demodulator
.demod(*sample
) { 
 158                             tnc
.handle_frame(frame
); 
 160                                 let n 
= tnc
.read_kiss(&mut buf
); 
 162                                     let _ 
= kiss_out_tx
.try_send(buf
[0..n
].into
()); 
 169                     tnc
.set_data_carrier_detect(demodulator
.data_carrier_detect()); 
 171                 SoundmodemEvent
::Start 
=> { 
 172                     input
.start(event_tx
.clone()); 
 173                     output
.start(event_tx
.clone(), out_buffer
.clone()); 
 175                 SoundmodemEvent
::Close 
=> { 
 176                     ptt_driver
.ptt_off(); 
 179                 SoundmodemEvent
::DidReadFromOutputBuffer 
{ len
, timestamp 
} => { 
 180                     let (occupied
, internal_latency
) = { 
 181                         let out_buffer 
= out_buffer
.read().unwrap
(); 
 182                         (out_buffer
.samples
.len(), out_buffer
.latency
) 
 184                     let internal_latency 
= (internal_latency
.as_secs_f32() * 48000.0) as usize; 
 185                     let dynamic_latency 
= 
 186                         len
.saturating_sub((timestamp
.elapsed().as_secs_f32() * 48000.0) as usize); 
 187                     modulator
.update
_o
utp
ut
_b
uffer
( 
 190                         internal_latency 
+ dynamic_latency
, 
 193                 SoundmodemEvent
::OutputUnderrun 
=> { 
 194                     // TODO: cancel transmission, send empty data frame to host 
 199             let new_ptt 
= tnc
.ptt(); 
 204                     ptt_driver
.ptt_off(); 
 209             // Let the modulator do what it wants 
 210             while let Some(action
) = modulator
.run() { 
 212                     ModulatorAction
::SetIdle(idling
) => { 
 213                         out_buffer
.write().unwrap
().idl
ing 
= idling
; 
 215                     ModulatorAction
::GetNextFrame 
=> { 
 216                         modulator
.provide_next_frame(tnc
.read_tx_frame()); 
 218                     ModulatorAction
::ReadOutput 
=> loop { 
 219                         let n 
= modulator
.read_output_samples(&mut out_samples
); 
 223                         let mut out_buffer 
= out_buffer
.write().unwrap
(); 
 224                         for s 
in &out_samples
[0..n
] { 
 225                             out_buffer
.samples
.push_back(*s
); 
 228                     ModulatorAction
::TransmissionWillEnd(in_samples
) => { 
 229                         tnc
.set_tx_end_time(in_samples
); 
 237 pub trait InputSource
: Send 
+ Sync 
+ '
static { 
 238     fn start(&self, samples
: SyncSender
<SoundmodemEvent
>); 
 242 pub struct InputSoundcard 
{ 
 243     // TODO: allow for inversion both here and in output 
 244     cpal_name
: Option
<String
>, 
 245     end_tx
: Mutex
<Option
<Sender
<()>>>, 
 248 impl InputSoundcard 
{ 
 249     pub fn new() -> Self { 
 252             end_tx
: Mutex
::new(None
), 
 256     pub fn new_with_card(card_name
: String
) -> Self { 
 258             cpal_name
: Some(card_name
), 
 259             end_tx
: Mutex
::new(None
), 
 264 impl InputSource 
for InputSoundcard 
{ 
 265     fn start(&self, samples
: SyncSender
<SoundmodemEvent
>) { 
 266         let (end_tx
, end_rx
) = channel(); 
 267         let cpal_name 
= self.cpal_name
.clone(); 
 268         std
::thread
::spawn(move || { 
 269             let host 
= cpal
::default_host(); 
 270             let device 
= if let Some(name
) = cpal_name
.as_deref() { 
 273                     .find
(|d
| d
.name().unwrap
() == name
) 
 276                 host
.default_input_device().unwrap
() 
 278             let mut configs 
= device
.supported_input_configs().unwrap
(); 
 280                 .find
(|c
| c
.channels() == 1 && c
.sample_format() == SampleFormat
::I16
) 
 282                 .with_sample_rate(SampleRate(48000)); 
 286                     move |data
: &[i16], _info
: &cpal
::InputCallbackInfo
| { 
 287                         let out
: Vec
<i16> = data
.iter
().map(|s
| *s
).collect(); 
 288                         let _ 
= samples
.try_send(SoundmodemEvent
::BasebandInput(out
.into
())); 
 292                         debug
!("error occurred in soundcard input: {e:?}"); 
 297             stream
.play().unwrap
(); 
 298             let _ 
= end_rx
.recv(); 
 300         *self.end_tx
.lock().unwrap
() = Some(end_tx
); 
 304         let _ 
= self.end_tx
.lock().unwrap
().take(); 
 308 pub struct InputRrcFile 
{ 
 310     end_tx
: Mutex
<Option
<Sender
<()>>>, 
 314     pub fn new(path
: PathBuf
) -> Self { 
 317             end_tx
: Mutex
::new(None
), 
 322 impl InputSource 
for InputRrcFile 
{ 
 323     fn start(&self, samples
: SyncSender
<SoundmodemEvent
>) { 
 324         let (end_tx
, end_rx
) = channel(); 
 325         let path 
= self.path
.clone(); 
 326         std
::thread
::spawn(move || { 
 327             // TODO: error handling 
 328             let mut file 
= File
::open(path
).unwrap
(); 
 329             let mut baseband 
= vec
![]; 
 330             file
.read_to_end(&mut baseband
).unwrap
(); 
 332             // assuming 48 kHz for now 
 333             const TICK
: Duration 
= Duration
::from_millis(25); 
 334             const SAMPLES_PER_TICK
: usize = 1200; 
 336             let mut next_tick 
= Instant
::now() + TICK
; 
 337             let mut buf 
= [0i16; SAMPLES_PER_TICK
]; 
 340             for sample 
in baseband
 
 342                 .map(|pair
| i16::from_le_bytes([pair
[0], pair
[1]])) 
 346                 if idx 
== SAMPLES_PER_TICK 
{ 
 347                     if let Err(e
) = samples
.try_send(SoundmodemEvent
::BasebandInput(buf
.into
())) { 
 348                         debug
!("overflow feeding soundmodem: {e:?}"); 
 350                     next_tick 
= next_tick 
+ TICK
; 
 352                     std
::thread
::sleep(next_tick
.duration_since(Instant
::now())); 
 354                 if end_rx
.try_recv() != Err(TryRecvError
::Empty
) { 
 359         *self.end_tx
.lock().unwrap
() = Some(end_tx
); 
 363         let _ 
= self.end_tx
.lock().unwrap
().take(); 
 367 pub struct NullInputSource 
{ 
 368     end_tx
: Mutex
<Option
<Sender
<()>>>, 
 371 impl NullInputSource 
{ 
 372     pub fn new() -> Self { 
 374             end_tx
: Mutex
::new(None
), 
 379 impl InputSource 
for NullInputSource 
{ 
 380     fn start(&self, samples
: SyncSender
<SoundmodemEvent
>) { 
 381         let (end_tx
, end_rx
) = channel(); 
 382         std
::thread
::spawn(move || { 
 383             // assuming 48 kHz for now 
 384             const TICK
: Duration 
= Duration
::from_millis(25); 
 385             const SAMPLES_PER_TICK
: usize = 1200; 
 386             let mut next_tick 
= Instant
::now() + TICK
; 
 389                 std
::thread
::sleep(next_tick
.duration_since(Instant
::now())); 
 390                 next_tick 
= next_tick 
+ TICK
; 
 391                 if end_rx
.try_recv() != Err(TryRecvError
::Empty
) { 
 394                 if let Err(e
) = samples
.try_send(SoundmodemEvent
::BasebandInput( 
 395                     [0i16; SAMPLES_PER_TICK
].into
(), 
 397                     debug
!("overflow feeding soundmodem: {e:?}"); 
 401         *self.end_tx
.lock().unwrap
() = Some(end_tx
); 
 405         let _ 
= self.end_tx
.lock().unwrap
().take(); 
 409 pub struct OutputBuffer 
{ 
 411     // TODO: something more efficient 
 412     samples
: VecDeque
<i16>, 
 417     pub fn new() -> Self { 
 420             samples
: VecDeque
::new(), 
 421             latency
: Duration
::ZERO
, 
 426 pub trait OutputSink
: Send 
+ Sync 
+ '
static { 
 427     fn start(&self, event_tx
: SyncSender
<SoundmodemEvent
>, buffer
: Arc
<RwLock
<OutputBuffer
>>); 
 431 pub struct OutputRrcFile 
{ 
 433     end_tx
: Mutex
<Option
<Sender
<()>>>, 
 437     pub fn new(path
: PathBuf
) -> Self { 
 440             end_tx
: Mutex
::new(None
), 
 445 impl OutputSink 
for OutputRrcFile 
{ 
 446     fn start(&self, event_tx
: SyncSender
<SoundmodemEvent
>, buffer
: Arc
<RwLock
<OutputBuffer
>>) { 
 447         let (end_tx
, end_rx
) = channel(); 
 448         let path 
= self.path
.clone(); 
 449         std
::thread
::spawn(move || { 
 450             // TODO: error handling 
 451             let mut file 
= File
::create(path
).unwrap
(); 
 453             // assuming 48 kHz for now 
 454             const TICK
: Duration 
= Duration
::from_millis(25); 
 455             const SAMPLES_PER_TICK
: usize = 1200; 
 457             // flattened BE i16s for writing 
 458             let mut buf 
= [0u8; SAMPLES_PER_TICK 
* 2]; 
 459             let mut next_tick 
= Instant
::now() + TICK
; 
 462                 std
::thread
::sleep(next_tick
.duration_since(Instant
::now())); 
 463                 next_tick 
= next_tick 
+ TICK
; 
 464                 if end_rx
.try_recv() != Err(TryRecvError
::Empty
) { 
 467                 // For now only write deliberately modulated (non-idling) samples 
 468                 // Multiple transmissions will get smooshed together 
 469                 let mut buf_used 
= 0; 
 471                 let mut buffer 
= buffer
.write().unwrap
(); 
 472                 for out 
in buf
.chunks_mut(2) { 
 473                     if let Some(s
) = buffer
.samples
.pop_front() { 
 474                         let be 
= s
.to_le_bytes(); 
 475                         out
.copy_from_slice(&[be
[0], be
[1]]); 
 477                     } else if !buffer
.idl
ing 
{ 
 478                         debug
!("output rrc file had underrun"); 
 479                         let _ 
= event_tx
.send(SoundmodemEvent
::OutputUnderrun
); 
 483                 if let Err(e
) = file
.write_all(&buf
[0..buf_used
]) { 
 484                     debug
!("failed to write to rrc file: {e:?}"); 
 487                 let _ 
= event_tx
.send(SoundmodemEvent
::DidReadFromOutputBuffer 
{ 
 489                     timestamp
: Instant
::now(), 
 493         *self.end_tx
.lock().unwrap
() = Some(end_tx
); 
 497         let _ 
= self.end_tx
.lock().unwrap
().take(); 
 501 pub struct NullOutputSink 
{ 
 502     end_tx
: Mutex
<Option
<Sender
<()>>>, 
 505 impl NullOutputSink 
{ 
 506     pub fn new() -> Self { 
 508             end_tx
: Mutex
::new(None
), 
 513 impl OutputSink 
for NullOutputSink 
{ 
 514     fn start(&self, event_tx
: SyncSender
<SoundmodemEvent
>, buffer
: Arc
<RwLock
<OutputBuffer
>>) { 
 515         let (end_tx
, end_rx
) = channel(); 
 516         std
::thread
::spawn(move || { 
 517             // assuming 48 kHz for now 
 518             const TICK
: Duration 
= Duration
::from_millis(25); 
 519             const SAMPLES_PER_TICK
: usize = 1200; 
 520             let mut next_tick 
= Instant
::now() + TICK
; 
 523                 std
::thread
::sleep(next_tick
.duration_since(Instant
::now())); 
 524                 next_tick 
= next_tick 
+ TICK
; 
 525                 if end_rx
.try_recv() != Err(TryRecvError
::Empty
) { 
 529                 let mut buffer 
= buffer
.write().unwrap
(); 
 531                 for _ 
in 0..SAMPLES_PER_TICK 
{ 
 532                     if !buffer
.samples
.pop_front().is
_some
() { 
 534                             debug
!("null output had underrun"); 
 535                             let _ 
= event_tx
.send(SoundmodemEvent
::OutputUnderrun
); 
 542                 let _ 
= event_tx
.send(SoundmodemEvent
::DidReadFromOutputBuffer 
{ 
 544                     timestamp
: Instant
::now(), 
 548         *self.end_tx
.lock().unwrap
() = Some(end_tx
); 
 552         let _ 
= self.end_tx
.lock().unwrap
().take(); 
 556 pub struct OutputSoundcard 
{ 
 557     // TODO: allow for inversion both here and in output 
 558     cpal_name
: Option
<String
>, 
 559     end_tx
: Mutex
<Option
<Sender
<()>>>, 
 562 impl OutputSoundcard 
{ 
 563     pub fn new() -> Self { 
 566             end_tx
: Mutex
::new(None
), 
 570     pub fn new_with_card(card_name
: String
) -> Self { 
 572             cpal_name
: Some(card_name
), 
 573             end_tx
: Mutex
::new(None
), 
 578 impl OutputSink 
for OutputSoundcard 
{ 
 579     fn start(&self, event_tx
: SyncSender
<SoundmodemEvent
>, buffer
: Arc
<RwLock
<OutputBuffer
>>) { 
 580         let (end_tx
, end_rx
) = channel(); 
 581         let cpal_name 
= self.cpal_name
.clone(); 
 582         std
::thread
::spawn(move || { 
 583             let host 
= cpal
::default_host(); 
 584             let device 
= if let Some(name
) = cpal_name
.as_deref() { 
 585                 host
.output_devices() 
 587                     .find
(|d
| d
.name().unwrap
() == name
) 
 590                 host
.default_output_device().unwrap
() 
 592             let mut configs 
= device
.supported_output_configs().unwrap
(); 
 593             // TODO: more error handling 
 595                 .find
(|c
| c
.channels() == 1 && c
.sample_format() == SampleFormat
::I16
) 
 597                 .with_sample_rate(SampleRate(48000)); 
 599                 .build_output_stream( 
 601                     move |data
: &mut [i16], info
: &cpal
::OutputCallbackInfo
| { 
 603                         let ts 
= info
.timestamp(); 
 606                             .duration_since(&ts
.callback
) 
 607                             .unwrap
_or
(Duration
::ZERO
); 
 608                         let mut buffer 
= buffer
.write().unwrap
(); 
 609                         buffer
.latency 
= latency
; 
 610                         for out 
in data
.iter
_m
ut
() { 
 611                             if let Some(s
) = buffer
.samples
.pop_front() { 
 614                             } else if buffer
.idl
ing 
{ 
 617                                 debug
!("output soundcard had underrun"); 
 618                                 let _ 
= event_tx
.send(SoundmodemEvent
::OutputUnderrun
); 
 622                         //debug!("latency is {} ms, taken {taken}", latency.as_millis()); 
 623                         let _ 
= event_tx
.send(SoundmodemEvent
::DidReadFromOutputBuffer 
{ 
 625                             timestamp
: Instant
::now(), 
 630                         debug
!("error occurred in soundcard output: {e:?}"); 
 635             stream
.play().unwrap
(); 
 636             let _ 
= end_rx
.recv(); 
 638         *self.end_tx
.lock().unwrap
() = Some(end_tx
); 
 642         let _ 
= self.end_tx
.lock().unwrap
().take(); 
 646 pub trait Ptt
: Send 
+ '
static { 
 647     fn ptt_on(&mut self); 
 648     fn ptt_off(&mut self); 
 651 /// There is no PTT because this TNC will never make transmissions on a real radio. 
 655     pub fn new() -> Self { 
 660 impl Ptt 
for NullPtt 
{ 
 661     fn ptt_on(&mut self) {} 
 662     fn ptt_off(&mut self) {}