]> code.octet-stream.net Git - m17rt/blob - m17core/src/modem.rs
fa06af6c8f4bfe24855d46096784408871d72156
[m17rt] / m17core / src / modem.rs
1 use crate::decode::{
2 parse_lsf, parse_packet, parse_stream, sync_burst_correlation, SyncBurst, SYNC_THRESHOLD,
3 };
4 use crate::protocol::{Frame, LsfFrame, PacketFrame, StreamFrame};
5 use crate::shaping::RRC_48K;
6 use log::debug;
7
8 pub trait Demodulator {
9 fn demod(&mut self, sample: i16) -> Option<Frame>;
10 fn data_carrier_detect(&self) -> bool;
11 }
12
13 /// Converts a sequence of samples into frames.
14 pub struct SoftDemodulator {
15 /// Circular buffer of incoming samples for calculating the RRC filtered value
16 filter_win: [i16; 81],
17 /// Current position in filter_win
18 filter_cursor: usize,
19 /// Circular buffer of shaped samples for performing decodes based on the last 192 symbols
20 rx_win: [f32; 1920],
21 /// Current position in rx_cursor
22 rx_cursor: usize,
23 /// A position that we are considering decoding due to decent sync
24 candidate: Option<DecodeCandidate>,
25 /// How many samples have we received?
26 sample: u64,
27 /// Remaining samples to ignore so once we already parse a frame we flush it out in full
28 suppress: u16,
29 }
30
31 impl SoftDemodulator {
32 pub fn new() -> Self {
33 SoftDemodulator {
34 filter_win: [0i16; 81],
35 filter_cursor: 0,
36 rx_win: [0f32; 1920],
37 rx_cursor: 0,
38 candidate: None,
39 sample: 0,
40 suppress: 0,
41 }
42 }
43 }
44
45 impl Demodulator for SoftDemodulator {
46 fn demod(&mut self, sample: i16) -> Option<Frame> {
47 self.filter_win[self.filter_cursor] = sample;
48 self.filter_cursor = (self.filter_cursor + 1) % 81;
49 let mut out: f32 = 0.0;
50 for i in 0..81 {
51 let filter_idx = (self.filter_cursor + i) % 81;
52 out += RRC_48K[i] * self.filter_win[filter_idx] as f32;
53 }
54
55 self.rx_win[self.rx_cursor] = out;
56 self.rx_cursor = (self.rx_cursor + 1) % 1920;
57
58 self.sample += 1;
59
60 if self.suppress > 0 {
61 self.suppress -= 1;
62 return None;
63 }
64
65 let mut burst_window = [0f32; 71];
66 for i in 0..71 {
67 let c = (self.rx_cursor + i) % 1920;
68 burst_window[i] = self.rx_win[c];
69 }
70
71 for burst in [
72 SyncBurst::Lsf,
73 SyncBurst::Bert,
74 SyncBurst::Stream,
75 SyncBurst::Packet,
76 ] {
77 let (diff, max, shift) = sync_burst_correlation(burst.target(), &burst_window);
78 if diff < SYNC_THRESHOLD {
79 let mut new_candidate = true;
80 if let Some(c) = self.candidate.as_mut() {
81 if diff > c.diff {
82 c.age += 1;
83 new_candidate = false;
84 }
85 }
86 if new_candidate {
87 self.candidate = Some(DecodeCandidate {
88 burst,
89 age: 1,
90 diff,
91 gain: max,
92 shift,
93 });
94 }
95 }
96 if diff >= SYNC_THRESHOLD
97 && self
98 .candidate
99 .as_ref()
100 .map(|c| c.burst == burst)
101 .unwrap_or(false)
102 {
103 if let Some(c) = self.candidate.take() {
104 let start_idx = self.rx_cursor + 1920 - (c.age as usize);
105 let start_sample = self.sample - c.age as u64;
106 let mut pkt_samples = [0f32; 192];
107 for i in 0..192 {
108 let rx_idx = (start_idx + i * 10) % 1920;
109 pkt_samples[i] = (self.rx_win[rx_idx] - c.shift) / c.gain;
110 }
111 match c.burst {
112 SyncBurst::Lsf => {
113 debug!(
114 "Found LSF at sample {} diff {} max {} shift {}",
115 start_sample, c.diff, c.gain, c.shift
116 );
117 if let Some(frame) = parse_lsf(&pkt_samples) {
118 self.suppress = 191 * 10;
119 return Some(Frame::Lsf(frame));
120 }
121 }
122 SyncBurst::Bert => {
123 debug!("Found BERT at sample {} diff {}", start_sample, c.diff);
124 }
125 SyncBurst::Stream => {
126 debug!(
127 "Found STREAM at sample {} diff {} max {} shift {}",
128 start_sample, c.diff, c.gain, c.shift
129 );
130 if let Some(frame) = parse_stream(&pkt_samples) {
131 self.suppress = 191 * 10;
132 return Some(Frame::Stream(frame));
133 }
134 }
135 SyncBurst::Packet => {
136 debug!("Found PACKET at sample {} diff {}", start_sample, c.diff);
137 if let Some(frame) = parse_packet(&pkt_samples) {
138 self.suppress = 191 * 10;
139 return Some(Frame::Packet(frame));
140 }
141 }
142 }
143 }
144 }
145 }
146
147 None
148 }
149
150 fn data_carrier_detect(&self) -> bool {
151 false
152 }
153 }
154
155 impl Default for SoftDemodulator {
156 fn default() -> Self {
157 Self::new()
158 }
159 }
160
161 pub trait Modulator {
162 /// Inform the modulator how many samples remain pending for output and latency updates.
163 ///
164 /// For the buffer between `Modulator` and the process which is supplying samples to the
165 /// output sound card, `samples_to_play` is the number of bytes which the modulator has
166 /// provided that have not yet been picked up, and `capacity` is the maximum size we can
167 /// fill this particular buffer, i.e., maximum number of samples.
168 ///
169 /// Furthermore we attempt to track and account for the latency between the output
170 /// soundcard callback, and when those samples will actually be on the wire. CPAL helpfully
171 /// gives us an estimate. The latest estimate of latency is converted to a duration in terms
172 /// of number of samples and provided as `output_latency`.
173 ///
174 /// Finally, we give the modem a monotonic timer expressed in the number of samples of time
175 /// that have elapsed since the modem began operation. When the modulator advises the TNC
176 /// exactly when a transmission is expected to complete, it should be expressed in terms of
177 /// this number.
178 ///
179 /// Call this whenever bytes have been read out of the buffer, or the TNC may have something
180 /// new to send.
181 fn update_output_buffer(
182 &mut self,
183 samples_to_play: usize,
184 capacity: usize,
185 output_latency: usize,
186 now_samples: u64,
187 );
188
189 /// Supply the next frame available from the TNC, if it was requested.
190 fn next_frame(&mut self, frame: Option<ModulatorFrame>);
191
192 /// Calculate and write out output samples for the soundcard.
193 ///
194 /// Returns the number of bytes valid in `out`. Should generally be called in a loop until
195 /// 0 is returned.
196 fn read_output_samples(&mut self, out: &mut [i16]) -> usize;
197
198 /// Run the modulator and receive actions to process.
199 ///
200 /// Should be called in a loop until it returns `None`.
201 fn run(&mut self) -> Option<ModulatorAction>;
202 }
203
204 pub enum ModulatorAction {
205 /// If true, once all samples have been exhausted output should revert to equilibrium.
206 ///
207 /// If false, failure to pick up enough samples for output sound card is an underrun error.
208 SetIdle(bool),
209
210 /// Check with the TNC if there is a frame available for transmission.
211 ///
212 /// Call `next_frame()` with either the next frame, or `None` if TNC has nothing more to offer.
213 GetNextFrame,
214
215 /// Modulator wishes to send samples to the output buffer - call `read_output_samples`.
216 ReadOutput,
217
218 /// Advise the TNC that we will complete sending End Of Transmission at the given time and
219 TransmissionWillEnd(u64),
220 }
221
222 /// Frames for transmission, emitted by the TNC and received by the Modulator.
223 ///
224 /// The TNC is responsible for all timing decisions, making sure these frames are emitted in the
225 /// correct order, breaks between transmissions, PTT and CSMA. If the modulator is given a
226 /// `ModulatorFrame` value, its job is to transmit it immediately by modulating it into the output
227 /// buffer, or otherwise directly after any previously-supplied frames.
228 ///
229 /// The modulator controls the rate at which frames are drawn out of the TNC. Therefore if the send
230 /// rate is too high (or there is too much channel activity) then the effect of this backpressure is
231 /// that the TNC's internal queues will overflow and it will either discard earlier frames in the
232 /// current stream, or some packets awaiting transmission.
233 pub enum ModulatorFrame {
234 Preamble {
235 /// TNC's configured TxDelay setting, increments of 10ms.
236 ///
237 /// TNC fires PTT and it's up to modulator to apply the setting, taking advantage of whatever
238 /// buffering already exists in the sound card to reduce the artificial delay.
239 tx_delay: u8,
240 },
241 Lsf(LsfFrame),
242 Stream(StreamFrame),
243 Packet(PacketFrame),
244 // TODO: BertFrame
245 EndOfTransmission,
246 }
247
248 struct SoftModulator {
249 /// Next modulated frame to output - 1920 samples for 40ms frame plus 80 for ramp-up/ramp-down
250 next_transmission: [i16; 2000],
251 /// How much of next_transmission should in fact be transmitted
252 next_len: usize,
253 /// How much of next_transmission has been read out
254 next_read: usize,
255 /// How many pending zero samples to emit to align start of preamble with PTT taking effect
256 tx_delay_padding: usize,
257
258 /// Do we need to update idle state?
259 update_idle: bool,
260 /// What is that idle status?
261 idle: bool,
262
263 /// Do we need to report that a transmission end time? (True after we encoded an EOT)
264 report_tx_end: bool,
265 /// What will that end time be?
266 tx_end_time: u64,
267
268 /// Circular buffer of incoming samples for calculating the RRC filtered value
269 filter_win: [i16; 81],
270 /// Current position in filter_win
271 filter_cursor: usize,
272
273 /// Should we ask the TNC for another frame. True after each call to update_output_buffer.
274 try_get_frame: bool,
275
276 now: u64,
277 output_latency: usize,
278 samples_in_buf: usize,
279 buf_capacity: usize,
280 }
281
282 impl Modulator for SoftModulator {
283 fn update_output_buffer(
284 &mut self,
285 samples_to_play: usize,
286 capacity: usize,
287 output_latency: usize,
288 now_samples: u64,
289 ) {
290 self.now = now_samples;
291 self.output_latency = output_latency;
292 self.buf_capacity = capacity;
293 self.samples_in_buf = samples_to_play;
294
295 if capacity - samples_to_play >= 2000 {
296 self.try_get_frame = true;
297 }
298 }
299
300 fn next_frame(&mut self, frame: Option<ModulatorFrame>) {
301 match frame {
302 Some(_f) => {}
303 None => {
304 self.try_get_frame = false;
305 }
306 }
307
308 // this is where we write it to next_transmission
309
310 // this is where we set report_tx_end
311 todo!()
312 }
313
314 fn read_output_samples(&mut self, out: &mut [i16]) -> usize {
315 let mut written = 0;
316
317 // if we have pre-TX padding to accommodate TxDelay then expend that first
318 if self.tx_delay_padding > 0 {
319 let len = out.len().max(self.tx_delay_padding);
320 self.tx_delay_padding -= len;
321 for x in 0..len {
322 out[x] = 0;
323 }
324 written += len;
325 }
326
327 // then follow it with whatever might be left in next_transmission
328 let next_remaining = self.next_len - self.next_read;
329 if next_remaining > 0 {
330 let len = (out.len() - written).max(next_remaining);
331 out[written..(written + len)]
332 .copy_from_slice(&self.next_transmission[self.next_read..(self.next_read + len)]);
333 self.next_read += len;
334 written += len;
335 }
336
337 written
338 }
339
340 fn run(&mut self) -> Option<ModulatorAction> {
341 if self.next_read < self.next_len {
342 return Some(ModulatorAction::ReadOutput);
343 }
344
345 if self.update_idle {
346 self.update_idle = false;
347 return Some(ModulatorAction::SetIdle(self.idle));
348 }
349
350 if self.report_tx_end {
351 self.report_tx_end = false;
352 return Some(ModulatorAction::TransmissionWillEnd(self.tx_end_time));
353 }
354
355 if self.try_get_frame {
356 return Some(ModulatorAction::GetNextFrame);
357 }
358
359 None
360 }
361 }
362
363 #[derive(Debug)]
364 pub(crate) struct DecodeCandidate {
365 burst: SyncBurst,
366 age: u8,
367 diff: f32,
368 gain: f32,
369 shift: f32,
370 }