mfsk_core/fec/conv/
mod.rs1pub mod fano;
12
13use super::FecCodec;
14use crate::core::{FecOpts, FecResult};
15
16#[derive(Copy, Clone, Debug, Default)]
22pub struct ConvFano;
23
24impl ConvFano {
25 pub const NBITS: usize = 81;
27 pub const DEFAULT_DELTA: i32 = 17;
31 pub const DEFAULT_MAX_CYCLES: u64 = 10_000;
33 pub const METRIC_SCALE: f32 = 16.0;
35 pub const METRIC_BIAS: f32 = 0.0;
37}
38
39fn pack_msg_with_tail(info: &[u8]) -> [u8; 11] {
42 assert_eq!(info.len(), 50, "WSPR info payload must be 50 bits");
43 let mut packed = [0u8; 11];
44 for (i, &b) in info.iter().enumerate() {
45 if b & 1 != 0 {
46 packed[i / 8] |= 1 << (7 - (i % 8));
47 }
48 }
49 packed
51}
52
53impl FecCodec for ConvFano {
54 const N: usize = 162;
55 const K: usize = 50;
56
57 fn encode(&self, info: &[u8], codeword: &mut [u8]) {
58 assert_eq!(info.len(), Self::K);
59 assert_eq!(codeword.len(), Self::N);
60 let packed = pack_msg_with_tail(info);
61 let mut out = vec![0u8; 2 * Self::NBITS];
62 fano::conv_encode(&packed, Self::NBITS, &mut out);
63 codeword.copy_from_slice(&out);
64 }
65
66 fn decode_soft(&self, llr: &[f32], _opts: &FecOpts) -> Option<FecResult> {
67 assert_eq!(llr.len(), Self::N);
68 let bm = fano::build_branch_metrics(llr, Self::METRIC_BIAS, Self::METRIC_SCALE);
69 let res = fano::fano_decode(
70 &bm,
71 Self::NBITS,
72 Self::DEFAULT_DELTA,
73 Self::DEFAULT_MAX_CYCLES,
74 );
75 if !res.converged {
76 return None;
77 }
78
79 let mut info = vec![0u8; Self::K];
81 for i in 0..Self::K {
82 info[i] = (res.data[i / 8] >> (7 - (i % 8))) & 1;
83 }
84
85 let mut reencoded = vec![0u8; Self::N];
87 self.encode(&info, &mut reencoded);
88 let hard_errors = llr
89 .iter()
90 .zip(reencoded.iter())
91 .filter(|&(&l, &c)| (c == 1) != (l < 0.0))
92 .count() as u32;
93
94 Some(FecResult {
95 info,
96 hard_errors,
97 iterations: 0,
98 })
99 }
100}
101
102#[derive(Copy, Clone, Debug, Default)]
109pub struct ConvFano232;
110
111impl ConvFano232 {
112 pub const NBITS: usize = 103;
114 pub const DEFAULT_DELTA: i32 = 17;
117 pub const DEFAULT_MAX_CYCLES: u64 = 10_000;
121 pub const METRIC_SCALE: f32 = 16.0;
122 pub const METRIC_BIAS: f32 = 0.0;
123}
124
125fn pack_msg_with_tail_jt9(info: &[u8]) -> [u8; 13] {
129 assert_eq!(info.len(), 72, "JT9 info payload must be 72 bits");
130 let mut packed = [0u8; 13];
131 for (i, &b) in info.iter().enumerate() {
132 if b & 1 != 0 {
133 packed[i / 8] |= 1 << (7 - (i % 8));
134 }
135 }
136 packed
138}
139
140impl FecCodec for ConvFano232 {
141 const N: usize = 206;
142 const K: usize = 72;
143
144 fn encode(&self, info: &[u8], codeword: &mut [u8]) {
145 assert_eq!(info.len(), Self::K);
146 assert_eq!(codeword.len(), Self::N);
147 let packed = pack_msg_with_tail_jt9(info);
148 let mut out = vec![0u8; 2 * Self::NBITS];
149 fano::conv_encode(&packed, Self::NBITS, &mut out);
150 codeword.copy_from_slice(&out);
151 }
152
153 fn decode_soft(&self, llr: &[f32], _opts: &FecOpts) -> Option<FecResult> {
154 assert_eq!(llr.len(), Self::N);
155 let bm = fano::build_branch_metrics(llr, Self::METRIC_BIAS, Self::METRIC_SCALE);
156 let res = fano::fano_decode(
157 &bm,
158 Self::NBITS,
159 Self::DEFAULT_DELTA,
160 Self::DEFAULT_MAX_CYCLES,
161 );
162 if !res.converged {
163 return None;
164 }
165 let mut info = vec![0u8; Self::K];
166 for i in 0..Self::K {
167 info[i] = (res.data[i / 8] >> (7 - (i % 8))) & 1;
168 }
169 let mut reencoded = vec![0u8; Self::N];
170 self.encode(&info, &mut reencoded);
171 let hard_errors = llr
172 .iter()
173 .zip(reencoded.iter())
174 .filter(|&(&l, &c)| (c == 1) != (l < 0.0))
175 .count() as u32;
176 Some(FecResult {
177 info,
178 hard_errors,
179 iterations: 0,
180 })
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn encode_then_decode_roundtrip() {
190 let codec = ConvFano;
191 let mut info = vec![0u8; 50];
193 for (i, slot) in info.iter_mut().enumerate() {
194 *slot = (((i * 7) ^ 0x2a) & 1) as u8;
195 }
196 let mut cw = vec![0u8; 162];
197 codec.encode(&info, &mut cw);
198
199 let llr: Vec<f32> = cw
201 .iter()
202 .map(|&b| if b == 0 { 8.0 } else { -8.0 })
203 .collect();
204 let r = codec
205 .decode_soft(&llr, &FecOpts::default())
206 .expect("perfect LLRs must decode");
207 assert_eq!(r.info, info);
208 assert_eq!(r.hard_errors, 0);
209 }
210
211 #[test]
212 fn jt9_encode_decode_roundtrip() {
213 let codec = ConvFano232;
214 let mut info = vec![0u8; 72];
215 for (i, slot) in info.iter_mut().enumerate() {
216 *slot = (((i * 11) ^ 0x55) & 1) as u8;
217 }
218 let mut cw = vec![0u8; 206];
219 codec.encode(&info, &mut cw);
220 let llr: Vec<f32> = cw
221 .iter()
222 .map(|&b| if b == 0 { 8.0 } else { -8.0 })
223 .collect();
224 let r = codec
225 .decode_soft(&llr, &FecOpts::default())
226 .expect("perfect LLRs must decode");
227 assert_eq!(r.info, info);
228 assert_eq!(r.hard_errors, 0);
229 }
230
231 #[test]
232 fn jt9_tolerates_a_few_errors() {
233 let codec = ConvFano232;
234 let info: Vec<u8> = (0..72).map(|i| i as u8 & 1).collect();
235 let mut cw = vec![0u8; 206];
236 codec.encode(&info, &mut cw);
237 let mut llr: Vec<f32> = cw
238 .iter()
239 .map(|&b| if b == 0 { 6.0 } else { -6.0 })
240 .collect();
241 for &pos in &[3usize, 17, 42, 91, 155, 199] {
242 llr[pos] = -llr[pos] * 0.3;
243 }
244 let r = codec
245 .decode_soft(&llr, &FecOpts::default())
246 .expect("should correct 6 weak errors");
247 assert_eq!(r.info, info);
248 }
249
250 #[test]
251 fn tolerates_a_few_errors() {
252 let codec = ConvFano;
253 let info: Vec<u8> = (0..50).map(|i| i as u8 & 1).collect();
254 let mut cw = vec![0u8; 162];
255 codec.encode(&info, &mut cw);
256 let mut llr: Vec<f32> = cw
258 .iter()
259 .map(|&b| if b == 0 { 6.0 } else { -6.0 })
260 .collect();
261 for &pos in &[3usize, 17, 42, 91, 155] {
264 llr[pos] = -llr[pos] * 0.3;
265 }
266 let r = codec
267 .decode_soft(&llr, &FecOpts::default())
268 .expect("should correct 5 weak errors");
269 assert_eq!(r.info, info);
270 }
271}