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 {
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");
+ }
}
}
}
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,
.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);
}
}
);
/// Supply the next frame available from the TNC, if it was requested.
- fn next_frame(&mut self, frame: Option<ModulatorFrame>);
+ fn provide_next_frame(&mut self, frame: Option<ModulatorFrame>);
/// Calculate and write out output samples for the soundcard.
///
}
pub struct SoftModulator {
+ // TODO: 2000 was overflowing around EOT, track down why
/// Next modulated frame to output - 1920 samples for 40ms frame plus 80 for ramp-down
- next_transmission: [i16; 2000],
+ next_transmission: [i16; 4000],
/// How much of next_transmission should in fact be transmitted
next_len: usize,
/// How much of next_transmission has been read out
impl SoftModulator {
pub fn new() -> Self {
Self {
- next_transmission: [0i16; 2000],
+ next_transmission: [0i16; 4000],
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,
}
fn push_sample(&mut self, dibit: f32) {
- // 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
- self.filter_win[self.filter_cursor] = dibit * 3785.0;
- self.filter_cursor = (self.filter_cursor + 1) % 81;
- let mut out: f32 = 0.0;
- for i in 0..81 {
- let filter_idx = (self.filter_cursor + i) % 81;
- out += RRC_48K[i] * self.filter_win[filter_idx];
+ // TODO: 48 kHz assumption again
+ 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 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 {
+ self.filter_win[self.filter_cursor] = dibit * 35461.0;
+ } else {
+ self.filter_win[self.filter_cursor] = 0.0;
+ }
+ self.filter_cursor = (self.filter_cursor + 1) % 81;
+ let mut out: f32 = 0.0;
+ for i in 0..81 {
+ let filter_idx = (self.filter_cursor + i) % 81;
+ out += RRC_48K[i] * self.filter_win[filter_idx];
+ }
+ self.next_transmission[self.next_len] = out as i16;
+ self.next_len += 1;
}
- self.next_transmission[self.next_len] = out as i16;
- self.next_len += 1;
}
fn request_frame_if_space(&mut self) {
self.request_frame_if_space();
}
- fn next_frame(&mut self, frame: Option<ModulatorFrame>) {
+ fn provide_next_frame(&mut self, frame: Option<ModulatorFrame>) {
let Some(frame) = frame else {
self.try_get_frame = false;
return;
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
// 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;
// then follow it with whatever might be left in next_transmission
let next_remaining = self.next_len - self.next_read;
if next_remaining > 0 {
- let len = (out.len() - written).max(next_remaining);
+ let len = (out.len() - written).min(next_remaining);
out[written..(written + len)]
.copy_from_slice(&self.next_transmission[self.next_read..(self.next_read + len)]);
self.next_read += len;
}
}
+impl Default for SoftModulator {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
#[derive(Debug)]
pub(crate) struct DecodeCandidate {
burst: SyncBurst,