]> code.octet-stream.net Git - m17rt/blob - m17app/src/soundmodem.rs
Encoding LICH and round trip tests
[m17rt] / m17app / src / soundmodem.rs
1 use std::io::{self, ErrorKind, Read, Write};
2
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};
8 use log::debug;
9 use m17core::kiss::MAX_FRAME_LEN;
10 use m17core::modem::{Demodulator, SoftDemodulator};
11 use m17core::tnc::SoftTnc;
12 use std::fs::File;
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};
17
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>>>,
22 }
23
24 impl Soundmodem {
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));
30 Self {
31 event_tx,
32 kiss_out_rx: Arc::new(Mutex::new(kiss_out_rx)),
33 partial_kiss_out: Arc::new(Mutex::new(None)),
34 }
35 }
36 }
37
38 struct PartialKissOut {
39 output: Arc<[u8]>,
40 idx: usize,
41 }
42
43 impl Read for Soundmodem {
44 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
45 {
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());
50 buf[0..to_write]
51 .copy_from_slice(&partial.output[partial.idx..(partial.idx + to_write)]);
52 if to_write == remaining {
53 *partial_kiss_out = None;
54 } else {
55 partial.idx += to_write;
56 }
57 return Ok(to_write);
58 }
59 }
60 let output = {
61 let rx = self.kiss_out_rx.lock().unwrap();
62 rx.recv()
63 .map_err(|s| io::Error::new(ErrorKind::Other, format!("{:?}", s)))?
64 };
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 {
69 output,
70 idx: to_write,
71 })
72 }
73 Ok(to_write)
74 }
75 }
76
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()));
80 Ok(buf.len())
81 }
82
83 fn flush(&mut self) -> std::io::Result<()> {
84 Ok(())
85 }
86 }
87
88 impl Tnc for Soundmodem {
89 fn try_clone(&mut self) -> Result<Self, TncError> {
90 Ok(Self {
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(),
94 })
95 }
96
97 fn start(&mut self) -> Result<(), TncError> {
98 let _ = self.event_tx.send(SoundmodemEvent::Start);
99 Ok(())
100 }
101
102 fn close(&mut self) -> Result<(), TncError> {
103 let _ = self.event_tx.send(SoundmodemEvent::Close);
104 Ok(())
105 }
106 }
107
108 pub enum SoundmodemEvent {
109 Kiss(Arc<[u8]>),
110 BasebandInput(Arc<[i16]>),
111 Start,
112 Close,
113 }
114
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>,
120 ) {
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() {
127 match ev {
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
132 }
133 SoundmodemEvent::BasebandInput(b) => {
134 for sample in &*b {
135 if let Some(frame) = demod.demod(*sample) {
136 tnc.handle_frame(frame);
137 loop {
138 let n = tnc.read_kiss(&mut buf);
139 if n > 0 {
140 let _ = kiss_out_tx.try_send(buf[0..n].into());
141 } else {
142 break;
143 }
144 }
145 }
146 }
147 }
148 SoundmodemEvent::Start => input.start(event_tx.clone()),
149 SoundmodemEvent::Close => break,
150 }
151 }
152 });
153 }
154
155 pub trait InputSource: Send + Sync + 'static {
156 fn start(&self, samples: SyncSender<SoundmodemEvent>);
157 fn close(&self);
158 }
159
160 pub struct InputSoundcard {
161 cpal_name: Option<String>,
162 end_tx: Mutex<Option<Sender<()>>>,
163 }
164
165 impl InputSoundcard {
166 pub fn new() -> Self {
167 Self {
168 cpal_name: None,
169 end_tx: Mutex::new(None),
170 }
171 }
172
173 pub fn new_with_card(card_name: String) -> Self {
174 Self {
175 cpal_name: Some(card_name),
176 end_tx: Mutex::new(None),
177 }
178 }
179 }
180
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() {
188 host.input_devices()
189 .unwrap()
190 .find(|d| d.name().unwrap() == name)
191 .unwrap()
192 } else {
193 host.default_input_device().unwrap()
194 };
195 let mut configs = device.supported_input_configs().unwrap();
196 let config = configs
197 .find(|c| c.channels() == 1 && c.sample_format() == SampleFormat::I16)
198 .unwrap()
199 .with_sample_rate(SampleRate(48000));
200 let stream = device
201 .build_input_stream(
202 &config.into(),
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()));
207 },
208 |e| {
209 // TODO: abort?
210 debug!("error occurred in soundcard input: {e:?}");
211 },
212 None,
213 )
214 .unwrap();
215 stream.play().unwrap();
216 let _ = end_rx.recv();
217 });
218 *self.end_tx.lock().unwrap() = Some(end_tx);
219 }
220
221 fn close(&self) {
222 let _ = self.end_tx.lock().unwrap().take();
223 }
224 }
225
226 pub struct InputRrcFile {
227 path: PathBuf,
228 end_tx: Mutex<Option<Sender<()>>>,
229 }
230
231 impl InputRrcFile {
232 pub fn new(path: PathBuf) -> Self {
233 Self {
234 path,
235 end_tx: Mutex::new(None),
236 }
237 }
238 }
239
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();
249
250 // assuming 48 kHz for now
251 const TICK: Duration = Duration::from_millis(25);
252 const SAMPLES_PER_TICK: usize = 1200;
253
254 let mut next_tick = Instant::now() + TICK;
255 let mut buf = [0i16; SAMPLES_PER_TICK];
256 let mut idx = 0;
257
258 for sample in baseband
259 .chunks(2)
260 .map(|pair| i16::from_le_bytes([pair[0], pair[1]]))
261 {
262 buf[idx] = sample;
263 idx += 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:?}");
267 }
268 next_tick = next_tick + TICK;
269 idx = 0;
270 std::thread::sleep(next_tick.duration_since(Instant::now()));
271 }
272 if end_rx.try_recv() != Err(TryRecvError::Empty) {
273 break;
274 }
275 }
276 });
277 *self.end_tx.lock().unwrap() = Some(end_tx);
278 }
279
280 fn close(&self) {
281 let _ = self.end_tx.lock().unwrap().take();
282 }
283 }