]> code.octet-stream.net Git - m17rt/blob - m17app/src/soundmodem.rs
8b7f80f74aa4765aba9e74bd3667375c87b91c33
[m17rt] / m17app / src / soundmodem.rs
1 use std::io::{self, ErrorKind, Read, Write};
2
3 use crate::tnc::{Tnc, TncError};
4 use log::debug;
5 use m17core::kiss::MAX_FRAME_LEN;
6 use m17core::modem::{Demodulator, SoftDemodulator};
7 use m17core::tnc::SoftTnc;
8 use std::fs::File;
9 use std::path::PathBuf;
10 use std::sync::mpsc::{channel, sync_channel, Receiver, Sender, SyncSender, TryRecvError};
11 use std::sync::{Arc, Mutex};
12 use std::time::{Duration, Instant};
13
14 pub struct Soundmodem {
15 event_tx: SyncSender<SoundmodemEvent>,
16 kiss_out_rx: Arc<Mutex<Receiver<Arc<[u8]>>>>,
17 partial_kiss_out: Arc<Mutex<Option<PartialKissOut>>>,
18 }
19
20 impl Soundmodem {
21 pub fn new_with_input<T: InputSource>(input: T) -> Self {
22 // must create TNC here
23 let (event_tx, event_rx) = sync_channel(128);
24 let (kiss_out_tx, kiss_out_rx) = sync_channel(128);
25 spawn_soundmodem_worker(event_tx.clone(), event_rx, kiss_out_tx, Box::new(input));
26 Self {
27 event_tx,
28 kiss_out_rx: Arc::new(Mutex::new(kiss_out_rx)),
29 partial_kiss_out: Arc::new(Mutex::new(None)),
30 }
31 }
32 }
33
34 struct PartialKissOut {
35 output: Arc<[u8]>,
36 idx: usize,
37 }
38
39 impl Read for Soundmodem {
40 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
41 {
42 let mut partial_kiss_out = self.partial_kiss_out.lock().unwrap();
43 if let Some(partial) = partial_kiss_out.as_mut() {
44 let remaining = partial.output.len() - partial.idx;
45 let to_write = remaining.min(buf.len());
46 buf[0..to_write]
47 .copy_from_slice(&partial.output[partial.idx..(partial.idx + to_write)]);
48 if to_write == remaining {
49 *partial_kiss_out = None;
50 } else {
51 partial.idx += to_write;
52 }
53 return Ok(to_write);
54 }
55 }
56 let output = {
57 let rx = self.kiss_out_rx.lock().unwrap();
58 rx.recv()
59 .map_err(|s| io::Error::new(ErrorKind::Other, format!("{:?}", s)))?
60 };
61 let to_write = output.len().min(buf.len());
62 buf[0..to_write].copy_from_slice(&output[0..to_write]);
63 if to_write != output.len() {
64 *self.partial_kiss_out.lock().unwrap() = Some(PartialKissOut {
65 output,
66 idx: to_write,
67 })
68 }
69 Ok(to_write)
70 }
71 }
72
73 impl Write for Soundmodem {
74 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
75 let _ = self.event_tx.try_send(SoundmodemEvent::Kiss(buf.into()));
76 Ok(buf.len())
77 }
78
79 fn flush(&mut self) -> std::io::Result<()> {
80 Ok(())
81 }
82 }
83
84 impl Tnc for Soundmodem {
85 fn try_clone(&mut self) -> Result<Self, TncError> {
86 Ok(Self {
87 event_tx: self.event_tx.clone(),
88 kiss_out_rx: self.kiss_out_rx.clone(),
89 partial_kiss_out: self.partial_kiss_out.clone(),
90 })
91 }
92
93 fn start(&mut self) -> Result<(), TncError> {
94 let _ = self.event_tx.send(SoundmodemEvent::Start);
95 Ok(())
96 }
97
98 fn close(&mut self) -> Result<(), TncError> {
99 let _ = self.event_tx.send(SoundmodemEvent::Close);
100 Ok(())
101 }
102 }
103
104 pub enum SoundmodemEvent {
105 Kiss(Arc<[u8]>),
106 BasebandInput(Arc<[i16]>),
107 Start,
108 Close,
109 }
110
111 fn spawn_soundmodem_worker(
112 event_tx: SyncSender<SoundmodemEvent>,
113 event_rx: Receiver<SoundmodemEvent>,
114 kiss_out_tx: SyncSender<Arc<[u8]>>,
115 input: Box<dyn InputSource>,
116 ) {
117 std::thread::spawn(move || {
118 // TODO: should be able to provide a custom Demodulator for a soundmodem
119 let mut demod = SoftDemodulator::new();
120 let mut tnc = SoftTnc::new();
121 let mut buf = [0u8; MAX_FRAME_LEN];
122 while let Ok(ev) = event_rx.recv() {
123 match ev {
124 SoundmodemEvent::Kiss(k) => {
125 let _n = tnc.write_kiss(&k);
126 // TODO: what does it mean if we fail to write it all?
127 // Probably we have to read frames for tx first - revisit this during tx
128 }
129 SoundmodemEvent::BasebandInput(b) => {
130 for sample in &*b {
131 if let Some(frame) = demod.demod(*sample) {
132 tnc.handle_frame(frame);
133 loop {
134 let n = tnc.read_kiss(&mut buf);
135 if n > 0 {
136 let _ = kiss_out_tx.try_send(buf[0..n].into());
137 } else {
138 break;
139 }
140 }
141 }
142 }
143 }
144 SoundmodemEvent::Start => input.start(event_tx.clone()),
145 SoundmodemEvent::Close => break,
146 }
147 }
148 });
149 }
150
151 pub trait InputSource: Send + Sync + 'static {
152 fn start(&self, samples: SyncSender<SoundmodemEvent>);
153 fn close(&self);
154 }
155
156 pub struct InputSoundcard {
157 cpal_name: String,
158 }
159
160 impl InputSource for InputSoundcard {
161 fn start(&self, samples: SyncSender<SoundmodemEvent>) {
162 todo!()
163 }
164
165 fn close(&self) {
166 todo!()
167 }
168 }
169
170 pub struct InputRrcFile {
171 path: PathBuf,
172 end_tx: Mutex<Option<Sender<()>>>,
173 }
174
175 impl InputRrcFile {
176 pub fn new(path: PathBuf) -> Self {
177 Self {
178 path,
179 end_tx: Mutex::new(None),
180 }
181 }
182 }
183
184 impl InputSource for InputRrcFile {
185 fn start(&self, samples: SyncSender<SoundmodemEvent>) {
186 let (end_tx, end_rx) = channel();
187 let path = self.path.clone();
188 std::thread::spawn(move || {
189 // TODO: error handling
190 let mut file = File::open(path).unwrap();
191 let mut baseband = vec![];
192 file.read_to_end(&mut baseband).unwrap();
193
194 // assuming 48 kHz for now
195 const TICK: Duration = Duration::from_millis(25);
196 const SAMPLES_PER_TICK: usize = 1200;
197
198 let mut next_tick = Instant::now() + TICK;
199 let mut buf = [0i16; SAMPLES_PER_TICK];
200 let mut idx = 0;
201
202 for sample in baseband
203 .chunks(2)
204 .map(|pair| i16::from_le_bytes([pair[0], pair[1]]))
205 {
206 buf[idx] = sample;
207 idx += 1;
208 if idx == SAMPLES_PER_TICK {
209 if let Err(e) = samples.try_send(SoundmodemEvent::BasebandInput(buf.into())) {
210 debug!("overflow feeding soundmodem: {e:?}");
211 }
212 next_tick = next_tick + TICK;
213 idx = 0;
214 std::thread::sleep(next_tick.duration_since(Instant::now()));
215 }
216 if end_rx.try_recv() != Err(TryRecvError::Empty) {
217 break;
218 }
219 }
220 });
221 *self.end_tx.lock().unwrap() = Some(end_tx);
222 }
223
224 fn close(&self) {
225 let _ = self.end_tx.lock().unwrap().take();
226 }
227 }