]> code.octet-stream.net Git - m17rt/blobdiff - m17core/src/modem.rs
Fix timing bugs and add documentation
[m17rt] / m17core / src / modem.rs
index 35a23a74d5eb650716719e062e6ccb0547a2cdb7..43ad5acb3f543279ee7f34b9583121f6c4daf2e8 100644 (file)
@@ -27,8 +27,10 @@ pub struct SoftDemodulator {
     candidate: Option<DecodeCandidate>,
     /// How many samples have we received?
     sample: u64,
-    /// Remaining samples to ignore so once we already parse a frame we flush it out in full
-    suppress: u16,
+    /// Remaining samples to read in before attempting to decode the current candidate
+    samples_until_decode: Option<u16>,
+    /// Do we think there is a data carrier, i.e., channel in use? If so, at what sample does it expire?
+    dcd: Option<u64>,
 }
 
 impl SoftDemodulator {
@@ -40,7 +42,26 @@ impl SoftDemodulator {
             rx_cursor: 0,
             candidate: None,
             sample: 0,
-            suppress: 0,
+            samples_until_decode: None,
+            dcd: None,
+        }
+    }
+}
+
+impl SoftDemodulator {
+    fn dcd_until(&mut self, end_sample: u64) {
+        if self.dcd.is_none() {
+            debug!("SoftDemodulator DCD on");
+        }
+        self.dcd = Some(end_sample);
+    }
+
+    fn check_dcd(&mut self) {
+        if let Some(end_sample) = self.dcd {
+            if self.sample > end_sample {
+                self.dcd = None;
+                debug!("SoftDemodulator DCD off");
+            }
         }
     }
 }
