9#include "JS8_Include/commons.h"
10#include "JS8_Mode/FrequencyTracker.h"
11#include "JS8_Mode/whitening_processor.h"
12#include "ldpc_feedback.h"
13#include "soft_combiner.h"
16#include <QLoggingCategory>
21#include <boost/crc.hpp>
22#include <boost/math/ccmath/round.hpp>
23#include <boost/multi_index/key.hpp>
24#include <boost/multi_index/ordered_index.hpp>
25#include <boost/multi_index/ranked_index.hpp>
26#include <boost/multi_index_container.hpp>
35#include <initializer_list>
45#include <unordered_map>
48#include <vendor/Eigen/Dense>
50Q_DECLARE_LOGGING_CATEGORY(decoder_js8);
101constexpr double cos_approx(
double x) {
102 constexpr auto RAD_360 = std::numbers::pi * 2;
103 constexpr auto RAD_180 = std::numbers::pi;
104 constexpr auto RAD_90 = std::numbers::pi / 2;
111 constexpr auto poly = [](
double const x) {
112 constexpr std::array coefficients = {
114 -0.49999999999999994,
115 0.041666666666666664,
116 -0.001388888888888889,
117 0.000024801587301587,
118 -0.00000027557319223986,
119 0.00000000208767569878681,
120 -0.00000000001147074513875176,
121 0.0000000000000477947733238733
124 auto const x2 = x * x;
125 auto const x4 = x2 * x2;
126 auto const x6 = x4 * x2;
127 auto const x8 = x4 * x4;
128 auto const x10 = x8 * x2;
129 auto const x12 = x8 * x4;
130 auto const x14 = x12 * x2;
131 auto const x16 = x8 * x8;
133 return coefficients[0] + coefficients[1] * x2 + coefficients[2] * x4 +
134 coefficients[3] * x6 + coefficients[4] * x8 +
135 coefficients[5] * x10 + coefficients[6] * x12 +
136 coefficients[7] * x14 + coefficients[8] * x16;
141 x -=
static_cast<long long>(x / RAD_360) * RAD_360;
151 return x > RAD_90 ? -poly(RAD_180 - x) : poly(x);
182constexpr int N = 174;
184constexpr int M = N - K;
185constexpr int KK = 87;
186constexpr int ND = 58;
187constexpr int NS = 21;
188constexpr int NN = NS + ND;
189constexpr float ASYNCMIN = 1.5f;
190constexpr int NFSRCH = 5;
191constexpr std::size_t NMAXCAND = 300;
192constexpr int NFILT = 1400;
193constexpr int NROWS = 8;
194constexpr int NFOS = 2;
195constexpr int NSSY = 4;
196constexpr int NP = 3200;
197constexpr int NP2 = 2812;
198constexpr float TAU = 2.0f * std::numbers::pi_v<float>;
199constexpr auto ZERO = std::complex<float>{0.0f, 0.0f};
219 inline static constexpr int NSUBMODE = 0;
220 inline static constexpr auto NCOSTAS = JS8::Costas::Type::ORIGINAL;
221 inline static constexpr int NSPS = JS8A_SYMBOL_SAMPLES;
222 inline static constexpr int NTXDUR = JS8A_TX_SECONDS;
223 inline static constexpr int NDOWNSPS = 32;
224 inline static constexpr int NDD = 100;
225 inline static constexpr int JZ = 62;
226 inline static constexpr float ASTART = 0.5f;
227 inline static constexpr float BASESUB = 40.0f;
230 inline static constexpr float AZ = (12000.0f / NSPS) * 0.64f;
231 inline static constexpr int NMAX = NTXDUR * JS8_RX_SAMPLE_RATE;
232 inline static constexpr int NFFT1 = NSPS * NFOS;
233 inline static constexpr int NSTEP = NSPS / NSSY;
234 inline static constexpr int NHSYM = NMAX / NSTEP - 3;
235 inline static constexpr int NDOWN = NSPS / NDOWNSPS;
236 inline static constexpr int NQSYMBOL = NDOWNSPS / 4;
237 inline static constexpr int NDFFT1 = NSPS * NDD;
238 inline static constexpr int NDFFT2 = NDFFT1 / NDOWN;
239 inline static constexpr int NP2 = NN * NDOWNSPS;
240 inline static constexpr float TSTEP = NSTEP / 12000.0f;
241 inline static constexpr int JSTRT = ASTART / TSTEP;
242 inline static constexpr float DF = 12000.0f / NFFT1;
249 inline static constexpr int NSUBMODE = 1;
250 inline static constexpr auto NCOSTAS = JS8::Costas::Type::MODIFIED;
251 inline static constexpr int NSPS = JS8B_SYMBOL_SAMPLES;
252 inline static constexpr int NTXDUR = JS8B_TX_SECONDS;
253 inline static constexpr int NDOWNSPS = 20;
254 inline static constexpr int NDD = 100;
255 inline static constexpr int JZ = 144;
256 inline static constexpr float ASTART = 0.2f;
257 inline static constexpr float BASESUB = 39.0f;
260 inline static constexpr float AZ = (12000.0f / NSPS) * 0.8f;
261 inline static constexpr int NMAX = NTXDUR * JS8_RX_SAMPLE_RATE;
262 inline static constexpr int NFFT1 = NSPS * NFOS;
263 inline static constexpr int NSTEP = NSPS / NSSY;
264 inline static constexpr int NHSYM = NMAX / NSTEP - 3;
265 inline static constexpr int NDOWN = NSPS / NDOWNSPS;
266 inline static constexpr int NQSYMBOL = NDOWNSPS / 4;
267 inline static constexpr int NDFFT1 = NSPS * NDD;
268 inline static constexpr int NDFFT2 = NDFFT1 / NDOWN;
269 inline static constexpr int NP2 = NN * NDOWNSPS;
270 inline static constexpr float TSTEP = NSTEP / 12000.0f;
271 inline static constexpr int JSTRT = ASTART / TSTEP;
272 inline static constexpr float DF = 12000.0f / NFFT1;
279 inline static constexpr int NSUBMODE = 2;
280 inline static constexpr auto NCOSTAS = JS8::Costas::Type::MODIFIED;
281 inline static constexpr int NSPS = JS8C_SYMBOL_SAMPLES;
282 inline static constexpr int NTXDUR = JS8C_TX_SECONDS;
283 inline static constexpr int NDOWNSPS = 12;
284 inline static constexpr int NDD = 120;
285 inline static constexpr int JZ = 172;
286 inline static constexpr float ASTART = 0.1f;
287 inline static constexpr float BASESUB = 38.0f;
290 inline static constexpr float AZ = (12000.0f / NSPS) * 0.6f;
291 inline static constexpr int NMAX = NTXDUR * JS8_RX_SAMPLE_RATE;
292 inline static constexpr int NFFT1 = NSPS * NFOS;
293 inline static constexpr int NSTEP = NSPS / NSSY;
294 inline static constexpr int NHSYM = NMAX / NSTEP - 3;
295 inline static constexpr int NDOWN = NSPS / NDOWNSPS;
296 inline static constexpr int NQSYMBOL = NDOWNSPS / 4;
297 inline static constexpr int NDFFT1 = NSPS * NDD;
298 inline static constexpr int NDFFT2 = NDFFT1 / NDOWN;
299 inline static constexpr int NP2 = NN * NDOWNSPS;
300 inline static constexpr float TSTEP = NSTEP / 12000.0f;
301 inline static constexpr int JSTRT = ASTART / TSTEP;
302 inline static constexpr float DF = 12000.0f / NFFT1;
313 inline static constexpr int NSUBMODE = 4;
314 inline static constexpr auto NCOSTAS = JS8::Costas::Type::MODIFIED;
315 inline static constexpr int NSPS = JS8E_SYMBOL_SAMPLES;
316 inline static constexpr int NTXDUR =
318 inline static constexpr int NDOWNSPS = 32;
319 inline static constexpr int NDD = 94;
320 inline static constexpr int JZ = 32;
321 inline static constexpr float ASTART = 0.5f;
322 inline static constexpr float BASESUB = 42.0f;
325 inline static constexpr float AZ = (12000.0f / NSPS) * 0.64f;
326 inline static constexpr int NMAX = NTXDUR * JS8_RX_SAMPLE_RATE;
327 inline static constexpr int NFFT1 = NSPS * NFOS;
328 inline static constexpr int NSTEP = NSPS / NSSY;
329 inline static constexpr int NHSYM = NMAX / NSTEP - 3;
330 inline static constexpr int NDOWN = NSPS / NDOWNSPS;
331 inline static constexpr int NQSYMBOL = NDOWNSPS / 4;
332 inline static constexpr int NDFFT1 = NSPS * NDD;
333 inline static constexpr int NDFFT2 = NDFFT1 / NDOWN;
334 inline static constexpr int NP2 = NN * NDOWNSPS;
335 inline static constexpr float TSTEP = NSTEP / 12000.0f;
336 inline static constexpr int JSTRT = ASTART / TSTEP;
337 inline static constexpr float DF = 12000.0f / NFFT1;
344 inline static constexpr int NSUBMODE = 8;
345 inline static constexpr auto NCOSTAS = JS8::Costas::Type::MODIFIED;
346 inline static constexpr int NSPS = JS8I_SYMBOL_SAMPLES;
347 inline static constexpr int NTXDUR = JS8I_TX_SECONDS;
348 inline static constexpr int NDOWNSPS = 12;
349 inline static constexpr int NDD = 125;
350 inline static constexpr int JZ = 250;
351 inline static constexpr float ASTART = 0.1f;
352 inline static constexpr float BASESUB = 36.0f;
355 inline static constexpr float AZ = (12000.0f / NSPS) * 0.64f;
356 inline static constexpr int NMAX = NTXDUR * JS8_RX_SAMPLE_RATE;
357 inline static constexpr int NFFT1 = NSPS * NFOS;
358 inline static constexpr int NSTEP = NSPS / NSSY;
359 inline static constexpr int NHSYM = NMAX / NSTEP - 3;
360 inline static constexpr int NDOWN = NSPS / NDOWNSPS;
361 inline static constexpr int NQSYMBOL = NDOWNSPS / 4;
362 inline static constexpr int NDFFT1 = NSPS * NDD;
363 inline static constexpr int NDFFT2 = NDFFT1 / NDOWN;
364 inline static constexpr int NP2 = NN * NDOWNSPS;
365 inline static constexpr float TSTEP = NSTEP / 12000.0f;
366 inline static constexpr int JSTRT = ASTART / TSTEP;
367 inline static constexpr float DF = 12000.0f / NFFT1;
375constexpr auto BASELINE_DEGREE = 5;
376constexpr auto BASELINE_SAMPLE = 10;
381constexpr auto BASELINE_MIN = 500;
382constexpr auto BASELINE_MAX = 2500;
388static_assert(BASELINE_DEGREE & 1,
"Degree must be odd");
389static_assert(BASELINE_SAMPLE >= 0 && BASELINE_SAMPLE <= 100,
390 "Sample must be a percentage");
402constexpr auto BASELINE_NODES = []() {
409 auto nodes = std::array<double, BASELINE_DEGREE + 1>{};
410 constexpr auto slice = std::numbers::pi / (2.0 * nodes.size());
412 for (std::size_t i = 0; i < nodes.size(); ++i) {
413 nodes[i] = 0.5 * (1.0 - cos_approx(slice * (2.0 * i + 1)));
452template <std::
floating_po
int T>
class KahanSum {
457 KahanSum(T sum = T(0)) : m_sum(sum), m_compensation(T(0)) {}
459 KahanSum &operator=(T
const sum) {
461 m_compensation = T(0);
466 KahanSum &operator+=(T
const value) {
467 T
const y = value - m_compensation;
468 T
const t = m_sum + y;
470 m_compensation = (t - m_sum) - y;
476 operator T()
const {
return m_sum; }
481class FFTWPlanManager {
483 enum class Type { DS, BB, CF, CB, SD, CS, count };
487 FFTWPlanManager(FFTWPlanManager
const &) =
delete;
488 FFTWPlanManager &operator=(FFTWPlanManager
const &) =
delete;
489 FFTWPlanManager(FFTWPlanManager &&) =
delete;
490 FFTWPlanManager &operator=(FFTWPlanManager &&) =
delete;
494 FFTWPlanManager() { m_plans.fill(
nullptr); }
499 std::lock_guard<std::mutex> lock(fftw_mutex);
501 for (
auto &plan : m_plans) {
503 fftwf_destroy_plan(plan);
509 fftwf_plan
const &operator[](Type
const type)
const noexcept {
510 return m_plans[
static_cast<std::size_t
>(type)];
515 fftwf_plan &operator[](Type
const type)
noexcept {
516 return m_plans[
static_cast<std::size_t
>(type)];
521 auto begin() noexcept {
return m_plans.begin(); }
522 auto end() noexcept {
return m_plans.end(); }
523 auto begin() const noexcept {
return m_plans.begin(); }
524 auto end() const noexcept {
return m_plans.end(); }
529 std::array<fftwf_plan, static_cast<std::size_t>(Type::count)> m_plans;
541 Sync(
float const freq,
float const step,
float const sync)
542 : freq(freq), step(step), sync(sync) {}
557namespace MI = boost::multi_index;
558using SyncIndex = MI::multi_index_container<
559 Sync, MI::indexed_by<
560 MI::ordered_non_unique<MI::tag<Tag::Freq>, MI::key<&Sync::freq>>,
561 MI::ranked_non_unique<MI::tag<Tag::Rank>, MI::key<&Sync::sync>>,
562 MI::ordered_non_unique<MI::tag<Tag::Sync>, MI::key<&Sync::sync>,
573 Decode(
int type, std::string data) : type(type), data(std::move(data)) {}
575 bool operator==(Decode
const &)
const noexcept =
default;
578 std::size_t operator()(Decode
const &decode)
const noexcept {
579 std::size_t
const h1 = std::hash<int>{}(decode.type);
580 std::size_t
const h2 = std::hash<std::string>{}(decode.data);
581 return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
585 using Map = std::unordered_map<Decode, int, Hash>;
593constexpr int BP_MAX_ROWS = 7;
594constexpr int BP_MAX_CHECKS = 3;
595constexpr int BP_MAX_ITERATIONS = 30;
597constexpr std::array<std::array<int, BP_MAX_CHECKS>, N> Mn = {
598 {{0, 24, 68}, {1, 4, 72}, {2, 31, 67}, {3, 50, 60}, {5, 62, 69},
599 {6, 32, 78}, {7, 49, 85}, {8, 36, 42}, {9, 40, 64}, {10, 13, 63},
600 {11, 74, 76}, {12, 22, 80}, {14, 15, 81}, {16, 55, 65}, {17, 52, 59},
601 {18, 30, 51}, {19, 66, 83}, {20, 28, 71}, {21, 23, 43}, {25, 34, 75},
602 {26, 35, 37}, {27, 39, 41}, {29, 53, 54}, {33, 48, 86}, {38, 56, 57},
603 {44, 73, 82}, {45, 61, 79}, {46, 47, 84}, {58, 70, 77}, {0, 49, 52},
604 {1, 46, 83}, {2, 24, 78}, {3, 5, 13}, {4, 6, 79}, {7, 33, 54},
605 {8, 35, 68}, {9, 42, 82}, {10, 22, 73}, {11, 16, 43}, {12, 56, 75},
606 {14, 26, 55}, {15, 27, 28}, {17, 18, 58}, {19, 39, 62}, {20, 34, 51},
607 {21, 53, 63}, {23, 61, 77}, {25, 31, 76}, {29, 71, 84}, {30, 64, 86},
608 {32, 38, 50}, {36, 47, 74}, {37, 69, 70}, {40, 41, 67}, {44, 66, 85},
609 {45, 80, 81}, {48, 65, 72}, {57, 59, 65}, {60, 64, 84}, {0, 13, 20},
610 {1, 12, 58}, {2, 66, 81}, {3, 31, 72}, {4, 35, 53}, {5, 42, 45},
611 {6, 27, 74}, {7, 32, 70}, {8, 48, 75}, {9, 57, 63}, {10, 47, 67},
612 {11, 18, 44}, {14, 49, 60}, {15, 21, 25}, {16, 71, 79}, {17, 39, 54},
613 {19, 34, 50}, {22, 24, 33}, {23, 62, 86}, {26, 38, 73}, {28, 77, 82},
614 {29, 69, 76}, {30, 68, 83}, {21, 36, 85}, {37, 40, 80}, {41, 43, 56},
615 {46, 52, 61}, {51, 55, 78}, {59, 74, 80}, {0, 38, 76}, {1, 15, 40},
616 {2, 30, 53}, {3, 35, 77}, {4, 44, 64}, {5, 56, 84}, {6, 13, 48},
617 {7, 20, 45}, {8, 14, 71}, {9, 19, 61}, {10, 16, 70}, {11, 33, 46},
618 {12, 67, 85}, {17, 22, 42}, {18, 63, 72}, {23, 47, 78}, {24, 69, 82},
619 {25, 79, 86}, {26, 31, 39}, {27, 55, 68}, {28, 62, 65}, {29, 41, 49},
620 {32, 36, 81}, {34, 59, 73}, {37, 54, 83}, {43, 51, 60}, {50, 52, 71},
621 {57, 58, 66}, {46, 55, 75}, {0, 18, 36}, {1, 60, 74}, {2, 7, 65},
622 {3, 59, 83}, {4, 33, 38}, {5, 25, 52}, {6, 31, 56}, {8, 51, 66},
623 {9, 11, 14}, {10, 50, 68}, {12, 13, 64}, {15, 30, 42}, {16, 19, 35},
624 {17, 79, 85}, {20, 47, 58}, {21, 39, 45}, {22, 32, 61}, {23, 29, 73},
625 {24, 41, 63}, {26, 48, 84}, {27, 37, 72}, {28, 43, 80}, {34, 67, 69},
626 {40, 62, 75}, {44, 48, 70}, {49, 57, 86}, {47, 53, 82}, {12, 54, 78},
627 {76, 77, 81}, {0, 1, 23}, {2, 5, 74}, {3, 55, 86}, {4, 43, 52},
628 {6, 49, 82}, {7, 9, 27}, {8, 54, 61}, {10, 28, 66}, {11, 32, 39},
629 {13, 15, 19}, {14, 34, 72}, {16, 30, 38}, {17, 35, 56}, {18, 45, 75},
630 {20, 41, 83}, {21, 33, 58}, {22, 25, 60}, {24, 59, 64}, {26, 63, 79},
631 {29, 36, 65}, {31, 44, 71}, {37, 50, 85}, {40, 76, 78}, {42, 55, 67},
632 {46, 73, 81}, {39, 51, 77}, {53, 60, 70}, {45, 57, 68}}};
636 std::array<int, BP_MAX_ROWS> neighbors;
639constexpr std::array<CheckNode, M> Nm = {{{6, {0, 29, 59, 88, 117, 146, 0}},
640 {6, {1, 30, 60, 89, 118, 146, 0}},
641 {6, {2, 31, 61, 90, 119, 147, 0}},
642 {6, {3, 32, 62, 91, 120, 148, 0}},
643 {6, {1, 33, 63, 92, 121, 149, 0}},
644 {6, {4, 32, 64, 93, 122, 147, 0}},
645 {6, {5, 33, 65, 94, 123, 150, 0}},
646 {6, {6, 34, 66, 95, 119, 151, 0}},
647 {6, {7, 35, 67, 96, 124, 152, 0}},
648 {6, {8, 36, 68, 97, 125, 151, 0}},
649 {6, {9, 37, 69, 98, 126, 153, 0}},
650 {6, {10, 38, 70, 99, 125, 154, 0}},
651 {6, {11, 39, 60, 100, 127, 144, 0}},
652 {6, {9, 32, 59, 94, 127, 155, 0}},
653 {6, {12, 40, 71, 96, 125, 156, 0}},
654 {6, {12, 41, 72, 89, 128, 155, 0}},
655 {6, {13, 38, 73, 98, 129, 157, 0}},
656 {6, {14, 42, 74, 101, 130, 158, 0}},
657 {6, {15, 42, 70, 102, 117, 159, 0}},
658 {6, {16, 43, 75, 97, 129, 155, 0}},
659 {6, {17, 44, 59, 95, 131, 160, 0}},
660 {6, {18, 45, 72, 82, 132, 161, 0}},
661 {6, {11, 37, 76, 101, 133, 162, 0}},
662 {6, {18, 46, 77, 103, 134, 146, 0}},
663 {6, {0, 31, 76, 104, 135, 163, 0}},
664 {6, {19, 47, 72, 105, 122, 162, 0}},
665 {6, {20, 40, 78, 106, 136, 164, 0}},
666 {6, {21, 41, 65, 107, 137, 151, 0}},
667 {6, {17, 41, 79, 108, 138, 153, 0}},
668 {6, {22, 48, 80, 109, 134, 165, 0}},
669 {6, {15, 49, 81, 90, 128, 157, 0}},
670 {6, {2, 47, 62, 106, 123, 166, 0}},
671 {6, {5, 50, 66, 110, 133, 154, 0}},
672 {6, {23, 34, 76, 99, 121, 161, 0}},
673 {6, {19, 44, 75, 111, 139, 156, 0}},
674 {6, {20, 35, 63, 91, 129, 158, 0}},
675 {6, {7, 51, 82, 110, 117, 165, 0}},
676 {6, {20, 52, 83, 112, 137, 167, 0}},
677 {6, {24, 50, 78, 88, 121, 157, 0}},
678 {7, {21, 43, 74, 106, 132, 154, 171}},
679 {6, {8, 53, 83, 89, 140, 168, 0}},
680 {6, {21, 53, 84, 109, 135, 160, 0}},
681 {6, {7, 36, 64, 101, 128, 169, 0}},
682 {6, {18, 38, 84, 113, 138, 149, 0}},
683 {6, {25, 54, 70, 92, 141, 166, 0}},
684 {7, {26, 55, 64, 95, 132, 159, 173}},
685 {6, {27, 30, 85, 99, 116, 170, 0}},
686 {6, {27, 51, 69, 103, 131, 143, 0}},
687 {6, {23, 56, 67, 94, 136, 141, 0}},
688 {6, {6, 29, 71, 109, 142, 150, 0}},
689 {6, {3, 50, 75, 114, 126, 167, 0}},
690 {6, {15, 44, 86, 113, 124, 171, 0}},
691 {6, {14, 29, 85, 114, 122, 149, 0}},
692 {6, {22, 45, 63, 90, 143, 172, 0}},
693 {6, {22, 34, 74, 112, 144, 152, 0}},
694 {7, {13, 40, 86, 107, 116, 148, 169}},
695 {6, {24, 39, 84, 93, 123, 158, 0}},
696 {6, {24, 57, 68, 115, 142, 173, 0}},
697 {6, {28, 42, 60, 115, 131, 161, 0}},
698 {6, {14, 57, 87, 111, 120, 163, 0}},
699 {7, {3, 58, 71, 113, 118, 162, 172}},
700 {6, {26, 46, 85, 97, 133, 152, 0}},
701 {5, {4, 43, 77, 108, 140, 0, 0}},
702 {6, {9, 45, 68, 102, 135, 164, 0}},
703 {6, {8, 49, 58, 92, 127, 163, 0}},
704 {6, {13, 56, 57, 108, 119, 165, 0}},
705 {6, {16, 54, 61, 115, 124, 153, 0}},
706 {6, {2, 53, 69, 100, 139, 169, 0}},
707 {6, {0, 35, 81, 107, 126, 173, 0}},
708 {5, {4, 52, 80, 104, 139, 0, 0}},
709 {6, {28, 52, 66, 98, 141, 172, 0}},
710 {6, {17, 48, 73, 96, 114, 166, 0}},
711 {6, {1, 56, 62, 102, 137, 156, 0}},
712 {6, {25, 37, 78, 111, 134, 170, 0}},
713 {6, {10, 51, 65, 87, 118, 147, 0}},
714 {6, {19, 39, 67, 116, 140, 159, 0}},
715 {6, {10, 47, 80, 88, 145, 168, 0}},
716 {6, {28, 46, 79, 91, 145, 171, 0}},
717 {6, {5, 31, 86, 103, 144, 168, 0}},
718 {6, {26, 33, 73, 105, 130, 164, 0}},
719 {5, {11, 55, 83, 87, 138, 0, 0}},
720 {6, {12, 55, 61, 110, 145, 170, 0}},
721 {6, {25, 36, 79, 104, 143, 150, 0}},
722 {6, {16, 30, 81, 112, 120, 160, 0}},
723 {5, {27, 48, 58, 93, 136, 0, 0}},
724 {6, {6, 54, 82, 100, 130, 167, 0}},
725 {6, {23, 49, 77, 105, 142, 148, 0}}}};
729int bpdecode174(std::array<float, N>
const &llr, std::array<int8_t, K> &decoded,
730 std::array<int8_t, N> &cw) {
732 std::array<std::array<float, BP_MAX_CHECKS>, N> tov =
734 std::array<std::array<float, BP_MAX_ROWS>, M> toc =
736 std::array<std::array<float, BP_MAX_ROWS>, M> tanhtoc =
739 std::array<float, N> zn = {};
740 std::array<int, M> synd = {};
746 for (
int i = 0; i < M; ++i) {
747 for (
int j = 0; j < Nm[i].valid_neighbors; ++j) {
748 toc[i][j] = llr[Nm[i].neighbors[j]];
753 for (
int iter = 0; iter <= BP_MAX_ITERATIONS; ++iter) {
755 for (
int i = 0; i < N; ++i) {
757 llr[i] + std::accumulate(tov[i].begin(),
758 tov[i].begin() + BP_MAX_CHECKS, 0.0f);
762 for (
int i = 0; i < N; ++i)
763 cw[i] = zn[i] > 0 ? 1 : 0;
766 for (
int i = 0; i < M; ++i) {
768 for (
int j = 0; j < Nm[i].valid_neighbors; ++j) {
769 synd[i] += cw[Nm[i].neighbors[j]];
771 if (synd[i] % 2 != 0)
777 std::copy(cw.begin() + M, cw.end(), decoded.begin());
781 for (
int i = 0; i < N; ++i) {
782 if ((2 * cw[i] - 1) * llr[i] < 0.0f) {
792 int nd = ncheck - nclast;
793 ncnt = (nd < 0) ? 0 : ncnt + 1;
794 if (ncnt >= 5 && iter >= 10 && ncheck > 15) {
801 for (
int i = 0; i < M; ++i) {
802 for (
int j = 0; j < Nm[i].valid_neighbors; ++j) {
803 int ibj = Nm[i].neighbors[j];
805 for (
int k = 0; k < BP_MAX_CHECKS; ++k) {
806 if (Mn[ibj][k] == i) {
807 toc[i][j] -= tov[ibj][k];
814 for (
int i = 0; i < M; ++i) {
815 for (
int j = 0; j < 7;
818 tanhtoc[i][j] = std::tanh(-toc[i][j] / 2.0f);
822 for (
int i = 0; i < N; ++i) {
823 for (
int j = 0; j < BP_MAX_CHECKS; ++j) {
827 for (
int k = 0; k < Nm[ichk].valid_neighbors; ++k) {
828 if (Nm[ichk].neighbors[k] != i) {
829 Tmn *= tanhtoc[ichk][k];
832 tov[i][j] = 2.0f * std::atanh(-Tmn);
847constexpr std::string_view alphabet =
848 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-+";
850static_assert(alphabet.size() == 64);
857constexpr auto alphabetWord = []() {
858 constexpr std::uint8_t invalid = 0xff;
860 constexpr auto words = []() {
861 std::array<std::uint8_t, 256> words{};
863 for (
auto &word : words)
866 for (std::size_t i = 0; i < alphabet.size(); ++i) {
867 words[
static_cast<std::uint8_t
>(alphabet[i])] =
868 static_cast<std::uint8_t
>(i);
874 return [words](
char const value) {
875 if (
auto const word = words[value]; word != invalid) {
879 throw std::runtime_error(
"Invalid character in message");
885static_assert(alphabetWord(
'0') == 0);
886static_assert(alphabetWord(
'A') == 10);
887static_assert(alphabetWord(
'a') == 36);
888static_assert(alphabetWord(
'-') == 62);
889static_assert(alphabetWord(
'+') == 63);
891template <
typename T> std::uint16_t CRC12(T
const &range) {
892 return boost::augmented_crc<12, 0xc06>(range.data(), range.size()) ^ 42;
895bool checkCRC12(std::array<std::int8_t, KK>
const &decoded) {
896 std::array<uint8_t, 11> bits = {};
898 for (std::size_t i = 0; i < decoded.size(); ++i) {
900 bits[i / 8] |= (1 << (7 - (i % 8)));
905 uint16_t crc = (
static_cast<uint16_t
>(bits[9] & 0x1F) << 7) |
906 (
static_cast<uint16_t
>(bits[10]) >> 1);
915 return crc == CRC12(bits);
918std::string extractmessage174(std::array<int8_t, KK>
const &decoded) {
923 if (checkCRC12(decoded)) {
928 std::array<uint8_t, 12> words;
930 for (std::size_t i = 0; i < 12; ++i) {
931 words[i] = (decoded[i * 6 + 0] << 5) | (decoded[i * 6 + 1] << 4) |
932 (decoded[i * 6 + 2] << 3) | (decoded[i * 6 + 3] << 2) |
933 (decoded[i * 6 + 4] << 1) | (decoded[i * 6 + 5] << 0);
938 for (
auto const word : words)
939 message += alphabet[word];
963constexpr auto parity = []() {
964 constexpr std::size_t Rows = 87;
965 constexpr std::size_t Cols = 87;
967 using ElementType = std::uint64_t;
968 constexpr std::size_t ElementSize =
969 std::numeric_limits<ElementType>::digits;
971 constexpr auto matrix = []() {
972 constexpr std::array<std::string_view, Rows> Data = {
973 "23bba830e23b6b6f50982e",
"1f8e55da218c5df3309052",
974 "ca7b3217cd92bd59a5ae20",
"56f78313537d0f4382964e",
975 "6be396b5e2e819e373340c",
"293548a138858328af4210",
976 "cb6c6afcdc28bb3f7c6e86",
"3f2a86f5c5bd225c961150",
977 "849dd2d63673481860f62c",
"56cdaec6e7ae14b43feeee",
978 "04ef5cfa3766ba778f45a4",
"c525ae4bd4f627320a3974",
979 "41fd9520b2e4abeb2f989c",
"7fb36c24085a34d8c1dbc4",
980 "40fc3e44bb7d2bb2756e44",
"d38ab0a1d2e52a8ec3bc76",
981 "3d0f929ef3949bd84d4734",
"45d3814f504064f80549ae",
982 "f14dbf263825d0bd04b05e",
"db714f8f64e8ac7af1a76e",
983 "8d0274de71e7c1a8055eb0",
"51f81573dd4049b082de14",
984 "d8f937f31822e57c562370",
"b6537f417e61d1a7085336",
985 "ecbd7c73b9cd34c3720c8a",
"3d188ea477f6fa41317a4e",
986 "1ac4672b549cd6dba79bcc",
"a377253773ea678367c3f6",
987 "0dbd816fba1543f721dc72",
"ca4186dd44c3121565cf5c",
988 "29c29dba9c545e267762fe",
"1616d78018d0b4745ca0f2",
989 "fe37802941d66dde02b99c",
"a9fa8e50bcb032c85e3304",
990 "83f640f1a48a8ebc0443ea",
"3776af54ccfbae916afde6",
991 "a8fc906976c35669e79ce0",
"f08a91fb2e1f78290619a8",
992 "cc9da55fe046d0cb3a770c",
"d36d662a69ae24b74dcbd8",
993 "40907b01280f03c0323946",
"d037db825175d851f3af00",
994 "1bf1490607c54032660ede",
"0af7723161ec223080be86",
995 "eca9afa0f6b01d92305edc",
"7a8dec79a51e8ac5388022",
996 "9059dfa2bb20ef7ef73ad4",
"6abb212d9739dfc02580f2",
997 "f6ad4824b87c80ebfce466",
"d747bfc5fd65ef70fbd9bc",
998 "612f63acc025b6ab476f7c",
"05209a0abb530b9e7e34b0",
999 "45b7ab6242b77474d9f11a",
"6c280d2a0523d9c4bc5946",
1000 "f1627701a2d692fd9449e6",
"8d9071b7e7a6a2eed6965e",
1001 "bf4f56e073271f6ab4bf80",
"c0fc3ec4fb7d2bb2756644",
1002 "57da6d13cb96a7689b2790",
"a9fa2eefa6f8796a355772",
1003 "164cc861bdd803c547f2ac",
"cc6de59755420925f90ed2",
1004 "a0c0033a52ab6299802fd2",
"b274db8abd3c6f396ea356",
1005 "97d4169cb33e7435718d90",
"81cfc6f18c35b1e1f17114",
1006 "481a2a0df8a23583f82d6c",
"081c29a10d468ccdbcecb6",
1007 "2c4142bf42b01e71076acc",
"a6573f3dc8b16c9d19f746",
1008 "c87af9a5d5206abca532a8",
"012dee2198eba82b19a1da",
1009 "b1ca4ea2e3d173bad4379c",
"b33ec97be83ce413f9acc8",
1010 "5b0f7742bca86b8012609a",
"37d8e0af9258b9e8c5f9b2",
1011 "35ad3fb0faeb5f1b0c30dc",
"6114e08483043fd3f38a8a",
1012 "cd921fdf59e882683763f6",
"95e45ecd0135aca9d6e6ae",
1013 "2e547dd7a05f6597aac516",
"14cd0f642fc0c5fe3a65ca",
1014 "3a0a1dfd7eee29c2e827e0",
"c8b5dffc335095dcdcaf2a",
1015 "3dd01a59d86310743ec752",
"8abdb889efbe39a510a118",
1016 "3f231f212055371cf3e2a2"};
1018 constexpr std::size_t Total = (Rows * Cols + ElementSize - 1);
1019 constexpr std::size_t Count = Total / ElementSize;
1020 constexpr std::array<std::uint8_t, 4> Masks = {0x8, 0x4, 0x2, 0x1};
1022 std::array<ElementType, Count> data{};
1024 for (std::size_t row = 0; row < Rows; ++row) {
1025 std::size_t col = 0;
1027 for (
auto const c : Data[row]) {
1028 std::uint8_t
const value =
1029 (c >=
'0' && c <=
'9') ? c -
'0'
1030 : (c >=
'a' && c <=
'f') ? c -
'a' + 10
1031 : (c >=
'A' && c <=
'F') ? c -
'A' + 10
1032 : throw
"Invalid hex";
1034 for (
auto const mask : Masks) {
1038 auto const index = row * Cols + col;
1039 data[index / ElementSize] |=
1040 (ElementType(1) << (index % ElementSize));
1049 return [matrix](std::size_t
const row, std::size_t
const col) {
1050 auto const index = row * Cols + col;
1051 return (matrix[index / ElementSize] >> (index % ElementSize)) & 1;
1063template <
typename Mode>
class DecodeMode {
1066 std::array<float, Mode::NFFT1> nuttal;
1067 std::array<std::array<std::array<std::complex<float>, Mode::NDOWNSPS>, 7>,
1070 alignas(64) std::array<std::complex<float>, Mode::NDOWNSPS> csymb;
1071 alignas(64) std::array<std::complex<float>, Mode::NMAX> filter;
1072 alignas(64) std::array<std::complex<float>, Mode::NMAX> cfilt;
1073 alignas(64) std::array<std::complex<float>, Mode::NDFFT1 / 2 + 1> ds_cx;
1074 alignas(64) std::array<std::complex<float>, Mode::NFFT1 / 2 + 1> sd;
1075 alignas(64) std::array<std::complex<float>, NP> cd0;
1076 std::array<float, Mode::NMAX> dd;
1077 std::array<std::array<float, Mode::NHSYM>, Mode::NSPS> s;
1078 std::array<float, Mode::NSPS> savg;
1079 FFTWPlanManager plans;
1081 js8::SoftCombiner<N> m_softCombiner;
1082 bool m_enableFreqTracking =
true;
1083 bool m_enableTimingTracking =
true;
1084 float m_llrErasureThreshold = js8::llrErasureThreshold();
1085 bool m_enableLdpcFeedback = js8::ldpcFeedbackEnabled();
1086 int m_maxLdpcPasses = js8::ldpcFeedbackMaxPasses();
1088 using Plan = FFTWPlanManager::Type;
1090 static constexpr auto Costas = JS8::Costas::array(Mode::NCOSTAS);
1095 static constexpr auto Taper = [] {
1096 std::array<std::array<float, Mode::NDD + 1>, 2> taper{};
1098 for (
size_t i = 0; i <= Mode::NDD; ++i) {
1101 (1.0f + cos_approx(i * std::numbers::pi_v<float> / Mode::NDD));
1103 taper[1][i] = value;
1104 taper[0][Mode::NDD - i] = value;
1112 using Points = Eigen::Matrix<double, BASELINE_NODES.size(), 2>;
1114 Eigen::Matrix<double, BASELINE_NODES.size(), BASELINE_NODES.size()>;
1115 using Coefficients = Eigen::Vector<double, BASELINE_NODES.size()>;
1126 inline auto evaluate(
float const x)
const {
1127 return [
this]<Eigen::Index... I>(
1128 float const x, std::integer_sequence<Eigen::Index, I...>) {
1129 auto baseline = 0.0;
1130 auto exponent = 1.0;
1132 ((baseline += (c[I * 2] + c[I * 2 + 1] * x) * exponent,
1136 return static_cast<float>(baseline);
1137 }(x, std::make_integer_sequence<Eigen::Index,
1138 Coefficients::SizeAtCompileTime / 2>{});
1141 std::optional<Decode> js8dec(
bool const syncStats,
bool const lsubtract,
1142 float &f1,
float &xdt,
int &nharderrors,
1143 float &xsnr, JS8::Event::Emitter emitEvent) {
1144 constexpr float FR = 12000.0f / Mode::NFFT1;
1145 constexpr float FS2 = 12000.0f / Mode::NDOWN;
1146 constexpr float DT2 = 1.0f / FS2;
1148 float const coarseStartHz = f1;
1149 float const coarseStartDt = xdt;
1152 static_cast<int>(std::round(f1 / FR));
1153 float const scaled_value =
1154 0.1f * (savg[index] - Mode::BASESUB);
1156 std::pow(10.0f, scaled_value);
1158 float delfbest = 0.0f;
1167 int i0 =
static_cast<int>(std::round((xdt + Mode::ASTART) * FS2));
1172 for (
int idt = i0 - Mode::NQSYMBOL; idt <= i0 + Mode::NQSYMBOL; ++idt) {
1173 float const sync = syncjs8d(idt, 0.0f);
1183 float const xdt2 = ibest * DT2;
1187 i0 =
static_cast<int>(std::round(xdt2 * FS2));
1190 for (
int ifr = -NFSRCH; ifr <= NFSRCH; ++ifr) {
1191 float const delf = ifr * 0.5f;
1192 float const sync = syncjs8d(i0, delf);
1202 float const dphi = -delfbest * ((2.0f * std::numbers::pi_v<float>) /
1204 std::complex<float>
const wstep =
1205 std::polar(1.0f, dphi);
1206 std::complex<float> w = {1.0f, 0.0f};
1208 for (
int i = 0; i < NP2; ++i) {
1218 float const sync = syncjs8d(i0, 0.0f);
1220 std::array<std::array<float, NN>, NROWS> s2;
1222 js8::FrequencyTracker freqTracker;
1223 if (m_enableFreqTracking) {
1224 freqTracker.
reset(0.0, FS2);
1231 double const timingMaxShift =
1232 std::clamp(0.08 *
static_cast<double>(Mode::NDOWNSPS), 0.5, 2.0);
1234 js8::TimingTracker timingTracker;
1235 if (m_enableTimingTracking) {
1236 timingTracker.
reset(0.0, 0.15, 0.35, timingMaxShift);
1241 auto const estimateResidualHz =
1242 [&](
int expectedTone) -> std::optional<float> {
1244 return std::nullopt;
1246 if (expectedTone < 0 || expectedTone + 1 >= Mode::NDOWNSPS)
1247 return std::nullopt;
1249 float const m0 = std::norm(csymb[expectedTone]);
1250 float const mplus = std::norm(csymb[expectedTone + 1]);
1251 float const mminus =
1252 expectedTone > 0 ? std::norm(csymb[expectedTone - 1]) : 0.0f;
1255 return std::nullopt;
1257 float const ratio = m0 / (mplus + mminus + 1e-12f);
1259 return std::nullopt;
1261 float const denom = mminus - 2.0f * m0 + mplus;
1262 if (std::abs(denom) < 1e-9f)
1263 return std::nullopt;
1265 float delta = 0.5f * (mminus - mplus) / denom;
1266 delta = std::clamp(delta, -0.5f, 0.5f);
1268 return delta * (FS2 / Mode::NDOWNSPS);
1271 auto const logTracker = [&](
char const *tag) {
1272 if (decoder_js8().isDebugEnabled()) {
1274 qCDebug(decoder_js8)
1275 <<
"freqTracker" << tag <<
"coarseHz" << coarseStartHz
1276 <<
"fineHz" << f1 <<
"refinedHz"
1277 << f1 +
static_cast<float>(freqTracker.
currentHz())
1282 if (timingTracker.
enabled()) {
1283 qCDebug(decoder_js8)
1284 <<
"timingTracker" << tag <<
"coarseDt" << coarseStartDt
1285 <<
"fineDt" << xdt <<
"refinedDt"
1286 << xdt +
static_cast<float>(
1289 <<
static_cast<float>(
1295 auto const goertzelEnergy =
1296 [&](
int start,
int expectedTone) -> std::optional<float> {
1297 if (start < 0 || start + Mode::NDOWNSPS > NP2)
1298 return std::nullopt;
1300 std::array<std::complex<float>, Mode::NDOWNSPS> tmp;
1301 std::copy(cd0.begin() + start, cd0.begin() + start + Mode::NDOWNSPS,
1305 freqTracker.
apply(tmp.data(), Mode::NDOWNSPS);
1308 auto const wstep = std::polar(
1309 1.0f,
static_cast<float>(-TAU * expectedTone / Mode::NDOWNSPS));
1310 auto phase = std::complex<float>{1.0f, 0.0f};
1311 std::complex<float> acc{0.0f, 0.0f};
1313 for (
auto const &sample : tmp) {
1314 acc += sample * std::conj(phase);
1318 return std::norm(acc);
1321 for (
int k = 0; k < NN; ++k) {
1324 int const i1Base = ibest + k * Mode::NDOWNSPS;
1325 int const timingShift = timingTracker.
enabled()
1326 ?
static_cast<int>(std::round(
1329 int i1 = i1Base + timingShift;
1333 }
else if (i1 + Mode::NDOWNSPS > NP2) {
1334 i1 = NP2 - Mode::NDOWNSPS;
1339 if (i1 >= 0 && i1 + Mode::NDOWNSPS <= NP2) {
1340 std::copy(cd0.begin() + i1, cd0.begin() + i1 + Mode::NDOWNSPS,
1344 freqTracker.
apply(csymb.data(), Mode::NDOWNSPS);
1348 fftwf_execute(plans[Plan::CS]);
1352 for (
int i = 0; i < NROWS; ++i) {
1353 s2[i][k] = std::abs(csymb[i]) / 1000.0f;
1357 bool const isPilot =
1358 (k < 7) || (k >= 36 && k < 43) || (k >= 72 && k < 79);
1361 int costasBlock = 0;
1362 int costasColumn = k;
1364 if (k >= 36 && k < 43) {
1366 costasColumn = k - 36;
1367 }
else if (k >= 72 && k < 79) {
1369 costasColumn = k - 72;
1372 int const expectedTone = Costas[costasBlock][costasColumn];
1374 if (
auto const residual =
1375 estimateResidualHz(expectedTone)) {
1376 freqTracker.
update(*residual);
1379 if (timingTracker.
enabled()) {
1380 auto const e0 = goertzelEnergy(i1, expectedTone);
1382 goertzelEnergy(i1 - 1, expectedTone);
1383 auto const eLate = goertzelEnergy(i1 + 1, expectedTone);
1385 float const toneMag = s2[expectedTone][k];
1387 if (e0 && eEarly && eLate && toneMag > 1e-6f) {
1388 float const denom = *e0 + 1e-6f;
1389 float const grad = (*eLate - *eEarly) / denom;
1392 double const weight = std::clamp(
1393 static_cast<double>(toneMag / 5.0f), 0.0, 1.0);
1394 double const errorSamples = std::clamp(
1395 0.25 *
static_cast<double>(grad), -1.0, 1.0);
1397 timingTracker.
update(errorSamples, weight);
1408 for (std::size_t costas = 0;
costas < Costas.size(); ++
costas) {
1409 auto const offset =
costas * 36;
1411 for (std::size_t column = 0; column < 7; ++column) {
1415 auto const max_row = std::distance(
1417 std::max_element(s2.begin(), s2.end(),
1418 [index = offset + column](
1419 auto const &rowA,
auto const &rowB) {
1420 return rowA[index] < rowB[index];
1425 if (Costas[costas][column] == max_row)
1433 logTracker(
"sync_fail");
1434 return std::nullopt;
1439 JS8::Event::SyncState{JS8::Event::SyncState::Type::CANDIDATE,
1443 {.candidate = nsync}});
1445 std::array<std::array<float, ND>, NROWS> s1;
1449 for (
int row = 0; row < NROWS; ++row) {
1450 std::copy(s2[row].begin() + 7, s2[row].begin() + 36,
1452 std::copy(s2[row].begin() + 43, s2[row].begin() + 72,
1453 s1[row].begin() + 29);
1457 std::array<int, ND> symbolWinners = {};
1459 for (
int j = 0; j < ND; ++j) {
1461 float best = s1[0][j];
1463 for (
int i = 1; i < NROWS; ++i) {
1464 if (s1[i][j] > best) {
1470 symbolWinners[j] = winner;
1473 auto const whitening = js8::WhiteningProcessor<NROWS, ND, N>::process(
1474 s1, symbolWinners, m_llrErasureThreshold,
1475 decoder_js8().isDebugEnabled());
1477 auto llr0 = whitening.llr0;
1478 auto llr1 = whitening.llr1;
1482 if (!whitening.erasureApplied) {
1483 std::size_t erasuresAfterThreshold = 0;
1484 double sumAbsPreErasure = 0.0;
1485 double sumAbsPostErasure = 0.0;
1487 auto const applyErasureThreshold = [&](
auto &llr) {
1488 for (
auto const value : llr)
1489 sumAbsPreErasure += std::abs(value);
1491 if (m_llrErasureThreshold > 0.0f) {
1492 for (
auto &value : llr) {
1493 if (std::abs(value) < m_llrErasureThreshold) {
1495 ++erasuresAfterThreshold;
1498 sumAbsPostErasure += std::abs(value);
1501 for (
auto const value : llr)
1502 sumAbsPostErasure += std::abs(value);
1506 applyErasureThreshold(llr0);
1507 applyErasureThreshold(llr1);
1509 if (decoder_js8().isDebugEnabled()) {
1511 static_cast<double>(llr0.size() + llr1.size());
1512 double const avgPre =
1513 total > 0.0 ? sumAbsPreErasure / total : 0.0;
1514 double const avgPost =
1515 total > 0.0 ? sumAbsPostErasure / total : 0.0;
1517 qCDebug(decoder_js8)
1518 <<
"LLR erasure threshold" << m_llrErasureThreshold
1519 <<
"erasures:" << erasuresAfterThreshold
1520 <<
"avg|LLR| pre/post:" << avgPre << avgPost;
1524 auto const ttl = std::chrono::seconds{Mode::NTXDUR * 2};
1526 m_softCombiner.flush(ttl);
1529 m_softCombiner.makeKey(Mode::NSUBMODE, f1, xdt, llr0, llr1);
1530 auto combined = m_softCombiner.combine(key, llr0, llr1, ttl);
1532 auto llr0Combined = combined.llr0;
1533 auto llr1Combined = combined.llr1;
1535 std::array<int8_t, K> decoded;
1536 std::array<int8_t, N> cw;
1538 int totalLdpcPasses = 0;
1539 bool usedFeedbackPass =
false;
1540 bool feedbackTurnedSuccess =
false;
1541 int feedbackConfident = 0;
1542 int feedbackUncertain = 0;
1544 auto const tryDecode = [&](std::array<float, N>
const &llrInput,
1545 int ipass) -> std::optional<Decode> {
1546 nharderrors = bpdecode174(llrInput, decoded, cw);
1549 if (std::all_of(cw.begin(), cw.end(),
1550 [](
int x) { return x == 0; })) {
1551 return std::nullopt;
1554 if (nharderrors >= 0 && nharderrors < 60 &&
1555 !(sync < 2.0f && nharderrors > 35) &&
1556 !(ipass > 2 && nharderrors > 39) &&
1557 !(ipass == 4 && nharderrors > 30)) {
1558 if (checkCRC12(decoded)) {
1560 emitEvent(JS8::Event::SyncState{
1561 JS8::Event::SyncState::Type::DECODED,
1565 {.decoded = sync}});
1567 auto message = extractmessage174(decoded);
1570 (decoded[72] << 2) | (decoded[73] << 1) | decoded[74];
1572 std::array<int, NN> itone;
1574 JS8::encode(i3bit, Costas, message.data(), itone.data());
1577 subtractjs8(genjs8refsig(itone, f1), xdt2);
1581 for (std::size_t i = 0; i < itone.size(); ++i) {
1582 xsig += std::pow(s2[itone[i]][i], 2);
1586 std::max(10.0f * std::log10(std::max(
1587 xsig / xbase - 1.0f, 1.259e-10f)) -
1591 m_softCombiner.markDecoded(combined.key);
1593 logTracker(
"decoded");
1594 return std::make_optional<Decode>(i3bit, message);
1600 return std::nullopt;
1604 for (
int ipass = 1; ipass <= 4 && totalLdpcPasses < m_maxLdpcPasses;
1606 auto &llr = ipass == 2 ? llr1Combined : llr0Combined;
1610 std::fill(llr0Combined.begin(), llr0Combined.begin() + 24,
1612 else if (ipass == 4)
1613 std::fill(llr0Combined.begin() + 24, llr0Combined.begin() + 48,
1616 std::array<float, N> llrPrimary = llr;
1617 if (
auto result = tryDecode(llrPrimary, ipass)) {
1625 if (m_enableLdpcFeedback && totalLdpcPasses < m_maxLdpcPasses) {
1626 std::array<float, N> llrRefined;
1629 js8::refineLlrsWithLdpcFeedback(
1630 llrPrimary, cw, m_llrErasureThreshold, llrRefined,
1631 confident, uncertain);
1633 if (decoder_js8().isDebugEnabled()) {
1634 qCDebug(decoder_js8)
1635 <<
"LDPC feedback pass"
1636 <<
"ipass" << ipass <<
"confident" << confident
1637 <<
"uncertain" << uncertain;
1640 usedFeedbackPass =
true;
1641 feedbackConfident += confident;
1642 feedbackUncertain += uncertain;
1644 if (
auto result = tryDecode(llrRefined, ipass)) {
1646 feedbackTurnedSuccess =
true;
1647 if (decoder_js8().isDebugEnabled()) {
1648 qCDebug(decoder_js8)
1649 <<
"LDPC feedback succeeded on second pass"
1650 <<
"ipass" << ipass <<
"confident"
1651 << feedbackConfident <<
"uncertain"
1652 << feedbackUncertain <<
"passes" << totalLdpcPasses;
1661 if (decoder_js8().isDebugEnabled()) {
1662 qCDebug(decoder_js8)
1663 <<
"LDPC feedback summary"
1664 <<
"used" << usedFeedbackPass <<
"success"
1665 << feedbackTurnedSuccess <<
"confident" << feedbackConfident
1666 <<
"uncertain" << feedbackUncertain <<
"passes"
1671 return std::nullopt;
1695 void baselinejs8(
int const ia,
int const ib) {
1701 using boost::math::ccmath::round;
1703 constexpr auto bmin =
1704 static_cast<std::size_t
>(round(BASELINE_MIN / Mode::DF));
1705 constexpr auto bmax =
1706 static_cast<std::size_t
>(round(BASELINE_MAX / Mode::DF));
1707 constexpr auto size = bmax - bmin + 1;
1708 constexpr auto arm = size / (2 * BASELINE_NODES.size());
1713 auto const data = savg.begin() + bmin;
1714 auto const end = data + size;
1718 std::transform(data, end, data, [](
float const value) {
1719 return 10.0f * std::log10(value);
1725 for (std::size_t i = 0; i < BASELINE_NODES.size(); ++i) {
1726 auto const node = size * BASELINE_NODES[i];
1727 auto const base = data +
static_cast<int>(std::round(node));
1728 auto span = std::vector<float>(std::clamp(base - arm, data, end),
1729 std::clamp(base + arm, data, end));
1731 auto const n = span.size() * BASELINE_SAMPLE / 100;
1733 std::nth_element(span.begin(), span.begin() + n, span.end());
1735 p.row(i) << node, span[n];
1742 Eigen::VectorXd x = p.col(0);
1743 Eigen::VectorXd y = p.col(1);
1746 for (Eigen::Index i = 1; i < V.cols(); ++i) {
1747 V.col(i) = V.col(i - 1).cwiseProduct(x);
1752 c = V.colPivHouseholderQr().solve(y);
1761 auto const mapIndex = [ia, ib, last = size - 1](
int const i) {
1762 return (i - ia) * last / float(ib - ia);
1773 for (
int i = ia; i <= ib; ++i) {
1774 savg[i] = evaluate(mapIndex(i)) + 0.65f;
1785 void computeBasebandFFT() {
1790 float *fftw_real =
reinterpret_cast<float *
>(ds_cx.data());
1795 std::copy(dd.begin(), dd.end(), fftw_real);
1796 std::fill(fftw_real + dd.size(), fftw_real + Mode::NDFFT1, 0.0f);
1798 fftwf_execute(plans[Plan::BB]);
1807 void js8_downsample(
float const f0) {
1814 constexpr float DF = 12000.0f / Mode::NDFFT1;
1815 constexpr float BAUD = 12000.0f / Mode::NSPS;
1817 float const ft = f0 + 8.5f * BAUD;
1818 float const fb = f0 - 1.5f * BAUD;
1819 int const i0 =
static_cast<int>(std::round(f0 / DF));
1821 std::min(
static_cast<int>(std::round(ft / DF)), Mode::NDFFT1 / 2);
1822 int const ib = std::max(0,
static_cast<int>(std::round(fb / DF)));
1824 std::size_t
const NDD_SIZE = Mode::NDD + 1;
1825 std::size_t
const RANGE_SIZE = it - ib + 1;
1827 std::fill_n(cd0.begin(), Mode::NDFFT2, ZERO);
1829 std::copy(ds_cx.begin() + ib, ds_cx.begin() + ib + RANGE_SIZE,
1836 auto const head = cd0.begin();
1837 auto const tail = cd0.begin() + RANGE_SIZE;
1839 std::transform(head, head + NDD_SIZE, Taper[0].begin(), head,
1840 std::multiplies<>());
1841 std::transform(tail - NDD_SIZE, tail, Taper[1].begin(), tail - NDD_SIZE,
1842 std::multiplies<>());
1848 std::rotate(cd0.begin(), cd0.begin() + (i0 - ib),
1849 cd0.begin() + Mode::NDFFT2);
1856 fftwf_execute(plans[Plan::DS]);
1862 float const factor =
1863 1.0f / std::sqrt(
static_cast<float>(Mode::NDFFT1) * Mode::NDFFT2);
1865 std::transform(cd0.begin(), cd0.end(), cd0.begin(),
1866 [factor](
auto &value) { return value * factor; });
1940 std::vector<Sync> syncjs8(
int nfa,
int nfb) {
1945 for (
int j = 0; j < Mode::NHSYM; ++j) {
1946 int const ia = j * Mode::NSTEP;
1947 int const ib = ia + Mode::NFFT1;
1949 if (ib > Mode::NMAX)
1952 std::transform(dd.begin() + ia, dd.begin() + ib, nuttal.begin(),
1953 reinterpret_cast<float *
>(sd.data()),
1954 std::multiplies<float>{});
1956 fftwf_execute(plans[Plan::SD]);
1960 for (
int i = 0; i < Mode::NSPS; ++i) {
1961 auto const power = std::norm(sd[i]);
1969 int const nwin = nfb - nfa;
1984 std::max(0,
static_cast<int>(std::round(nfa / Mode::DF)));
1985 auto const ib =
static_cast<int>(std::round(nfb / Mode::DF));
1990 baselinejs8(ia, ib);
1996 for (
int i = ia; i <= ib; ++i) {
1997 float max_value = -std::numeric_limits<float>::infinity();
1998 int max_index = -Mode::JZ;
2000 for (
int j = -Mode::JZ; j <= Mode::JZ; ++j) {
2001 std::array<std::array<float, 3>, 2> t{};
2003 for (
int p = 0; p < 3; ++p) {
2004 for (
int n = 0; n < 7; ++n) {
2006 j + Mode::JSTRT + NSSY * n + p * 36 * NSSY;
2008 if (offset >= 0 && offset < Mode::NHSYM) {
2011 t[0][p] += s[i + NFOS * Costas[p][n]][offset];
2016 for (
int freq = 0; freq < 7; ++freq) {
2017 t[1][p] += s[i + NFOS * freq][offset];
2030 auto const compute_sync = [&t](
int start,
int end) {
2034 for (
int i = start; i <= end; ++i) {
2039 return tx / ((t0 - tx) / 6.0f);
2042 if (
auto const sync_value =
2043 std::max({compute_sync(0, 2), compute_sync(0, 1),
2044 compute_sync(1, 2)});
2045 sync_value > max_value) {
2046 max_value = sync_value;
2051 sync.emplace(Mode::DF * i, Mode::TSTEP * (max_index + 0.5f),
2062 auto &freqIndex = sync.get<Tag::Freq>();
2063 auto &rankIndex = sync.get<Tag::Rank>();
2064 auto &syncIndex = sync.get<Tag::Sync>();
2073 auto const normalize =
2074 [sync = rankIndex.nth(rankIndex.size() * 4 / 10)->sync](
2075 Sync &entry) { entry.sync /= sync; };
2077 for (
auto it = freqIndex.begin(); it != freqIndex.end(); ++it) {
2078 freqIndex.modify(it, normalize);
2085 for (
auto it = syncIndex.begin();
2086 it != syncIndex.end() &&
candidates.size() < NMAXCAND;
2087 it = syncIndex.begin()) {
2092 if (it->sync < ASYNCMIN || std::isnan(it->sync))
2103 freqIndex.erase(freqIndex.lower_bound(it->freq - Mode::AZ),
2104 freqIndex.upper_bound(it->freq + Mode::AZ));
2115 float syncjs8d(
int const i0,
float const delf) {
2116 constexpr float BASE_DPHI = TAU * (1.0f / (12000.0f / Mode::NDOWN));
2122 std::array<std::complex<float>, Mode::NDOWNSPS> freqAdjust;
2125 float const dphi = BASE_DPHI * delf;
2132 for (
int i = 0; i < Mode::NDOWNSPS; ++i) {
2133 freqAdjust[i] = std::polar(1.0f, phi);
2134 if (phi = std::fmod(phi + dphi, TAU); phi < 0.0f) {
2139 freqAdjust.fill(std::complex<float>{1.0f, 0.0f});
2147 for (
int i = 0; i < 3; ++i) {
2148 for (
int j = 0; j < 7; ++j) {
2149 if (
auto const offset =
2150 36 * i * Mode::NDOWNSPS + i0 + j * Mode::NDOWNSPS;
2151 offset >= 0 && offset + Mode::NDOWNSPS <= Mode::NP2) {
2152 sync += std::norm(std::transform_reduce(
2155 cd0.begin() + offset,
2156 std::complex<float>{},
2162 fa * csyncs[i][j][&fa - &freqAdjust[0]]);
2175 std::vector<std::complex<float>>
2176 genjs8refsig(std::array<int, NN>
const &itone,
float const f0) {
2183 float const BFPI = TAU * f0 * (1.0f / 12000.0f);
2186 std::vector<std::complex<float>> cref;
2187 cref.reserve(NN * Mode::NSPS);
2189 for (
int i = 0; i < NN; ++i) {
2194 BFPI + TAU *
static_cast<float>(itone[i]) / Mode::NSPS;
2199 for (std::size_t is = 0; is < Mode::NSPS; ++is) {
2200 cref.push_back(std::polar(1.0f, phi));
2201 phi = std::fmod(phi + dphi, TAU);
2217 void subtractjs8(std::vector<std::complex<float>>
const &cref,
2219 auto const nstart =
static_cast<int>(dt * 12000.0f);
2220 std::size_t
const cref_start =
2221 (nstart < 0) ? static_cast<std::size_t>(-nstart) : 0;
2222 std::size_t
const dd_start =
2223 (nstart > 0) ?
static_cast<std::size_t
>(nstart) : 0;
2225 std::min(cref.size() - cref_start, dd.size() - dd_start);
2229 for (std::size_t i = 0; i < size; ++i) {
2230 cfilt[i] = dd[dd_start + i] * std::conj(cref[cref_start + i]);
2235 std::fill(cfilt.begin() + size, cfilt.end(), ZERO);
2239 fftwf_execute(plans[Plan::CF]);
2243 std::transform(cfilt.begin(), cfilt.end(), filter.begin(),
2244 cfilt.begin(), std::multiplies<>());
2248 fftwf_execute(plans[Plan::CB]);
2252 for (std::size_t i = 0; i < size; ++i) {
2254 2.0f * std::real(cfilt[i] * cref[cref_start + i]);
2262 m_enableFreqTracking =
2263 std::getenv(
"JS8_DISABLE_FREQ_TRACKING") ==
nullptr;
2264 m_enableTimingTracking =
2265 std::getenv(
"JS8_DISABLE_TIMING_TRACKING") ==
nullptr;
2273 constexpr float a0 = 0.3635819f;
2274 constexpr float a1 = -0.4891775f;
2275 constexpr float a2 = 0.1365995f;
2276 constexpr float a3 = -0.0106411f;
2282 float const pi = 4.0f * std::atan(1.0f);
2285 for (std::size_t i = 0; i < nuttal.size(); ++i) {
2290 KahanSum value = a0;
2292 value += a1 * std::cos(2 * pi * i / nuttal.size());
2293 value += a2 * std::cos(4 * pi * i / nuttal.size());
2294 value += a3 * std::cos(6 * pi * i / nuttal.size());
2302 for (
auto &value : nuttal)
2303 value = value / sum * nuttal.size() / 300.0f;
2307 for (
int i = 0; i < 7; ++i) {
2308 float const dphia = TAU * Costas[0][i] / Mode::NDOWNSPS;
2309 float const dphib = TAU * Costas[1][i] / Mode::NDOWNSPS;
2310 float const dphic = TAU * Costas[2][i] / Mode::NDOWNSPS;
2316 for (
int j = 0; j < Mode::NDOWNSPS; ++j) {
2317 csyncs[0][i][j] = std::polar(1.0f, phia);
2318 csyncs[1][i][j] = std::polar(1.0f, phib);
2319 csyncs[2][i][j] = std::polar(1.0f, phic);
2321 phia = std::fmod(phia + dphia, TAU);
2322 phib = std::fmod(phib + dphib, TAU);
2323 phic = std::fmod(phic + dphic, TAU);
2333 for (
int j = -NFILT / 2; j <= NFILT / 2; ++j) {
2334 int const index = j + NFILT / 2;
2335 float const value = std::pow(std::cos(pi * j / NFILT), 2);
2337 filter[index].real(value);
2345 std::fill(std::transform(filter.begin(), filter.begin() + NFILT + 1,
2347 [sum](
auto const value) {
2348 return std::complex<float>(
2349 value.real() / sum, 0.0f);
2351 filter.end(), ZERO);
2355 std::rotate(filter.begin(), filter.begin() + NFILT / 2,
2356 filter.begin() + NFILT + 1);
2360 fftwf_plan fftw_plan;
2362 std::lock_guard<std::mutex> lock(fftw_mutex);
2364 fftw_plan = fftwf_plan_dft_1d(
2365 Mode::NMAX,
reinterpret_cast<fftwf_complex *
>(filter.data()),
2366 reinterpret_cast<fftwf_complex *
>(filter.data()), FFTW_FORWARD,
2367 FFTW_ESTIMATE_PATIENT);
2370 throw std::runtime_error(
"Failed to create FFT plan");
2374 fftwf_execute(fftw_plan);
2377 std::lock_guard<std::mutex> lock(fftw_mutex);
2378 fftwf_destroy_plan(fftw_plan);
2383 std::transform(filter.begin(), filter.end(), filter.begin(),
2384 [factor = 1.0f / Mode::NMAX](
auto value) {
2385 return value * factor;
2391 std::lock_guard<std::mutex> lock(fftw_mutex);
2393 plans[Plan::DS] = fftwf_plan_dft_1d(
2394 Mode::NDFFT2,
reinterpret_cast<fftwf_complex *
>(cd0.data()),
2395 reinterpret_cast<fftwf_complex *
>(cd0.data()), FFTW_BACKWARD,
2396 FFTW_ESTIMATE_PATIENT);
2398 plans[Plan::BB] = fftwf_plan_dft_r2c_1d(
2399 Mode::NDFFT1,
reinterpret_cast<float *
>(ds_cx.data()),
2400 reinterpret_cast<fftwf_complex *
>(ds_cx.data()),
2401 FFTW_ESTIMATE_PATIENT);
2403 plans[Plan::CF] = fftwf_plan_dft_1d(
2404 Mode::NMAX,
reinterpret_cast<fftwf_complex *
>(cfilt.data()),
2405 reinterpret_cast<fftwf_complex *
>(cfilt.data()), FFTW_FORWARD,
2406 FFTW_ESTIMATE_PATIENT);
2408 plans[Plan::CB] = fftwf_plan_dft_1d(
2409 Mode::NMAX,
reinterpret_cast<fftwf_complex *
>(cfilt.data()),
2410 reinterpret_cast<fftwf_complex *
>(cfilt.data()), FFTW_BACKWARD,
2411 FFTW_ESTIMATE_PATIENT);
2413 plans[Plan::SD] = fftwf_plan_dft_r2c_1d(
2414 Mode::NFFT1,
reinterpret_cast<float *
>(sd.data()),
2415 reinterpret_cast<fftwf_complex *
>(sd.data()),
2416 FFTW_ESTIMATE_PATIENT);
2418 plans[Plan::CS] = fftwf_plan_dft_1d(
2419 Mode::NDOWNSPS,
reinterpret_cast<fftwf_complex *
>(csymb.data()),
2420 reinterpret_cast<fftwf_complex *
>(csymb.data()), FFTW_FORWARD,
2421 FFTW_ESTIMATE_PATIENT);
2423 for (
auto plan : plans) {
2425 throw std::runtime_error(
"Failed to create FFT plan");
2431 std::size_t operator()(
struct dec_data
const &data,
int const kpos,
2432 int const ksz, JS8::Event::Emitter emitEvent) {
2435 auto const pos = std::max(0, kpos);
2436 auto const sz = std::max(0, ksz);
2438 assert(sz <= Mode::NMAX);
2440 if (data.params.syncStats)
2441 emitEvent(JS8::Event::SyncStart{pos, sz});
2443 auto const ddCopy = [](
auto const begin,
auto const end,
2445 std::transform(begin, end, to, [](
auto const value) {
2446 return static_cast<float>(value);
2452 if ((JS8_RX_SAMPLE_SIZE - pos) < sz) {
2455 int const firstsize = JS8_RX_SAMPLE_SIZE - pos;
2456 int const secondsize = sz - firstsize;
2458 ddCopy(std::begin(data.d2) + pos,
2459 std::begin(data.d2) + pos + firstsize, dd.begin());
2460 ddCopy(std::begin(data.d2), std::begin(data.d2) + secondsize,
2461 dd.begin() + firstsize);
2465 ddCopy(std::begin(data.d2) + pos, std::begin(data.d2) + pos + sz,
2469 Decode::Map decodes;
2470 auto const ttl = std::chrono::seconds{Mode::NTXDUR * 2};
2471 m_softCombiner.flush(ttl);
2473 for (
int ipass = 1; ipass <= 3; ++ipass) {
2479 auto candidates = syncjs8(data.params.nfa, data.params.nfb);
2486 [nfqso = data.params.nfqso](
auto const &a,
auto const &b) {
2487 auto const a_dist = std::abs(a.freq - nfqso);
2488 auto const b_dist = std::abs(b.freq - nfqso);
2490 if (a_dist < 10.0f && b_dist >= 10.0f)
2492 if (b_dist < 10.0f && a_dist >= 10.0f)
2495 return std::tie(a_dist, a.freq) < std::tie(b_dist, b.freq);
2501 computeBasebandFFT();
2503 bool const subtract = ipass < 3;
2504 bool improved =
false;
2508 int nharderrors = -1;
2510 if (
auto decode = js8dec(data.params.syncStats, subtract, f1,
2511 xdt, nharderrors, xsnr, emitEvent)) {
2516 auto const snr =
static_cast<int>(std::round(xsnr));
2523 if (
auto [it, inserted] =
2524 decodes.try_emplace(std::move(*decode), snr);
2525 inserted || it->second < snr) {
2535 emitEvent(JS8::Event::Decoded{
2536 data.params.nutc, snr, xdt - Mode::ASTART, f1,
2537 it->first.data, it->first.type,
2538 1.0f - nharderrors / 60.0f, Mode::NSUBMODE});
2552 return decodes.size();
2559template class DecodeMode<ModeA>;
2560template class DecodeMode<ModeB>;
2561template class DecodeMode<ModeC>;
2562template class DecodeMode<ModeE>;
2563template class DecodeMode<ModeI>;
2572class Worker :
public QObject {
2591 struct DecodeEntry {
2592 std::variant<DecodeMode<ModeA>, DecodeMode<ModeB>,
2593 DecodeMode<ModeC>, DecodeMode<ModeE>,
2600 template <
typename DecodeModeType>
2601 DecodeEntry(std::in_place_type_t<DecodeModeType>,
int mode,
2602 int &kpos,
int &ksz)
2603 : decode(std::in_place_type<DecodeModeType>), mode(mode),
2604 kpos(kpos), ksz(ksz) {}
2614 template <
typename ModeType>
2615 DecodeEntry makeDecodeEntry(
int shift,
int &kpos,
int &ksz) {
2616 return DecodeEntry(std::in_place_type<DecodeMode<ModeType>>,
2617 1 << shift, kpos, ksz);
2620 std::array<DecodeEntry, 5> m_decodes = {
2621 {makeDecodeEntry<ModeI>(4, m_data.params.kposI, m_data.params.kszI),
2622 makeDecodeEntry<ModeE>(3, m_data.params.kposE, m_data.params.kszE),
2623 makeDecodeEntry<ModeC>(2, m_data.params.kposC, m_data.params.kszC),
2624 makeDecodeEntry<ModeB>(1, m_data.params.kposB, m_data.params.kszB),
2625 makeDecodeEntry<ModeA>(0, m_data.params.kposA,
2626 m_data.params.kszA)}};
2631 explicit Impl(
struct dec_data &data) : m_data(data) {}
2636 void operator()(::JS8::Event::Emitter emitEvent) {
2641 auto const set = m_data.params.nsubmodes;
2642 std::size_t sum = 0;
2653 for (
auto &entry : m_decodes) {
2654 if ((set & entry.mode) == entry.mode) {
2656 [&](
auto &&decode) {
2657 sum += decode(m_data, entry.kpos, entry.ksz,
2673 QSemaphore *m_semaphore;
2674 std::atomic<bool> m_quit =
false;
2680 explicit Worker(QSemaphore *semaphore, QObject *parent =
nullptr)
2681 : QObject(parent), m_semaphore(semaphore) {}
2687 void stop() { m_quit =
true; }
2692 void copy() { m_data =
dec_data; };
2699 void decodeEvent(::JS8::Event::Variant
const &);
2715 std::unique_ptr<Impl> impl = std::make_unique<Impl>(m_data);
2722 m_semaphore->acquire();
2727 (*impl)([
this](::JS8::Event::Variant
const &event) {
2728 emit decodeEvent(event);
2741JS8::Decoder::Decoder(QObject *parent)
2742 : QObject(parent), m_semaphore(0), m_worker(new JS8::
Worker(&m_semaphore)) {
2743 m_worker->moveToThread(&m_thread);
2745 connect(&m_thread, &QThread::started, m_worker, &JS8::Worker::run);
2746 connect(&m_thread, &QThread::finished, m_worker, &QObject::deleteLater);
2747 connect(m_worker, &JS8::Worker::decodeEvent,
this, &Decoder::decodeEvent);
2750void JS8::Decoder::start(QThread::Priority priority) {
2751 m_thread.start(priority);
2754void JS8::Decoder::quit() {
2756 m_semaphore.release();
2761void JS8::Decoder::decode() {
2763 m_semaphore.release();
2776void encode(
int const type, Costas::Array
const &costas,
2777 const char *
const message,
int *
const tones) {
2794 std::array<std::uint8_t, 11> bytes = {};
2800 for (
int i = 0, j = 0; i < 12; i += 4, j += 3) {
2801 std::uint32_t words = (alphabetWord(message[i]) << 18) |
2802 (alphabetWord(message[i + 1]) << 12) |
2803 (alphabetWord(message[i + 2]) << 6) |
2804 alphabetWord(message[i + 3]);
2806 bytes[j] = words >> 16;
2807 bytes[j + 1] = words >> 8;
2808 bytes[j + 2] = words;
2815 bytes[9] = (type & 0b111) << 5;
2821 auto const crc = CRC12(bytes);
2826 bytes[9] |= (crc >> 7) & 0x1F;
2827 bytes[10] = (crc & 0x7F) << 1;
2848 auto costasData = tones;
2849 auto parityData = tones + 7;
2850 auto outputData = tones + 43;
2854 for (
auto const &array : costas) {
2855 std::copy(array.begin(), array.end(), costasData);
2863 std::size_t outputBits = 0;
2864 std::size_t outputByte = 0;
2865 std::uint8_t outputMask = 0x80;
2866 std::uint8_t outputWord = 0;
2867 std::uint8_t parityWord = 0;
2869 for (std::size_t i = 0; i < 87; ++i) {
2881 std::size_t parityBits = 0;
2882 std::size_t parityByte = 0;
2883 std::uint8_t parityMask = 0x80;
2885 for (std::size_t j = 0; j < 87; ++j) {
2886 parityBits += parity(i, j) && (bytes[parityByte] & parityMask);
2888 (parityMask == 1) ? (++parityByte, 0x80) : (parityMask >> 1);
2894 parityWord = (parityWord << 1) | (parityBits & 1);
2896 (outputWord << 1) | ((bytes[outputByte] & outputMask) != 0);
2898 (outputMask == 1) ? (++outputByte, 0x80) : (outputMask >> 1);
2902 if (++outputBits == 3) {
2903 *parityData++ = parityWord;
2904 *outputData++ = outputWord;
QMultiMap< quint32, QString > candidates(QString word, bool includeTwoEdits)
Generate candidate words that are one or two edit distances away.
Definition JSC_checker.cpp:239
double currentHz() const noexcept
Get the current frequency estimate in Hz.
Definition FrequencyTracker.cpp:56
void reset(double initial_hz, double sample_rate_hz, double alpha=0.15, double max_step_hz=0.3, double max_error_hz=5.0)
Reset the FrequencyTracker with initial parameters.
Definition FrequencyTracker.cpp:24
double averageStepHz() const noexcept
Get the average step size in Hz.
Definition FrequencyTracker.cpp:63
void update(double residual_hz, double weight=1.0)
Update the FrequencyTracker with a new residual frequency measurement.
Definition FrequencyTracker.cpp:93
bool enabled() const noexcept
Check if the FrequencyTracker is enabled.
Definition FrequencyTracker.cpp:49
void disable()
Disable the FrequencyTracker.
Definition FrequencyTracker.cpp:41
void apply(std::complex< float > *data, int count) const
Apply frequency correction to the provided data.
Definition FrequencyTracker.cpp:73
void reset(double initial_samples, double alpha=0.15, double max_step=0.35, double max_total_error=2.0)
Tracks residual timing (sample) offset between the symbol clock and the signal.
Definition FrequencyTracker.cpp:118
double averageStepSamples() const noexcept
Get the average step size in samples.
Definition FrequencyTracker.cpp:155
double currentSamples() const noexcept
Get the current timing estimate in samples.
Definition FrequencyTracker.cpp:148
void disable()
Disable the TimingTracker.
Definition FrequencyTracker.cpp:133
bool enabled() const noexcept
Check if the TimingTracker is enabled.
Definition FrequencyTracker.cpp:141
void update(double residual_samples, double weight=1.0)
Update the TimingTracker with a new residual timing measurement.
Definition FrequencyTracker.cpp:165
Costas::Type costas(int const submode)
Get the Costas array type of the submode.
Definition JS8Submode.cpp:258