72 static Result process(std::array<std::array<float, ND>, NROWS>
const &s1,
73 std::array<int, ND>
const &symbolWinners,
74 float erasureThreshold,
bool debug) {
76 [](std::vector<float> &values) -> std::optional<float> {
80 auto const mid = values.size() / 2;
82 std::nth_element(values.begin(), values.begin() + mid,
84 float med = values[mid];
86 if ((values.size() % 2) == 0 && mid > 0) {
87 std::nth_element(values.begin(), values.begin() + (mid - 1),
89 med = 0.5f * (med + values[mid - 1]);
97 auto const toneNoise =
98 [&]() -> std::optional<std::array<float, NROWS>> {
99 std::array<std::vector<float>, NROWS> toneSamples;
100 std::array<float, NROWS> noise = {};
103 for (
int j = 0; j < ND; ++j) {
104 int const winner = symbolWinners[j];
106 for (
int i = 0; i < NROWS; ++i) {
108 toneSamples[i].push_back(s1[i][j]);
114 for (
int i = 0; i < NROWS; ++i) {
115 if (
auto m = median(toneSamples[i]); m) {
129 if (toneNoise && debug) {
130 std::ostringstream oss;
133 for (
auto const value : *toneNoise)
136 qCDebug(decoder_js8).noquote() << oss.str().c_str();
141 auto const symbolNoise = [&]() -> std::optional<std::vector<float>> {
142 std::vector<float> noise;
145 for (
int j = 0; j < ND; ++j) {
146 std::vector<float> bins;
147 bins.reserve(NROWS - 1);
149 int const winner = symbolWinners[j];
151 for (
int i = 0; i < NROWS; ++i) {
153 bins.push_back(s1[i][j]);
156 if (
auto m = median(bins); m) {
166 if (symbolNoise && !symbolNoise->empty() && debug) {
167 auto const [minIt, maxIt] =
168 std::minmax_element(symbolNoise->begin(), symbolNoise->end());
169 float const avg = std::accumulate(symbolNoise->begin(),
170 symbolNoise->end(), 0.0f) /
171 static_cast<float>(symbolNoise->size());
174 <<
"symbolNoise avg/min/max" << avg << *minIt << *maxIt;
179 bool const disableWhitening =
180 std::getenv(
"JS8_DISABLE_WHITENING") !=
nullptr;
181 bool const whiteningAvailable = toneNoise && symbolNoise &&
182 !symbolNoise->empty() &&
184 bool const applyErasureInWhitening =
185 whiteningAvailable && erasureThreshold > 0.0f;
186 double sumAbsPre = 0.0;
187 double sumAbsPost = 0.0;
188 std::size_t erasures = 0;
190 for (
int j = 0; j < ND; ++j) {
191 int const i1 = 3 * j;
192 int const i2 = 3 * j + 1;
193 int const i4 = 3 * j + 2;
195 std::array<float, NROWS> ps;
197 for (
int i = 0; i < NROWS; ++i)
201 result.
llr0[i1] = std::max({ps[4], ps[5], ps[6], ps[7]}) -
202 std::max({ps[0], ps[1], ps[2], ps[3]});
203 result.
llr0[i2] = std::max({ps[2], ps[3], ps[6], ps[7]}) -
204 std::max({ps[0], ps[1], ps[4], ps[5]});
205 result.
llr0[i4] = std::max({ps[1], ps[3], ps[5], ps[7]}) -
206 std::max({ps[0], ps[2], ps[4], ps[6]});
209 x = std::log(x + 1e-32f);
212 result.
llr1[i1] = std::max({ps[4], ps[5], ps[6], ps[7]}) -
213 std::max({ps[0], ps[1], ps[2], ps[3]});
214 result.
llr1[i2] = std::max({ps[2], ps[3], ps[6], ps[7]}) -
215 std::max({ps[0], ps[1], ps[4], ps[5]});
216 result.
llr1[i4] = std::max({ps[1], ps[3], ps[5], ps[7]}) -
217 std::max({ps[0], ps[2], ps[4], ps[6]});
219 if (whiteningAvailable) {
220 int const winner = symbolWinners[j];
221 float const tn = std::max(0.0f, (*toneNoise)[winner]);
222 float const sn = std::max(0.0f, (*symbolNoise)[j]);
223 float const localNoise = std::sqrt(tn * sn + 1e-12f);
225 auto const applyWhitening = [&](
float &value) {
226 float const pre = std::abs(value);
229 if (localNoise > 0.0f && std::isfinite(localNoise)) {
233 if (applyErasureInWhitening &&
234 std::abs(value) < erasureThreshold) {
239 sumAbsPost += std::abs(value);
242 applyWhitening(result.
llr0[i1]);
243 applyWhitening(result.
llr0[i2]);
244 applyWhitening(result.
llr0[i4]);
245 applyWhitening(result.
llr1[i1]);
246 applyWhitening(result.
llr1[i2]);
247 applyWhitening(result.
llr1[i4]);
251 auto const normalizeLLR = [](
auto &llr) {
253 float sum_of_squares = 0.0f;
255 for (
auto const value : llr) {
257 sum_of_squares += value * value;
260 float const llrav = sum / llr.size();
261 float const llr2av = sum_of_squares / llr.size();
262 float const variance = llr2av - llrav * llrav;
263 float const llrsig = std::sqrt(variance > 0.0f ? variance : llr2av);
265 for (
float &val : llr)
266 val = (val / llrsig) * 2.83f;
271 normalizeLLR(result.
llr0);
272 normalizeLLR(result.
llr1);
274 if (whiteningAvailable && debug) {
276 static_cast<double>(result.
llr0.size() + result.
llr1.size());
277 double const avgPre = total > 0.0 ? sumAbsPre / total : 0.0;
278 double const avgPost = total > 0.0 ? sumAbsPost / total : 0.0;
280 qCDebug(decoder_js8) <<
"LLR whitening applied"
281 <<
"avg|LLR| pre/post:" << avgPre << avgPost
282 <<
"erasures:" << erasures;