@@ -59,18 +80,67 @@ impl Demodulator for SoftDemodulator {
         self.rx_cursor = (self.rx_cursor + 1) % 1920;
 
         self.sample += 1;
+        self.check_dcd();
 
-        if self.suppress > 0 {
-            self.suppress -= 1;
-            return None;
+        if let Some(samples_until_decode) = self.samples_until_decode {
+            let sud = samples_until_decode - 1;
+            if sud > 0 {
+                self.samples_until_decode = Some(sud);
+                return None;
+            }
+            self.samples_until_decode = None;
+
+            if let Some(c) = self.candidate.take() {
+                // we have capacity for 192 symbols * 10 upsamples
+                // we have calculated that the ideal sample point for 192nd symbol is right on the edge
+                // so take samples from the 10th slot all the way through.
+                let start_idx = self.rx_cursor + 1920 + 9;
+                let mut pkt_samples = [0f32; 192];
+                for i in 0..192 {
+                    let rx_idx = (start_idx + i * 10) % 1920;
+                    pkt_samples[i] = (self.rx_win[rx_idx] - c.shift) / c.gain;
+                }
+                match c.burst {
+                    SyncBurst::Lsf => {
+                        if let Some(frame) = parse_lsf(&pkt_samples) {
+                            return Some(Frame::Lsf(frame));
+                        }
+                    }
+                    SyncBurst::Bert => {
+                        // TODO: BERT
+                    }
+                    SyncBurst::Stream => {
+                        if let Some(frame) = parse_stream(&pkt_samples) {
+                            return Some(Frame::Stream(frame));
+                        }
+                    }
+                    SyncBurst::Packet => {
+                        if let Some(frame) = parse_packet(&pkt_samples) {
+                            return Some(Frame::Packet(frame));
+                        }
+                    }
+                    SyncBurst::Preamble | SyncBurst::EndOfTransmission => {
+                        // should never be chosen as a candidate
+                    }
+                }
+            }
         }
 
-        let mut burst_window = [0f32; 71];
-        for i in 0..71 {
-            let c = (self.rx_cursor + i) % 1920;
+        let mut burst_window = [0f32; 8];
+        for i in 0..8 {
+            let c = (self.rx_cursor + 1920 - 1 - ((7 - i) * 10)) % 1920;
             burst_window[i] = self.rx_win[c];
         }
 
+        for burst in [SyncBurst::Preamble, SyncBurst::EndOfTransmission] {
+            let (diff, _, _) = sync_burst_correlation(burst.target(), &burst_window);
+            if diff < SYNC_THRESHOLD {
+                // arbitrary choice, 240 samples = 5ms
+                // these bursts keep repeating so it will keep pushing out the DCD end time
+                self.dcd_until(self.sample + 240);
+            }
+        }
+
         for burst in [
             SyncBurst::Lsf,
             SyncBurst::Bert,
@@ -103,47 +173,17 @@ impl Demodulator for SoftDemodulator {
                     .map(|c| c.burst == burst)
                     .unwrap_or(false)
             {
-                if let Some(c) = self.candidate.take() {
-                    let start_idx = self.rx_cursor + 1920 - (c.age as usize);
-                    let start_sample = self.sample - c.age as u64;
-                    let mut pkt_samples = [0f32; 192];
-                    for i in 0..192 {
-                        let rx_idx = (start_idx + i * 10) % 1920;
-                        pkt_samples[i] = (self.rx_win[rx_idx] - c.shift) / c.gain;
-                    }
-                    match c.burst {
-                        SyncBurst::Lsf => {
-                            debug!(
-                                "Found LSF at sample {} diff {} max {} shift {}",
-                                start_sample, c.diff, c.gain, c.shift
-                            );
-                            if let Some(frame) = parse_lsf(&pkt_samples) {
-                                self.suppress = 191 * 10;
-                                return Some(Frame::Lsf(frame));
-                            }
-                        }
-                        SyncBurst::Bert => {
-                            debug!("Found BERT at sample {} diff {}", start_sample, c.diff);
-                        }
-                        SyncBurst::Stream => {
-                            debug!(
-                                "Found STREAM at sample {} diff {} max {} shift {}",
-                                start_sample, c.diff, c.gain, c.shift
-                            );
-                            if let Some(frame) = parse_stream(&pkt_samples) {
-                                self.suppress = 191 * 10;
-                                return Some(Frame::Stream(frame));
-                            }
-                        }
-                        SyncBurst::Packet => {
-                            debug!("Found PACKET at sample {} diff {}", start_sample, c.diff);
-                            if let Some(frame) = parse_packet(&pkt_samples) {
-                                self.suppress = 191 * 10;
-                                return Some(Frame::Packet(frame));
-                            }
-                        }
-                    }
-                }
+                // wait until the rest of the frame is in the buffer
+                let c = self.candidate.as_ref().unwrap();
+                self.samples_until_decode = Some((184 * 10) - (c.age as u16));
+                debug!(
+                    "Found {:?} at sample {} diff {}",
+                    c.burst,
+                    self.sample - c.age as u64,
+                    c.diff
+                );
+                // After any of these frame types you would expect to see a full EOT
+                self.dcd_until(self.sample + 1920 + 1920);
             }
         }
 
@@ -293,6 +333,7 @@ impl SoftModulator {
             next_len: 0,
             next_read: 0,
             tx_delay_padding: 0,
+            // TODO: actually set this to false when we are worried about underrun
             update_idle: true,
             idle: true,
             calculate_tx_end: false,
@@ -344,7 +385,6 @@ impl Modulator for SoftModulator {
         capacity: usize,
         output_latency: usize,
     ) {
-        //log::debug!("modulator update_output_buffer {samples_to_play} {capacity} {output_latency}");
         self.output_latency = output_latency;
         self.buf_capacity = capacity;
         self.samples_in_buf = samples_to_play;
@@ -372,12 +412,9 @@ impl Modulator for SoftModulator {
             ModulatorFrame::Preamble { tx_delay } => {
                 // TODO: Stop assuming 48 kHz everywhere. 24 kHz should be fine too.
                 let tx_delay_samples = tx_delay as usize * 480;
-                // TxDelay and output latency have the same effect - account for whichever is bigger.
-                // We want our sound card DAC hitting preamble right when PTT fully engages.
-                // The modulator calls the shots here - TNC hands over Preamble and asserts PTT, then
-                // waits to be told when transmission will be complete. This estimate will not be
-                // made and delivered until we generate the EOT frame.
-                self.tx_delay_padding = tx_delay_samples.max(self.output_latency);
+                // Our output latency gives us a certain amount of unavoidable TxDelay
+                // So only introduce artificial delay if the requested TxDelay exceeds that
+                self.tx_delay_padding = tx_delay_samples.saturating_sub(self.output_latency);
 
                 // We should be starting from a filter_win of zeroes
                 // Transmission is effectively smeared by 80 taps and we'll capture that in EOT
@@ -419,7 +456,7 @@ impl Modulator for SoftModulator {
 
         // if we have pre-TX padding to accommodate TxDelay then expend that first
         if self.tx_delay_padding > 0 {
-            let len = out.len().max(self.tx_delay_padding);
+            let len = out.len().min(self.tx_delay_padding);
             self.tx_delay_padding -= len;
             for x in 0..len {
                 out[x] = 0;
@@ -463,6 +500,12 @@ impl Modulator for SoftModulator {
     }
 }
 
+impl Default for SoftModulator {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 #[derive(Debug)]
 pub(crate) struct DecodeCandidate {
     burst: SyncBurst,