X-Git-Url: https://code.octet-stream.net/m17rt/blobdiff_plain/4cfda08117c4288a5408d45db1ef4be82f4facaa..3903e719137aba15d30dd58b8d917965ec602400:/m17core/src/modem.rs diff --git a/m17core/src/modem.rs b/m17core/src/modem.rs index b2ab773..43ad5ac 100644 --- a/m17core/src/modem.rs +++ b/m17core/src/modem.rs @@ -27,8 +27,10 @@ pub struct SoftDemodulator { candidate: Option, /// 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, + /// Do we think there is a data carrier, i.e., channel in use? If so, at what sample does it expire? + dcd: Option, } 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, @@ -311,12 +352,11 @@ impl SoftModulator { for i in 0..10 { // Right now we are encoding everything as 1.0-scaled dibit floats // This is a bit silly but it will do for a minute - // Max theoretical gain from the RRC filter is 4.328 - // Let's bump everything to a baseline of 16383 / 4.328 = 3785.35 - // This is not particularly high but at least we won't ever hit the top + // Max possible gain from the RRC filter with upsampling is about 0.462 + // Let's bump everything to a baseline of 16383 / 0.462 = 35461 + // For normal signals this yields roughly 0.5 magnitude which is plenty if i == 0 { - // 10x the impulse with zeroes between for upsampling - self.filter_win[self.filter_cursor] = dibit * 3785.0 * 10.0; + self.filter_win[self.filter_cursor] = dibit * 35461.0; } else { self.filter_win[self.filter_cursor] = 0.0; } @@ -345,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; @@ -373,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 @@ -420,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; @@ -464,6 +500,12 @@ impl Modulator for SoftModulator { } } +impl Default for SoftModulator { + fn default() -> Self { + Self::new() + } +} + #[derive(Debug)] pub(crate) struct DecodeCandidate { burst: SyncBurst,