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