| 176 | // Cost: ~0.15ms on ESP32-S3. No kernel memory. |
| 177 | |
| 178 | void initLogRebin() { |
| 179 | // Pre-compute bin edges aligned with CQ center frequencies. |
| 180 | // CQ center[i] = fmin * exp(logRatio * i / (bands-1)), so |
| 181 | // binToFreq(i) returns these centers for both modes. |
| 182 | // |
| 183 | // Edges are placed at the geometric mean of adjacent centers: |
| 184 | // edge[i] = sqrt(center[i-1] * center[i]) |
| 185 | // = fmin * exp(logRatio * (2*i - 1) / (2*(bands-1))) |
| 186 | // |
| 187 | // edge[0] and edge[bands] extend half a bin beyond fmin/fmax. |
| 188 | const int bands = mTotalBands; |
| 189 | mLogBinEdges.resize(bands + 1); |
| 190 | float logRatio = logf(mFmax / mFmin); |
| 191 | if (bands <= 1) { |
| 192 | mLogBinEdges[0] = mFmin; |
| 193 | mLogBinEdges[1] = mFmax; |
| 194 | } else { |
| 195 | float denom = 2.0f * static_cast<float>(bands - 1); |
| 196 | // Edge below first center (half-bin below fmin) |
| 197 | mLogBinEdges[0] = |
| 198 | mFmin * expf(-logRatio / denom); |
| 199 | // Intermediate edges: geometric mean of adjacent CQ centers |
| 200 | for (int i = 1; i < bands; i++) { |
| 201 | mLogBinEdges[i] = |
| 202 | mFmin * |
| 203 | expf(logRatio * (2.0f * static_cast<float>(i) - 1.0f) / |
| 204 | denom); |
| 205 | } |
| 206 | // Edge above last center (half-bin above fmax) |
| 207 | mLogBinEdges[bands] = |
| 208 | mFmax * expf(logRatio / denom); |
| 209 | } |
| 210 | |
| 211 | // Clamp top bin edge to Nyquist — prevents incomplete bin coverage |
| 212 | // and bad normalization when fmax is close to Nyquist. |
| 213 | float nyquist = static_cast<float>(mSampleRate) / 2.0f; |
| 214 | if (mLogBinEdges[bands] > nyquist) { |
| 215 | mLogBinEdges[bands] = nyquist; |
| 216 | } |
| 217 | |
| 218 | computeBinEdgesQ16(); |
| 219 | |
| 220 | // Pre-compute bin mapping LUTs |
| 221 | buildLogBinLut(mLogBinLut, mInputSamples, |
| 222 | static_cast<float>(mSampleRate), 0, mTotalBands); |
| 223 | buildLinearBinLut(mLinearBinLut, mInputSamples); |
| 224 | |
| 225 | // Pre-compute window as Q15 integer coefficients |
| 226 | computeWindow(mWindowBuf, mInputSamples, mWindow); |
| 227 | |
| 228 | // Pre-compute bin-width normalization factors for LOG_REBIN. |
| 229 | // Without normalization, wider high-frequency bins accumulate more |
| 230 | // sidelobe energy than narrow low-frequency bins, creating visible |
| 231 | // "aliasing" artifacts during a tone sweep. |
| 232 | computeLogRebinNormFactors(mLogBinNormFactors, mLogBinLut, |
| 233 | mInputSamples, static_cast<float>(mSampleRate), |
| 234 | 0, mTotalBands); |
| 235 | } |