From 2e0801e57b130bfd75bd840e0cdb2a0150ec48ac Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 8 Mar 2026 02:08:57 +0100 Subject: [PATCH] FT4 decoder: nearly all decode OK --- ft8/ft4.cpp | 1162 +++++++++++++++++++++++------------------ ft8/ft4.h | 16 +- sdrbench/test_ft4.cpp | 2 +- 3 files changed, 664 insertions(+), 516 deletions(-) diff --git a/ft8/ft4.cpp b/ft8/ft4.cpp index 33ffc8a06..9d42da3d6 100644 --- a/ft8/ft4.cpp +++ b/ft8/ft4.cpp @@ -22,6 +22,27 @@ namespace FT8 { +namespace { + +constexpr int kFT4SymbolSamples = 576; +constexpr int kFT4TotalSymbols = 103; +constexpr int kFT4FrameSamples = kFT4TotalSymbols * kFT4SymbolSamples; + +const std::array, 4> kFT4SyncTones = {{ + {{0, 1, 3, 2}}, + {{1, 0, 2, 3}}, + {{2, 3, 1, 0}}, + {{3, 2, 0, 1}} +}}; + +const std::array kFT4Rvec = {{ + 0,1,0,0,1,0,1,0,0,1,0,1,1,1,1,0,1,0,0,0,1,0,0,1,1,0,1,1,0, + 1,0,0,1,0,1,1,0,0,0,0,1,0,0,0,1,0,1,0,0,1,1,1,1,0,0,1,0,1, + 0,1,0,1,0,1,1,0,1,1,1,1,1,0,0,0,1,0,1 +}}; + +} // 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. @@ -101,23 +122,21 @@ void FT4::start_work() // and symbol zero at si0. float FT4::one_coarse_strength(const FFTEngine::ffts_t &bins, int bi0, int si0) { - int costas[] = {3, 1, 4, 0, 6, 5, 2}; - - // assert(si0 >= 0 && si0 + 72 + 8 <= (int)bins.size()); - // assert(bi0 >= 0 && bi0 + 8 <= (int)bins[0].size()); + // assert(si0 >= 0 && si0 + 103 <= (int)bins.size()); + // assert(bi0 >= 0 && bi0 + 4 <= (int)bins[0].size()); float sig = 0.0; float noise = 0.0; if (params.coarse_all >= 0) { - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { float mx; int mxi = -1; float sum = 0; - for (int i = 0; i < 8; i++) + for (int i = 0; i < 4; i++) { float x = std::abs(bins[si0 + si][bi0 + i]); sum += x; @@ -129,21 +148,34 @@ float FT4::one_coarse_strength(const FFTEngine::ffts_t &bins, int bi0, int si0) } } - if (si >= 0 && si < 7) + int blockIndex = -1; + int symbolInBlock = -1; + + if (si < 4) { - float x = std::abs(bins[si0 + si][bi0 + costas[si - 0]]); - sig += x; - noise += sum - x; + blockIndex = 0; + symbolInBlock = si; } - else if (si >= 36 && si < 36 + 7) + else if (si >= 33 && si < 37) { - float x = std::abs(bins[si0 + si][bi0 + costas[si - 36]]); - sig += x; - noise += sum - x; + blockIndex = 1; + symbolInBlock = si - 33; } - else if (si >= 72 && si < 72 + 7) + else if (si >= 66 && si < 70) { - float x = std::abs(bins[si0 + si][bi0 + costas[si - 72]]); + blockIndex = 2; + symbolInBlock = si - 66; + } + else if (si >= 99) + { + blockIndex = 3; + symbolInBlock = si - 99; + } + + if (blockIndex >= 0) + { + int expectedTone = kFT4SyncTones[blockIndex][symbolInBlock]; + float x = std::abs(bins[si0 + si][bi0 + expectedTone]); sig += x; noise += sum - x; } @@ -157,20 +189,24 @@ float FT4::one_coarse_strength(const FFTEngine::ffts_t &bins, int bi0, int si0) else { // coarse_all = -1 - // just costas symbols - for (int si = 0; si < 7; si++) + // just sync symbols + for (int blockIndex = 0; blockIndex < 4; blockIndex++) { - for (int bi = 0; bi < 8; bi++) - { - float x = 0; - x += std::abs(bins[si0 + si][bi0 + bi]); - x += std::abs(bins[si0 + 36 + si][bi0 + bi]); - x += std::abs(bins[si0 + 72 + si][bi0 + bi]); + int start = blockIndex * 33; - if (bi == costas[si]) { - sig += x; - } else { - noise += x; + for (int si = 0; si < 4; si++) + { + int expectedTone = kFT4SyncTones[blockIndex][si]; + + for (int bi = 0; bi < 4; bi++) + { + float x = std::abs(bins[si0 + start + si][bi0 + bi]); + + if (bi == expectedTone) { + sig += x; + } else { + noise += x; + } } } } @@ -179,11 +215,11 @@ float FT4::one_coarse_strength(const FFTEngine::ffts_t &bins, int bi0, int si0) if (params.coarse_strength_how == 0) { return sig - noise; } else if (params.coarse_strength_how == 1) { - return sig - noise / 7; + return sig - noise / 3; } else if (params.coarse_strength_how == 2) { - return sig / (noise / 7); + return sig / (noise / 3); } else if (params.coarse_strength_how == 3) { - return sig / (sig + (noise / 7)); + return sig / (sig + (noise / 3)); } else if (params.coarse_strength_how == 4) { return sig; } else if (params.coarse_strength_how == 5) { @@ -201,8 +237,8 @@ float FT4::one_coarse_strength(const FFTEngine::ffts_t &bins, int bi0, int si0) // use whole FFT bins. int FT4::blocksize(int rate) { - // FT8 symbol length is 1920 at 12000 samples/second. - int xblock = (1920*rate) / 12000; + // FT4 symbol length is 576 at 12000 samples/second. + int xblock = (576*rate) / 12000; int block = xblock; return block; } @@ -222,16 +258,16 @@ std::vector FT4::coarse(const FFTEngine::ffts_t &bins, int si0, int si int max_bin = max_hz_ / bin_hz; std::vector strengths; - for (int bi = min_bin; bi < max_bin && bi + 8 <= nbins; bi++) + for (int bi = min_bin; bi < max_bin && bi + 4 <= nbins; bi++) { std::vector sv; - for (int si = si0; si < si1 && si + 79 < (int)bins.size(); si++) + for (int si = si0; si < si1 && si + 103 <= (int)bins.size(); si++) { float s = one_coarse_strength(bins, bi, si); Strength st; st.strength_ = s; - st.hz_ = bi * 6.25; + st.hz_ = bi * 20.833; st.off_ = si * block; sv.push_back(st); } @@ -389,7 +425,7 @@ void FT4::go(int npasses) samples_.resize(nice); } - // assert(min_hz_ >= 0 && max_hz_ + 50 <= rate_ / 2); + // assert(min_hz_ >= 0 && max_hz_ + 93.6 <= rate_ / 2); // can we reduce the sample rate? int nrate = -1; @@ -397,7 +433,7 @@ void FT4::go(int npasses) { if (xrate < rate_ && (params.oddrate || (rate_ % xrate) == 0)) { - if (((max_hz_ - min_hz_) + 50 + 2 * params.go_extra) < params.nyquist * (xrate / 2)) + if (((max_hz_ - min_hz_) + 93.6 + 2 * params.go_extra) < params.nyquist * (xrate / 2)) { nrate = xrate; break; @@ -415,8 +451,8 @@ void FT4::go(int npasses) float delta_hz; // how much it moved down samples_ = reduce_rate( samples_, - min_hz_ - 3.1 - params.go_extra, - max_hz_ + 50 - 3.1 + params.go_extra, + min_hz_ - 11.7 - params.go_extra, + max_hz_ + 93.6 - 11.7 + params.go_extra, rate_, nrate, delta_hz @@ -455,7 +491,7 @@ void FT4::go(int npasses) } } - // assert(max_hz_ + 50 < nrate / 2); + // assert(max_hz_ + 93.6 < nrate / 2); // assert(min_hz_ >= 0); float ratio = nrate / (float)rate_; @@ -468,9 +504,9 @@ void FT4::go(int npasses) // start_ is the sample number of 0.5 seconds, the nominal start time. // make sure there's at least tplus*rate_ samples after the end. - if (start_ + params.tplus * rate_ + 79 * block + block > samples_.size()) + if (start_ + params.tplus * rate_ + 103 * block + block > samples_.size()) { - int need = start_ + params.tplus * rate_ + 79 * block - samples_.size(); + int need = start_ + params.tplus * rate_ + 103 * block - samples_.size(); // round up to a whole second, to ease fft plan caching. if ((need % rate_) != 0) { @@ -509,8 +545,8 @@ void FT4::go(int npasses) if (d.hz0 >= min_hz_ && d.hz0 <= max_hz_) { - // reconstruct correct 79 symbols from LDPC output. - std::vector re79 = recode(d.bits); + // reconstruct correct 103 symbols from LDPC output. + std::vector re103 = recode(d.bits); // fine up hz/off again now that we have more samples float best_hz = (d.hz0 + d.hz1) / 2.0; @@ -518,7 +554,7 @@ void FT4::go(int npasses) search_both_known( samples_, rate_, - re79, + re103, best_hz, best_off, best_hz, @@ -526,7 +562,7 @@ void FT4::go(int npasses) ); // subtract from nsamples_. - subtract(re79, best_hz, best_hz, best_off); + subtract(re103, best_hz, best_hz, best_off); any += 1; } } @@ -563,7 +599,7 @@ void FT4::go(int npasses) for (int hz_frac_i = 0; hz_frac_i < params.coarse_hz_n; hz_frac_i++) { // shift down by hz_frac - float hz_frac = hz_frac_i * (6.25 / params.coarse_hz_n); + float hz_frac = hz_frac_i * (20.833 / params.coarse_hz_n); std::vector samples1; if (hz_frac_i == 0) { @@ -639,29 +675,30 @@ void FT4::go(int npasses) } // -// what's the strength of the Costas sync blocks of +// what's the strength of the FT4 sync blocks of // the signal starting at hz and off? // float FT4::one_strength(const std::vector &samples200, float hz, int off) { - int bin0 = round(hz / 6.25); - int costas[] = {3, 1, 4, 0, 6, 5, 2}; - int starts[] = {0, 36, 72}; + int bin0 = round(hz / 20.833); float sig = 0; float noise = 0; - for (int which = 0; which < 3; which++) + // FT4 has 4 sync blocks of 4 symbols each at positions 0-3, 33-36, 66-69, 99-102 + for (int blockIndex = 0; blockIndex < 4; blockIndex++) { - int start = starts[which]; + int start = blockIndex * 33; // Sync blocks start at 0, 33, 66, 99 - for (int si = 0; si < 7; si++) + for (int si = 0; si < 4; si++) { auto fft = fftEngine_->one_fft(samples200, off + (si + start) * 32, 32); - for (int bi = 0; bi < 8; bi++) + int expectedTone = kFT4SyncTones[blockIndex][si]; + + for (int bi = 0; bi < 4; bi++) { float x = std::abs(fft[bin0 + bi]); - if (bi == costas[si]) { + if (bi == expectedTone) { sig += x; } else { noise += x; @@ -673,11 +710,11 @@ float FT4::one_strength(const std::vector &samples200, float hz, int off) if (params.strength_how == 0) { return sig - noise; } else if (params.strength_how == 1) { - return sig - noise / 7; + return sig - noise / 3; } else if (params.strength_how == 2) { - return sig / (noise / 7); + return sig / (noise / 3); } else if (params.strength_how == 3) { - return sig / (sig + (noise / 7)); + return sig / (sig + (noise / 3)); } else if (params.strength_how == 4) { return sig; } else if (params.strength_how == 5) { @@ -704,14 +741,14 @@ float FT4::one_strength_known( ) { int block = blocksize(rate); - // assert(syms.size() == 79); - int bin0 = round(hz / 6.25); + // assert(syms.size() == 103); + int bin0 = round(hz / 20.833); float sig = 0; float noise = 0; float sum7 = 0; std::complex prev = 0; - for (int si = 0; si < 79; si += params.known_sparse) + for (int si = 0; si < 103; si += params.known_sparse) { auto fft = fftEngine_->one_fft(samples, off + si * block, block); @@ -727,7 +764,7 @@ float FT4::one_strength_known( } else { - for (int bi = 0; bi < 8; bi++) + for (int bi = 0; bi < 4; bi++) { float x = std::abs(fft[bin0 + bi]); @@ -743,11 +780,11 @@ float FT4::one_strength_known( if (params.known_strength_how == 0) { return sig - noise; } else if (params.known_strength_how == 1) { - return sig - noise / 7; + return sig - noise / 3; } else if (params.known_strength_how == 2) { - return sig / (noise / 7); + return sig / (noise / 3); } else if (params.known_strength_how == 3) { - return sig / (sig + (noise / 7)); + return sig / (sig + (noise / 3)); } else if (params.known_strength_how == 4) { return sig; } else if (params.known_strength_how == 5) { @@ -775,12 +812,12 @@ int FT4::search_time_fine( } // - // shift in frequency to put hz at 25. + // shift in frequency to put hz at 46.8. // only shift the samples we need, both for speed, // and try to always shift down the same number of samples // to make it easier to cache fftw plans. // - int len = (offN - off0) + 79 * 32 + 32; + int len = (offN - off0) + 103 * 32 + 32; if (off0 + len > (int)samples200.size()) { @@ -793,9 +830,9 @@ int FT4::search_time_fine( int best_off = -1; float best_sum = 0.0; - for (int g = 0; g <= (offN - off0) && g + 79 * 32 <= len; g += gran) + for (int g = 0; g <= (offN - off0) && g + 103 * 32 <= len; g += gran) { - float sum = one_strength(downsamples200, 25, g); + float sum = one_strength(downsamples200, 46.8, g); if (sum > best_sum || best_off == -1) { @@ -825,7 +862,7 @@ int FT4::search_time_fine_known( } // nearest FFT bin center. - float hz0 = round(hz / 6.25) * 6.25; + float hz0 = round(hz / 20.833) * 20.833; // move hz to hz0, so it is centered in a symbol-sized bin. std::vector downsamples = fft_shift_f(bins, rate, hz - hz0); int best_off = -1; @@ -834,7 +871,7 @@ int FT4::search_time_fine_known( for (int g = off0; g <= offN; g += gran) { - if (g >= 0 && g + 79 * block <= (int)downsamples.size()) + if (g >= 0 && g + 103 * block <= (int)downsamples.size()) { float sum = one_strength_known(downsamples, rate, syms, hz0, g); @@ -856,7 +893,7 @@ int FT4::search_time_fine_known( // // search for costas blocks in an MxN time/frequency grid. -// hz0 +/- hz_win in hz_inc increments. hz0 should be near 25. +// hz0 +/- hz_win in hz_inc increments. hz0 should be near 46.8. // off0 +/- off_win in off_inc incremenents. // std::vector FT4::search_both( @@ -869,7 +906,7 @@ std::vector FT4::search_both( int off_win ) { - // assert(hz0 >= 25 - 6.25 / 2 && hz0 <= 25 + 6.25 / 2); + // assert(hz0 >= 46.8 - 20.833 / 2 && hz0 <= 46.8 + 20.833 / 2); std::vector strengths; @@ -914,7 +951,7 @@ void FT4::search_both_known( float &off_out ) { - // assert(hz0 >= 0 && hz0 + 50 < rate / 2); + // assert(hz0 >= 0 && hz0 + 93.6 < rate / 2); int off0 = round(off_secs0 * (float)rate); int off_win = params.third_off_win * blocksize(rate_); @@ -1049,8 +1086,8 @@ std::vector FT4::fft_shift_f( return out; } -// shift the frequency by a fraction of 6.25, -// to center hz on bin 4 (25 hz). +// shift the frequency by a fraction of 20.833, +// to center hz on bin 2 (46.8 hz). std::vector FT4::shift200( const std::vector &samples200, int off, @@ -1058,82 +1095,85 @@ std::vector FT4::shift200( float hz ) { - if (std::abs(hz - 25) < 0.001 && off == 0 && len == (int)samples200.size()) { + if (std::abs(hz - 46.8) < 0.001 && off == 0 && len == (int)samples200.size()) { return samples200; } else { - return fft_shift(samples200, off, len, 200, hz - 25.0); + return fft_shift(samples200, off, len, 666.7, hz - 46.8); } - // return hilbert_shift(samples200, hz - 25.0, hz - 25.0, 200); + // return hilbert_shift(samples200, hz - 46.8, hz - 46.8, 200); } -// returns a mini-FFT of 79 8-tone symbols. -FFTEngine::ffts_t FT4::extract(const std::vector &samples200, float, int off) +// returns a mini-FFT of 103 4-tone symbols. +// returns a mini-FFT of 103 4-tone symbols. +FFTEngine::ffts_t FT4::extract(const std::vector &samples625, float, int off) { - FFTEngine::ffts_t bins3 = fftEngine_->ffts(samples200, off, 32); - FFTEngine::ffts_t m79(79); + FFTEngine::ffts_t bins3 = fftEngine_->ffts(samples625, off, 32); + FFTEngine::ffts_t m103(103); - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { - m79[si].resize(8); + m103[si].resize(4); if (si < (int)bins3.size()) { - for (int bi = 0; bi < 8; bi++) + for (int bi = 0; bi < 4; bi++) { - auto x = bins3[si][4 + bi]; - m79[si][bi] = x; + auto x = bins3[si][2 + bi]; + m103[si][bi] = x; } } else { - for (int bi = 0; bi < 8; bi++) { - m79[si][bi] = 0; + for (int bi = 0; bi < 4; bi++) { + m103[si][bi] = 0; } } } - return m79; + return m103; } // -// m79 is a 79x8 array of complex. +// m103 is a 103x4 array of complex. // -FFTEngine::ffts_t FT4::un_gray_code_c(const FFTEngine::ffts_t &m79) +FFTEngine::ffts_t FT4::un_gray_code_c(const FFTEngine::ffts_t &m103) { - FFTEngine::ffts_t m79a(79); - int map[] = {0, 1, 3, 2, 6, 4, 5, 7}; + FFTEngine::ffts_t m103a(103); + // FT4 Gray code mapping for 4-FSK: {0, 1, 3, 2} + int map[] = {0, 1, 3, 2}; - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { - m79a[si].resize(8); + m103a[si].resize(4); - for (int bi = 0; bi < 8; bi++) { - m79a[si][map[bi]] = m79[si][bi]; + for (int bi = 0; bi < 4; bi++) { + m103a[si][map[bi]] = m103[si][bi]; } } - return m79a; + return m103a; } // -// m79 is a 79x8 array of float. +// m103 is a 103x4 array of float. // -std::vector> FT4::un_gray_code_r(const std::vector> &m79) +std::vector> FT4::un_gray_code_r(const std::vector> &m103) { - std::vector> m79a(79); - int map[] = {0, 1, 3, 2, 6, 4, 5, 7}; + std::vector> m103a(103); + // FT4 Gray code mapping for 4-FSK: {0, 1, 3, 2} + int map[] = {0, 1, 3, 2}; - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { - m79a[si].resize(8); + m103a[si].resize(4); - for (int bi = 0; bi < 8; bi++) { - m79a[si][map[bi]] = m79[si][bi]; + for (int bi = 0; bi < 4; bi++) { + m103a[si][map[bi]] = m103[si][bi]; } } - return m79a; + return m103a; } // @@ -1166,25 +1206,25 @@ std::vector> FT4::un_gray_code_r_gen(const std::vector> FT4::convert_to_snr(const std::vector> &m79) +std::vector> FT4::convert_to_snr(const std::vector> &m103) { if (params.snr_how < 0 || params.snr_win < 0) { - return m79; + return m103; } // // for each symbol time, what's its "noise" level? // - std::vector mm(79); + std::vector mm(103); - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { - std::vector v(8); + std::vector v(4); float sum = 0.0; - for (int bi = 0; bi < 8; bi++) + for (int bi = 0; bi < 4; bi++) { - float x = m79[si][bi]; + float x = m103[si][bi]; v[bi] = x; sum += x; } @@ -1194,19 +1234,19 @@ std::vector> FT4::convert_to_snr(const std::vector> FT4::convert_to_snr(const std::vector> n79(79); + std::vector> n103(103); - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { float sum = 0; @@ -1231,23 +1271,23 @@ std::vector> FT4::convert_to_snr(const std::vector= 0 && dd < 79) { + if (dd >= 0 && dd < 103) { sum += mm[dd] * winwin[wi]; } else if (dd < 0) { sum += mm[0] * winwin[wi]; } else { - sum += mm[78] * winwin[wi]; + sum += mm[102] * winwin[wi]; } } - n79[si].resize(8); + n103[si].resize(4); - for (int bi = 0; bi < 8; bi++) { - n79[si][bi] = m79[si][bi] / sum; + for (int bi = 0; bi < 4; bi++) { + n103[si][bi] = m103[si][bi] / sum; } } - return n79; + return n103; } // @@ -1255,26 +1295,26 @@ std::vector> FT4::convert_to_snr(const std::vector>> FT4::c_convert_to_snr( - const std::vector>> &m79 + const std::vector>> &m103 ) { if (params.snr_how < 0 || params.snr_win < 0) { - return m79; + return m103; } // // for each symbol time, what's its "noise" level? // - std::vector mm(79); + std::vector mm(103); - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { - std::vector v(8); + std::vector v(4); float sum = 0.0; - for (int bi = 0; bi < 8; bi++) + for (int bi = 0; bi < 4; bi++) { - float x = std::abs(m79[si][bi]); + float x = std::abs(m103[si][bi]); v[bi] = x; sum += x; } @@ -1285,18 +1325,18 @@ std::vector>> FT4::c_convert_to_snr( if (params.snr_how == 0) { // median - mm[si] = (v[3] + v[4]) / 2; + mm[si] = (v[1] + v[2]) / 2; } else if (params.snr_how == 1) { - mm[si] = sum / 8; + mm[si] = sum / 4; } else if (params.snr_how == 2) { // all but strongest tone. - mm[si] = (v[0] + v[1] + v[2] + v[3] + v[4] + v[5] + v[6]) / 7; + mm[si] = (v[0] + v[1] + v[2]) / 3; } else if (params.snr_how == 3) { mm[si] = v[0]; // weakest tone } else if (params.snr_how == 4) { - mm[si] = v[7]; // strongest tone + mm[si] = v[3]; // strongest tone } else if (params.snr_how == 5) { - mm[si] = v[6]; // second-strongest tone + mm[si] = v[2]; // second-strongest tone } else { mm[si] = 1.0; } @@ -1311,9 +1351,9 @@ std::vector>> FT4::c_convert_to_snr( winwin.push_back(1.0); } - std::vector>> n79(79); + std::vector>> n103(103); - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { float sum = 0; @@ -1321,23 +1361,23 @@ std::vector>> FT4::c_convert_to_snr( { int wi = dd - (si - params.snr_win); - if (dd >= 0 && dd < 79) { + if (dd >= 0 && dd < 103) { sum += mm[dd] * winwin[wi]; } else if (dd < 0) { sum += mm[0] * winwin[wi]; } else { - sum += mm[78] * winwin[wi]; + sum += mm[102] * winwin[wi]; } } - n79[si].resize(8); + n103[si].resize(4); - for (int bi = 0; bi < 8; bi++) { - n79[si][bi] = m79[si][bi] / sum; + for (int bi = 0; bi < 4; bi++) { + n103[si][bi] = m103[si][bi] / sum; } } - return n79; + return n103; } std::vector> FT4::convert_to_snr_gen(const FT4Params& params, int nbSymbolBits, const std::vector> &mags) @@ -1434,45 +1474,61 @@ std::vector> FT4::convert_to_snr_gen(const FT4Params& params, // distribution of noise. // void FT4::make_stats( - const std::vector> &m79, + const std::vector> &m103, Stats &bests, Stats &all ) { - int costas[] = {3, 1, 4, 0, 6, 5, 2}; - - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { - if (si < 7 || (si >= 36 && si < 36 + 7) || si >= 72) + // FT4 sync blocks at positions: 0-3, 33-36, 66-69, 99-102 + int blockIndex = -1; + int symbolInBlock = -1; + + if (si < 4) { - // Costas. - int ci; + blockIndex = 0; + symbolInBlock = si; + } + else if (si >= 33 && si < 37) + { + blockIndex = 1; + symbolInBlock = si - 33; + } + else if (si >= 66 && si < 70) + { + blockIndex = 2; + symbolInBlock = si - 66; + } + else if (si >= 99) + { + blockIndex = 3; + symbolInBlock = si - 99; + } - if (si >= 72) { - ci = si - 72; - } else if (si >= 36) { - ci = si - 36; - } else { - ci = si; - } + if (blockIndex >= 0) + { + // FT4 sync symbol + int expectedTone = kFT4SyncTones[blockIndex][symbolInBlock]; - for (int bi = 0; bi < 8; bi++) + for (int bi = 0; bi < 4; bi++) { - float x = m79[si][bi]; + float x = m103[si][bi]; all.add(x); - if (bi == costas[ci]) { + if (bi == expectedTone) { bests.add(x); } } } else { + // Data symbol - find strongest tone float mx = 0; - for (int bi = 0; bi < 8; bi++) + for (int bi = 0; bi < 4; bi++) { - float x = m79[si][bi]; + float x = m103[si][bi]; if (x > mx) { mx = x; @@ -1489,6 +1545,7 @@ void FT4::make_stats( // // generalized version of the above for any number of symbols and no Costas // used by FT-chirp decoder +// Probably not needed here. The FT8 one may be used. // void FT4::make_stats_gen( const std::vector> &mags, @@ -1519,7 +1576,7 @@ void FT4::make_stats_gen( } // -// convert 79x8 complex FFT bins to magnitudes. +// convert 103x4 complex FFT bins to magnitudes. // // exploits local phase coherence by decreasing magnitudes of bins // whose phase is far from the phases of nearby strongest tones. @@ -1529,28 +1586,28 @@ void FT4::make_stats_gen( // number of cycles and thus preserves phase from one symbol to the // next. // -std::vector> FT4::soft_c2m(const FFTEngine::ffts_t &c79) +std::vector> FT4::soft_c2m(const FFTEngine::ffts_t &c103) { - std::vector> m79(79); - std::vector raw_phases(79); // of strongest tone in each symbol time + std::vector> m103(103); + std::vector raw_phases(103); // of strongest tone in each symbol time - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { - m79[si].resize(8); + m103[si].resize(4); int mxi = -1; float mx; float mx_phase; - for (int bi = 0; bi < 8; bi++) + for (int bi = 0; bi < 4; bi++) { - float x = std::abs(c79[si][bi]); - m79[si][bi] = x; + float x = std::abs(c103[si][bi]); + m103[si][bi] = x; if (mxi < 0 || x > mx) { mxi = bi; mx = x; - mx_phase = std::arg(c79[si][bi]); // -pi .. pi + mx_phase = std::arg(c103[si][bi]); // -pi .. pi } } @@ -1558,20 +1615,20 @@ std::vector> FT4::soft_c2m(const FFTEngine::ffts_t &c79) } if (params.soft_phase_win <= 0) { - return m79; + return m103; } // phase around each symbol. - std::vector phases(79); + std::vector phases(103); // for each symbol time, median of nearby phases - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { std::vector v; for (int si1 = si - params.soft_phase_win; si1 <= si + params.soft_phase_win; si1++) { - if (si1 >= 0 && si1 < 79) + if (si1 >= 0 && si1 < 103) { float x = raw_phases[si1]; v.push_back(x); @@ -1614,12 +1671,12 @@ std::vector> FT4::soft_c2m(const FFTEngine::ffts_t &c79) } // project each tone against the median phase around that symbol time. - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { - for (int bi = 0; bi < 8; bi++) + for (int bi = 0; bi < 4; bi++) { - float mag = std::abs(c79[si][bi]); - float angle = std::arg(c79[si][bi]); + float mag = std::abs(c103[si][bi]); + float angle = std::arg(c103[si][bi]); float d = angle - phases[si]; float factor = 0.1; @@ -1627,11 +1684,11 @@ std::vector> FT4::soft_c2m(const FFTEngine::ffts_t &c79) factor = cos(d); } - m79[si][bi] = factor * mag; + m103[si][bi] = factor * mag; } } - return m79; + return m103; } // @@ -1717,94 +1774,74 @@ float FT4::bayes( } // -// c79 is 79x8 complex tones, before un-gray-coding. +// c103 is 103x4 complex tones, before un-gray-coding. // -void FT4::soft_decode(const FFTEngine::ffts_t &c79, float ll174[]) +void FT4::soft_decode(const FFTEngine::ffts_t &c103, float ll174[]) { - std::vector> m79(79); - // m79 = absolute values of c79. + std::vector> m103(103); + // m103 = absolute values of c103. // still pre-un-gray-coding so we know which - // are the correct Costas tones. - m79 = soft_c2m(c79); - m79 = convert_to_snr(m79); + // are the correct sync tones. + m103 = soft_c2m(c103); + m103 = convert_to_snr(m103); // statistics to decide soft probabilities. // distribution of strongest tones, and // distribution of noise. Stats bests(params.problt_how_sig, params.log_tail, params.log_rate); Stats all(params.problt_how_noise, params.log_tail, params.log_rate); - make_stats(m79, bests, all); - m79 = un_gray_code_r(m79); + make_stats(m103, bests, all); + m103 = un_gray_code_r(m103); int lli = 0; - // tone numbers that make second index bit zero or one. - int zeroi[4][3]; - int onei[4][3]; + // tone numbers that make each bit zero or one (for 4-FSK, 2 bits per symbol). + int zeroi[2][2]; + int onei[2][2]; - for (int biti = 0; biti < 3; biti++) + for (int biti = 0; biti < 2; biti++) { if (biti == 0) { - // high bit + // high bit (bit 1) zeroi[0][0] = 0; zeroi[1][0] = 1; - zeroi[2][0] = 2; - zeroi[3][0] = 3; - onei[0][0] = 4; - onei[1][0] = 5; - onei[2][0] = 6; - onei[3][0] = 7; + onei[0][0] = 2; + onei[1][0] = 3; } if (biti == 1) { - // middle bit + // low bit (bit 0) zeroi[0][1] = 0; - zeroi[1][1] = 1; - zeroi[2][1] = 4; - zeroi[3][1] = 5; - onei[0][1] = 2; + zeroi[1][1] = 2; + onei[0][1] = 1; onei[1][1] = 3; - onei[2][1] = 6; - onei[3][1] = 7; - } - - if (biti == 2) - { - // low bit - zeroi[0][2] = 0; - zeroi[1][2] = 2; - zeroi[2][2] = 4; - zeroi[3][2] = 6; - onei[0][2] = 1; - onei[1][2] = 3; - onei[2][2] = 5; - onei[3][2] = 7; } } - for (int i79 = 0; i79 < 79; i79++) + for (int i103 = 0; i103 < 103; i103++) { - if (i79 < 7 || (i79 >= 36 && i79 < 36 + 7) || i79 >= 72) { - // Costas, skip + // FT4 sync blocks at positions: 0-3, 33-36, 66-69, 99-102 + if ((i103 < 4) || (i103 >= 33 && i103 < 37) || (i103 >= 66 && i103 < 70) || (i103 >= 99)) { + // FT4 sync block, skip continue; } - // for each of the three bits, look at the strongest tone + // for each of the two bits, look at the strongest tone // that would make it a zero, and the strongest tone that // would make it a one. use Bayes to decide which is more // likely, comparing each against the distribution of noise // and the distribution of strongest tones. // most-significant-bit first. - for (int biti = 0; biti < 3; biti++) + for (int biti = 0; biti < 2; biti++) { // strongest tone that would make this bit be zero. int got_best_zero = 0; float best_zero = 0; - for (int i = 0; i < 4; i++) + for (int i = 0; i < 2; i++) { - float x = m79[i79][zeroi[i][biti]]; + float x = m103[i103][zeroi[i][biti]]; if (got_best_zero == 0 || x > best_zero) { @@ -1817,9 +1854,9 @@ void FT4::soft_decode(const FFTEngine::ffts_t &c79, float ll174[]) int got_best_one = 0; float best_one = 0; - for (int i = 0; i < 4; i++) + for (int i = 0; i < 2; i++) { - float x = m79[i79][onei[i][biti]]; + float x = m103[i103][onei[i][biti]]; if (got_best_one == 0 || x > best_one) { got_best_one = 1; @@ -1916,43 +1953,58 @@ void FT4::soft_decode_mags(FT4Params& params, const std::vector maxes[79]; + FFTEngine::ffts_t c103 = c_convert_to_snr(c103x); + std::vector> maxes(103); - for (int i = 0; i < 79; i++) + for (int i = 0; i < 103; i++) { std::complex m; - if (i < 7) + // FT4 sync blocks at positions: 0-3, 33-36, 66-69, 99-102 + int blockIndex = -1; + int symbolInBlock = -1; + + if (i < 4) { - // Costas. - m = c79[i][costas[i]]; + blockIndex = 0; + symbolInBlock = i; } - else if (i >= 36 && i < 36 + 7) + else if (i >= 33 && i < 37) { - // Costas. - m = c79[i][costas[i - 36]]; + blockIndex = 1; + symbolInBlock = i - 33; } - else if (i >= 72) + else if (i >= 66 && i < 70) { - // Costas. - m = c79[i][costas[i - 72]]; + blockIndex = 2; + symbolInBlock = i - 66; + } + else if (i >= 99) + { + blockIndex = 3; + symbolInBlock = i - 99; + } + + if (blockIndex >= 0) + { + // FT4 sync symbol: use expected sync tone. + int expectedTone = kFT4SyncTones[blockIndex][symbolInBlock]; + m = c103[i][expectedTone]; } else { int got = 0; - for (int j = 0; j < 8; j++) + for (int j = 0; j < 4; j++) { - if (got == 0 || std::abs(c79[i][j]) > std::abs(m)) + if (got == 0 || std::abs(c103[i][j]) > std::abs(m)) { got = 1; - m = c79[i][j]; + m = c103[i][j]; } } } @@ -1960,21 +2012,21 @@ void FT4::c_soft_decode(const FFTEngine::ffts_t &c79x, float ll174[]) maxes[i] = m; } - std::vector> m79(79); + std::vector> m103(103); - for (int i = 0; i < 79; i++) + for (int i = 0; i < 103; i++) { - m79[i].resize(8); + m103[i].resize(4); - for (int j = 0; j < 8; j++) + for (int j = 0; j < 4; j++) { - std::complex c = c79[i][j]; + std::complex c = c103[i][j]; int n = 0; float sum = 0; for (int k = i - params.c_soft_win; k <= i + params.c_soft_win; k++) { - if (k < 0 || k >= 79) { + if (k < 0 || k >= 103) { continue; } @@ -1986,7 +2038,7 @@ void FT4::c_soft_decode(const FFTEngine::ffts_t &c79x, float ll174[]) { // we're expecting all genuine tones to have // about the same phase and magnitude. - // so set m79[i][j] to the distance from the + // so set m103[i][j] to the distance from the // phase/magnitude predicted by surrounding // genuine-looking tones. std::complex c1 = maxes[k]; @@ -1997,7 +2049,7 @@ void FT4::c_soft_decode(const FFTEngine::ffts_t &c79x, float ll174[]) n += 1; } - m79[i][j] = 0 - (sum / n); + m103[i][j] = 0 - (sum / n); } } @@ -2006,79 +2058,59 @@ void FT4::c_soft_decode(const FFTEngine::ffts_t &c79x, float ll174[]) // distribution of noise. Stats bests(params.problt_how_sig, params.log_tail, params.log_rate); Stats all(params.problt_how_noise, params.log_tail, params.log_rate); - make_stats(m79, bests, all); - m79 = un_gray_code_r(m79); + make_stats(m103, bests, all); + m103 = un_gray_code_r(m103); int lli = 0; - // tone numbers that make second index bit zero or one. - int zeroi[4][3]; - int onei[4][3]; + // tone numbers that make each bit zero or one (for 4-FSK, 2 bits per symbol). + int zeroi[2][2]; + int onei[2][2]; - for (int biti = 0; biti < 3; biti++) + for (int biti = 0; biti < 2; biti++) { if (biti == 0) { - // high bit + // high bit (bit 1) zeroi[0][0] = 0; zeroi[1][0] = 1; - zeroi[2][0] = 2; - zeroi[3][0] = 3; - onei[0][0] = 4; - onei[1][0] = 5; - onei[2][0] = 6; - onei[3][0] = 7; + onei[0][0] = 2; + onei[1][0] = 3; } if (biti == 1) { - // middle bit + // low bit (bit 0) zeroi[0][1] = 0; - zeroi[1][1] = 1; - zeroi[2][1] = 4; - zeroi[3][1] = 5; - onei[0][1] = 2; + zeroi[1][1] = 2; + onei[0][1] = 1; onei[1][1] = 3; - onei[2][1] = 6; - onei[3][1] = 7; - } - - if (biti == 2) - { - // low bit - zeroi[0][2] = 0; - zeroi[1][2] = 2; - zeroi[2][2] = 4; - zeroi[3][2] = 6; - onei[0][2] = 1; - onei[1][2] = 3; - onei[2][2] = 5; - onei[3][2] = 7; } } - for (int i79 = 0; i79 < 79; i79++) + for (int i103 = 0; i103 < 103; i103++) { - if (i79 < 7 || (i79 >= 36 && i79 < 36 + 7) || i79 >= 72) { - // Costas, skip + // FT4 sync blocks at positions: 0-3, 33-36, 66-69, 99-102 + if ((i103 < 4) || (i103 >= 33 && i103 < 37) || (i103 >= 66 && i103 < 70) || (i103 >= 99)) { + // FT4 sync block, skip continue; } - // for each of the three bits, look at the strongest tone + // for each of the two bits, look at the strongest tone // that would make it a zero, and the strongest tone that // would make it a one. use Bayes to decide which is more // likely, comparing each against the distribution of noise // and the distribution of strongest tones. // most-significant-bit first. - for (int biti = 0; biti < 3; biti++) + for (int biti = 0; biti < 2; biti++) { // strongest tone that would make this bit be zero. int got_best_zero = 0; float best_zero = 0; - for (int i = 0; i < 4; i++) + for (int i = 0; i < 2; i++) { - float x = m79[i79][zeroi[i][biti]]; + float x = m103[i103][zeroi[i][biti]]; if (got_best_zero == 0 || x > best_zero) { @@ -2091,9 +2123,9 @@ void FT4::c_soft_decode(const FFTEngine::ffts_t &c79x, float ll174[]) int got_best_one = 0; float best_one = 0; - for (int i = 0; i < 4; i++) + for (int i = 0; i < 2; i++) { - float x = m79[i79][onei[i][biti]]; + float x = m103[i103][onei[i][biti]]; if (got_best_one == 0 || x > best_one) { @@ -2147,10 +2179,10 @@ void FT4::set_ones_zeroes(int ones[], int zeroes[], int nbBits, int bitIndex) } // -// turn 79 symbol numbers into 174 bits. -// strip out the three Costas sync blocks, -// leaving 58 symbol numbers. -// each represents three bits. +// turn 103 symbol numbers into 174 bits. +// strip out the four FT4 sync blocks, +// leaving 87 symbol numbers. +// each represents two bits (4-FSK). // (all post-un-gray-code). // str is per-symbol strength; must be positive. // each returned element is < 0 for 1, > 0 for zero, @@ -2158,20 +2190,26 @@ void FT4::set_ones_zeroes(int ones[], int zeroes[], int nbBits, int bitIndex) // std::vector FT4::extract_bits(const std::vector &syms, const std::vector str) { - // assert(syms.size() == 79); - // assert(str.size() == 79); + // assert(syms.size() == 103); + // assert(str.size() == 103); std::vector bits; - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { - if (si < 7 || (si >= 36 && si < 36 + 7) || si >= 72) + // FT4 sync blocks at positions: 0-3, 33-36, 66-69, 99-102 + bool inSync = (si < 4) + || (si >= 33 && si < 37) + || (si >= 66 && si < 70) + || (si >= 99); + + if (inSync) { - // costas -- skip + // sync block -- skip } else { - bits.push_back((syms[si] & 4) == 0 ? str[si] : -str[si]); + // Extract 2 bits per symbol for 4-FSK bits.push_back((syms[si] & 2) == 0 ? str[si] : -str[si]); bits.push_back((syms[si] & 1) == 0 ? str[si] : -str[si]); } @@ -2184,11 +2222,11 @@ std::vector FT4::extract_bits(const std::vector &syms, const std::ve // that they have the same phase, by summing the complex // correlations for each possible pair and using the max. void FT4::soft_decode_pairs( - const FFTEngine::ffts_t &m79x, + const FFTEngine::ffts_t &m103x, float ll174[] ) { - FFTEngine::ffts_t m79 = c_convert_to_snr(m79x); + FFTEngine::ffts_t m103 = c_convert_to_snr(m103x); struct BitInfo { @@ -2196,7 +2234,7 @@ void FT4::soft_decode_pairs( float one; // and one }; - std::vector bitinfo(79 * 3); + std::vector bitinfo(103 * 2); for (int i = 0; i < (int)bitinfo.size(); i++) { @@ -2206,26 +2244,26 @@ void FT4::soft_decode_pairs( Stats all(params.problt_how_noise, params.log_tail, params.log_rate); Stats bests(params.problt_how_sig, params.log_tail, params.log_rate); - int map[] = {0, 1, 3, 2, 6, 4, 5, 7}; // un-gray-code + int map[] = {0, 1, 3, 2}; // un-gray-code - for (int si = 0; si < 79; si += 2) + for (int si = 0; si < 103; si += 2) { float mx = 0; - float corrs[8 * 8]; + float corrs[4 * 4]; - for (int s1 = 0; s1 < 8; s1++) + for (int s1 = 0; s1 < 4; s1++) { - for (int s2 = 0; s2 < 8; s2++) + for (int s2 = 0; s2 < 4; s2++) { // sum up the correlations. - std::complex csum = m79[si][s1]; + std::complex csum = m103[si][s1]; - if (si + 1 < 79) { - csum += m79[si + 1][s2]; + if (si + 1 < 103) { + csum += m103[si + 1][s2]; } float x = std::abs(csum); - corrs[s1 * 8 + s2] = x; + corrs[s1 * 4 + s2] = x; if (x > mx) { mx = x; @@ -2235,9 +2273,9 @@ void FT4::soft_decode_pairs( // first symbol int i = map[s1]; - for (int bit = 0; bit < 3; bit++) + for (int bit = 0; bit < 2; bit++) { - int bitind = (si + 0) * 3 + (2 - bit); + int bitind = (si + 0) * 2 + (1 - bit); if ((i & (1 << bit))) { @@ -2256,13 +2294,13 @@ void FT4::soft_decode_pairs( } // second symbol - if (si + 1 < 79) + if (si + 1 < 103) { i = map[s2]; - for (int bit = 0; bit < 3; bit++) + for (int bit = 0; bit < 2; bit++) { - int bitind = (si + 1) * 3 + (2 - bit); + int bitind = (si + 1) * 2 + (1 - bit); if ((i & (1 << bit))) { @@ -2283,12 +2321,12 @@ void FT4::soft_decode_pairs( } } - if (si == 0 || si == 36 || si == 72) { - bests.add(corrs[3 * 8 + 1]); - } else if (si == 2 || si == 38 || si == 74) { - bests.add(corrs[4 * 8 + 0]); - } else if (si == 4 || si == 40 || si == 76) { - bests.add(corrs[6 * 8 + 5]); + // FT4 sync blocks are [0..3], [33..36], [66..69], [99..102]. + // Since we process pairs, sync pairs are start/start+1 and start+2/start+3. + if (si == 0 || si == 33 || si == 66 || si == 99) { + bests.add(corrs[kFT4SyncTones[(si / 33)][0] * 4 + kFT4SyncTones[(si / 33)][1]]); + } else if (si == 2 || si == 35 || si == 68 || si == 101) { + bests.add(corrs[kFT4SyncTones[((si - 2) / 33)][2] * 4 + kFT4SyncTones[((si - 2) / 33)][3]]); } else { bests.add(mx); } @@ -2296,17 +2334,17 @@ void FT4::soft_decode_pairs( int lli = 0; - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { - if (si < 7 || (si >= 36 && si < 36 + 7) || si >= 72) { - // costas + if ((si < 4) || (si >= 33 && si < 37) || (si >= 66 && si < 70) || (si >= 99)) { + // FT4 sync continue; } - for (int i = 0; i < 3; i++) + for (int i = 0; i < 2; i++) { - float best_zero = bitinfo[si * 3 + i].zero; - float best_one = bitinfo[si * 3 + i].one; + 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); ll174[lli++] = ll; @@ -2316,11 +2354,11 @@ void FT4::soft_decode_pairs( } void FT4::soft_decode_triples( - const FFTEngine::ffts_t &m79x, + const FFTEngine::ffts_t &m103x, float ll174[] ) { - FFTEngine::ffts_t m79 = c_convert_to_snr(m79x); + FFTEngine::ffts_t m103 = c_convert_to_snr(m103x); struct BitInfo { @@ -2328,7 +2366,7 @@ void FT4::soft_decode_triples( float one; // and one }; - std::vector bitinfo(79 * 3); + std::vector bitinfo(103 * 2); for (int i = 0; i < (int)bitinfo.size(); i++) { @@ -2339,31 +2377,31 @@ void FT4::soft_decode_triples( Stats all(params.problt_how_noise, params.log_tail, params.log_rate); Stats bests(params.problt_how_sig, params.log_tail, params.log_rate); - int map[] = {0, 1, 3, 2, 6, 4, 5, 7}; // un-gray-code + int map[] = {0, 1, 3, 2}; // un-gray-code - for (int si = 0; si < 79; si += 3) + for (int si = 0; si < 103; si += 3) { float mx = 0; - float corrs[8 * 8 * 8]; + float corrs[4 * 4 * 4]; - for (int s1 = 0; s1 < 8; s1++) + for (int s1 = 0; s1 < 4; s1++) { - for (int s2 = 0; s2 < 8; s2++) + for (int s2 = 0; s2 < 4; s2++) { - for (int s3 = 0; s3 < 8; s3++) + for (int s3 = 0; s3 < 4; s3++) { - std::complex csum = m79[si][s1]; + std::complex csum = m103[si][s1]; - if (si + 1 < 79) { - csum += m79[si + 1][s2]; + if (si + 1 < 103) { + csum += m103[si + 1][s2]; } - if (si + 2 < 79) { - csum += m79[si + 2][s3]; + if (si + 2 < 103) { + csum += m103[si + 2][s3]; } float x = std::abs(csum); - corrs[s1 * 64 + s2 * 8 + s3] = x; + corrs[s1 * 16 + s2 * 4 + s3] = x; if (x > mx) { mx = x; @@ -2373,9 +2411,9 @@ void FT4::soft_decode_triples( // first symbol int i = map[s1]; - for (int bit = 0; bit < 3; bit++) + for (int bit = 0; bit < 2; bit++) { - int bitind = (si + 0) * 3 + (2 - bit); + int bitind = (si + 0) * 2 + (1 - bit); if ((i & (1 << bit))) { @@ -2394,13 +2432,13 @@ void FT4::soft_decode_triples( } // second symbol - if (si + 1 < 79) + if (si + 1 < 103) { i = map[s2]; - for (int bit = 0; bit < 3; bit++) + for (int bit = 0; bit < 2; bit++) { - int bitind = (si + 1) * 3 + (2 - bit); + int bitind = (si + 1) * 2 + (1 - bit); if ((i & (1 << bit))) { @@ -2420,13 +2458,13 @@ void FT4::soft_decode_triples( } // third symbol - if (si + 2 < 79) + if (si + 2 < 103) { i = map[s3]; - for (int bit = 0; bit < 3; bit++) + for (int bit = 0; bit < 2; bit++) { - int bitind = (si + 2) * 3 + (2 - bit); + int bitind = (si + 2) * 2 + (1 - bit); if ((i & (1 << bit))) { @@ -2448,29 +2486,43 @@ void FT4::soft_decode_triples( } } - // costas: 3, 1, 4, 0, 6, 5, 2 - if (si == 0 || si == 36 || si == 72) { - bests.add(corrs[3 * 64 + 1 * 8 + 4]); - } else if (si == 3 || si == 39 || si == 75) { - bests.add(corrs[0 * 64 + 6 * 8 + 5]); - } else { + // FT4 sync blocks: [0..3], [33..36], [66..69], [99..102]. + // Use sync-only triple windows (start/start+1/start+2) when available. + int blockIndex = -1; + int symbolInBlock = -1; + + if (si == 0 || si == 33 || si == 66 || si == 99) + { + blockIndex = si / 33; + symbolInBlock = 0; + } + + if (blockIndex >= 0) + { + int t1 = kFT4SyncTones[blockIndex][symbolInBlock + 0]; + int t2 = kFT4SyncTones[blockIndex][symbolInBlock + 1]; + int t3 = kFT4SyncTones[blockIndex][symbolInBlock + 2]; + bests.add(corrs[t1 * 16 + t2 * 4 + t3]); + } + else + { bests.add(mx); } } int lli = 0; - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { - if (si < 7 || (si >= 36 && si < 36 + 7) || si >= 72) { - // costas + if ((si < 4) || (si >= 33 && si < 37) || (si >= 66 && si < 70) || (si >= 99)) { + // FT4 sync continue; } - for (int i = 0; i < 3; i++) + for (int i = 0; i < 2; i++) { - float best_zero = bitinfo[si * 3 + i].zero; - float best_one = bitinfo[si * 3 + i].one; + 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); ll174[lli++] = ll; @@ -2520,7 +2572,12 @@ int FT4::decode(const float ll174[], int a174[], FT4Params& _params, int use_osd // reconstruct all 174. comment += "OSD-" + std::to_string(got_depth) + "-" + std::to_string(ldpc_ok); OSD::ldpc_encode(oplain, a174); - return 1; + + if (OSD::check_crc(a174)) { + return 1; + } else { + comment = "CRC fail after " + comment; + } } else { @@ -2631,7 +2688,7 @@ std::vector> FT4::fbandpass( } // -// move hz down to 25, filter+convert to 200 samples/second. +// move hz down to 46.8, filter+convert to 666.7 samples/second. // // like fft_shift(). one big FFT, move bins down and // zero out those outside the band, then IFFT, @@ -2651,7 +2708,7 @@ std::vector FT4::down_v7_f(const std::vector> &bins, { int nbins = bins.size(); float bin_hz = rate_ / (float)len; - int down = round((hz - 25) / bin_hz); + int down = round((hz - 46.8) / bin_hz); std::vector> bins1(nbins); for (int i = 0; i < nbins; i++) @@ -2665,20 +2722,20 @@ std::vector FT4::down_v7_f(const std::vector> &bins, } } - // now filter to fit in 200 samples/second. + // now filter to fit in 666.7 samples/second. - float low_inner = 25.0 - params.shoulder200_extra; + float low_inner = 46.8 - params.shoulder200_extra; float low_outer = low_inner - params.shoulder200; if (low_outer < 0) { low_outer = 0; } - float high_inner = 75 - 6.25 + params.shoulder200_extra; + float high_inner = 93.6*1.5 - 20.833 + params.shoulder200_extra; float high_outer = high_inner + params.shoulder200; - if (high_outer > 100) { - high_outer = 100; + if (high_outer > 333.35) { + high_outer = 333.35; } bins1 = fbandpass( @@ -2690,8 +2747,8 @@ std::vector FT4::down_v7_f(const std::vector> &bins, high_outer ); - // convert back to time domain and down-sample to 200 samples/second. - int blen = round(len * (200.0 / rate_)); + // convert back to time domain and down-sample to 666.7 samples/second. + int blen = round(len * (666.7 / rate_)); std::vector> bbins(blen / 2 + 1); for (int i = 0; i < (int)bbins.size(); i++) { @@ -2719,11 +2776,11 @@ std::vector FT4::down_v7_f(const std::vector> &bins, // // - // move down to 25 hz and re-sample to 200 samples/second, + // move down to 46.8 hz and re-sample to 666.7 samples/second, // i.e. 32 samples/symbol. // std::vector samples200 = down_v7_f(bins, len, hz); - int off200 = round((off / (float)rate_) * 200.0); + int off200 = round((off / (float)rate_) * 666.7); int ret = one_iter(samples200, off200, hz); return ret; } @@ -2738,7 +2795,7 @@ int FT4::one_iter(const std::vector &samples200, int best_off, float hz_f { std::vector strengths = search_both( samples200, - 25, + 46.8, params.second_hz_n, params.second_hz_win, best_off, @@ -2769,7 +2826,7 @@ int FT4::one_iter(const std::vector &samples200, int best_off, float hz_f } else { - int ret = one_iter1(samples200, best_off, 25, hz_for_cb, hz_for_cb); + int ret = one_iter1(samples200, best_off, 46.8, hz_for_cb, hz_for_cb); return ret; } @@ -2778,43 +2835,75 @@ int FT4::one_iter(const std::vector &samples200, int best_off, float hz_f // // estimate SNR, yielding numbers vaguely similar to WSJT-X. -// m79 is a 79x8 complex FFT output. +// m103 is a 103x4 complex FFT output. // -float FT4::guess_snr(const FFTEngine::ffts_t &m79) +float FT4::guess_snr(const FFTEngine::ffts_t &m103) { - int costas[] = {3, 1, 4, 0, 6, 5, 2}; float pnoises = 0; float psignals = 0; - for (int i = 0; i < 7; i++) + for (int si = 0; si < 103; si++) { - psignals += std::abs(m79[i][costas[i]]); - psignals += std::abs(m79[36 + i][costas[i]]); - psignals += std::abs(m79[72 + i][costas[i]]); - pnoises += std::abs(m79[i][(costas[i] + 4) % 8]); - pnoises += std::abs(m79[36 + i][(costas[i] + 4) % 8]); - pnoises += std::abs(m79[72 + i][(costas[i] + 4) % 8]); - } - - for (int i = 0; i < 79; i++) - { - if (i < 7 || (i >= 36 && i < 36 + 7) || (i >= 72 && i < 72 + 7)) { + if (si >= (int)m103.size() || m103[si].size() < 4) { continue; } - std::vector v(8); + std::vector v(4); + float sum = 0; - for (int j = 0; j < 8; j++) { - v[j] = std::abs(m79[i][j]); + for (int bi = 0; bi < 4; bi++) + { + v[bi] = std::abs(m103[si][bi]); + sum += v[bi]; } - std::sort(v.begin(), v.end()); - psignals += v[7]; // strongest tone, probably the signal - pnoises += (v[2] + v[3] + v[4]) / 3; + int blockIndex = -1; + int symbolInBlock = -1; + + if (si < 4) + { + blockIndex = 0; + symbolInBlock = si; + } + else if (si >= 33 && si < 37) + { + blockIndex = 1; + symbolInBlock = si - 33; + } + else if (si >= 66 && si < 70) + { + blockIndex = 2; + symbolInBlock = si - 66; + } + else if (si >= 99) + { + blockIndex = 3; + symbolInBlock = si - 99; + } + + if (blockIndex >= 0) + { + // FT4 sync symbols: use known sync tone. + int expectedTone = kFT4SyncTones[blockIndex][symbolInBlock]; + psignals += v[expectedTone]; + pnoises += (sum - v[expectedTone]) / 3; + } + else + { + // Data symbols: strongest tone is likely signal. + std::sort(v.begin(), v.end()); + psignals += v[3]; + pnoises += (v[0] + v[1] + v[2]) / 3; + } + } + + pnoises /= 103; + psignals /= 103; + + if (pnoises < 1e-12f) { + pnoises = 1e-12f; } - pnoises /= 79; - psignals /= 79; pnoises *= pnoises; // square yields power psignals *= psignals; float raw = psignals / pnoises; @@ -2842,7 +2931,7 @@ float FT4::guess_snr(const FFTEngine::ffts_t &m79) // a phase that depends on the symbol's frequency; // a particular offset error causes a phase error // that depends on frequency. -// hz0 is actual FFT bin number of m79[...][0] (always 4). +// hz0 is actual FFT bin number of m103[...][0]. // // the output adj_hz is relative to the FFT bin center; // a positive number means the real signal seems to be @@ -2851,38 +2940,55 @@ float FT4::guess_snr(const FFTEngine::ffts_t &m79) // adj_off is the amount to change the offset, in samples. // should be subtracted from offset. // -void FT4::fine(const FFTEngine::ffts_t &m79, int, float &adj_hz, float &adj_off) +void FT4::fine(const FFTEngine::ffts_t &m103, int, float &adj_hz, float &adj_off) { adj_hz = 0.0; adj_off = 0.0; - // tone number for each of the 79 symbols. - int sym[79]; - float symval[79]; - float symphase[79]; - int costas[] = {3, 1, 4, 0, 6, 5, 2}; + // tone number for each of the 103 symbols. + int sym[103]; + float symval[103]; + float symphase[103]; + const int nSymbols = std::min(103, (int)m103.size()); - for (int i = 0; i < 79; i++) + for (int i = 0; i < nSymbols; i++) { - if (i < 7) + int blockIndex = -1; + int symbolInBlock = -1; + + if (i < 4) { - sym[i] = costas[i]; + blockIndex = 0; + symbolInBlock = i; } - else if (i >= 36 && i < 36 + 7) + else if (i >= 33 && i < 37) { - sym[i] = costas[i - 36]; + blockIndex = 1; + symbolInBlock = i - 33; } - else if (i >= 72) + else if (i >= 66 && i < 70) { - sym[i] = costas[i - 72]; + blockIndex = 2; + symbolInBlock = i - 66; + } + else if (i >= 99) + { + blockIndex = 3; + symbolInBlock = i - 99; + } + + if (blockIndex >= 0) + { + // FT4 sync symbol: use expected sync tone. + sym[i] = kFT4SyncTones[blockIndex][symbolInBlock]; } else { int mxj = -1; float mx = 0; - for (int j = 0; j < 8; j++) + for (int j = 0; j < 4; j++) { - float x = std::abs(m79[i][j]); + float x = std::abs(m103[i][j]); if (mxj < 0 || x > mx) { @@ -2894,14 +3000,14 @@ void FT4::fine(const FFTEngine::ffts_t &m79, int, float &adj_hz, float &adj_off) sym[i] = mxj; } - symphase[i] = std::arg(m79[i][sym[i]]); - symval[i] = std::abs(m79[i][sym[i]]); + symphase[i] = std::arg(m103[i][sym[i]]); + symval[i] = std::abs(m103[i][sym[i]]); } float sum = 0; float weight_sum = 0; - for (int i = 0; i < 79 - 1; i++) + for (int i = 0; i < nSymbols - 1; i++) { float d = symphase[i + 1] - symphase[i]; @@ -2918,9 +3024,13 @@ void FT4::fine(const FFTEngine::ffts_t &m79, int, float &adj_hz, float &adj_off) weight_sum += w; } + if (weight_sum == 0.0f) { + return; + } + float mean = sum / weight_sum; float err_rad = mean; // radians per symbol time - float err_hz = (err_rad / (2 * M_PI)) / 0.16; // cycles per symbol time + float err_hz = (err_rad / (2 * M_PI)) / 0.048; // FT4 symbol time is 0.048s // if each symbol's phase is a bit more than we expect, // that means the real frequency is a bit higher @@ -2931,7 +3041,7 @@ void FT4::fine(const FFTEngine::ffts_t &m79, int, float &adj_hz, float &adj_off) // now think about offset error. // // the higher tones have many cycles per - // symbol -- e.g. tone 7 has 11 cycles + // symbol -- high tones have more cycles // in each symbol. a one- or two-sample // offset error at such a high tone will // change the phase by pi or more, @@ -2945,10 +3055,10 @@ void FT4::fine(const FFTEngine::ffts_t &m79, int, float &adj_hz, float &adj_off) float early = 0.0; float late = 0.0; - for (int i = 1; i < 79; i++) + for (int i = 1; i < nSymbols; i++) { - float ph0 = std::arg(m79[i - 1][sym[i - 1]]); - float ph = std::arg(m79[i][sym[i]]); + float ph0 = symphase[i - 1]; + float ph = symphase[i]; float d = ph - ph0; d -= err_rad; // correct for hz error. @@ -3047,7 +3157,7 @@ void FT4::fine(const FFTEngine::ffts_t &m79, int, float &adj_hz, float &adj_off) } // -// the signal is at roughly 25 hz in samples200. +// the signal is centered around 46.8 Hz in samples200. // // return 2 if it decodes to a brand-new message. // return 1 if it decodes but we've already seen it, @@ -3062,7 +3172,7 @@ int FT4::one_iter1( float hz1_for_cb ) { - // put best_hz in the middle of bin 4, at 25.0. + // put best_hz at the FT4 center frequency. std::vector samples200 = shift200( samples200x, 0, @@ -3070,8 +3180,8 @@ int FT4::one_iter1( best_hz ); - // mini 79x8 FFT. - FFTEngine::ffts_t m79 = extract(samples200, 25, best_off); + // mini 103x4 FFT. + FFTEngine::ffts_t m103 = extract(samples200, 46.8, best_off); // look at symbol-to-symbol phase change to try // to improve best_hz and best_off. @@ -3079,7 +3189,7 @@ int FT4::one_iter1( { float adj_hz = 0; float adj_off = 0; - fine(m79, 4, adj_hz, adj_off); + fine(m103, 4, adj_hz, adj_off); if (params.do_fine_hz == 0) { adj_hz = 0; @@ -3089,7 +3199,7 @@ int FT4::one_iter1( adj_off = 0; } - if (fabs(adj_hz) < 6.25 / 4 && fabs(adj_off) < 4) + if (fabs(adj_hz) < 20.833 / 4 && fabs(adj_off) < 4) { best_hz += adj_hz; best_off += round(adj_off); @@ -3099,7 +3209,7 @@ int FT4::one_iter1( } samples200 = shift200(samples200x, 0, samples200x.size(), best_hz); - m79 = extract(samples200, 25, best_off); + m103 = extract(samples200, 46.8, best_off); } } @@ -3108,9 +3218,9 @@ int FT4::one_iter1( if (params.soft_ones) { if (params.soft_ones == 1) { - soft_decode(m79, ll174); + soft_decode(m103, ll174); } else { - c_soft_decode(m79, ll174); + c_soft_decode(m103, ll174); } int ret = try_decode( @@ -3122,7 +3232,7 @@ int FT4::one_iter1( hz1_for_cb, params.use_osd, "", - m79 + m103 ); if (ret) { @@ -3133,7 +3243,7 @@ int FT4::one_iter1( if (params.soft_pairs) { float p174[174]; - soft_decode_pairs(m79, p174); + soft_decode_pairs(m103, p174); int ret = try_decode( samples200, p174, @@ -3143,7 +3253,7 @@ int FT4::one_iter1( hz1_for_cb, params.use_osd, "", - m79 + m103 ); if (ret) { @@ -3158,7 +3268,7 @@ int FT4::one_iter1( if (params.soft_triples) { float p174[174]; - soft_decode_triples(m79, p174); + soft_decode_triples(m103, p174); int ret = try_decode( samples200, p174, @@ -3168,7 +3278,7 @@ int FT4::one_iter1( hz1_for_cb, params.use_osd, "", - m79 + m103 ); if (ret) { @@ -3219,7 +3329,7 @@ int FT4::one_iter1( hz1_for_cb, 0, "hint1", - m79 + m103 ); if (ret) { @@ -3264,7 +3374,7 @@ int FT4::one_iter1( hz1_for_cb, 0, "hint2", - m79 + m103 ); if (ret) { @@ -3281,10 +3391,10 @@ int FT4::one_iter1( // perhaps revealing a weaker signal underneath, // to be decoded in a subsequent pass. // -// re79[] holds the error-corrected symbol numbers. +// re103[] holds the error-corrected symbol numbers. // void FT4::subtract( - const std::vector re79, + const std::vector re103, float hz0, float hz1, float off_sec @@ -3301,21 +3411,21 @@ void FT4::subtract( std::vector moved = fftEngine_->hilbert_shift(nsamples_, diff0, diff1, rate_); FFTEngine::ffts_t bins = fftEngine_->ffts(moved, off0, block); - if (bin0 + 8 > (int)bins[0].size()) { + if (bin0 + 4 > (int)bins[0].size()) { return; } - if ((int)bins.size() < 79) { + if ((int)bins.size() < 103) { return; } - std::vector phases(79); - std::vector amps(79); + std::vector phases(103); + std::vector amps(103); - for (int i = 0; i < 79; i++) + for (int i = 0; i < 103; i++) { - int sym = bin0 + re79[i]; + int sym = bin0 + re103[i]; std::complex c = bins[i][sym]; phases[i] = std::arg(c); // FFT multiplies magnitudes by number of bins, @@ -3331,10 +3441,10 @@ void FT4::subtract( // initial ramp part of first symbol. { - int sym = bin0 + re79[0]; + int sym = bin0 + re103[0]; float phase = phases[0]; float amp = amps[0]; - float hz = 6.25 * sym; + float hz = 20.833 * sym; float dtheta = 2 * M_PI / (rate_ / hz); // advance per sample for (int jj = 0; jj < ramp; jj++) @@ -3347,12 +3457,12 @@ void FT4::subtract( } } - for (int si = 0; si < 79; si++) + for (int si = 0; si < 103; si++) { - int sym = bin0 + re79[si]; + int sym = bin0 + re103[si]; float phase = phases[si]; float amp = amps[si]; - float hz = 6.25 * sym; + float hz = 20.833 * sym; float dtheta = 2 * M_PI / (rate_ / hz); // advance per sample // we've already done the first ramp for this symbol. @@ -3377,15 +3487,15 @@ void FT4::subtract( float hz1; float phase1; - if (si + 1 >= 79) + if (si + 1 >= 103) { hz1 = hz; phase1 = phase; } else { - int sym1 = bin0 + re79[si + 1]; - hz1 = 6.25 * sym1; + int sym1 = bin0 + re103[si + 1]; + hz1 = 20.833 * sym1; phase1 = phases[si + 1]; } @@ -3415,7 +3525,7 @@ void FT4::subtract( float adj = target - actual; int end = block + ramp; - if (si == 79 - 1) { + if (si == 103 - 1) { end = block; } @@ -3425,7 +3535,7 @@ void FT4::subtract( float x = amp * cos(theta); // trail off to zero at the very end. - if (si == 79 - 1) { + if (si == 103 - 1) { x *= 1.0 - ((jj - (block - ramp)) / (float)ramp); } @@ -3456,7 +3566,7 @@ int FT4::try_decode( float, int use_osd, const char *comment1, - const FFTEngine::ffts_t &m79 + const FFTEngine::ffts_t &m103 ) { int a174[174]; @@ -3477,29 +3587,29 @@ int FT4::try_decode( } } - // reconstruct correct 79 symbols from LDPC output. - std::vector re79 = recode(a174); + // reconstruct correct 103 symbols from LDPC output. + std::vector re103 = recode(a174); if (params.do_third == 1) { // fine-tune offset and hz for better subtraction. - float best_off = best_off_samples / 200.0; + float best_off = best_off_samples / 666.7; search_both_known( samples200, - 200, - re79, + 666.7, + re103, best_hz, best_off, best_hz, best_off ); - best_off_samples = round(best_off * 200.0); + best_off_samples = round(best_off * 666.7); } - // convert starting sample # from 200 samples/second back to rate_. + // convert starting sample # from 666.7 samples/second back to rate_. // also hz. - float best_off = best_off_samples / 200.0; // convert to seconds - best_hz = hz0_for_cb + (best_hz - 25.0); + float best_off = best_off_samples / 666.7; // convert to seconds + best_hz = hz0_for_cb + (best_hz - 46.8); if (params.do_third == 2) { @@ -3507,7 +3617,7 @@ int FT4::try_decode( search_both_known( samples_, rate_, - re79, + re103, best_hz, best_off, best_hz, @@ -3515,13 +3625,23 @@ int FT4::try_decode( ); } - float snr = guess_snr(m79); + float snr = guess_snr(m103); + + int a91[91]; + + for (int i = 0; i < 91; i++) { + a91[i] = a174[i]; + } + + for (int i = 0; i < 77; i++) { + a91[i] ^= kFT4Rvec[i]; + } if (cb_) { cb_mu_.lock(); int ret = cb_->hcb( - a174, + a91, best_hz + down_hz_, best_off, comment.c_str(), @@ -3535,7 +3655,7 @@ int FT4::try_decode( if (ret == 2) { // a new decode. subtract it from nsamples_. - subtract(re79, best_hz, best_hz, best_off); + subtract(re103, best_hz, best_hz, best_off); } return ret; @@ -3559,36 +3679,64 @@ int FT4::try_decode( std::vector FT4::recode(int a174[]) { int i174 = 0; - int costas[] = {3, 1, 4, 0, 6, 5, 2}; - std::vector out79; + std::vector out103; - for (int i79 = 0; i79 < 79; i79++) + // FT4 uses 4 sync blocks of 4 symbols each (4-FSK) + // Sync blocks at symbol positions: 0-3, 33-36, 66-69, 99-102 + // 87 data symbols carrying 174 bits (2 bits per symbol) + + for (int i103 = 0; i103 < 103; i103++) { - if (i79 < 7) + // Check if this is a sync symbol position + bool inSync = false; + int blockIndex = -1; + int symbolInBlock = -1; + + if (i103 >= 0 && i103 < 4) { - out79.push_back(costas[i79]); + inSync = true; + blockIndex = 0; + symbolInBlock = i103; } - else if (i79 >= 36 && i79 < 36 + 7) + else if (i103 >= 33 && i103 < 37) { - out79.push_back(costas[i79 - 36]); + inSync = true; + blockIndex = 1; + symbolInBlock = i103 - 33; } - else if (i79 >= 72) + else if (i103 >= 66 && i103 < 70) { - out79.push_back(costas[i79 - 72]); + inSync = true; + blockIndex = 2; + symbolInBlock = i103 - 66; + } + else if (i103 >= 99) + { + inSync = true; + blockIndex = 3; + symbolInBlock = i103 - 99; + } + + if (inSync) + { + // Insert FT4 sync tone from kFT4SyncTones + out103.push_back(kFT4SyncTones[blockIndex][symbolInBlock]); } else { - int sym = (a174[i174 + 0] << 2) | (a174[i174 + 1] << 1) | (a174[i174 + 2] << 0); - i174 += 3; - // gray code - int map[] = {0, 1, 3, 2, 5, 6, 4, 7}; + // Data symbol: 2 bits encoded as 4-FSK with Gray coding + int sym = (a174[i174 + 0] << 1) | (a174[i174 + 1] << 0); + i174 += 2; + // FT4 Gray code mapping for 4-FSK (Table 3 in FT4_FT8_QEX.pdf): + // 00 -> 0, 01 -> 1, 11 -> 2, 10 -> 3 + int map[] = {0, 1, 3, 2}; sym = map[sym]; - out79.push_back(sym); + out103.push_back(sym); } } - // assert(out79.size() == 79); + // assert(out103.size() == 103); // assert(i174 == 174); - return out79; + return out103; } FT4DecoderLight::~FT4DecoderLight() diff --git a/ft8/ft4.h b/ft8/ft4.h index fb934a4ed..5691bcea2 100644 --- a/ft8/ft4.h +++ b/ft8/ft4.h @@ -511,9 +511,9 @@ private: int one_iter(const std::vector &samples200, int best_off, float hz_for_cb); // // estimate SNR, yielding numbers vaguely similar to WSJT-X. - // m79 is a 79x8 complex FFT output. + // m103 is a 103x4 complex FFT output. // - float guess_snr(const FFTEngine::ffts_t &m79); + float guess_snr(const FFTEngine::ffts_t &m103); // // compare phases of successive symbols to guess whether // the starting offset is a little too high or low. @@ -525,7 +525,7 @@ private: // a phase that depends on the symbol's frequency; // a particular offset error causes a phase error // that depends on frequency. - // hz0 is actual FFT bin number of m79[...][0] (always 4). + // hz0 is actual FFT bin number of m103[...][0]. // // the output adj_hz is relative to the FFT bin center; // a positive number means the real signal seems to be @@ -534,16 +534,16 @@ private: // adj_off is the amount to change the offset, in samples. // should be subtracted from offset. // - void fine(const FFTEngine::ffts_t &m79, int, float &adj_hz, float &adj_off); + void fine(const FFTEngine::ffts_t &m103, int, float &adj_hz, float &adj_off); // // subtract a corrected decoded signal from nsamples_, // perhaps revealing a weaker signal underneath, // to be decoded in a subsequent pass. // - // re79[] holds the error-corrected symbol numbers. + // re103[] holds the error-corrected symbol numbers. // void subtract( - const std::vector re79, + const std::vector re103, float hz0, float hz1, float off_sec @@ -565,7 +565,7 @@ private: float, int use_osd, const char *comment1, - const FFTEngine::ffts_t &m79 + const FFTEngine::ffts_t &m103 ); // // given 174 bits corrected by LDPC, work @@ -576,7 +576,7 @@ private: // std::vector recode(int a174[]); // - // the signal is at roughly 25 hz in samples200. + // the signal is centered around 46.8 Hz in samples200. // // return 2 if it decodes to a brand-new message. // return 1 if it decodes but we've already seen it, diff --git a/sdrbench/test_ft4.cpp b/sdrbench/test_ft4.cpp index ee1e59ec7..90ba74922 100644 --- a/sdrbench/test_ft4.cpp +++ b/sdrbench/test_ft4.cpp @@ -216,7 +216,7 @@ void MainBench::testFT4(const QString& wavFile, const QString& argsStr) wfile.close(); - FT8::FT4DecoderLight decoder; + FT8::FT4Decoder decoder; decoder.getParams().nthreads = nthreads; decoder.getParams().use_osd = 1; decoder.getParams().osd_depth = 6;