From 81f7d2ff99d8a4426bf3aa20fe1cde256c1a348b Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 13 Mar 2026 18:37:09 +0100 Subject: [PATCH] FT8 demod: fix duplicated code (1) --- ft8/ft4.cpp | 90 ++------------------------ ft8/ft4.h | 177 ++++------------------------------------------------ ft8/ft8.cpp | 131 +++++++++++++++++++------------------- ft8/ft8.h | 37 ++++++----- 4 files changed, 104 insertions(+), 331 deletions(-) diff --git a/ft8/ft4.cpp b/ft8/ft4.cpp index 66c6d8892..a4e529e80 100644 --- a/ft8/ft4.cpp +++ b/ft8/ft4.cpp @@ -1547,88 +1547,6 @@ std::vector> FT4::soft_c2m(const FFTEngine::ffts_t &c103) return m103; } -// -// guess the probability that a bit is zero vs one, -// based on strengths of strongest tones that would -// give it those values. for soft LDPC decoding. -// -// returns log-likelihood, zero is positive, one is negative. -// -float FT4::bayes( - FT4Params& params, - float best_zero, - float best_one, - int lli, - Stats &bests, - Stats &all -) -{ - float maxlog = 4.97; - float ll = 0; - float pzero = 0.5; - float pone = 0.5; - - if (params.use_apriori) - { - pzero = 1.0 - apriori174[lli]; - pone = apriori174[lli]; - } - - // - // Bayes combining rule normalization from: - // http://cs.wellesley.edu/~anderson/writing/naive-bayes.pdf - // - // a = P(zero)P(e0|zero)P(e1|zero) - // b = P(one)P(e0|one)P(e1|one) - // p = a / (a + b) - // - // also see Mark Owen's book Practical Signal Processing, - // Chapter 6. - // - - // zero - float a = pzero * bests.problt(best_zero) * (1.0 - all.problt(best_one)); - // printf("FT8::bayes: a: %f bp: %f ap: %f \n", a, bests.problt(best_zero), all.problt(best_one)); - - if (params.bayes_how == 1) { - a *= all.problt(all.mean() + (best_zero - best_one)); - } - - // one - float b = pone * bests.problt(best_one) * (1.0 - all.problt(best_zero)); - // printf("FT8::bayes: b: %f bp: %f ap: %f \n", b, bests.problt(best_one), all.problt(best_zero)); - - if (params.bayes_how == 1) { - b *= all.problt(all.mean() + (best_one - best_zero)); - } - - float p; - - if (a + b == 0) { - p = 0.5; - } else { - p = a / (a + b); - } - - // printf("FT8::bayes: all.mean: %f a: %f b: %f p: %f\n", all.mean(), a, b, p); - - if (1 - p == 0.0) { - ll = maxlog; - } else { - ll = log(p / (1 - p)); - } - - if (ll > maxlog) { - ll = maxlog; - } - - if (ll < -maxlog) { - ll = -maxlog; - } - - return ll; -} - // // c103 is 103x4 complex tones, before un-gray-coding. // @@ -1720,7 +1638,7 @@ void FT4::soft_decode(const FFTEngine::ffts_t &c103, float ll174[]) } } - float ll = bayes(params, best_zero, best_one, lli, bests, all); + float ll = FT8::bayes(params, best_zero, best_one, lli, bests, all); ll174[lli++] = ll; } } @@ -1909,7 +1827,7 @@ void FT4::c_soft_decode(const FFTEngine::ffts_t &c103x, float ll174[]) } } - float ll = bayes(params, best_zero, best_one, lli, bests, all); + float ll = FT8::bayes(params, best_zero, best_one, lli, bests, all); ll174[lli++] = ll; } } @@ -2084,7 +2002,7 @@ void FT4::soft_decode_pairs( float best_zero = bitinfo[si * 2 + i].zero; float best_one = bitinfo[si * 2 + i].one; // ll174[lli++] = best_zero > best_one ? 4.99 : -4.99; - float ll = bayes(params, best_zero, best_one, lli, bests, all); + float ll = FT8::bayes(params, best_zero, best_one, lli, bests, all); ll174[lli++] = ll; } } @@ -2262,7 +2180,7 @@ void FT4::soft_decode_triples( float best_zero = bitinfo[si * 2 + i].zero; float best_one = bitinfo[si * 2 + i].one; // ll174[lli++] = best_zero > best_one ? 4.99 : -4.99; - float ll = bayes(params, best_zero, best_one, lli, bests, all); + float ll = FT8::bayes(params, best_zero, best_one, lli, bests, all); ll174[lli++] = ll; } } diff --git a/ft8/ft4.h b/ft8/ft4.h index 0b54aa97c..f7d7ebc4a 100644 --- a/ft8/ft4.h +++ b/ft8/ft4.h @@ -17,162 +17,23 @@ class QThread; namespace FT8 { -// 1920-point FFT at 12000 samples/second -// 6.25 Hz spacing, 0.16 seconds/symbol +// FT4 characteristics: +// 576-point FFT at 12000 samples/second +// 23.4 Hz spacing, 0.048 seconds/symbol // encode chain: // 77 bits // append 14 bits CRC (for 91 bits) // LDPC(174,91) yields 174 bits -// that's 58 3-bit FSK-8 symbols -// gray code each 3 bits -// insert three 7-symbol Costas sync arrays -// at symbol #s 0, 36, 72 of final signal -// thus: 79 FSK-8 symbols -// total transmission time is 12.64 seconds +// that's 87 2-bit FSK-4 symbols +// gray code each 2 bits +// insert four 4-symbol Costas sync arrays +// at positions: 0-3, 33-36, 66-69, 99-102 +// thus: 103 FSK-4 symbols +// add 2 ramp symbols at start and end to make 105 symbols +// total transmission time is 5.04 seconds // tunable parameters -class FT8_API FT4Params -{ -public: - int nthreads; // number of parallel threads, for multi-core - int npasses_one; // number of spectral subtraction passes - int npasses_two; // number of spectral subtraction passes - int ldpc_iters; // how hard LDPC decoding should work - int snr_win; // averaging window, in symbols, for SNR conversion - int snr_how; // technique to measure "N" for SNR. 0 means median of the 8 tones. - float shoulder200; // for 200 sps bandpass filter - float shoulder200_extra; // for bandpass filter - float second_hz_win; // +/- hz - int second_hz_n; // divide total window into this many pieces - float second_off_win; // +/- search window in symbol-times - int second_off_n; - int third_hz_n; - float third_hz_win; - int third_off_n; - float third_off_win; - float log_tail; - float log_rate; - int problt_how_noise; - int problt_how_sig; - int use_apriori; - int use_hints; // 1 means use all hints, 2 means just CQ hints - int win_type; - int use_osd; - int osd_depth; // 6; // don't increase beyond 6, produces too much garbage - int osd_ldpc_thresh; // demand this many correct LDPC parity bits before OSD - int ncoarse; // number of offsets per hz produced by coarse() - int ncoarse_blocks; - float tminus; // start looking at 0.5 - tminus seconds - float tplus; - int coarse_off_n; - int coarse_hz_n; - float already_hz; - float overlap; - int overlap_edges; - float nyquist; - int oddrate; - float pass0_frac; - int reduce_how; - float go_extra; - int do_reduce; - int pass_threshold; - int strength_how; - int known_strength_how; - int coarse_strength_how; - float reduce_shoulder; - float reduce_factor; - float reduce_extra; - float coarse_all; - int second_count; - int soft_phase_win; - float subtract_ramp; - int subtract_edge_symbols; // model one extra tapered symbol at frame start/end during subtraction (does not yield significant improvement) - int soft_ones; - int soft_pairs; - int soft_triples; - int do_second; - int do_fine_hz; - int do_fine_off; - int do_third; - float fine_thresh; - int fine_max_off; - int fine_max_tone; - int known_sparse; - float c_soft_weight; - int c_soft_win; - int bayes_how; - - FT4Params() - { - nthreads = 8; // number of parallel threads, for multi-core - npasses_one = 3; // number of spectral subtraction passes - npasses_two = 3; // number of spectral subtraction passes - ldpc_iters = 25; // how hard LDPC decoding should work - snr_win = 7; // averaging window, in symbols, for SNR conversion - snr_how = 3; // technique to measure "N" for SNR. 0 means median of the 8 tones. - shoulder200 = 10; // for 200 sps bandpass filter - shoulder200_extra = 0.0; // for bandpass filter - second_hz_win = 3.5; // +/- hz - second_hz_n = 8; // divide total window into this many pieces - second_off_win = 0.5; // +/- search window in symbol-times - second_off_n = 10; - third_hz_n = 3; - third_hz_win = 0.25; - third_off_n = 4; - third_off_win = 0.075; - log_tail = 0.1; - log_rate = 8.0; - problt_how_noise = 0; // Gaussian - problt_how_sig = 0; // Gaussian - use_apriori = 1; - use_hints = 2; // 1 means use all hints, 2 means just CQ hints - win_type = 1; - use_osd = 1; - osd_depth = 0; // 6; // don't increase beyond 6, produces too much garbage - osd_ldpc_thresh = 70; // demand this many correct LDPC parity bits before OSD - ncoarse = 1; // number of offsets per hz produced by coarse() - ncoarse_blocks = 1; - tminus = 2.2; // start looking at 0.5 - tminus seconds - tplus = 2.4; - coarse_off_n = 4; - coarse_hz_n = 4; - already_hz = 27; - overlap = 20; - overlap_edges = 0; - nyquist = 0.925; - oddrate = 1; - pass0_frac = 1.0; - reduce_how = 2; - go_extra = 3.5; - do_reduce = 1; - pass_threshold = 1; - strength_how = 4; - known_strength_how = 7; - coarse_strength_how = 6; - reduce_shoulder = -1; - reduce_factor = 0.25; - reduce_extra = 0; - coarse_all = -1; - second_count = 3; - soft_phase_win = 2; - subtract_ramp = 0.11; - subtract_edge_symbols = 0; - soft_ones = 2; - soft_pairs = 1; - soft_triples = 1; - do_second = 1; - do_fine_hz = 1; - do_fine_off = 1; - do_third = 2; - fine_thresh = 0.19; - fine_max_off = 2; - fine_max_tone = 4; - known_sparse = 1; - c_soft_weight = 7; - c_soft_win = 2; - bayes_how = 1; - } -}; // class FT4Params +using FT4Params = FT8Params; class FT8_API FT4ParamsLight { @@ -393,22 +254,6 @@ private: // next. // std::vector> soft_c2m(const FFTEngine::ffts_t &c79); - // - // guess the probability that a bit is zero vs one, - // based on strengths of strongest tones that would - // give it those values. for soft LDPC decoding. - // - // returns log-likelihood, zero is positive, one is negative. - // - static float bayes( - FT4Params& params, - float best_zero, - float best_one, - int lli, - Stats &bests, - Stats &all - ); - // // c79 is 79x8 complex tones, before un-gray-coding. // void soft_decode(const FFTEngine::ffts_t &c79, float ll174[]); diff --git a/ft8/ft8.cpp b/ft8/ft8.cpp index fa2bf6636..df02529a5 100644 --- a/ft8/ft8.cpp +++ b/ft8/ft8.cpp @@ -47,6 +47,71 @@ namespace FT8 { +namespace { + +template +float bayesImpl( + const double apriori174[], + ParamsT& params, + float best_zero, + float best_one, + int lli, + Stats &bests, + Stats &all +) +{ + float maxlog = 4.97; + float ll = 0; + float pzero = 0.5; + float pone = 0.5; + + if (params.use_apriori) + { + pzero = 1.0 - apriori174[lli]; + pone = apriori174[lli]; + } + + // zero + float a = pzero * bests.problt(best_zero) * (1.0 - all.problt(best_one)); + + if (params.bayes_how == 1) { + a *= all.problt(all.mean() + (best_zero - best_one)); + } + + // one + float b = pone * bests.problt(best_one) * (1.0 - all.problt(best_zero)); + + if (params.bayes_how == 1) { + b *= all.problt(all.mean() + (best_one - best_zero)); + } + + float p; + + if (a + b == 0) { + p = 0.5; + } else { + p = a / (a + b); + } + + if (1 - p == 0.0) { + ll = maxlog; + } else { + ll = log(p / (1 - p)); + } + + if (ll > maxlog) { + ll = maxlog; + } + + if (ll < -maxlog) { + ll = -maxlog; + } + + return ll; +} + +} // namespace + // a-priori probability of each of the 174 LDPC codeword // bits being one. measured from reconstructed correct // codewords, into ft8bits, then python bprob.py. @@ -1675,72 +1740,10 @@ float FT8::bayes( Stats &all ) { - float maxlog = 4.97; - float ll = 0; - float pzero = 0.5; - float pone = 0.5; - - if (params.use_apriori) - { - pzero = 1.0 - apriori174[lli]; - pone = apriori174[lli]; - } - - // - // Bayes combining rule normalization from: - // http://cs.wellesley.edu/~anderson/writing/naive-bayes.pdf - // - // a = P(zero)P(e0|zero)P(e1|zero) - // b = P(one)P(e0|one)P(e1|one) - // p = a / (a + b) - // - // also see Mark Owen's book Practical Signal Processing, - // Chapter 6. - // - - // zero - float a = pzero * bests.problt(best_zero) * (1.0 - all.problt(best_one)); - // printf("FT8::bayes: a: %f bp: %f ap: %f \n", a, bests.problt(best_zero), all.problt(best_one)); - - if (params.bayes_how == 1) { - a *= all.problt(all.mean() + (best_zero - best_one)); - } - - // one - float b = pone * bests.problt(best_one) * (1.0 - all.problt(best_zero)); - // printf("FT8::bayes: b: %f bp: %f ap: %f \n", b, bests.problt(best_one), all.problt(best_zero)); - - if (params.bayes_how == 1) { - b *= all.problt(all.mean() + (best_one - best_zero)); - } - - float p; - - if (a + b == 0) { - p = 0.5; - } else { - p = a / (a + b); - } - - // printf("FT8::bayes: all.mean: %f a: %f b: %f p: %f\n", all.mean(), a, b, p); - - if (1 - p == 0.0) { - ll = maxlog; - } else { - ll = log(p / (1 - p)); - } - - if (ll > maxlog) { - ll = maxlog; - } - - if (ll < -maxlog) { - ll = -maxlog; - } - - return ll; + return bayesImpl(apriori174, params, best_zero, best_one, lli, bests, all); } + // // c79 is 79x8 complex tones, before un-gray-coding. // diff --git a/ft8/ft8.h b/ft8/ft8.h index fb1b9533c..c30f7a656 100644 --- a/ft8/ft8.h +++ b/ft8/ft8.h @@ -36,6 +36,21 @@ class QThread; namespace FT8 { + +// FT8 characteristics: +// 1920-point FFT at 12000 samples/second +// 6.25 Hz spacing, 0.16 seconds/symbol +// encode chain: +// 77 bits +// append 14 bits CRC (for 91 bits) +// LDPC(174,91) yields 174 bits +// that's 58 3-bit FSK-8 symbols +// gray code each 3 bits +// insert three 7-symbol Costas sync arrays +// at symbol #s 0, 36, 72 of final signal +// thus: 79 FSK-8 symbols +// total transmission time is 12.64 seconds + // Callback interface to get the results class FT8_API CallbackInterface { @@ -52,7 +67,6 @@ public: virtual QString get_name() = 0; }; - class FT8_API Strength { public: @@ -71,20 +85,7 @@ struct FT8_API cdecode int *bits; // 174 }; -// 1920-point FFT at 12000 samples/second -// 6.25 Hz spacing, 0.16 seconds/symbol -// encode chain: -// 77 bits -// append 14 bits CRC (for 91 bits) -// LDPC(174,91) yields 174 bits -// that's 58 3-bit FSK-8 symbols -// gray code each 3 bits -// insert three 7-symbol Costas sync arrays -// at symbol #s 0, 36, 72 of final signal -// thus: 79 FSK-8 symbols -// total transmission time is 12.64 seconds - -// tunable parameters +// tunable parameters for all FT decoders class FT8_API FT8Params { public: @@ -140,6 +141,7 @@ public: int second_count; int soft_phase_win; float subtract_ramp; + int subtract_edge_symbols; // model one extra tapered symbol at frame start/end during subtraction int soft_ones; int soft_pairs; int soft_triples; @@ -209,6 +211,7 @@ public: second_count = 3; soft_phase_win = 2; subtract_ramp = 0.11; + subtract_edge_symbols = 0; soft_ones = 2; soft_pairs = 1; soft_triples = 1; @@ -230,6 +233,8 @@ public: class FT8_API FT8 : public QObject { Q_OBJECT + friend class FT4; + public: FT8( const std::vector &samples, @@ -457,6 +462,7 @@ private: // next. // std::vector> soft_c2m(const FFTEngine::ffts_t &c79); +public: // // guess the probability that a bit is zero vs one, // based on strengths of strongest tones that would @@ -472,6 +478,7 @@ private: Stats &bests, Stats &all ); +private: // // c79 is 79x8 complex tones, before un-gray-coding. //