| 154 | } |
| 155 | |
| 156 | void DssRenderer::pushRow(const QVector<float>& binsDbm) |
| 157 | { |
| 158 | const int n = binsDbm.size(); |
| 159 | std::array<float, kCols> nr; |
| 160 | |
| 161 | if (n <= 0) { |
| 162 | nr.fill(-200.0f); |
| 163 | } else { |
| 164 | std::array<float, kCols> raw; |
| 165 | if (n == kCols) { |
| 166 | for (int c = 0; c < kCols; ++c) { |
| 167 | raw[c] = binsDbm[c]; |
| 168 | } |
| 169 | } else { |
| 170 | const double step = static_cast<double>(n) / kCols; |
| 171 | for (int c = 0; c < kCols; ++c) { |
| 172 | // Peak-preserving downsample: take the strongest bin in the source |
| 173 | // span so signals survive as ridges. Upsampling (n < kCols) |
| 174 | // collapses the span to a single source bin. |
| 175 | int i0 = static_cast<int>(std::floor(c * step)); |
| 176 | int i1 = static_cast<int>(std::ceil((c + 1) * step)); |
| 177 | i0 = std::clamp(i0, 0, n - 1); |
| 178 | i1 = std::clamp(i1, i0 + 1, n); |
| 179 | float mx = binsDbm[i0]; |
| 180 | for (int i = i0 + 1; i < i1; ++i) { |
| 181 | mx = std::max(mx, binsDbm[i]); |
| 182 | } |
| 183 | raw[c] = mx; |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | // Temporal median-of-3 impulse rejection. A strong broadband |
| 188 | // interference burst lasts only ~1 FFT frame but, once stored, becomes a |
| 189 | // full-height "wall" that recedes (and flickers) across the whole |
| 190 | // surface. As the outlier of {this, prev, prev2}, such a 1-frame spike |
| 191 | // is discarded here before it ever enters the height history. Steady |
| 192 | // signals are the median of ~equal values, so they pass through |
| 193 | // unchanged (this is an outlier rejector, not a low-pass). |
| 194 | if (m_rawHistCount >= 2) { |
| 195 | for (int c = 0; c < kCols; ++c) { |
| 196 | nr[c] = median3(raw[c], m_rawPrev1[c], m_rawPrev2[c]); |
| 197 | } |
| 198 | } else { |
| 199 | nr = raw; |
| 200 | } |
| 201 | // Shift the raw history (store the ORIGINAL raw row, not the median). |
| 202 | m_rawPrev2 = m_rawPrev1; |
| 203 | m_rawPrev1 = raw; |
| 204 | m_rawHistCount = std::min(m_rawHistCount + 1, 2); |
| 205 | |
| 206 | // Spatial 3-tap low-pass — the textbook cure for the peak-detector |
| 207 | // "comb" striping (a video-bandwidth analogue). |
| 208 | std::array<float, kCols> sm = nr; |
| 209 | for (int c = 0; c < kCols; ++c) { |
| 210 | const float a = nr[std::max(0, c - 1)]; |
| 211 | const float b = nr[c]; |
| 212 | const float d = nr[std::min(kCols - 1, c + 1)]; |
| 213 | sm[c] = 0.25f * a + 0.5f * b + 0.25f * d; |