]> code.octet-stream.net Git - m17rt/blob - m17core/src/modem.rs
fast demod bin
[m17rt] / m17core / src / modem.rs
1 use crate::decode::{
2 SYNC_THRESHOLD, SyncBurst, parse_lsf, parse_packet, parse_stream, sync_burst_correlation,
3 };
4 use crate::encode::{
5 encode_lsf, encode_packet, encode_stream, generate_end_of_transmission, generate_preamble,
6 };
7 use crate::protocol::{Frame, LsfFrame, PacketFrame, StreamFrame};
8 use crate::shaping::RRC_48K;
9 use log::debug;
10
11 pub trait Demodulator {
12 /// Handle the next sample.
13 ///
14 /// If a frame can be decoded, return it, along with an indication of many errors were fixed by FEC.
15 fn demod(&mut self, sample: i16) -> Option<(Frame, u8)>;
16 /// Does somebody else appear to be transmitting at the moment?
17 fn data_carrier_detect(&self) -> bool;
18 }
19
20 /// Converts a sequence of samples into frames.
21 pub struct SoftDemodulator {
22 /// Circular buffer of incoming samples for calculating the RRC filtered value
23 filter_win: [i16; 81],
24 /// Current position in filter_win
25 filter_cursor: usize,
26 /// Circular buffer of shaped samples for performing decodes based on the last 192 symbols
27 rx_win: [f32; 1920],
28 /// Current position in rx_cursor
29 rx_cursor: usize,
30 /// A position that we are considering decoding due to decent sync
31 candidate: Option<DecodeCandidate>,
32 /// How many samples have we received?
33 sample: u64,
34 /// Remaining samples to read in before attempting to decode the current candidate
35 samples_until_decode: Option<u16>,
36 /// Do we think there is a data carrier, i.e., channel in use? If so, at what sample does it expire?
37 dcd: Option<u64>,
38 }
39
40 impl SoftDemodulator {
41 pub fn new() -> Self {
42 SoftDemodulator {
43 filter_win: [0i16; 81],
44 filter_cursor: 0,
45 rx_win: [0f32; 1920],
46 rx_cursor: 0,
47 candidate: None,
48 sample: 0,
49 samples_until_decode: None,
50 dcd: None,
51 }
52 }
53 }
54
55 impl SoftDemodulator {
56 fn dcd_until(&mut self, end_sample: u64) {
57 if self.dcd.is_none() {
58 debug!("SoftDemodulator DCD on");
59 }
60 self.dcd = Some(end_sample);
61 }
62
63 fn check_dcd(&mut self) {
64 if let Some(end_sample) = self.dcd {
65 if self.sample > end_sample {
66 self.dcd = None;
67 debug!("SoftDemodulator DCD off");
68 }
69 }
70 }
71 }
72
73 impl Demodulator for SoftDemodulator {
74 fn demod(&mut self, sample: i16) -> Option<(Frame, u8)> {
75 self.filter_win[self.filter_cursor] = sample;
76 self.filter_cursor = (self.filter_cursor + 1) % 81;
77 let mut out: f32 = 0.0;
78 for i in 0..81 {
79 let filter_idx = (self.filter_cursor + i) % 81;
80 out += RRC_48K[i] * self.filter_win[filter_idx] as f32;
81 }
82
83 self.rx_win[self.rx_cursor] = out;
84 self.rx_cursor = (self.rx_cursor + 1) % 1920;
85
86 self.sample += 1;
87 self.check_dcd();
88
89 if let Some(samples_until_decode) = self.samples_until_decode {
90 let sud = samples_until_decode - 1;
91 if sud > 0 {
92 self.samples_until_decode = Some(sud);
93 return None;
94 }
95 self.samples_until_decode = None;
96
97 if let Some(c) = self.candidate.take() {
98 // we have capacity for 192 symbols * 10 upsamples
99 // we have calculated that the ideal sample point for 192nd symbol is right on the edge
100 // so take samples from the 10th slot all the way through.
101 let start_idx = self.rx_cursor + 1920 + 9;
102 let mut pkt_samples = [0f32; 192];
103 for i in 0..192 {
104 let rx_idx = (start_idx + i * 10) % 1920;
105 pkt_samples[i] = (self.rx_win[rx_idx] - c.shift) / c.gain;
106 }
107 match c.burst {
108 SyncBurst::Lsf => {
109 if let Some((frame, errors)) = parse_lsf(&pkt_samples) {
110 return Some((Frame::Lsf(frame), errors));
111 }
112 }
113 SyncBurst::Bert => {
114 // TODO: BERT
115 }
116 SyncBurst::Stream => {
117 if let Some((frame, errors)) = parse_stream(&pkt_samples) {
118 return Some((Frame::Stream(frame), errors));
119 }
120 }
121 SyncBurst::Packet => {
122 if let Some((frame, errors)) = parse_packet(&pkt_samples) {
123 return Some((Frame::Packet(frame), errors));
124 }
125 }
126 SyncBurst::Preamble | SyncBurst::EndOfTransmission => {
127 // should never be chosen as a candidate
128 }
129 }
130 }
131 }
132
133 let mut burst_window = [0f32; 8];
134 for i in 0..8 {
135 let c = (self.rx_cursor + 1920 - 1 - ((7 - i) * 10)) % 1920;
136 burst_window[i] = self.rx_win[c];
137 }
138
139 for burst in [SyncBurst::Preamble, SyncBurst::EndOfTransmission] {
140 let (diff, _, _) = sync_burst_correlation(burst.target(), &burst_window);
141 if diff < SYNC_THRESHOLD {
142 // arbitrary choice, 240 samples = 5ms
143 // these bursts keep repeating so it will keep pushing out the DCD end time
144 self.dcd_until(self.sample + 240);
145 }
146 }
147
148 for burst in [
149 SyncBurst::Lsf,
150 SyncBurst::Bert,
151 SyncBurst::Stream,
152 SyncBurst::Packet,
153 ] {
154 let (diff, max, shift) = sync_burst_correlation(burst.target(), &burst_window);
155 if diff < SYNC_THRESHOLD {
156 let mut new_candidate = true;
157 if let Some(c) = self.candidate.as_mut() {
158 if diff > c.diff {
159 c.age += 1;
160 new_candidate = false;
161 }
162 }
163 if new_candidate {
164 self.candidate = Some(DecodeCandidate {
165 burst,
166 age: 1,
167 diff,
168 gain: max,
169 shift,
170 });
171 }
172 }
173 if diff >= SYNC_THRESHOLD
174 && self
175 .candidate
176 .as_ref()
177 .map(|c| c.burst == burst)
178 .unwrap_or(false)
179 {
180 // wait until the rest of the frame is in the buffer
181 let c = self.candidate.as_ref().unwrap();
182 self.samples_until_decode = Some((184 * 10) - (c.age as u16));
183 debug!(
184 "Found {:?} at sample {} diff {}",
185 c.burst,
186 self.sample - c.age as u64,
187 c.diff
188 );
189 // After any of these frame types you would expect to see a full EOT
190 self.dcd_until(self.sample + 1920 + 1920);
191 }
192 }
193
194 None
195 }
196
197 fn data_carrier_detect(&self) -> bool {
198 false
199 }
200 }
201
202 impl Default for SoftDemodulator {
203 fn default() -> Self {
204 Self::new()
205 }
206 }
207
208 pub trait Modulator {
209 /// Inform the modulator how many samples remain pending for output and latency updates.
210 ///
211 /// For the buffer between `Modulator` and the process which is supplying samples to the
212 /// output sound card, `samples_to_play` is the number of bytes which the modulator has
213 /// provided that have not yet been picked up, and `capacity` is the maximum size we can
214 /// fill this particular buffer, i.e., maximum number of samples.
215 ///
216 /// Furthermore we attempt to track and account for the latency between the output
217 /// soundcard callback, and when those samples will actually be on the wire. CPAL helpfully
218 /// gives us an estimate. The latest estimate of latency is converted to a duration in terms
219 /// of number of samples and provided as `output_latency`. Added to this is the current
220 /// number of samples we expect remain to be processed from the last read.
221 ///
222 /// Call this whenever bytes have been read out of the buffer.
223 fn update_output_buffer(
224 &mut self,
225 samples_to_play: usize,
226 capacity: usize,
227 output_latency: usize,
228 );
229
230 /// Supply the next frame available from the TNC, if it was requested.
231 fn provide_next_frame(&mut self, frame: Option<ModulatorFrame>);
232
233 /// Calculate and write out output samples for the soundcard.
234 ///
235 /// Returns the number of bytes valid in `out`. Should generally be called in a loop until
236 /// 0 is returned.
237 fn read_output_samples(&mut self, out: &mut [i16]) -> usize;
238
239 /// Run the modulator and receive actions to process.
240 ///
241 /// Should be called in a loop until it returns `None`.
242 fn run(&mut self) -> Option<ModulatorAction>;
243 }
244
245 pub enum ModulatorAction {
246 /// If true, once all samples have been exhausted output should revert to equilibrium.
247 ///
248 /// If false, failure to pick up enough samples for output sound card is an underrun error.
249 SetIdle(bool),
250
251 /// Check with the TNC if there is a frame available for transmission.
252 ///
253 /// Call `next_frame()` with either the next frame, or `None` if TNC has nothing more to offer.
254 GetNextFrame,
255
256 /// Modulator wishes to send samples to the output buffer - call `read_output_samples`.
257 ReadOutput,
258
259 /// Advise the TNC that we will complete sending End Of Transmission after the given number of
260 /// samples has elapsed, and therefore PTT should be deasserted at this time.
261 TransmissionWillEnd(usize),
262 }
263
264 /// Frames for transmission, emitted by the TNC and received by the Modulator.
265 ///
266 /// The TNC is responsible for all timing decisions, making sure these frames are emitted in the
267 /// correct order, breaks between transmissions, PTT and CSMA. If the modulator is given a
268 /// `ModulatorFrame` value, its job is to transmit it immediately by modulating it into the output
269 /// buffer, or otherwise directly after any previously-supplied frames.
270 ///
271 /// The modulator controls the rate at which frames are drawn out of the TNC. Therefore if the send
272 /// rate is too high (or there is too much channel activity) then the effect of this backpressure is
273 /// that the TNC's internal queues will overflow and it will either discard earlier frames in the
274 /// current stream, or some packets awaiting transmission.
275 pub enum ModulatorFrame {
276 Preamble {
277 /// TNC's configured TxDelay setting, increments of 10ms.
278 ///
279 /// TNC fires PTT and it's up to modulator to apply the setting, taking advantage of whatever
280 /// buffering already exists in the sound card to reduce the artificial delay.
281 tx_delay: u8,
282 },
283 Lsf(LsfFrame),
284 Stream(StreamFrame),
285 Packet(PacketFrame),
286 // TODO: BertFrame
287 EndOfTransmission,
288 }
289
290 pub struct SoftModulator {
291 // TODO: 2000 was overflowing around EOT, track down why
292 /// Next modulated frame to output - 1920 samples for 40ms frame plus 80 for ramp-down
293 next_transmission: [i16; 4000],
294 /// How much of next_transmission should in fact be transmitted
295 next_len: usize,
296 /// How much of next_transmission has been read out
297 next_read: usize,
298 /// How many pending zero samples to emit to align start of preamble with PTT taking effect
299 tx_delay_padding: usize,
300
301 /// Do we need to update idle state?
302 update_idle: bool,
303 /// What is that idle status?
304 idle: bool,
305
306 /// Do we need to calculate a transmission end time?
307 ///
308 /// (True after we encoded an EOT.) We will wait until we get a precise timing update.
309 calculate_tx_end: bool,
310 /// Do we need to report a transmission end time?
311 ///
312 /// This is a duration expressed in number of samples.
313 report_tx_end: Option<usize>,
314
315 /// Circular buffer of most recently output samples for calculating the RRC filtered value.
316 ///
317 /// This should naturally degrade to an oldest value plus 80 zeroes after an EOT.
318 filter_win: [f32; 81],
319 /// Current position in filter_win
320 filter_cursor: usize,
321
322 /// Should we ask the TNC for another frame. True after each call to update_output_buffer.
323 try_get_frame: bool,
324
325 /// Expected delay beyond the buffer to reach the DAC
326 output_latency: usize,
327 /// Number of samples we have placed in the buffer for the output soundcard not yet picked up.
328 samples_in_buf: usize,
329 /// Total size to which the output buffer is allowed to expand.
330 buf_capacity: usize,
331 }
332
333 impl SoftModulator {
334 pub fn new() -> Self {
335 Self {
336 next_transmission: [0i16; 4000],
337 next_len: 0,
338 next_read: 0,
339 tx_delay_padding: 0,
340 // TODO: actually set this to false when we are worried about underrun
341 update_idle: true,
342 idle: true,
343 calculate_tx_end: false,
344 report_tx_end: None,
345 filter_win: [0f32; 81],
346 filter_cursor: 0,
347 try_get_frame: false,
348 output_latency: 0,
349 samples_in_buf: 0,
350 buf_capacity: 0,
351 }
352 }
353
354 fn push_sample(&mut self, dibit: f32) {
355 // TODO: 48 kHz assumption again
356 for i in 0..10 {
357 // Right now we are encoding everything as 1.0-scaled dibit floats
358 // This is a bit silly but it will do for a minute
359 // Max possible gain from the RRC filter with upsampling is about 0.462
360 // Let's bump everything to a baseline of 16383 / 0.462 = 35461
361 // For normal signals this yields roughly 0.5 magnitude which is plenty
362 if i == 0 {
363 self.filter_win[self.filter_cursor] = dibit * 35461.0;
364 } else {
365 self.filter_win[self.filter_cursor] = 0.0;
366 }
367 self.filter_cursor = (self.filter_cursor + 1) % 81;
368 let mut out: f32 = 0.0;
369 for i in 0..81 {
370 let filter_idx = (self.filter_cursor + i) % 81;
371 out += RRC_48K[i] * self.filter_win[filter_idx];
372 }
373 self.next_transmission[self.next_len] = out as i16;
374 self.next_len += 1;
375 }
376 }
377
378 fn request_frame_if_space(&mut self) {
379 if self.buf_capacity - self.samples_in_buf >= 2000 {
380 self.try_get_frame = true;
381 }
382 }
383 }
384
385 impl Modulator for SoftModulator {
386 fn update_output_buffer(
387 &mut self,
388 samples_to_play: usize,
389 capacity: usize,
390 output_latency: usize,
391 ) {
392 self.output_latency = output_latency;
393 self.buf_capacity = capacity;
394 self.samples_in_buf = samples_to_play;
395
396 if self.calculate_tx_end {
397 self.calculate_tx_end = false;
398 // next_transmission should already have been read out to the buffer by now
399 // so we don't have to consider it
400 self.report_tx_end = Some(self.samples_in_buf + self.output_latency);
401 }
402
403 self.request_frame_if_space();
404 }
405
406 fn provide_next_frame(&mut self, frame: Option<ModulatorFrame>) {
407 let Some(frame) = frame else {
408 self.try_get_frame = false;
409 return;
410 };
411
412 self.next_len = 0;
413 self.next_read = 0;
414
415 match frame {
416 ModulatorFrame::Preamble { tx_delay } => {
417 // TODO: Stop assuming 48 kHz everywhere. 24 kHz should be fine too.
418 let tx_delay_samples = tx_delay as usize * 480;
419 // Our output latency gives us a certain amount of unavoidable TxDelay
420 // So only introduce artificial delay if the requested TxDelay exceeds that
421 self.tx_delay_padding = tx_delay_samples.saturating_sub(self.output_latency);
422
423 // We should be starting from a filter_win of zeroes
424 // Transmission is effectively smeared by 80 taps and we'll capture that in EOT
425 for dibit in generate_preamble() {
426 self.push_sample(dibit);
427 }
428 }
429 ModulatorFrame::Lsf(lsf_frame) => {
430 for dibit in encode_lsf(&lsf_frame) {
431 self.push_sample(dibit);
432 }
433 }
434 ModulatorFrame::Stream(stream_frame) => {
435 for dibit in encode_stream(&stream_frame) {
436 self.push_sample(dibit);
437 }
438 }
439 ModulatorFrame::Packet(packet_frame) => {
440 for dibit in encode_packet(&packet_frame) {
441 self.push_sample(dibit);
442 }
443 }
444 ModulatorFrame::EndOfTransmission => {
445 for dibit in generate_end_of_transmission() {
446 self.push_sample(dibit);
447 }
448 for _ in 0..80 {
449 // This is not a real symbol value
450 // However we want to flush the filter
451 self.push_sample(0f32);
452 }
453 self.calculate_tx_end = true;
454 }
455 }
456 }
457
458 fn read_output_samples(&mut self, out: &mut [i16]) -> usize {
459 let mut written = 0;
460
461 // if we have pre-TX padding to accommodate TxDelay then expend that first
462 if self.tx_delay_padding > 0 {
463 let len = out.len().min(self.tx_delay_padding);
464 self.tx_delay_padding -= len;
465 for x in 0..len {
466 out[x] = 0;
467 }
468 written += len;
469 }
470
471 // then follow it with whatever might be left in next_transmission
472 let next_remaining = self.next_len - self.next_read;
473 if next_remaining > 0 {
474 let len = (out.len() - written).min(next_remaining);
475 out[written..(written + len)]
476 .copy_from_slice(&self.next_transmission[self.next_read..(self.next_read + len)]);
477 self.next_read += len;
478 written += len;
479 }
480
481 written
482 }
483
484 fn run(&mut self) -> Option<ModulatorAction> {
485 // Time-sensitive for accuracy, so handle it first
486 if let Some(end) = self.report_tx_end.take() {
487 return Some(ModulatorAction::TransmissionWillEnd(end));
488 }
489
490 if self.next_read < self.next_len {
491 return Some(ModulatorAction::ReadOutput);
492 }
493
494 if self.update_idle {
495 self.update_idle = false;
496 return Some(ModulatorAction::SetIdle(self.idle));
497 }
498
499 if self.try_get_frame {
500 return Some(ModulatorAction::GetNextFrame);
501 }
502
503 None
504 }
505 }
506
507 impl Default for SoftModulator {
508 fn default() -> Self {
509 Self::new()
510 }
511 }
512
513 #[derive(Debug)]
514 pub(crate) struct DecodeCandidate {
515 burst: SyncBurst,
516 age: u8,
517 diff: f32,
518 gain: f32,
519 shift: f32,
520 }