| 571 | }; |
| 572 | |
| 573 | void initOctaveWise(int samples, int bands, float fmin, float fmax, |
| 574 | int sr) { |
| 575 | |
| 576 | |
| 577 | // Use floor so the top octave covers the remaining frequency range |
| 578 | // rather than creating a tiny sliver octave with 1 bin. |
| 579 | int numOctaves = static_cast<int>(floorf(log2f(fmax / fmin))); |
| 580 | if (numOctaves < 1) |
| 581 | numOctaves = 1; |
| 582 | |
| 583 | // Log-spaced center frequencies for all bins |
| 584 | float logRatio = logf(fmax / fmin); |
| 585 | FASTLED_STACK_ARRAY(float, centerFreqs, bands); |
| 586 | for (int i = 0; i < bands; i++) { |
| 587 | centerFreqs[i] = |
| 588 | fmin * |
| 589 | expf(logRatio * static_cast<float>(i) / |
| 590 | static_cast<float>(bands - 1)); |
| 591 | } |
| 592 | |
| 593 | // Assign each bin to an octave: oct j spans [fmin*2^j, fmin*2^(j+1)) |
| 594 | FASTLED_STACK_ARRAY(int, binOctave, bands); |
| 595 | for (int i = 0; i < bands; i++) { |
| 596 | int oct = |
| 597 | static_cast<int>(floorf(log2f(centerFreqs[i] / fmin))); |
| 598 | if (oct < 0) |
| 599 | oct = 0; |
| 600 | if (oct >= numOctaves) |
| 601 | oct = numOctaves - 1; |
| 602 | binOctave[i] = oct; |
| 603 | } |
| 604 | |
| 605 | // Build per-octave CQ kernel sets |
| 606 | mOctaves.resize(numOctaves); |
| 607 | mMaxBinsPerOctave = 0; |
| 608 | for (int oct = 0; oct < numOctaves; oct++) { |
| 609 | int first = -1, last = -1; |
| 610 | for (int i = 0; i < bands; i++) { |
| 611 | if (binOctave[i] == oct) { |
| 612 | if (first < 0) |
| 613 | first = i; |
| 614 | last = i; |
| 615 | } |
| 616 | } |
| 617 | |
| 618 | OctaveInfo &oi = mOctaves[oct]; |
| 619 | fl::memset(&oi.cfg, 0, sizeof(oi.cfg)); |
| 620 | oi.kernels = nullptr; |
| 621 | if (first < 0) { |
| 622 | oi.firstBin = 0; |
| 623 | oi.numBins = 0; |
| 624 | continue; |
| 625 | } |
| 626 | |
| 627 | oi.firstBin = first; |
| 628 | oi.numBins = last - first + 1; |
| 629 | if (oi.numBins > mMaxBinsPerOctave) { |
| 630 | mMaxBinsPerOctave = oi.numBins; |