[refactor](trx-ftx): move ft2_encode to ft2 module, remove all allow clauses
Move ft2_encode from ft4/ to ft2/ where it belongs. Remove all module-level #[allow] suppressions and fix the underlying issues: - Remove dead code: wf_mag_at, xor_rows, unused Monitor IFFT fields, OsdBox.size - Gate encode174_to_bits with #[cfg(test)] (only used in tests) - Convert 40+ C-style index loops to idiomatic iterators - Add targeted #[allow(clippy::too_many_arguments)] on two OSD functions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -49,16 +49,7 @@ pub(crate) fn get_cand_offset(wf: &Waterfall, cand: &Candidate) -> usize {
|
|||||||
offset.max(0) as usize
|
offset.max(0) as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn wf_mag_at(wf: &Waterfall, base: usize, idx: isize) -> &WfElem {
|
// Default element for out-of-bounds waterfall access
|
||||||
let i = (base as isize + idx).max(0) as usize;
|
|
||||||
if i < wf.mag.len() {
|
|
||||||
&wf.mag[i]
|
|
||||||
} else {
|
|
||||||
&DEFAULT_WF_ELEM
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Leaked reference for out-of-bounds default
|
|
||||||
pub(crate) static DEFAULT_WF_ELEM: WfElem = WfElem {
|
pub(crate) static DEFAULT_WF_ELEM: WfElem = WfElem {
|
||||||
mag: -120.0,
|
mag: -120.0,
|
||||||
phase: 0.0,
|
phase: 0.0,
|
||||||
@@ -256,8 +247,8 @@ pub fn pack_bits(bit_array: &[u8], num_bits: usize, packed: &mut [u8]) {
|
|||||||
}
|
}
|
||||||
let mut mask: u8 = 0x80;
|
let mut mask: u8 = 0x80;
|
||||||
let mut byte_idx = 0;
|
let mut byte_idx = 0;
|
||||||
for i in 0..num_bits {
|
for &bit in bit_array.iter().take(num_bits) {
|
||||||
if bit_array[i] != 0 {
|
if bit != 0 {
|
||||||
packed[byte_idx] |= mask;
|
packed[byte_idx] |= mask;
|
||||||
}
|
}
|
||||||
mask >>= 1;
|
mask >>= 1;
|
||||||
@@ -312,19 +303,19 @@ pub fn ftx_post_decode_snr(wf: &Waterfall, cand: &Candidate, message: &FtxMessag
|
|||||||
let mut sum_snr = 0.0f32;
|
let mut sum_snr = 0.0f32;
|
||||||
let mut n_valid = 0;
|
let mut n_valid = 0;
|
||||||
|
|
||||||
for sym in 0..nn {
|
for (sym, &tone) in tones.iter().enumerate().take(nn) {
|
||||||
let block_abs = cand.time_offset as i32 + sym as i32;
|
let block_abs = cand.time_offset as i32 + sym as i32;
|
||||||
if block_abs < 0 || block_abs >= wf.num_blocks as i32 {
|
if block_abs < 0 || block_abs >= wf.num_blocks as i32 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let p_offset = base + sym * wf.block_stride;
|
let p_offset = base + sym * wf.block_stride;
|
||||||
let sig_db = wf_mag_safe(wf, p_offset + tones[sym] as usize).mag;
|
let sig_db = wf_mag_safe(wf, p_offset + tone as usize).mag;
|
||||||
|
|
||||||
let mut noise_min = 0.0f32;
|
let mut noise_min = 0.0f32;
|
||||||
let mut found_noise = false;
|
let mut found_noise = false;
|
||||||
for t in 0..num_tones {
|
for t in 0..num_tones {
|
||||||
if t == tones[sym] as usize {
|
if t == tone as usize {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let db = wf_mag_safe(wf, p_offset + t).mag;
|
let db = wf_mag_safe(wf, p_offset + t).mag;
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ pub(crate) fn encode174(message: &[u8], codeword: &mut [u8]) {
|
|||||||
let mut col_idx: usize = FTX_LDPC_K_BYTES - 1;
|
let mut col_idx: usize = FTX_LDPC_K_BYTES - 1;
|
||||||
|
|
||||||
// Compute the LDPC checksum bits and store them in codeword
|
// Compute the LDPC checksum bits and store them in codeword
|
||||||
for i in 0..FTX_LDPC_M {
|
for gen_row in FTX_LDPC_GENERATOR.iter().take(FTX_LDPC_M) {
|
||||||
let mut nsum: u8 = 0;
|
let mut nsum: u8 = 0;
|
||||||
for j in 0..FTX_LDPC_K_BYTES {
|
for j in 0..FTX_LDPC_K_BYTES {
|
||||||
nsum ^= parity8(message[j] & FTX_LDPC_GENERATOR[i][j]);
|
nsum ^= parity8(message[j] & gen_row[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !nsum.is_multiple_of(2) {
|
if !nsum.is_multiple_of(2) {
|
||||||
@@ -55,7 +55,7 @@ pub(crate) fn encode174(message: &[u8], codeword: &mut [u8]) {
|
|||||||
///
|
///
|
||||||
/// Each element of the returned array is 0 or 1.
|
/// Each element of the returned array is 0 or 1.
|
||||||
/// Uses the same (174, 91) LDPC generator as `encode174`.
|
/// Uses the same (174, 91) LDPC generator as `encode174`.
|
||||||
#[allow(dead_code)]
|
#[cfg(test)]
|
||||||
pub(crate) fn encode174_to_bits(a91: &[u8; FTX_LDPC_K_BYTES]) -> [u8; super::protocol::FTX_LDPC_N] {
|
pub(crate) fn encode174_to_bits(a91: &[u8; FTX_LDPC_K_BYTES]) -> [u8; super::protocol::FTX_LDPC_N] {
|
||||||
use super::protocol::FTX_LDPC_N;
|
use super::protocol::FTX_LDPC_N;
|
||||||
let mut codeword_packed = [0u8; FTX_LDPC_N_BYTES];
|
let mut codeword_packed = [0u8; FTX_LDPC_N_BYTES];
|
||||||
|
|||||||
@@ -247,8 +247,7 @@ fn parse_cq_modifier(s: &str) -> Option<i32> {
|
|||||||
let mut nlet = 0;
|
let mut nlet = 0;
|
||||||
let mut m: i32 = 0;
|
let mut m: i32 = 0;
|
||||||
|
|
||||||
for i in 3..8.min(bytes.len()) {
|
for &c in &bytes[3..8.min(bytes.len())] {
|
||||||
let c = bytes[i];
|
|
||||||
if c == b' ' || c == 0 {
|
if c == b' ' || c == 0 {
|
||||||
break;
|
break;
|
||||||
} else if c.is_ascii_digit() {
|
} else if c.is_ascii_digit() {
|
||||||
@@ -511,16 +510,14 @@ fn pack58(hash_table: Option<&mut CallsignHashTable>, callsign: &str) -> Option<
|
|||||||
|
|
||||||
let mut result: u64 = 0;
|
let mut result: u64 = 0;
|
||||||
let mut c11 = String::with_capacity(12);
|
let mut c11 = String::with_capacity(12);
|
||||||
let mut length = 0;
|
|
||||||
|
|
||||||
for ch in src.chars() {
|
for (length, ch) in src.chars().enumerate() {
|
||||||
if ch == '<' || length >= 11 {
|
if ch == '<' || length >= 11 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
c11.push(ch);
|
c11.push(ch);
|
||||||
let j = nchar(ch, CharTable::AlphanumSpaceSlash)?;
|
let j = nchar(ch, CharTable::AlphanumSpaceSlash)?;
|
||||||
result = result * 38 + j as u64;
|
result = result * 38 + j as u64;
|
||||||
length += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
save_callsign(hash_table, &c11)?;
|
save_callsign(hash_table, &c11)?;
|
||||||
@@ -1156,9 +1153,9 @@ pub fn ftx_message_decode_free(msg: &FtxMessage) -> String {
|
|||||||
for idx in (0..13).rev() {
|
for idx in (0..13).rev() {
|
||||||
// Divide the long integer in b71 by 42
|
// Divide the long integer in b71 by 42
|
||||||
let mut rem: u16 = 0;
|
let mut rem: u16 = 0;
|
||||||
for i in 0..9 {
|
for b in &mut b71 {
|
||||||
rem = (rem << 8) | b71[i] as u16;
|
rem = (rem << 8) | *b as u16;
|
||||||
b71[i] = (rem / 42) as u8;
|
*b = (rem / 42) as u8;
|
||||||
rem %= 42;
|
rem %= 42;
|
||||||
}
|
}
|
||||||
c14[idx] = charn(rem as i32, CharTable::Full) as u8;
|
c14[idx] = charn(rem as i32, CharTable::Full) as u8;
|
||||||
@@ -1183,9 +1180,9 @@ pub fn ftx_message_decode_telemetry_hex(msg: &FtxMessage) -> String {
|
|||||||
pub fn ftx_message_decode_telemetry(msg: &FtxMessage) -> [u8; 9] {
|
pub fn ftx_message_decode_telemetry(msg: &FtxMessage) -> [u8; 9] {
|
||||||
let mut telemetry = [0u8; 9];
|
let mut telemetry = [0u8; 9];
|
||||||
let mut carry: u8 = 0;
|
let mut carry: u8 = 0;
|
||||||
for i in 0..9 {
|
for (t, &p) in telemetry.iter_mut().zip(msg.payload.iter()) {
|
||||||
telemetry[i] = (carry << 7) | (msg.payload[i] >> 1);
|
*t = (carry << 7) | (p >> 1);
|
||||||
carry = msg.payload[i] & 0x01;
|
carry = p & 0x01;
|
||||||
}
|
}
|
||||||
telemetry
|
telemetry
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,11 @@
|
|||||||
pub mod callsign_hash;
|
pub mod callsign_hash;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod crc;
|
pub mod crc;
|
||||||
#[allow(dead_code, clippy::needless_range_loop)]
|
|
||||||
pub mod decode;
|
pub mod decode;
|
||||||
#[allow(clippy::needless_range_loop)]
|
|
||||||
pub mod encode;
|
pub mod encode;
|
||||||
#[allow(clippy::needless_range_loop)]
|
|
||||||
pub mod ldpc;
|
pub mod ldpc;
|
||||||
#[allow(clippy::explicit_counter_loop, clippy::needless_range_loop)]
|
|
||||||
pub mod message;
|
pub mod message;
|
||||||
#[allow(dead_code)]
|
|
||||||
pub mod monitor;
|
pub mod monitor;
|
||||||
#[allow(dead_code, clippy::needless_range_loop, clippy::too_many_arguments)]
|
|
||||||
pub mod osd;
|
pub mod osd;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
use num_complex::Complex32;
|
use num_complex::Complex32;
|
||||||
use realfft::RealFftPlanner;
|
use realfft::RealFftPlanner;
|
||||||
use rustfft::FftPlanner;
|
|
||||||
|
|
||||||
use super::protocol::FtxProtocol;
|
use super::protocol::FtxProtocol;
|
||||||
|
|
||||||
@@ -94,10 +93,6 @@ pub struct Monitor {
|
|||||||
fft_output: Vec<Complex32>,
|
fft_output: Vec<Complex32>,
|
||||||
fft_input: Vec<f32>,
|
fft_input: Vec<f32>,
|
||||||
real_fft: std::sync::Arc<dyn realfft::RealToComplex<f32>>,
|
real_fft: std::sync::Arc<dyn realfft::RealToComplex<f32>>,
|
||||||
// iFFT for resynthesis
|
|
||||||
nifft: usize,
|
|
||||||
ifft: std::sync::Arc<dyn rustfft::Fft<f32>>,
|
|
||||||
ifft_scratch: Vec<Complex32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hann_i(i: usize, n: usize) -> f32 {
|
fn hann_i(i: usize, n: usize) -> f32 {
|
||||||
@@ -137,11 +132,6 @@ impl Monitor {
|
|||||||
let fft_output = real_fft.make_output_vec();
|
let fft_output = real_fft.make_output_vec();
|
||||||
let fft_input = real_fft.make_input_vec();
|
let fft_input = real_fft.make_input_vec();
|
||||||
|
|
||||||
let nifft = 64;
|
|
||||||
let mut fft_planner = FftPlanner::<f32>::new();
|
|
||||||
let ifft = fft_planner.plan_fft_inverse(nifft);
|
|
||||||
let ifft_scratch = vec![Complex32::new(0.0, 0.0); ifft.get_inplace_scratch_len()];
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
symbol_period,
|
symbol_period,
|
||||||
min_bin,
|
min_bin,
|
||||||
@@ -158,9 +148,6 @@ impl Monitor {
|
|||||||
fft_output,
|
fft_output,
|
||||||
fft_input,
|
fft_input,
|
||||||
real_fft,
|
real_fft,
|
||||||
nifft,
|
|
||||||
ifft,
|
|
||||||
ifft_scratch,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,14 +76,6 @@ fn encode174_91_nocrc_bits(message91: &[u8], codeword: &mut [u8; FTX_LDPC_N]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// XOR two byte slices.
|
|
||||||
fn xor_rows(dst: &mut [u8], src: &[u8], len: usize) {
|
|
||||||
dst[..len]
|
|
||||||
.iter_mut()
|
|
||||||
.zip(&src[..len])
|
|
||||||
.for_each(|(d, s)| *d ^= s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Matrix-vector multiply for re-encoding in OSD.
|
/// Matrix-vector multiply for re-encoding in OSD.
|
||||||
fn mrbencode91(me: &[u8], codeword: &mut [u8], g2: &[u8], n: usize, k: usize) {
|
fn mrbencode91(me: &[u8], codeword: &mut [u8], g2: &[u8], n: usize, k: usize) {
|
||||||
codeword[..n].fill(0);
|
codeword[..n].fill(0);
|
||||||
@@ -114,24 +106,20 @@ fn nextpat91(mi: &mut [u8], k: usize, iorder: usize, iflag: &mut i32) {
|
|||||||
|
|
||||||
// Build new pattern in-place: zero out after ind, set the swap, pack remaining 1s at end
|
// Build new pattern in-place: zero out after ind, set the swap, pack remaining 1s at end
|
||||||
let ind_u = ind as usize;
|
let ind_u = ind as usize;
|
||||||
for i in (ind_u + 1)..k {
|
mi[(ind_u + 1)..k].fill(0);
|
||||||
mi[i] = 0;
|
|
||||||
}
|
|
||||||
mi[ind_u] = 1;
|
mi[ind_u] = 1;
|
||||||
|
|
||||||
let mut nz = iorder as i32;
|
let mut nz = iorder as i32;
|
||||||
for i in 0..k {
|
for &v in mi.iter().take(k) {
|
||||||
nz -= mi[i] as i32;
|
nz -= v as i32;
|
||||||
}
|
}
|
||||||
if nz > 0 {
|
if nz > 0 {
|
||||||
for i in (k - nz as usize)..k {
|
mi[(k - nz as usize)..k].fill(1);
|
||||||
mi[i] = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*iflag = -1;
|
*iflag = -1;
|
||||||
for i in 0..k {
|
for (i, &v) in mi.iter().enumerate().take(k) {
|
||||||
if mi[i] == 1 {
|
if v == 1 {
|
||||||
*iflag = i as i32;
|
*iflag = i as i32;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -145,7 +133,6 @@ struct OsdBox {
|
|||||||
pairs: Vec<[i32; 2]>,
|
pairs: Vec<[i32; 2]>,
|
||||||
capacity: usize,
|
capacity: usize,
|
||||||
count: usize,
|
count: usize,
|
||||||
size: usize,
|
|
||||||
last_pattern: i32,
|
last_pattern: i32,
|
||||||
next_index: i32,
|
next_index: i32,
|
||||||
}
|
}
|
||||||
@@ -160,7 +147,6 @@ impl OsdBox {
|
|||||||
pairs: vec![[-1, -1]; capacity],
|
pairs: vec![[-1, -1]; capacity],
|
||||||
capacity,
|
capacity,
|
||||||
count: 0,
|
count: 0,
|
||||||
size,
|
|
||||||
last_pattern: -1,
|
last_pattern: -1,
|
||||||
next_index: -1,
|
next_index: -1,
|
||||||
})
|
})
|
||||||
@@ -214,8 +200,8 @@ impl OsdBox {
|
|||||||
/// Compute hash of a bit pattern for OSD-2 lookup.
|
/// Compute hash of a bit pattern for OSD-2 lookup.
|
||||||
fn pattern_hash(e2: &[u8], ntau: usize) -> usize {
|
fn pattern_hash(e2: &[u8], ntau: usize) -> usize {
|
||||||
let mut ipat = 0usize;
|
let mut ipat = 0usize;
|
||||||
for i in 0..ntau {
|
for (i, &v) in e2.iter().enumerate().take(ntau) {
|
||||||
if e2[i] != 0 {
|
if v != 0 {
|
||||||
ipat |= 1 << (ntau - i - 1);
|
ipat |= 1 << (ntau - i - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,6 +218,7 @@ fn pattern_hash(e2: &[u8], ntau: usize) -> usize {
|
|||||||
/// `cw`: output 174-bit codeword.
|
/// `cw`: output 174-bit codeword.
|
||||||
/// `nhardmin`: output minimum hard errors.
|
/// `nhardmin`: output minimum hard errors.
|
||||||
/// `dmin`: output minimum distance.
|
/// `dmin`: output minimum distance.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn osd174_91(
|
pub fn osd174_91(
|
||||||
llr: &mut [f32; FTX_LDPC_N],
|
llr: &mut [f32; FTX_LDPC_N],
|
||||||
k: usize,
|
k: usize,
|
||||||
@@ -372,9 +359,7 @@ pub fn osd174_91(
|
|||||||
// OSD-1: exhaustive search over bit patterns of increasing order
|
// OSD-1: exhaustive search over bit patterns of increasing order
|
||||||
for iorder in 1..=nord {
|
for iorder in 1..=nord {
|
||||||
misub.iter_mut().for_each(|v| *v = 0);
|
misub.iter_mut().for_each(|v| *v = 0);
|
||||||
for i in (k - iorder)..k {
|
misub[(k - iorder)..k].fill(1);
|
||||||
misub[i] = 1;
|
|
||||||
}
|
|
||||||
let mut iflag = (k - iorder) as i32;
|
let mut iflag = (k - iorder) as i32;
|
||||||
|
|
||||||
while iflag >= 0 {
|
while iflag >= 0 {
|
||||||
@@ -408,8 +393,8 @@ pub fn osd174_91(
|
|||||||
e2[i] = e2sub[i];
|
e2[i] = e2sub[i];
|
||||||
}
|
}
|
||||||
let mut nd1kpt = 1;
|
let mut nd1kpt = 1;
|
||||||
for i in 0..nt.min(n - k) {
|
for &v in e2sub.iter().take(nt.min(n - k)) {
|
||||||
nd1kpt += e2sub[i] as i32;
|
nd1kpt += v as i32;
|
||||||
}
|
}
|
||||||
d1 = 0.0;
|
d1 = 0.0;
|
||||||
for i in 0..k {
|
for i in 0..k {
|
||||||
@@ -438,8 +423,8 @@ pub fn osd174_91(
|
|||||||
e2[i] = e2sub[i] ^ g2[(k + i) * k + n1 as usize];
|
e2[i] = e2sub[i] ^ g2[(k + i) * k + n1 as usize];
|
||||||
}
|
}
|
||||||
let mut nd1kpt = 2;
|
let mut nd1kpt = 2;
|
||||||
for i in 0..nt.min(n - k) {
|
for &v in e2.iter().take(nt.min(n - k)) {
|
||||||
nd1kpt += e2[i] as i32;
|
nd1kpt += v as i32;
|
||||||
}
|
}
|
||||||
if nd1kpt <= ntheta {
|
if nd1kpt <= ntheta {
|
||||||
mrbencode91(&me, &mut ce, &g2, n, k);
|
mrbencode91(&me, &mut ce, &g2, n, k);
|
||||||
@@ -486,9 +471,7 @@ pub fn osd174_91(
|
|||||||
|
|
||||||
// Search using base patterns
|
// Search using base patterns
|
||||||
misub.iter_mut().for_each(|v| *v = 0);
|
misub.iter_mut().for_each(|v| *v = 0);
|
||||||
for i in (k - nord)..k {
|
misub[(k - nord)..k].fill(1);
|
||||||
misub[i] = 1;
|
|
||||||
}
|
|
||||||
let mut iflag = (k - nord) as i32;
|
let mut iflag = (k - nord) as i32;
|
||||||
|
|
||||||
while iflag >= 0 {
|
while iflag >= 0 {
|
||||||
@@ -594,9 +577,7 @@ fn generator_matrix() -> &'static [[u8; FTX_LDPC_N]; FTX_LDPC_K] {
|
|||||||
let mut msg = [0u8; FTX_LDPC_K];
|
let mut msg = [0u8; FTX_LDPC_K];
|
||||||
msg[i] = 1;
|
msg[i] = 1;
|
||||||
if i < 77 {
|
if i < 77 {
|
||||||
for j in 77..FTX_LDPC_K {
|
msg[77..FTX_LDPC_K].fill(0);
|
||||||
msg[j] = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
encode174_91_nocrc_bits(&msg, &mut gen[i]);
|
encode174_91_nocrc_bits(&msg, &mut gen[i]);
|
||||||
}
|
}
|
||||||
@@ -620,6 +601,7 @@ fn generator_matrix() -> &'static [[u8; FTX_LDPC_N]; FTX_LDPC_K] {
|
|||||||
/// `ntype`: output decode type (0=fail, 1=BP, 2=OSD).
|
/// `ntype`: output decode type (0=fail, 1=BP, 2=OSD).
|
||||||
/// `nharderror`: output number of hard errors.
|
/// `nharderror`: output number of hard errors.
|
||||||
/// `dmin`: output minimum distance.
|
/// `dmin`: output minimum distance.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn ft2_decode174_91_osd(
|
pub fn ft2_decode174_91_osd(
|
||||||
llr: &mut [f32; FTX_LDPC_N],
|
llr: &mut [f32; FTX_LDPC_N],
|
||||||
keff: usize,
|
keff: usize,
|
||||||
@@ -732,8 +714,8 @@ pub fn ft2_decode174_91_osd(
|
|||||||
for i in 0..num_rows.min(7) {
|
for i in 0..num_rows.min(7) {
|
||||||
tanhtoc[i] = (-toc[m][i] / 2.0).tanh();
|
tanhtoc[i] = (-toc[m][i] / 2.0).tanh();
|
||||||
}
|
}
|
||||||
for j in 0..num_rows {
|
for &nm_val in FTX_LDPC_NM[m].iter().take(num_rows) {
|
||||||
let n = FTX_LDPC_NM[m][j] as usize - 1;
|
let n = nm_val as usize - 1;
|
||||||
if n >= FTX_LDPC_N {
|
if n >= FTX_LDPC_N {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,9 +78,9 @@ impl BitMetricsWorkspace {
|
|||||||
|
|
||||||
// Sync quality check: verify Costas patterns are detectable
|
// Sync quality check: verify Costas patterns are detectable
|
||||||
let mut sync_ok = 0;
|
let mut sync_ok = 0;
|
||||||
for group in 0..4 {
|
for (group, costas_group) in FT4_COSTAS_PATTERN.iter().enumerate() {
|
||||||
let base = group * 33;
|
let base = group * 33;
|
||||||
for i in 0..4 {
|
for (i, &costas_tone) in costas_group.iter().enumerate() {
|
||||||
if base + i >= FT2_FRAME_SYMBOLS {
|
if base + i >= FT2_FRAME_SYMBOLS {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ impl BitMetricsWorkspace {
|
|||||||
best = tone;
|
best = tone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if best == FT4_COSTAS_PATTERN[group][i] as usize {
|
if best == costas_tone as usize {
|
||||||
sync_ok += 1;
|
sync_ok += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ pub(crate) fn ft2_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
|||||||
let mut score_f: f32 = 0.0;
|
let mut score_f: f32 = 0.0;
|
||||||
let mut groups = 0;
|
let mut groups = 0;
|
||||||
|
|
||||||
for m in 0..FT2_NUM_SYNC {
|
for (m, costas_group) in FT4_COSTAS_PATTERN.iter().enumerate().take(FT2_NUM_SYNC) {
|
||||||
let mut sum = Complex32::new(0.0, 0.0);
|
let mut sum = Complex32::new(0.0, 0.0);
|
||||||
let mut complete = true;
|
let mut complete = true;
|
||||||
for k in 0..FT2_LENGTH_SYNC {
|
for (k, &costas_tone) in costas_group.iter().enumerate().take(FT2_LENGTH_SYNC) {
|
||||||
let block = 1 + FT2_SYNC_OFFSET * m + k;
|
let block = 1 + FT2_SYNC_OFFSET * m + k;
|
||||||
let block_abs = cand.time_offset as i32 + block as i32;
|
let block_abs = cand.time_offset as i32 + block as i32;
|
||||||
if block_abs < 0 || block_abs >= wf.num_blocks as i32 {
|
if block_abs < 0 || block_abs >= wf.num_blocks as i32 {
|
||||||
@@ -28,7 +28,7 @@ pub(crate) fn ft2_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let sym_offset = base + block * wf.block_stride;
|
let sym_offset = base + block * wf.block_stride;
|
||||||
let tone = FT4_COSTAS_PATTERN[m][k] as usize;
|
let tone = costas_tone as usize;
|
||||||
let elem = *wf_mag_safe(wf, sym_offset + tone);
|
let elem = *wf_mag_safe(wf, sym_offset + tone);
|
||||||
sum += wf_elem_to_complex(elem);
|
sum += wf_elem_to_complex(elem);
|
||||||
}
|
}
|
||||||
@@ -63,9 +63,9 @@ pub(crate) fn ft2_extract_likelihood(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let sym_offset = base + sym_idx * wf.block_stride;
|
let sym_offset = base + sym_idx * wf.block_stride;
|
||||||
for tone in 0..4 {
|
for (tone, symbol_row) in symbols.iter_mut().enumerate().take(4) {
|
||||||
let elem = *wf_mag_safe(wf, sym_offset + tone);
|
let elem = *wf_mag_safe(wf, sym_offset + tone);
|
||||||
symbols[tone][frame_sym] = wf_elem_to_complex(elem);
|
symbol_row[frame_sym] = wf_elem_to_complex(elem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -190,8 +190,8 @@ impl DownsampleContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply spectral window
|
// Apply spectral window
|
||||||
for i in 0..self.nfft2 {
|
for (b, &w) in band.iter_mut().zip(self.window.iter()) {
|
||||||
band[i] *= self.window[i];
|
*b *= w;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inverse FFT (in-place)
|
// Inverse FFT (in-place)
|
||||||
@@ -221,19 +221,27 @@ fn build_spectral_window(nfft2: usize, df: f32) -> Vec<f32> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Raised-cosine leading edge
|
// Raised-cosine leading edge
|
||||||
for i in 0..iwt.min(nfft2) {
|
for (i, w) in window.iter_mut().enumerate().take(iwt.min(nfft2)) {
|
||||||
window[i] = 0.5 * (1.0 + (std::f32::consts::PI * (iwt - 1 - i) as f32 / iwt as f32).cos());
|
*w = 0.5 * (1.0 + (std::f32::consts::PI * (iwt - 1 - i) as f32 / iwt as f32).cos());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flat passband
|
// Flat passband
|
||||||
for i in iwt..(iwt + iwf).min(nfft2) {
|
for w in window
|
||||||
window[i] = 1.0;
|
.iter_mut()
|
||||||
|
.skip(iwt)
|
||||||
|
.take((iwt + iwf).min(nfft2) - iwt)
|
||||||
|
{
|
||||||
|
*w = 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raised-cosine trailing edge
|
// Raised-cosine trailing edge
|
||||||
for i in (iwt + iwf)..(2 * iwt + iwf).min(nfft2) {
|
for (i, w) in window
|
||||||
window[i] =
|
.iter_mut()
|
||||||
0.5 * (1.0 + (std::f32::consts::PI * (i - (iwt + iwf)) as f32 / iwt as f32).cos());
|
.enumerate()
|
||||||
|
.take((2 * iwt + iwf).min(nfft2))
|
||||||
|
.skip(iwt + iwf)
|
||||||
|
{
|
||||||
|
*w = 0.5 * (1.0 + (std::f32::consts::PI * (i - (iwt + iwf)) as f32 / iwt as f32).cos());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Circular shift by iws bins
|
// Circular shift by iws bins
|
||||||
|
|||||||
@@ -8,12 +8,9 @@
|
|||||||
//! peaks in the averaged spectrum, downsample each candidate, compute 2D sync
|
//! peaks in the averaged spectrum, downsample each candidate, compute 2D sync
|
||||||
//! scores, extract bit metrics, and run multi-pass LDPC + OSD decode.
|
//! scores, extract bit metrics, and run multi-pass LDPC + OSD decode.
|
||||||
|
|
||||||
#[allow(clippy::needless_range_loop)]
|
|
||||||
pub mod bitmetrics;
|
pub mod bitmetrics;
|
||||||
pub(crate) mod decode;
|
pub(crate) mod decode;
|
||||||
#[allow(clippy::needless_range_loop)]
|
|
||||||
pub mod downsample;
|
pub mod downsample;
|
||||||
#[allow(clippy::needless_range_loop)]
|
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
|
|
||||||
pub(crate) use self::decode::{ft2_extract_likelihood, ft2_sync_score};
|
pub(crate) use self::decode::{ft2_extract_likelihood, ft2_sync_score};
|
||||||
@@ -50,6 +47,13 @@ pub fn ft2_frequency_offset_hz() -> f32 {
|
|||||||
-1.5 / FT2_SYMBOL_PERIOD_F
|
-1.5 / FT2_SYMBOL_PERIOD_F
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate FT2 tone sequence from payload data.
|
||||||
|
///
|
||||||
|
/// FT2 uses the FT4 framing with a doubled symbol rate.
|
||||||
|
pub fn ft2_encode(payload: &[u8], tones: &mut [u8]) {
|
||||||
|
crate::ft4::ft4_encode(payload, tones);
|
||||||
|
}
|
||||||
|
|
||||||
/// Raw frequency peak candidate from the averaged power spectrum.
|
/// Raw frequency peak candidate from the averaged power spectrum.
|
||||||
#[derive(Clone, Copy, Default)]
|
#[derive(Clone, Copy, Default)]
|
||||||
pub struct RawCandidate {
|
pub struct RawCandidate {
|
||||||
@@ -315,8 +319,8 @@ impl Ft2Pipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let inv_n_frames = 1.0 / n_frames as f32;
|
let inv_n_frames = 1.0 / n_frames as f32;
|
||||||
for bin in 1..FT2_NH1 {
|
for v in avg.iter_mut().take(FT2_NH1).skip(1) {
|
||||||
avg[bin] *= inv_n_frames;
|
*v *= inv_n_frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Smooth with 15-point moving average
|
// Smooth with 15-point moving average
|
||||||
@@ -620,17 +624,24 @@ impl Ft2Pipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scale and derive combined passes
|
// Scale and derive combined passes
|
||||||
for i in 0..FTX_LDPC_N {
|
let [ref mut pass0, ref mut pass1, ref mut pass2, ref mut pass3, ref mut pass4] =
|
||||||
llr_passes[0][i] *= 2.83;
|
llr_passes;
|
||||||
llr_passes[1][i] *= 2.83;
|
for v in pass0.iter_mut() {
|
||||||
llr_passes[2][i] *= 2.83;
|
*v *= 2.83;
|
||||||
|
}
|
||||||
let a = llr_passes[0][i];
|
for v in pass1.iter_mut() {
|
||||||
let b = llr_passes[1][i];
|
*v *= 2.83;
|
||||||
let c = llr_passes[2][i];
|
}
|
||||||
|
for v in pass2.iter_mut() {
|
||||||
|
*v *= 2.83;
|
||||||
|
}
|
||||||
|
for ((&a, &b), (&c, (p3, p4))) in pass0
|
||||||
|
.iter()
|
||||||
|
.zip(pass1.iter())
|
||||||
|
.zip(pass2.iter().zip(pass3.iter_mut().zip(pass4.iter_mut())))
|
||||||
|
{
|
||||||
// Pass 3: max-abs metric
|
// Pass 3: max-abs metric
|
||||||
llr_passes[3][i] = if a.abs() >= b.abs() && a.abs() >= c.abs() {
|
*p3 = if a.abs() >= b.abs() && a.abs() >= c.abs() {
|
||||||
a
|
a
|
||||||
} else if b.abs() >= c.abs() {
|
} else if b.abs() >= c.abs() {
|
||||||
b
|
b
|
||||||
@@ -639,7 +650,7 @@ impl Ft2Pipeline {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Pass 4: average
|
// Pass 4: average
|
||||||
llr_passes[4][i] = (a + b + c) / 3.0;
|
*p4 = (a + b + c) / 3.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi-pass LDPC decode using full BP+OSD decoder
|
// Multi-pass LDPC decode using full BP+OSD decoder
|
||||||
@@ -647,11 +658,11 @@ impl Ft2Pipeline {
|
|||||||
let mut message = FtxMessage::default();
|
let mut message = FtxMessage::default();
|
||||||
let mut apmask = [0u8; FTX_LDPC_N];
|
let mut apmask = [0u8; FTX_LDPC_N];
|
||||||
|
|
||||||
for pass in 0..5 {
|
for llr_pass in &llr_passes {
|
||||||
if ok {
|
if ok {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let mut log174 = llr_passes[pass];
|
let mut log174 = *llr_pass;
|
||||||
|
|
||||||
let mut message91 = [0u8; FTX_LDPC_K];
|
let mut message91 = [0u8; FTX_LDPC_K];
|
||||||
let mut cw = [0u8; FTX_LDPC_N];
|
let mut cw = [0u8; FTX_LDPC_N];
|
||||||
@@ -822,4 +833,14 @@ mod tests {
|
|||||||
assert_eq!(b, 0);
|
assert_eq!(b, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ft2_encode_matches_ft4() {
|
||||||
|
let payload = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x20];
|
||||||
|
let mut tones_ft4 = [0u8; FT4_NN];
|
||||||
|
let mut tones_ft2 = [0u8; FT4_NN];
|
||||||
|
crate::ft4::ft4_encode(&payload, &mut tones_ft4);
|
||||||
|
ft2_encode(&payload, &mut tones_ft2);
|
||||||
|
assert_eq!(tones_ft4, tones_ft2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ pub fn prepare_sync_waveforms() -> SyncWaveforms {
|
|||||||
for group in 0..4 {
|
for group in 0..4 {
|
||||||
let mut idx = 0usize;
|
let mut idx = 0usize;
|
||||||
let mut phase = 0.0f32;
|
let mut phase = 0.0f32;
|
||||||
for tone_idx in 0..4 {
|
for &costas_tone in FT4_COSTAS_PATTERN[group].iter() {
|
||||||
let tone = FT4_COSTAS_PATTERN[group][tone_idx] as f32;
|
let tone = costas_tone as f32;
|
||||||
let dphase = 4.0 * std::f32::consts::PI * tone / nss;
|
let dphase = 4.0 * std::f32::consts::PI * tone / nss;
|
||||||
let half_nss = (nss / 2.0) as usize;
|
let half_nss = (nss / 2.0) as usize;
|
||||||
for _step in 0..half_nss {
|
for _step in 0..half_nss {
|
||||||
@@ -68,9 +68,9 @@ pub fn prepare_sync_waveforms() -> SyncWaveforms {
|
|||||||
// Build frequency tweak phasors
|
// Build frequency tweak phasors
|
||||||
for idf in FT2_SYNC_TWEAK_MIN..=FT2_SYNC_TWEAK_MAX {
|
for idf in FT2_SYNC_TWEAK_MIN..=FT2_SYNC_TWEAK_MAX {
|
||||||
let tw_idx = (idf - FT2_SYNC_TWEAK_MIN) as usize;
|
let tw_idx = (idf - FT2_SYNC_TWEAK_MIN) as usize;
|
||||||
for n in 0..64 {
|
for (n, tw) in tweak_wave[tw_idx].iter_mut().enumerate() {
|
||||||
let phase = 4.0 * std::f32::consts::PI * idf as f32 * n as f32 / fs_down;
|
let phase = 4.0 * std::f32::consts::PI * idf as f32 * n as f32 / fs_down;
|
||||||
tweak_wave[tw_idx][n] = Complex32::new(phase.cos(), phase.sin());
|
*tw = Complex32::new(phase.cos(), phase.sin());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,11 +89,10 @@ fn sync_reference_bank() -> &'static SyncReferenceBank {
|
|||||||
let waveforms = prepare_sync_waveforms();
|
let waveforms = prepare_sync_waveforms();
|
||||||
let mut refs = [[[Complex32::new(0.0, 0.0); SYNC_SAMPLES]; SYNC_GROUP_COUNT]; NUM_TWEAKS];
|
let mut refs = [[[Complex32::new(0.0, 0.0); SYNC_SAMPLES]; SYNC_GROUP_COUNT]; NUM_TWEAKS];
|
||||||
|
|
||||||
for tw_idx in 0..NUM_TWEAKS {
|
for (tw_idx, refs_tw) in refs.iter_mut().enumerate() {
|
||||||
for group in 0..SYNC_GROUP_COUNT {
|
for (group, refs_group) in refs_tw.iter_mut().enumerate() {
|
||||||
for i in 0..SYNC_SAMPLES {
|
for (i, r) in refs_group.iter_mut().enumerate() {
|
||||||
refs[tw_idx][group][i] =
|
*r = (waveforms.sync_wave[group][i] * waveforms.tweak_wave[tw_idx][i]).conj();
|
||||||
(waveforms.sync_wave[group][i] * waveforms.tweak_wave[tw_idx][i]).conj();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,14 +131,13 @@ fn correlate_group_clipped(
|
|||||||
let mut usable = 0usize;
|
let mut usable = 0usize;
|
||||||
let n_samples = samples.len() as i32;
|
let n_samples = samples.len() as i32;
|
||||||
|
|
||||||
for i in 0..SYNC_SAMPLES {
|
for (i, &reference) in refs.iter().enumerate() {
|
||||||
let sample_idx = pos + i as i32 * SAMPLE_STRIDE as i32;
|
let sample_idx = pos + i as i32 * SAMPLE_STRIDE as i32;
|
||||||
if sample_idx < 0 || sample_idx >= n_samples {
|
if sample_idx < 0 || sample_idx >= n_samples {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sample = samples[sample_idx as usize];
|
let sample = samples[sample_idx as usize];
|
||||||
let reference = refs[i];
|
|
||||||
sum_re += sample.re * reference.re - sample.im * reference.im;
|
sum_re += sample.re * reference.re - sample.im * reference.im;
|
||||||
sum_im += sample.re * reference.im + sample.im * reference.re;
|
sum_im += sample.re * reference.im + sample.im * reference.re;
|
||||||
usable += 1;
|
usable += 1;
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ pub(crate) fn ft4_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
|||||||
let mut score: i32 = 0;
|
let mut score: i32 = 0;
|
||||||
let mut num_average: i32 = 0;
|
let mut num_average: i32 = 0;
|
||||||
|
|
||||||
for m in 0..FT4_NUM_SYNC {
|
for (m, costas_group) in FT4_COSTAS_PATTERN.iter().enumerate().take(FT4_NUM_SYNC) {
|
||||||
for k in 0..FT4_LENGTH_SYNC {
|
for (k, &sm_val) in costas_group.iter().enumerate().take(FT4_LENGTH_SYNC) {
|
||||||
let block = 1 + FT4_SYNC_OFFSET * m + k;
|
let block = 1 + FT4_SYNC_OFFSET * m + k;
|
||||||
let block_abs = cand.time_offset as i32 + block as i32;
|
let block_abs = cand.time_offset as i32 + block as i32;
|
||||||
if block_abs < 0 {
|
if block_abs < 0 {
|
||||||
@@ -29,7 +29,7 @@ pub(crate) fn ft4_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let p_offset = base + block * wf.block_stride;
|
let p_offset = base + block * wf.block_stride;
|
||||||
let sm = FT4_COSTAS_PATTERN[m][k] as usize;
|
let sm = sm_val as usize;
|
||||||
|
|
||||||
if sm > 0 {
|
if sm > 0 {
|
||||||
let a = wf_mag_safe(wf, p_offset + sm).mag_int();
|
let a = wf_mag_safe(wf, p_offset + sm).mag_int();
|
||||||
@@ -174,13 +174,6 @@ pub fn ft4_encode(payload: &[u8], tones: &mut [u8]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate FT2 tone sequence from payload data.
|
|
||||||
///
|
|
||||||
/// FT2 uses the FT4 framing with a doubled symbol rate.
|
|
||||||
pub fn ft2_encode(payload: &[u8], tones: &mut [u8]) {
|
|
||||||
ft4_encode(payload, tones);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -244,14 +237,4 @@ mod tests {
|
|||||||
ft4_encode(&payload, &mut tones2);
|
ft4_encode(&payload, &mut tones2);
|
||||||
assert_eq!(tones1, tones2);
|
assert_eq!(tones1, tones2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ft2_encode_matches_ft4() {
|
|
||||||
let payload = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x20];
|
|
||||||
let mut tones_ft4 = [0u8; FT4_NN];
|
|
||||||
let mut tones_ft2 = [0u8; FT4_NN];
|
|
||||||
ft4_encode(&payload, &mut tones_ft4);
|
|
||||||
ft2_encode(&payload, &mut tones_ft2);
|
|
||||||
assert_eq!(tones_ft4, tones_ft2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub(crate) fn ft8_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
|||||||
let mut num_average: i32 = 0;
|
let mut num_average: i32 = 0;
|
||||||
|
|
||||||
for m in 0..FT8_NUM_SYNC {
|
for m in 0..FT8_NUM_SYNC {
|
||||||
for k in 0..FT8_LENGTH_SYNC {
|
for (k, &sm_val) in FT8_COSTAS_PATTERN.iter().enumerate().take(FT8_LENGTH_SYNC) {
|
||||||
let block = FT8_SYNC_OFFSET * m + k;
|
let block = FT8_SYNC_OFFSET * m + k;
|
||||||
let block_abs = cand.time_offset as i32 + block as i32;
|
let block_abs = cand.time_offset as i32 + block as i32;
|
||||||
if block_abs < 0 {
|
if block_abs < 0 {
|
||||||
@@ -29,7 +29,7 @@ pub(crate) fn ft8_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let p_offset = base + block * wf.block_stride;
|
let p_offset = base + block * wf.block_stride;
|
||||||
let sm = FT8_COSTAS_PATTERN[k] as usize;
|
let sm = sm_val as usize;
|
||||||
|
|
||||||
if sm > 0 {
|
if sm > 0 {
|
||||||
let a = wf_mag_safe(wf, p_offset + sm).mag_int();
|
let a = wf_mag_safe(wf, p_offset + sm).mag_int();
|
||||||
|
|||||||
@@ -4,11 +4,8 @@
|
|||||||
|
|
||||||
pub mod common;
|
pub mod common;
|
||||||
mod decoder;
|
mod decoder;
|
||||||
#[allow(clippy::needless_range_loop)]
|
|
||||||
pub mod ft2;
|
pub mod ft2;
|
||||||
#[allow(clippy::needless_range_loop)]
|
|
||||||
pub mod ft4;
|
pub mod ft4;
|
||||||
#[allow(clippy::needless_range_loop)]
|
|
||||||
pub mod ft8;
|
pub mod ft8;
|
||||||
|
|
||||||
pub use decoder::{Ft8DecodeResult, Ft8Decoder};
|
pub use decoder::{Ft8DecodeResult, Ft8Decoder};
|
||||||
|
|||||||
Reference in New Issue
Block a user