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