]>
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) {}