]>
code.octet-stream.net Git - m17rt/blob - m17app/src/soundmodem.rs
1 use std
::io
::{self, ErrorKind
, Read
, Write
};
3 use crate::tnc
::{Tnc
, TncError
};
4 use cpal
::traits
::DeviceTrait
;
5 use cpal
::traits
::HostTrait
;
6 use cpal
::traits
::StreamTrait
;
7 use cpal
::{SampleFormat
, SampleRate
};
9 use m17core
::kiss
::MAX_FRAME_LEN
;
10 use m17core
::modem
::{Demodulator
, SoftDemodulator
};
11 use m17core
::tnc
::SoftTnc
;
13 use std
::path
::PathBuf
;
14 use std
::sync
::mpsc
::{channel
, sync_channel
, Receiver
, Sender
, SyncSender
, TryRecvError
};
15 use std
::sync
::{Arc
, Mutex
};
16 use std
::time
::{Duration
, Instant
};
18 pub struct Soundmodem
{
19 event_tx
: SyncSender
<SoundmodemEvent
>,
20 kiss_out_rx
: Arc
<Mutex
<Receiver
<Arc
<[u8]>>>>,
21 partial_kiss_out
: Arc
<Mutex
<Option
<PartialKissOut
>>>,
25 pub fn new_with_input
<T
: InputSource
>(input
: T
) -> Self {
26 // must create TNC here
27 let (event_tx
, event_rx
) = sync_channel(128);
28 let (kiss_out_tx
, kiss_out_rx
) = sync_channel(128);
29 spawn_soundmodem_worker(event_tx
.clone(), event_rx
, kiss_out_tx
, Box
::new(input
));
32 kiss_out_rx
: Arc
::new(Mutex
::new(kiss_out_rx
)),
33 partial_kiss_out
: Arc
::new(Mutex
::new(None
)),
38 struct PartialKissOut
{
43 impl Read
for Soundmodem
{
44 fn read(&mut self, buf
: &mut [u8]) -> io
::Result
<usize> {
46 let mut partial_kiss_out
= self.partial_kiss_out
.lock().unwrap
();
47 if let Some(partial
) = partial_kiss_out
.as_mut() {
48 let remaining
= partial
.output
.len() - partial
.idx
;
49 let to_write
= remaining
.min(buf
.len());
51 .copy_from_slice(&partial
.output
[partial
.idx
..(partial
.idx
+ to_write
)]);
52 if to_write
== remaining
{
53 *partial_kiss_out
= None
;
55 partial
.idx
+= to_write
;
61 let rx
= self.kiss_out_rx
.lock().unwrap
();
63 .map_err(|s
| io
::Error
::new(ErrorKind
::Other
, format
!("{:?}", s
)))?
65 let to_write
= output
.len().min(buf
.len());
66 buf
[0..to_write
].copy_from_slice(&output
[0..to_write
]);
67 if to_write
!= output
.len() {
68 *self.partial_kiss_out
.lock().unwrap
() = Some(PartialKissOut
{
77 impl Write
for Soundmodem
{
78 fn write(&mut self, buf
: &[u8]) -> std
::io
::Result
<usize> {
79 let _
= self.event_tx
.try_send(SoundmodemEvent
::Kiss(buf
.into
()));
83 fn flush(&mut self) -> std
::io
::Result
<()> {
88 impl Tnc
for Soundmodem
{
89 fn try_clone(&mut self) -> Result
<Self, TncError
> {
91 event_tx
: self.event_tx
.clone(),
92 kiss_out_rx
: self.kiss_out_rx
.clone(),
93 partial_kiss_out
: self.partial_kiss_out
.clone(),
97 fn start(&mut self) -> Result
<(), TncError
> {
98 let _
= self.event_tx
.send(SoundmodemEvent
::Start
);
102 fn close(&mut self) -> Result
<(), TncError
> {
103 let _
= self.event_tx
.send(SoundmodemEvent
::Close
);
108 pub enum SoundmodemEvent
{
110 BasebandInput(Arc
<[i16]>),
115 fn spawn_soundmodem_worker(
116 event_tx
: SyncSender
<SoundmodemEvent
>,
117 event_rx
: Receiver
<SoundmodemEvent
>,
118 kiss_out_tx
: SyncSender
<Arc
<[u8]>>,
119 input
: Box
<dyn InputSource
>,
121 std
::thread
::spawn(move || {
122 // TODO: should be able to provide a custom Demodulator for a soundmodem
123 let mut demod
= SoftDemodulator
::new();
124 let mut tnc
= SoftTnc
::new();
125 let mut buf
= [0u8; MAX_FRAME_LEN
];
126 while let Ok(ev
) = event_rx
.recv() {
128 SoundmodemEvent
::Kiss(k
) => {
129 let _n
= tnc
.write_kiss(&k
);
130 // TODO: what does it mean if we fail to write it all?
131 // Probably we have to read frames for tx first - revisit this during tx
133 SoundmodemEvent
::BasebandInput(b
) => {
135 if let Some(frame
) = demod
.demod(*sample
) {
136 tnc
.handle_frame(frame
);
138 let n
= tnc
.read_kiss(&mut buf
);
140 let _
= kiss_out_tx
.try_send(buf
[0..n
].into
());
148 SoundmodemEvent
::Start
=> input
.start(event_tx
.clone()),
149 SoundmodemEvent
::Close
=> break,
155 pub trait InputSource
: Send
+ Sync
+ '
static {
156 fn start(&self, samples
: SyncSender
<SoundmodemEvent
>);
160 pub struct InputSoundcard
{
161 cpal_name
: Option
<String
>,
162 end_tx
: Mutex
<Option
<Sender
<()>>>,
165 impl InputSoundcard
{
166 pub fn new() -> Self {
169 end_tx
: Mutex
::new(None
),
173 pub fn new_with_card(card_name
: String
) -> Self {
175 cpal_name
: Some(card_name
),
176 end_tx
: Mutex
::new(None
),
181 impl InputSource
for InputSoundcard
{
182 fn start(&self, samples
: SyncSender
<SoundmodemEvent
>) {
183 let (end_tx
, end_rx
) = channel();
184 let cpal_name
= self.cpal_name
.clone();
185 std
::thread
::spawn(move || {
186 let host
= cpal
::default_host();
187 let device
= if let Some(name
) = cpal_name
.as_deref() {
190 .find
(|d
| d
.name().unwrap
() == name
)
193 host
.default_input_device().unwrap
()
195 let mut configs
= device
.supported_input_configs().unwrap
();
197 .find
(|c
| c
.channels() == 1 && c
.sample_format() == SampleFormat
::I16
)
199 .with_sample_rate(SampleRate(48000));
203 move |data
: &[i16], _info
: &cpal
::InputCallbackInfo
| {
204 debug
!("input has given us {} samples", data
.len());
205 let out
: Vec
<i16> = data
.iter
().map(|s
| *s
).collect();
206 let _
= samples
.try_send(SoundmodemEvent
::BasebandInput(out
.into
()));
210 debug
!("error occurred in soundcard input: {e:?}");
215 stream
.play().unwrap
();
216 let _
= end_rx
.recv();
218 *self.end_tx
.lock().unwrap
() = Some(end_tx
);
222 let _
= self.end_tx
.lock().unwrap
().take();
226 pub struct InputRrcFile
{
228 end_tx
: Mutex
<Option
<Sender
<()>>>,
232 pub fn new(path
: PathBuf
) -> Self {
235 end_tx
: Mutex
::new(None
),
240 impl InputSource
for InputRrcFile
{
241 fn start(&self, samples
: SyncSender
<SoundmodemEvent
>) {
242 let (end_tx
, end_rx
) = channel();
243 let path
= self.path
.clone();
244 std
::thread
::spawn(move || {
245 // TODO: error handling
246 let mut file
= File
::open(path
).unwrap
();
247 let mut baseband
= vec
![];
248 file
.read_to_end(&mut baseband
).unwrap
();
250 // assuming 48 kHz for now
251 const TICK
: Duration
= Duration
::from_millis(25);
252 const SAMPLES_PER_TICK
: usize = 1200;
254 let mut next_tick
= Instant
::now() + TICK
;
255 let mut buf
= [0i16; SAMPLES_PER_TICK
];
258 for sample
in baseband
260 .map(|pair
| i16::from_le_bytes([pair
[0], pair
[1]]))
264 if idx
== SAMPLES_PER_TICK
{
265 if let Err(e
) = samples
.try_send(SoundmodemEvent
::BasebandInput(buf
.into
())) {
266 debug
!("overflow feeding soundmodem: {e:?}");
268 next_tick
= next_tick
+ TICK
;
270 std
::thread
::sleep(next_tick
.duration_since(Instant
::now()));
272 if end_rx
.try_recv() != Err(TryRecvError
::Empty
) {
277 *self.end_tx
.lock().unwrap
() = Some(end_tx
);
281 let _
= self.end_tx
.lock().unwrap
().take();