diff --git a/ft8/ft4.cpp b/ft8/ft4.cpp index d5eb38028..74ec99b8a 100644 --- a/ft8/ft4.cpp +++ b/ft8/ft4.cpp @@ -1,13 +1,29 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2026 // +// Copyright (C) 2026 Edouard Griffiths, F4EXB // // // -// Experimental FT4 decoder scaffold derived from FT8 decoder architecture. // +// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon // +// reformatted and adapted to Qt and SDRangel context and FT4 scheme // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// #include #include #include #include +#include +#include #include #include @@ -241,7 +257,7 @@ int FT4::blocksize(float rate) const // look for potential psignals by searching FFT bins for Costas symbol // blocks. returns a vector of candidate positions. // -std::vector FT4::coarse(const FFTEngine::ffts_t &bins, int si0, int si1) +std::vector FT4::coarse(const FFTEngine::ffts_t &bins, int si0, int si1) const { int block = blocksize(rate_); int nbins = bins[0].size(); @@ -420,13 +436,11 @@ void FT4::go(int npasses) int irate = round(rate_); for (int xrate = 100; xrate < rate_; xrate += 100) { - if (xrate < rate_ && (params.oddrate || (irate % xrate) == 0)) + if (xrate < rate_ && (params.oddrate || (irate % xrate) == 0) && + ((max_hz_ - min_hz_) + 93.6 + 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; - } + nrate = xrate; + break; } } @@ -459,7 +473,7 @@ void FT4::go(int npasses) t1 - t0); } - if (0) + if (false) { fprintf(stderr, "%.0f..%.0f, range %.0f, rate %.0f -> %d, delta hz %.0f, %.6f sec\n", min_hz_, @@ -583,7 +597,7 @@ void FT4::go(int npasses) // just do this once, reuse for every fractional fft_shift // and down_v7_f() to 200 sps. - std::vector> bins = fftEngine_->one_fft( + std::vector> fbins = fftEngine_->one_fft( samples_, 0, samples_.size()); for (int hz_frac_i = 0; hz_frac_i < params.coarse_hz_n; hz_frac_i++) @@ -595,7 +609,7 @@ void FT4::go(int npasses) if (hz_frac_i == 0) { samples1 = samples_; } else { - samples1 = fft_shift_f(bins, rate_, hz_frac); + samples1 = fft_shift_f(fbins, rate_, hz_frac); } for (int off_frac_i = 0; off_frac_i < params.coarse_off_n; off_frac_i++) @@ -650,7 +664,7 @@ void FT4::go(int npasses) } int off = order[ii].off_; - int ret = one_merge(bins, samples_.size(), hz, off); + int ret = one_merge(fbins, samples_.size(), hz, off); if (ret) { @@ -1963,9 +1977,9 @@ void FT4::soft_decode_pairs( // 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]]); + 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]]); + bests.add(corrs[kFT4SyncTones[(si - 2) / 33][2] * 4 + kFT4SyncTones[(si - 2) / 33][3]]); } else { bests.add(mx); } @@ -2052,7 +2066,7 @@ void FT4::soft_decode_triples( { int bitind = (si + 0) * 2 + (1 - bit); - if ((i & (1 << bit))) + if (i & (1 << bit)) { // symbol i would make this bit a one. if (x > bitinfo[bitind].one) { @@ -2077,7 +2091,7 @@ void FT4::soft_decode_triples( { int bitind = (si + 1) * 2 + (1 - bit); - if ((i & (1 << bit))) + if (i & (1 << bit)) { // symbol i would make this bit a one. if (x > bitinfo[bitind].one) { @@ -2103,7 +2117,7 @@ void FT4::soft_decode_triples( { int bitind = (si + 2) * 2 + (1 - bit); - if ((i & (1 << bit))) + if (i & (1 << bit)) { // symbol i would make this bit a one. if (x > bitinfo[bitind].one) { @@ -2174,7 +2188,7 @@ int FT4::decode(const float ll174[], int a174[], const FT8Params& _params, int u { int plain[174]; // will be 0/1 bits. int ldpc_ok = 0; // 83 will mean success. - LDPC::ldpc_decode((float *)ll174, _params.ldpc_iters, plain, &ldpc_ok); + LDPC::ldpc_decode(ll174, _params.ldpc_iters, plain, &ldpc_ok); int ok_thresh = 83; // 83 is perfect if (ldpc_ok >= ok_thresh) @@ -2200,7 +2214,7 @@ int FT4::decode(const float ll174[], int a174[], const FT8Params& _params, int u { int oplain[91]; int got_depth = -1; - int osd_ok = OSD::osd_decode((float *)ll174, _params.osd_depth, oplain, &got_depth); + int osd_ok = OSD::osd_decode(ll174, _params.osd_depth, oplain, &got_depth); if (osd_ok) { @@ -3142,22 +3156,22 @@ void FT4::subtract( // at start of this symbol's off-ramp. float theta = phase + (block - ramp) * dtheta; - float hz1; + float hz1_; float phase1; if (si + 1 >= 103) { - hz1 = hz; + hz1_ = hz; phase1 = phase; } else { int sym1 = bin0 + re103[si + 1]; - hz1 = sym1 * bin_hz; + hz1_ = sym1 * bin_hz; phase1 = phases[si + 1]; } - float dtheta1 = 2 * M_PI / (rate_ / hz1); + float dtheta1 = 2 * M_PI / (rate_ / hz1_); // add this to dtheta for each sample, to gradually // change the frequency. float inc = (dtheta1 - dtheta) / (2.0 * ramp); diff --git a/ft8/ft4.h b/ft8/ft4.h index 5c522fae7..31b45b1aa 100644 --- a/ft8/ft4.h +++ b/ft8/ft4.h @@ -1,8 +1,23 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2026 // +// Copyright (C) 2026 Edouard Griffiths, F4EXB // // // -// Experimental FT4 decoder scaffold derived from FT8 decoder architecture. // +// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon // +// reformatted and adapted to Qt and SDRangel context and FT4 scheme // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// + #ifndef ft4_h #define ft4_h @@ -67,7 +82,7 @@ public: // look for potential signals by searching FFT bins for Costas symbol // blocks. returns a vector of candidate positions. // - std::vector coarse(const FFTEngine::ffts_t &bins, int si0, int si1); + std::vector coarse(const FFTEngine::ffts_t &bins, int si0, int si1) const; FT8Params& getParams() { return params; } // diff --git a/ft8/ft8.cpp b/ft8/ft8.cpp index c2eb48843..e4f5fc7a6 100644 --- a/ft8/ft8.cpp +++ b/ft8/ft8.cpp @@ -238,7 +238,7 @@ int FT8::blocksize(int rate) const // look for potential psignals by searching FFT bins for Costas symbol // blocks. returns a vector of candidate positions. // -std::vector FT8::coarse(const FFTEngine::ffts_t &bins, int si0, int si1) +std::vector FT8::coarse(const FFTEngine::ffts_t &bins, int si0, int si1) const { int block = blocksize(rate_); int nbins = bins[0].size(); @@ -386,7 +386,7 @@ std::vector FT8::reduce_rate( void FT8::go(int npasses) { - if (0) + if (false) { fprintf(stderr, "go: %.0f .. %.0f, %.0f, rate=%d\n", min_hz_, max_hz_, max_hz_ - min_hz_, rate_); @@ -1849,7 +1849,7 @@ void FT8::soft_decode(const FFTEngine::ffts_t &c79, float ll174[]) const // ll174 is the resulting 174 soft bits of payload // used in FT-chirp modulation scheme - generalized to any number of symbol bits // -void FT8::soft_decode_mags(FT8Params& params, const std::vector>& mags_, int nbSymbolBits, float ll174[]) +void FT8::soft_decode_mags(const FT8Params& params, const std::vector>& mags_, int nbSymbolBits, float ll174[]) { if ((nbSymbolBits > 16) || (nbSymbolBits < 1)) { return; @@ -2166,7 +2166,7 @@ void FT8::set_ones_zeroes(int ones[], int zeroes[], int nbBits, int bitIndex) // each returned element is < 0 for 1, > 0 for zero, // scaled by str. // -std::vector FT8::extract_bits(const std::vector &syms, const std::vector str) const +std::vector FT8::extract_bits(const std::vector &syms, const std::vector& str) const { // assert(syms.size() == 79); // assert(str.size() == 79); @@ -3566,7 +3566,7 @@ int FT8::try_decode( // used to help ensure that subtraction subtracts // at the right place. // -std::vector FT8::recode(int a174[]) const +std::vector FT8::recode(const int a174[]) const { int i174 = 0; int costas[] = {3, 1, 4, 0, 6, 5, 2}; @@ -3620,8 +3620,8 @@ void FT8Decoder::entry( int rate, float min_hz, float max_hz, - int hints1[], - int hints2[], + const int hints1[], + const int hints2[], double time_left, double total_time_left, CallbackInterface *cb, diff --git a/ft8/ft8.h b/ft8/ft8.h index dd2f1b234..fc088a7c6 100644 --- a/ft8/ft8.h +++ b/ft8/ft8.h @@ -266,7 +266,7 @@ public: // look for potential signals by searching FFT bins for Costas symbol // blocks. returns a vector of candidate positions. // - std::vector coarse(const FFTEngine::ffts_t &bins, int si0, int si1); + std::vector coarse(const FFTEngine::ffts_t &bins, int si0, int si1) const; FT8Params& getParams() { return params; } // @@ -290,7 +290,7 @@ public: // ll174 is the resulting 174 soft bits of payload // used in FT-chirp modulation scheme - generalized to any number of symbol bits // - static void soft_decode_mags(FT8Params& params, const std::vector>& mags, int nbSymbolBits, float ll174[]); + static void soft_decode_mags(const FT8Params& params, const std::vector>& mags, int nbSymbolBits, float ll174[]); // // Generic Gray decoding for magnitudes (floats) @@ -497,7 +497,7 @@ private: // each returned element is < 0 for 1, > 0 for zero, // scaled by str. // - std::vector extract_bits(const std::vector &syms, const std::vector str) const; + std::vector extract_bits(const std::vector &syms, const std::vector& str) const; // decode successive pairs of symbols. exploits the likelihood // that they have the same phase, by summing the complex // correlations for each possible pair and using the max. @@ -615,7 +615,7 @@ private: // used to help ensure that subtraction subtracts // at the right place. // - std::vector recode(int a174[]) const; + std::vector recode(const int a174[]) const; // // the signal is at roughly 25 hz in samples200. // @@ -679,8 +679,8 @@ public: int rate, float min_hz, float max_hz, - int hints1[], - int hints2[], + const int hints1[], + const int hints2[], double time_left, double total_time_left, CallbackInterface *cb, diff --git a/ft8/libldpc.cpp b/ft8/libldpc.cpp index 822c4b626..e15eed5a2 100644 --- a/ft8/libldpc.cpp +++ b/ft8/libldpc.cpp @@ -76,7 +76,7 @@ int LDPC::ldpc_check(int codeword[]) // iters is how hard to try. // ok is the number of parity checks that worked out, // ok == 83 means success. -void LDPC::ldpc_decode(float llcodeword[], int iters, int plain[], int *ok) +void LDPC::ldpc_decode(const float llcodeword[], int iters, int plain[], int *ok) { REAL m[83][174]; REAL e[83][174]; diff --git a/ft8/libldpc.h b/ft8/libldpc.h index a83310870..1d7208ea5 100644 --- a/ft8/libldpc.h +++ b/ft8/libldpc.h @@ -26,7 +26,7 @@ namespace FT8 { class LDPC { public: - static void ldpc_decode(float llcodeword[], int iters, int plain[], int *ok); + static void ldpc_decode(const float llcodeword[], int iters, int plain[], int *ok); static void ldpc_decode_log(float codeword[], int iters, int plain[], int *ok); static void ft8_crc(int msg1[], int msglen, int out[14]); static void gauss_jordan(int rows, int cols, int m[174][2 * 91], int which[91], int *ok); diff --git a/ft8/osd.cpp b/ft8/osd.cpp index ae9fe8040..bc0965e96 100644 --- a/ft8/osd.cpp +++ b/ft8/osd.cpp @@ -96,7 +96,7 @@ void OSD::ldpc_encode(int plain[91], int codeword[174]) // ll174 is what was received. // ldpc-encode xplain; how close is the // result to what we received? -float OSD::osd_score(int xplain[91], float ll174[174]) +float OSD::osd_score(int xplain[91], const float ll174[174]) { int xcode[174]; ldpc_encode(xplain, xcode); @@ -163,7 +163,7 @@ void OSD::matmul(int a[91][91], int b[91], int c[91]) // first 91 bits are plaintext, remaining 83 are parity. // returns 0 or 1, with decoded plain bits in out91[]. // and actual depth used in *out_depth. -int OSD::osd_decode(float codeword[174], int depth, int out[91], int *out_depth) +int OSD::osd_decode(const float codeword[174], int depth, int out[91], int *out_depth) { // strength = abs(codeword) float strength[174]; diff --git a/ft8/osd.h b/ft8/osd.h index 1cc3df8c1..60ad3a30e 100644 --- a/ft8/osd.h +++ b/ft8/osd.h @@ -30,13 +30,12 @@ public: static const int gen_sys[174][91]; static int check_crc(const int a91[91]); static void ldpc_encode(int plain[91], int codeword[174]); - static float osd_score(int xplain[91], float ll174[174]); + static float osd_score(int xplain[91], const float ll174[174]); static int osd_check(const int plain[91]); static void matmul(int a[91][91], int b[91], int c[91]); - static int osd_decode(float codeword[174], int depth, int out[91], int *out_depth); + static int osd_decode(const float codeword[174], int depth, int out[91], int *out_depth); }; } // namespace FT8 #endif // osd_h - diff --git a/plugins/channelrx/demodft8/ft8demodbaseband.cpp b/plugins/channelrx/demodft8/ft8demodbaseband.cpp index eb7d3eecf..25610608c 100644 --- a/plugins/channelrx/demodft8/ft8demodbaseband.cpp +++ b/plugins/channelrx/demodft8/ft8demodbaseband.cpp @@ -330,16 +330,13 @@ void FT8DemodBaseband::tick() const qint64 periodIndex = nowMs / periodMs; const int periodOffsetMs = nowMs % periodMs; - if (periodOffsetMs < activeWindowMs) + if (periodOffsetMs < activeWindowMs && m_lastProcessPeriodIndex != periodIndex) { - if (m_lastProcessPeriodIndex != periodIndex) - { - const qint64 previousPeriodStartMs = (periodIndex - 1) * periodMs; - QDateTime periodTs = QDateTime::fromMSecsSinceEpoch(previousPeriodStartMs, Qt::UTC); - const int frameSamples = FT8DemodSettings::getDecoderFrameSamples(m_settings.m_decoderMode); - m_ft8Buffer.getCurrentBuffer(m_ft8WorkerBuffer, frameSamples); - emit bufferReady(m_ft8WorkerBuffer, periodTs); - m_lastProcessPeriodIndex = periodIndex; - } + const qint64 previousPeriodStartMs = (periodIndex - 1) * periodMs; + QDateTime periodTs = QDateTime::fromMSecsSinceEpoch(previousPeriodStartMs, Qt::UTC); + const int frameSamples = FT8DemodSettings::getDecoderFrameSamples(m_settings.m_decoderMode); + m_ft8Buffer.getCurrentBuffer(m_ft8WorkerBuffer, frameSamples); + emit bufferReady(m_ft8WorkerBuffer, periodTs); + m_lastProcessPeriodIndex = periodIndex; } } diff --git a/plugins/channelrx/demodft8/ft8demodworker.cpp b/plugins/channelrx/demodft8/ft8demodworker.cpp index 08ecd6ccc..d21fb8c28 100644 --- a/plugins/channelrx/demodft8/ft8demodworker.cpp +++ b/plugins/channelrx/demodft8/ft8demodworker.cpp @@ -184,7 +184,7 @@ void FT8DemodWorker::setDecoderMode(FT8DemodSettings::DecoderMode decoderMode) } -void FT8DemodWorker::processFT8(int16_t *buffer, int frameSampleCount, FT8Callback& ft8Callback, int *hints) +void FT8DemodWorker::processFT8(int16_t *buffer, int frameSampleCount, FT8Callback& ft8Callback, const int *hints) { m_ft8Decoder.getParams().nthreads = m_nbDecoderThreads; m_ft8Decoder.getParams().use_osd = m_useOSD ? 1 : 0; @@ -218,7 +218,7 @@ void FT8DemodWorker::processFT8(int16_t *buffer, int frameSampleCount, FT8Callba m_ft8Decoder.wait(m_decoderTimeBudget + 1.0); // add one second to budget to force quit threads } -void FT8DemodWorker::processFT4(int16_t *buffer, int frameSampleCount, FT8Callback& ft8Callback, int *hints) +void FT8DemodWorker::processFT4(int16_t *buffer, int frameSampleCount, FT8Callback& ft8Callback, const int *hints) { std::vector samples(frameSampleCount); std::transform( diff --git a/plugins/channelrx/demodft8/ft8demodworker.h b/plugins/channelrx/demodft8/ft8demodworker.h index 597d656e4..f7526197e 100644 --- a/plugins/channelrx/demodft8/ft8demodworker.h +++ b/plugins/channelrx/demodft8/ft8demodworker.h @@ -94,8 +94,8 @@ private: const QSet *m_validCallsigns; }; - void processFT8(int16_t *buffer, int frameSampleCount, FT8Callback& ft8Callback, int *hints); - void processFT4(int16_t *buffer, int frameSampleCount, FT8Callback& ft8Callback, int *hints); + void processFT8(int16_t *buffer, int frameSampleCount, FT8Callback& ft8Callback, const int *hints); + void processFT4(int16_t *buffer, int frameSampleCount, FT8Callback& ft8Callback, const int *hints); QString m_samplesPath; QString m_logsPath; diff --git a/sdrbench/test_ft4.cpp b/sdrbench/test_ft4.cpp index 4d6cd4603..b34b0d607 100644 --- a/sdrbench/test_ft4.cpp +++ b/sdrbench/test_ft4.cpp @@ -18,6 +18,11 @@ #include #include +#include +#include +#include +#include +#include #include "mainbench.h" #include "dsp/wavfilerecord.h" @@ -29,7 +34,7 @@ void MainBench::testFT4(const QString& wavFile, const QString& argsStr) { (void) wavFile; (void) argsStr; - qWarning("MainBench::testFT8: this version has no FT8 support"); + qWarning("MainBench::testFT4: this version has no FT8/FT4 support"); } #else