diff --git a/doc/img/FT8Demod_plugin_C1.png b/doc/img/FT8Demod_plugin_C1.png index a887f7a13..38b2d1b71 100644 Binary files a/doc/img/FT8Demod_plugin_C1.png and b/doc/img/FT8Demod_plugin_C1.png differ diff --git a/doc/img/FT8Demod_plugin_C1.xcf b/doc/img/FT8Demod_plugin_C1.xcf index 25e658931..61a9d0f1e 100644 Binary files a/doc/img/FT8Demod_plugin_C1.xcf and b/doc/img/FT8Demod_plugin_C1.xcf differ diff --git a/ft8/ft8.cpp b/ft8/ft8.cpp index 216989993..92359cf20 100644 --- a/ft8/ft8.cpp +++ b/ft8/ft8.cpp @@ -894,7 +894,7 @@ void FT8::go(int npasses) } int off = order[ii].off_; - int ret = one(bins, samples_.size(), hz, off); + int ret = one_merge(bins, samples_.size(), hz, off); if (ret) { @@ -2499,12 +2499,11 @@ int FT8::decode(const float ll174[], int a174[], int use_osd, std::string &comme if (ldpc_ok >= ok_thresh) { // plain[] is 91 systematic data bits, 83 parity bits. - for (int i = 0; i < 174; i++) - { + for (int i = 0; i < 174; i++) { a174[i] = plain[i]; } - if (check_crc(a174)) - { + + if (OSD::check_crc(a174)) { // success! return 1; } @@ -2512,17 +2511,15 @@ int FT8::decode(const float ll174[], int a174[], int use_osd, std::string &comme if (use_osd && params.osd_depth >= 0 && ldpc_ok >= params.osd_ldpc_thresh) { - extern int osd_decode(float codeword[174], int depth, int out[91], int *); - extern void ldpc_encode(int plain[91], int codeword[174]); - int oplain[91]; int got_depth = -1; - int osd_ok = osd_decode((float *)ll174, params.osd_depth, oplain, &got_depth); + int osd_ok = OSD::osd_decode((float *)ll174, params.osd_depth, oplain, &got_depth); + if (osd_ok) { // reconstruct all 174. comment += "OSD-" + std::to_string(got_depth) + "-" + std::to_string(ldpc_ok); - ldpc_encode(oplain, a174); + OSD::ldpc_encode(oplain, a174); return 1; } } @@ -2669,7 +2666,7 @@ std::vector FT8::down_v7_f(const std::vector> &bins, // // XXX merge with one_iter(). // -int FT8::one(const std::vector> &bins, int len, float hz, int off) +int FT8::one_merge(const std::vector> &bins, int len, float hz, int off) { // // set up to search for best frequency and time offset. @@ -3018,38 +3015,69 @@ int FT8::one_iter1( if (params.soft_ones) { - if (params.soft_ones == 1) - { + if (params.soft_ones == 1) { soft_decode(m79, ll174); - } - else - { + } else { c_soft_decode(m79, ll174); } - int ret = try_decode(samples200, ll174, best_hz, best_off, - hz0_for_cb, hz1_for_cb, 1, "", m79); - if (ret) + + int ret = try_decode( + samples200, + ll174, + best_hz, + best_off, + hz0_for_cb, + hz1_for_cb, + params.use_osd, + "", + m79 + ); + + if (ret) { return ret; + } } if (params.soft_pairs) { float p174[174]; soft_decode_pairs(m79, p174); - int ret = try_decode(samples200, p174, best_hz, best_off, - hz0_for_cb, hz1_for_cb, 1, "", m79); - if (ret) + int ret = try_decode( + samples200, + p174, + best_hz, + best_off, + hz0_for_cb, + hz1_for_cb, + params.use_osd, + "", + m79 + ); + + if (ret) { return ret; - if (params.soft_ones == 0) + } + + if (params.soft_ones == 0) { std::copy(p174, p174 + 174, ll174); + } } if (params.soft_triples) { float p174[174]; soft_decode_triples(m79, p174); - int ret = try_decode(samples200, p174, best_hz, best_off, - hz0_for_cb, hz1_for_cb, 1, "", m79); + int ret = try_decode( + samples200, + p174, + best_hz, + best_off, + hz0_for_cb, + hz1_for_cb, + params.use_osd, + "", + m79 + ); if (ret) return ret; } @@ -3085,10 +3113,20 @@ int FT8::one_iter1( n174[i] = ll174[i]; } } - int ret = try_decode(samples200, n174, best_hz, best_off, - hz0_for_cb, hz1_for_cb, 0, "hint1", m79); - if (ret) - { + + int ret = try_decode( + samples200, + n174, + best_hz, + best_off, + hz0_for_cb, + hz1_for_cb, + 0, + "hint1", + m79 + ); + + if (ret) { return ret; } } @@ -3120,10 +3158,20 @@ int FT8::one_iter1( n174[i] = ll174[i]; } } - int ret = try_decode(samples200, n174, best_hz, best_off, - hz0_for_cb, hz1_for_cb, 0, "hint2", m79); - if (ret) - { + + int ret = try_decode( + samples200, + n174, + best_hz, + best_off, + hz0_for_cb, + hz1_for_cb, + 0, + "hint2", + m79 + ); + + if (ret) { return ret; } } diff --git a/ft8/ft8.h b/ft8/ft8.h index 047ce6afb..0b9f90be1 100644 --- a/ft8/ft8.h +++ b/ft8/ft8.h @@ -154,6 +154,7 @@ struct FT8_API FT8Params 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() @@ -222,6 +223,7 @@ struct FT8_API FT8Params 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() @@ -563,7 +565,7 @@ public: // // XXX merge with one_iter(). // - int one(const std::vector> &bins, int len, float hz, int off); + int one_merge(const std::vector> &bins, int len, float hz, int off); // return 2 if it decodes to a brand-new message. // return 1 if it decodes but we've already seen it, // perhaps in a different pass. diff --git a/ft8/osd.cpp b/ft8/osd.cpp index 1fd016178..e178a0e40 100644 --- a/ft8/osd.cpp +++ b/ft8/osd.cpp @@ -34,7 +34,7 @@ namespace FT8 { // // check the FT8 CRC-14 // -int check_crc(const int a91[91]) +int OSD::check_crc(const int a91[91]) { int aa[91]; int non_zero = 0; @@ -73,7 +73,7 @@ int check_crc(const int a91[91]) // plain is 91 bits of plain-text. // returns a 174-bit codeword. // mimics wsjt-x's encode174_91.f90. -void ldpc_encode(int plain[91], int codeword[174]) +void OSD::ldpc_encode(int plain[91], int codeword[174]) { // the systematic 91 bits. for (int i = 0; i < 91; i++) @@ -97,7 +97,7 @@ void 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_score(int xplain[91], float ll174[174]) +float OSD::osd_score(int xplain[91], float ll174[174]) { int xcode[174]; ldpc_encode(xplain, xcode); @@ -121,7 +121,7 @@ float osd_score(int xplain[91], float ll174[174]) } // does a decode look plausible? -int osd_check(const int plain[91]) +int OSD::osd_check(const int plain[91]) { int allzero = 1; for (int i = 0; i < 91; i++) @@ -144,7 +144,7 @@ int osd_check(const int plain[91]) return 1; } -void matmul(int a[91][91], int b[91], int c[91]) +void OSD::matmul(int a[91][91], int b[91], int c[91]) { for (int i = 0; i < 91; i++) { @@ -164,10 +164,11 @@ void 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_decode(float codeword[174], int depth, int out[91], int *out_depth) +int OSD::osd_decode(float codeword[174], int depth, int out[91], int *out_depth) { // strength = abs(codeword) float strength[174]; + for (int i = 0; i < 174; i++) { float x = codeword[i]; @@ -176,58 +177,62 @@ int osd_decode(float codeword[174], int depth, int out[91], int *out_depth) // sort, strongest first; we'll use strongest 91. std::vector which(174); - for (int i = 0; i < 174; i++) + + for (int i = 0; i < 174; i++) { which[i] = i; - std::sort(which.begin(), - which.end(), - [=](int a, int b) - { - return strength[a] > strength[b]; - }); + } + + std::sort( + which.begin(), + which.end(), + [=](int a, int b) { + return strength[a] > strength[b]; + } + ); // gen_sys[174 rows][91 cols] has a row per each of the 174 codeword bits, // indicating how to generate it by xor with each of the 91 plain bits. // generator matrix, reordered strongest codeword bit first. int b[174][91 * 2]; + for (int i = 0; i < 174; i++) { int ii = which[i]; + for (int j = 0; j < 91 * 2; j++) { - if (j < 91) - { + if (j < 91) { b[i][j] = gen_sys[ii][j]; - } - else - { + } else { b[i][j] = 0; } } } int xwhich[174]; - for (int i = 0; i < 174; i++) + + for (int i = 0; i < 174; i++) { xwhich[i] = which[i]; + } int ok = 0; gauss_jordan(91, 174, b, xwhich, &ok); - if (ok == 0) - { + + if (ok == 0) { fprintf(stderr, "gauss_jordan failed\n"); } int gen1_inv[91][91]; + for (int i = 0; i < 91; i++) { - for (int j = 0; j < 91; j++) - { + for (int j = 0; j < 91; j++) { gen1_inv[i][j] = b[i][91 + j]; } } - for (int i = 0; i < 174; i++) - { + for (int i = 0; i < 174; i++) { which[i] = xwhich[i]; } @@ -235,6 +240,7 @@ int osd_decode(float codeword[174], int depth, int out[91], int *out_depth) // more or less strongest-first, converted from // log-likihood to 0/1. int y1[91]; + for (int i = 0; i < 91; i++) { int j = which[i]; @@ -251,9 +257,9 @@ int osd_decode(float codeword[174], int depth, int out[91], int *out_depth) matmul(gen1_inv, y1, xplain); // also does mod 2 int osd_thresh = -500; - float xscore = osd_score(xplain, codeword); int ch = osd_check(xplain); + if (xscore < osd_thresh && ch) { if (got_a_best == 0 || xscore < best_score) @@ -284,6 +290,7 @@ int osd_decode(float codeword[174], int depth, int out[91], int *out_depth) y1[i] ^= 1; float xscore = osd_score(xplain, codeword); int ch = osd_check(xplain); + if (xscore < osd_thresh && ch) { if (got_a_best == 0 || xscore < best_score) @@ -308,7 +315,7 @@ int osd_decode(float codeword[174], int depth, int out[91], int *out_depth) } } -int gen_sys[174][91] = { +const int OSD::gen_sys[174][91] = { { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, diff --git a/ft8/osd.h b/ft8/osd.h index db408fc99..5f78fc0c2 100644 --- a/ft8/osd.h +++ b/ft8/osd.h @@ -23,13 +23,17 @@ namespace FT8 { -extern int gen_sys[174][91]; -int check_crc(const int a91[91]); -void ldpc_encode(int plain[91], int codeword[174]); -float osd_score(int xplain[91], float ll174[174]); -int osd_check(const int plain[91]); -void matmul(int a[91][91], int b[91], int c[91]); -int osd_decode(float codeword[174], int depth, int out[91], int *out_depth); +class OSD +{ +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 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); +}; } // namepsace FT8 diff --git a/plugins/channelrx/demodft8/ft8demodbaseband.cpp b/plugins/channelrx/demodft8/ft8demodbaseband.cpp index b8345696d..b6fe2a23d 100644 --- a/plugins/channelrx/demodft8/ft8demodbaseband.cpp +++ b/plugins/channelrx/demodft8/ft8demodbaseband.cpp @@ -245,6 +245,18 @@ void FT8DemodBaseband::applySettings(const FT8DemodSettings& settings, bool forc m_ft8DemodWorker->setDecoderTimeBudget(settings.m_decoderTimeBudget); } + if ((settings.m_useOSD != m_settings.m_useOSD) || force) { + m_ft8DemodWorker->setUseOSD(settings.m_useOSD); + } + + if ((settings.m_osdDepth != m_settings.m_osdDepth) || force) { + m_ft8DemodWorker->setOSDDepth(settings.m_osdDepth); + } + + if ((settings.m_osdLDPCThreshold != m_settings.m_osdLDPCThreshold) || force) { + m_ft8DemodWorker->setOSDLDPCThreshold(settings.m_osdLDPCThreshold); + } + m_sink.applySettings(settings, force); m_settings = settings; } diff --git a/plugins/channelrx/demodft8/ft8demodfilterproxy.cpp b/plugins/channelrx/demodft8/ft8demodfilterproxy.cpp index ead8183f4..05c28922e 100644 --- a/plugins/channelrx/demodft8/ft8demodfilterproxy.cpp +++ b/plugins/channelrx/demodft8/ft8demodfilterproxy.cpp @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "ft8demodsettings.h" #include "ft8demodfilterproxy.h" FT8DemodFilterProxy::FT8DemodFilterProxy(QObject *parent) : @@ -56,6 +57,13 @@ void FT8DemodFilterProxy::setFilterLoc(const QString& locString) invalidateFilter(); } +void FT8DemodFilterProxy::setFilterInfo(const QString& infoString) +{ + m_filterActive = FILTER_INFO; + m_info= infoString; + invalidateFilter(); +} + bool FT8DemodFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { if (m_filterActive == FILTER_NONE) { @@ -64,30 +72,42 @@ bool FT8DemodFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sou if (m_filterActive == FILTER_UTC) { - QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + QModelIndex index = sourceModel()->index(sourceRow, FT8DemodSettings::MESSAGE_COL_UTC, sourceParent); return sourceModel()->data(index).toString() == m_utc; } if (m_filterActive == FILTER_DF) { - QModelIndex index = sourceModel()->index(sourceRow, 4, sourceParent); + QModelIndex index = sourceModel()->index(sourceRow, FT8DemodSettings::MESSAGE_COL_DF, sourceParent); int df = sourceModel()->data(index).toInt(); return (df >= m_df - 4) && (df <= m_df + 4); // +/- 4 Hz tolerance which is about one symbol width } if (m_filterActive == FILTER_CALL) { - QModelIndex indexCall1 = sourceModel()->index(sourceRow, 6, sourceParent); - QModelIndex indexCall2 = sourceModel()->index(sourceRow, 7, sourceParent); + QModelIndex indexCall1 = sourceModel()->index(sourceRow, FT8DemodSettings::MESSAGE_COL_CALL1, sourceParent); + QModelIndex indexCall2 = sourceModel()->index(sourceRow, FT8DemodSettings::MESSAGE_COL_CALL2, sourceParent); return (sourceModel()->data(indexCall1).toString() == m_call) || (sourceModel()->data(indexCall2).toString() == m_call); } if (m_filterActive == FILTER_LOC) { - QModelIndex index = sourceModel()->index(sourceRow, 8, sourceParent); + QModelIndex index = sourceModel()->index(sourceRow, FT8DemodSettings::MESSAGE_COL_LOC, sourceParent); return sourceModel()->data(index).toString() == m_loc; } + if (m_filterActive == FILTER_INFO) + { + QModelIndex index = sourceModel()->index(sourceRow, FT8DemodSettings::MESSAGE_COL_INFO, sourceParent); + const QString& content = sourceModel()->data(index).toString(); + + if (m_info.startsWith("OSD")) { + return content.startsWith("OSD"); + } else { + return !content.startsWith("OSD"); + } + } + return true; } diff --git a/plugins/channelrx/demodft8/ft8demodfilterproxy.h b/plugins/channelrx/demodft8/ft8demodfilterproxy.h index e51f29148..fb5496cb8 100644 --- a/plugins/channelrx/demodft8/ft8demodfilterproxy.h +++ b/plugins/channelrx/demodft8/ft8demodfilterproxy.h @@ -30,6 +30,7 @@ public: void setFilterDf(int df); void setFilterCall(const QString& utcString); void setFilterLoc(const QString& utcString); + void setFilterInfo(const QString& infoString); protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; @@ -41,7 +42,8 @@ private: FILTER_UTC, FILTER_DF, FILTER_CALL, - FILTER_LOC + FILTER_LOC, + FILTER_INFO }; bool dfInRange(int df) const; @@ -50,6 +52,7 @@ private: int m_df; QString m_call; QString m_loc; + QString m_info; }; #endif // INCLUDE_FT8DEMODFILTERPROXY_H diff --git a/plugins/channelrx/demodft8/ft8demodgui.cpp b/plugins/channelrx/demodft8/ft8demodgui.cpp index ab5101973..d94aec581 100644 --- a/plugins/channelrx/demodft8/ft8demodgui.cpp +++ b/plugins/channelrx/demodft8/ft8demodgui.cpp @@ -475,6 +475,24 @@ void FT8DemodGUI::on_settings_clicked() changed = true; } + if (settingsKeys.contains("useOSD")) + { + m_settings.m_useOSD = settings.m_useOSD; + changed = true; + } + + if (settingsKeys.contains("osdDepth")) + { + m_settings.m_osdDepth = settings.m_osdDepth; + changed = true; + } + + if (settingsKeys.contains("osdLDPCThreshold")) + { + m_settings.m_osdLDPCThreshold = settings.m_osdLDPCThreshold; + changed = true; + } + if (settingsKeys.contains("bandPresets")) { m_settings.m_bandPresets = settings.m_bandPresets; @@ -907,6 +925,8 @@ void FT8DemodGUI::filterMessages() m_messagesFilterProxy.setFilterUTC(m_selectedData.toString()); } else if (m_selectedColumn == FT8DemodSettings::MESSAGE_COL_DF) { m_messagesFilterProxy.setFilterDf(m_selectedData.toInt()); + } else if (m_selectedColumn == FT8DemodSettings::MESSAGE_COL_INFO) { + m_messagesFilterProxy.setFilterInfo(m_selectedData.toString()); } } diff --git a/plugins/channelrx/demodft8/ft8demodgui.ui b/plugins/channelrx/demodft8/ft8demodgui.ui index 6210e1ba0..ef87a048b 100644 --- a/plugins/channelrx/demodft8/ft8demodgui.ui +++ b/plugins/channelrx/demodft8/ft8demodgui.ui @@ -823,7 +823,7 @@ - Total number of decodes displayed + Total number of decodes since start 0 diff --git a/plugins/channelrx/demodft8/ft8demodsettings.cpp b/plugins/channelrx/demodft8/ft8demodsettings.cpp index 7c7d599fd..aa00e07b9 100644 --- a/plugins/channelrx/demodft8/ft8demodsettings.cpp +++ b/plugins/channelrx/demodft8/ft8demodsettings.cpp @@ -47,6 +47,9 @@ void FT8DemodSettings::resetToDefaults() m_logMessages = false; m_nbDecoderThreads = 3; m_decoderTimeBudget = 0.5; + m_useOSD = false; + m_osdDepth = 0; + m_osdLDPCThreshold = 70; m_volume = 1.0; m_inputFrequencyOffset = 0; m_rgbColor = QColor(0, 192, 255).rgb(); @@ -107,6 +110,9 @@ QByteArray FT8DemodSettings::serialize() const s.writeS32(8, m_nbDecoderThreads); s.writeFloat(9, m_decoderTimeBudget); s.writeBool(11, m_agc); + s.writeBool(12, m_useOSD); + s.writeS32(13, m_osdDepth); + s.writeS32(14, m_osdLDPCThreshold); s.writeString(16, m_title); s.writeBool(18, m_useReverseAPI); s.writeString(19, m_reverseAPIAddress); @@ -172,6 +178,9 @@ bool FT8DemodSettings::deserialize(const QByteArray& data) d.readS32(8, &m_nbDecoderThreads, 3); d.readFloat(9, &m_decoderTimeBudget, 0.5); d.readBool(11, &m_agc, false); + d.readBool(12, &m_useOSD, false); + d.readS32(13, &m_osdDepth, 0); + d.readS32(14, &m_osdLDPCThreshold, 70); d.readString(16, &m_title, "SSB Demodulator"); d.readBool(18, &m_useReverseAPI, false); d.readString(19, &m_reverseAPIAddress, "127.0.0.1"); diff --git a/plugins/channelrx/demodft8/ft8demodsettings.h b/plugins/channelrx/demodft8/ft8demodsettings.h index edf07297a..d6b13d014 100644 --- a/plugins/channelrx/demodft8/ft8demodsettings.h +++ b/plugins/channelrx/demodft8/ft8demodsettings.h @@ -72,6 +72,9 @@ struct FT8DemodSettings bool m_logMessages; int m_nbDecoderThreads; float m_decoderTimeBudget; + bool m_useOSD; + int m_osdDepth; + int m_osdLDPCThreshold; quint32 m_rgbColor; QString m_title; int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx). diff --git a/plugins/channelrx/demodft8/ft8demodsettingsdialog.cpp b/plugins/channelrx/demodft8/ft8demodsettingsdialog.cpp index bd9e9d6c1..62e2e6151 100644 --- a/plugins/channelrx/demodft8/ft8demodsettingsdialog.cpp +++ b/plugins/channelrx/demodft8/ft8demodsettingsdialog.cpp @@ -29,6 +29,11 @@ FT8DemodSettingsDialog::FT8DemodSettingsDialog(FT8DemodSettings& settings, QStri ui->setupUi(this); ui->decoderNbThreads->setValue(m_settings.m_nbDecoderThreads); ui->decoderTimeBudget->setValue(m_settings.m_decoderTimeBudget); + ui->osdEnable->setChecked(m_settings.m_useOSD); + ui->osdDepth->setValue(m_settings.m_osdDepth); + ui->osdDepthText->setText(tr("%1").arg(m_settings.m_osdDepth)); + ui->osdLDPCThreshold->setValue(m_settings.m_osdLDPCThreshold); + ui->osdLDPCThresholdText->setText(tr("%1").arg(m_settings.m_osdLDPCThreshold)); resizeBandsTable(); populateBandsTable(); connect(ui->bands, &QTableWidget::cellChanged, this, &FT8DemodSettingsDialog::textCellChanged); @@ -118,6 +123,35 @@ void FT8DemodSettingsDialog::on_decoderTimeBudget_valueChanged(double value) } } +void FT8DemodSettingsDialog::on_osdEnable_toggled(bool checked) +{ + m_settings.m_useOSD = checked; + + if (!m_settingsKeys.contains("useOSD")) { + m_settingsKeys.append("useOSD"); + } +} + +void FT8DemodSettingsDialog::on_osdDepth_valueChanged(int value) +{ + m_settings.m_osdDepth = value; + ui->osdDepthText->setText(tr("%1").arg(m_settings.m_osdDepth)); + + if (!m_settingsKeys.contains("osdDepth")) { + m_settingsKeys.append("osdDepth"); + } +} + +void FT8DemodSettingsDialog::on_osdLDPCThreshold_valueChanged(int value) +{ + m_settings.m_osdLDPCThreshold = value; + ui->osdLDPCThresholdText->setText(tr("%1").arg(m_settings.m_osdLDPCThreshold)); + + if (!m_settingsKeys.contains("osdLDPCThreshold")) { + m_settingsKeys.append("osdLDPCThreshold"); + } +} + void FT8DemodSettingsDialog::on_addBand_clicked() { int currentRow = ui->bands->currentRow(); diff --git a/plugins/channelrx/demodft8/ft8demodsettingsdialog.h b/plugins/channelrx/demodft8/ft8demodsettingsdialog.h index c59ac0b6f..931955e93 100644 --- a/plugins/channelrx/demodft8/ft8demodsettingsdialog.h +++ b/plugins/channelrx/demodft8/ft8demodsettingsdialog.h @@ -51,6 +51,9 @@ private slots: void reject(); void on_decoderNbThreads_valueChanged(int value); void on_decoderTimeBudget_valueChanged(double value); + void on_osdEnable_toggled(bool checked); + void on_osdDepth_valueChanged(int value); + void on_osdLDPCThreshold_valueChanged(int value); void on_addBand_clicked(); void on_deleteBand_clicked(); void on_moveBandUp_clicked(); diff --git a/plugins/channelrx/demodft8/ft8demodsettingsdialog.ui b/plugins/channelrx/demodft8/ft8demodsettingsdialog.ui index d3a83db7e..e55a833fa 100644 --- a/plugins/channelrx/demodft8/ft8demodsettingsdialog.ui +++ b/plugins/channelrx/demodft8/ft8demodsettingsdialog.ui @@ -7,7 +7,7 @@ 0 0 349 - 316 + 333 @@ -25,11 +25,8 @@ Decoder - - - 2 - - + + @@ -98,6 +95,124 @@ + + + + 2 + + + + + Toggle Ordered Statistics Decoding + + + OSD + + + true + + + + + + + Depth + + + + + + + + 24 + 24 + + + + Order Statistics Decoding depth + + + 0 + + + 6 + + + 1 + + + 0 + + + + + + + 0 + + + + + + + Qt::Vertical + + + + + + + LDPC Thr + + + + + + + + 24 + 24 + + + + Number of correct LDPC bits required to activate OSD + + + 60 + + + 83 + + + 1 + + + 70 + + + + + + + 60 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -282,6 +397,13 @@ + + + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+
diff --git a/plugins/channelrx/demodft8/ft8demodworker.cpp b/plugins/channelrx/demodft8/ft8demodworker.cpp index 8d5ae3ea1..99edd8eda 100644 --- a/plugins/channelrx/demodft8/ft8demodworker.cpp +++ b/plugins/channelrx/demodft8/ft8demodworker.cpp @@ -110,6 +110,9 @@ FT8DemodWorker::FT8DemodWorker() : m_recordSamples(false), m_nbDecoderThreads(6), m_decoderTimeBudget(0.5), + m_useOSD(false), + m_osdDepth(0), + m_osdLDPCThreshold(70), m_lowFreq(200), m_highFreq(3000), m_invalidSequence(true), @@ -157,6 +160,9 @@ void FT8DemodWorker::processBuffer(int16_t *buffer, QDateTime periodTS) int hints[2] = { 2, 0 }; // CQ FT8Callback ft8Callback(periodTS, m_baseFrequency, m_packing, channelReference); m_ft8Decoder.getParams().nthreads = m_nbDecoderThreads; + m_ft8Decoder.getParams().use_osd = m_useOSD ? 1 : 0; + m_ft8Decoder.getParams().osd_depth = m_osdDepth; + m_ft8Decoder.getParams().osd_ldpc_thresh = m_osdLDPCThreshold; std::vector samples(15*FT8DemodSettings::m_ft8SampleRate); std::transform( @@ -217,7 +223,7 @@ void FT8DemodWorker::processBuffer(int16_t *buffer, QDateTime periodTS) continue; } - QString logMessage = QString("%1 %2 Rx FT8 %3 %4 %5 %6 %7 %8") + QString logMessage = QString("%1 %2 Rx FT8 %3 %4 %5 %6 %7 %8 %9") .arg(periodTS.toString("yyyyMMdd_HHmmss")) .arg(baseFrequencyMHz, 9, 'f', 3) .arg(ft8Message.snr, 6) @@ -225,7 +231,8 @@ void FT8DemodWorker::processBuffer(int16_t *buffer, QDateTime periodTS) .arg(ft8Message.df, 4, 'f', 0) .arg(ft8Message.call1) .arg(ft8Message.call2) - .arg(ft8Message.loc); + .arg(ft8Message.loc) + .arg(ft8Message.decoderInfo); logMessage.remove(0, 2); logFile << logMessage.toStdString() << std::endl; } diff --git a/plugins/channelrx/demodft8/ft8demodworker.h b/plugins/channelrx/demodft8/ft8demodworker.h index 4bcc34ff9..71e362f1b 100644 --- a/plugins/channelrx/demodft8/ft8demodworker.h +++ b/plugins/channelrx/demodft8/ft8demodworker.h @@ -40,6 +40,9 @@ public: void setLogMessages(bool logMessages) { m_logMessages = logMessages; } void setNbDecoderThreads(int nbDecoderThreads) { m_nbDecoderThreads = nbDecoderThreads; } void setDecoderTimeBudget(float decoderTimeBudget) { m_decoderTimeBudget = decoderTimeBudget; } + void setUseOSD(bool useOSD) { m_useOSD = useOSD; } + void setOSDDepth(int osdDepth) { m_osdDepth = osdDepth; } + void setOSDLDPCThreshold(int osdLDPCThreshold) { m_osdLDPCThreshold = osdLDPCThreshold; } void setLowFrequency(int lowFreq) { m_lowFreq = lowFreq; } void setHighFrequency(int highFreq) { m_highFreq = highFreq; } void setReportingMessageQueue(MessageQueue *messageQueue) { m_reportingMessageQueue = messageQueue; } @@ -86,6 +89,9 @@ private: bool m_logMessages; int m_nbDecoderThreads; float m_decoderTimeBudget; + bool m_useOSD; + int m_osdDepth; + int m_osdLDPCThreshold; int m_lowFreq; int m_highFreq; bool m_invalidSequence; diff --git a/plugins/channelrx/demodft8/readme.md b/plugins/channelrx/demodft8/readme.md index 5dd2e37ce..e111f4e12 100644 --- a/plugins/channelrx/demodft8/readme.md +++ b/plugins/channelrx/demodft8/readme.md @@ -2,7 +2,7 @@

Introduction

-This plugin can be used to demodulate and decode FT8 signals. FT8 is used by amateur radio to perform contacts (QSOs) with very weak signals. It is used mostly but not limited to HF bands. The protocol and modulation details are described [here](http://www.rtmrtm.org/basicft8/) +This plugin can be used to demodulate and decode FT8 signals. FT8 is used by amateur radio to perform contacts (QSOs) with very weak signals. It is used mostly but not limited to HF bands. The protocol and modulation details are described in [QEX July/August 2020 article](https://www.iz3mez.it/wp-content/library/appunti/Protocols%20FT4%20FT8%20QEX.pdf) The decoder code is based on [ft8mon](https://github.com/rtmrtmrtmrtm/ft8mon) code written by Robert Morris, AB1HL. The core of the decoder is stored in a separate `libft8` library in the `ft8` folder of this repository and is placed under the `FT8` namespace. @@ -124,6 +124,7 @@ Toggles the filtering of messages. Messages are filtered based on the selected c - **Call1**: will filter messages matching the call1 area value either in the call1 or call2 areas - **Call2**: same as above but taking the call2 value - **Loc**: will filter messages matching the value in the locator (loc) area + - **Info**: will filter values starting with "OSD" or not starting with "OSD" thus filter messages decoded via OSD or not

C.5: Band preset selection

@@ -139,7 +140,26 @@ Empties the message table (C.10)

C.8: Log messages

-Toggles the logging of messages. Messages will be logged in the same format as the original WSJT-X format. The splitting and naming of files is different however. +Toggles the logging of messages. Messages will be logged in the same format as the original WSJT-X format except if the message has decoder information in which case this information is appended at the end of the line. + +Example: + +
+230128_003030    10.136 Rx FT8    -22  0.1 2049 KE0DKZ W2ZI EL99 OSD-0-71
+---- 1 ------    --2---           -3-  -4- --5- --6--- --7- --8- ---9----
+
+ + - **1**: Date and time of decoder slot in YYMMDD_HHmmss fomat + - **2**: Base frequency in MHz + - **3**: SNR in 2.5 kHz bandwidth + - **4**: Message start delay in seconds from standard sequence start + - **5**: Message carrier shift from base frequency in Hz + - **6**: First callsign area. May contain spaces (ex: "CQ DX") + - **7**: Second callsign area + - **8**: Locator area + - **9**: Decoder information if any + +The splitting and naming of files is different from WSJT-X scheme. The file name is constructed as follows where date is the day date in YYYYMMDD format: @@ -171,7 +191,7 @@ Displays the received messages in a table which columns are the following: - **Call1**: This is the first call area and may contain the caller callsign, a CQ or a custom 13 character message in which case the second call and locator areas are empty - **Call2**: This is the second call area and will contain the callsign of the responding station - **Loc**: Locator area which contains the 4 character Maidenhead locator, a report, an acknowledgement (RRR) or a greetings (73 or RR73) - - **Info**: FT8 decoder information if any. This comes from he original `ft8mon` code + - **Info**: FT8 decoder information if any. If OSD is active (see C.1.3) and OSD was activated it reports the OSD decoder status as `OSD-N-MM` where N is the OSD depth reached and MM is the number of correct LDPC bits.

C.1: More FT8 decoder settings

@@ -187,7 +207,21 @@ When processing the audio baseband several instances of the core decoder will be This is the time in seconds after which the decoder threads will be prompted to stop. It can be varied from 0.1 to 5 seconds. You can imagine that the longer the decoder runs the more messages it will harvest however the default of 0.5s will be enough in most cases. You can still experiment with it to find what value is the best in your own case. -

C.1.3: Band presets table

+

C.1.3: Toggle Ordered Statistics Decoding

+ +This toggles the Ordered Statistics Decoding (OSD). OSD is used if the CRC check fails after LDPC processing. Be careful with OSD as it will try to find solutions that validate the CRC but these solutions can be wrong and thus yield false messages. When post processing the results it is recommended to check against a database of valid callsigns. At least you should use a list of valid country prefixes and test the locator against the country prefix. + +With reasonable depth (C.1.4) and minimum correct LDPC bits (C.1.5) values the amount of false messages should be low so OSD may still be interesting. However if you require best accuracy you should filter messages in a post processing step as suggested above. + +

C.1.4: Ordered Statistics Decoding depth

+ +Sets the maximum depth of OSD search. + +

C.1.5: LDPC correct bits threshold

+ +Sets the minimum number of correct LDPC bits (out of 83) necessary to trigger OSD. + +

C.1.6: Band presets table

This table shows the band presets values that will appear in (C.5) @@ -197,23 +231,23 @@ This table shows the band presets values that will appear in (C.5) You can edit these values by clicking on the cell in the table. -

C.1.4: Add preset

+

C.1.7: Add preset

Use this button to create a new preset. It will take the values from the row of the selected cell in the table (if selected) and put the new preset at the bottom of the table -

C.1.5: Delete preset

+

C.1.8: Delete preset

Delete the preset designated by the selected cell in the table. -

C.1.6: Move up preset

+

C.1.9: Move up preset

Move up the preset designated by the selected cell in the table. -

C.1.7: Move down preset

+

C.1.10: Move down preset

Move down the preset designated by the selected cell in the table. -

C.1.8: Restore defaults

+

C.1.11: Restore defaults

This restores the default band preset values: @@ -235,10 +269,10 @@ This restores the default band preset values: Channel offsets are all set to 0 kHz. -

C.1.9 Commit changes

+

C.1.12 Commit changes

Click on the "OK" button to commit changes and close dialog. -

C.1.9 Cancel changes

+

C.1.13 Cancel changes

Click on the "Cancel" button to close dialog without making changes. diff --git a/sdrbench/test_ft8.cpp b/sdrbench/test_ft8.cpp index 1a1d0e2bf..b8c627da5 100644 --- a/sdrbench/test_ft8.cpp +++ b/sdrbench/test_ft8.cpp @@ -205,6 +205,7 @@ void MainBench::testFT8(const QString& wavFile, const QString& argsStr) FT8::FT8Decoder decoder; decoder.getParams().nthreads = nthreads; + decoder.getParams().use_osd = 0; decoder.entry( samples.data(),