FastLED 3.10.9
Loading...
Searching...
No Matches

◆ runOctaveWise()

void fl::audio::fft::Context::runOctaveWise ( span< const i16 > buffer,
Bins * out )
inlineprivate

Definition at line 693 of file fft_impl.cpp.hpp.

693 {
694 const int N = mInputSamples;
695 const int numOctaves = static_cast<int>(mOctaves.size());
696 const int numRawBins = N / 2 + 1;
697
698 out->setParams(mFmin, mFmax, mSampleRate);
699
700 // Copy input to working buffer
701 int workLen = N;
702 for (int i = 0; i < N; i++) {
703 mWorkBuf[i] =
704 (i < static_cast<int>(buffer.size())) ? buffer[i] : 0;
705 }
706
707 // FFT at full sample rate (for linear bins + top octave CQ)
708 kiss_fftr(mFftrCfg, mWorkBuf.data(), mFftOut.data());
709
710 // Deinterleave AoS → SoA and batch-compute magnitudes
711 FftScratch &s = scratch();
712 s.re.resize(numRawBins);
713 s.im.resize(numRawBins);
714 s.mag.resize(numRawBins);
715 deinterleave(mFftOut.data(), s.re.data(), s.im.data(), numRawBins);
716 batchMag(s.re.data(), s.im.data(), s.mag.data(), numRawBins);
717
718 computeLinearBins(s.mag.data(), N, out);
719
720 // Prepare CQ output bins
721 fl::vector<float> &rawBins = out->raw_mut();
722 rawBins.resize(mTotalBands);
723 for (int i = 0; i < mTotalBands; i++) {
724 rawBins[i] = 0.0f;
725 }
726
727 // Pre-allocate CQ accumulator once (avoids alloca in loop)
728 FASTLED_STACK_ARRAY(kiss_fft_cpx, cq, mMaxBinsPerOctave);
729
730 // Process octaves from top (highest freq) to bottom (lowest freq).
731 // Top octave uses the FFT already computed above.
732 // Each lower octave: decimate signal by 2x, then FFT + CQ.
733 for (int oct = numOctaves - 1; oct >= 0; oct--) {
734 const OctaveInfo &oi = mOctaves[oct];
735 if (oi.numBins <= 0 || !oi.kernels)
736 continue;
737
738 if (oct != numOctaves - 1) {
739 decimateBy2(mWorkBuf.data(), workLen);
740 workLen = workLen / 2;
741 // Zero-pad remainder so FFT sees clean input
742 for (int i = workLen; i < N; i++)
743 mWorkBuf[i] = 0;
744 kiss_fftr(mFftrCfg, mWorkBuf.data(), mFftOut.data());
745 }
746 // Zero the CQ accumulator and apply kernels
747 fl::memset(cq, 0, sizeof(kiss_fft_cpx) * oi.numBins);
748 apply_kernels(mFftOut.data(), cq, oi.kernels, oi.cfg);
749
750 for (int i = 0; i < oi.numBins; i++) {
751 int binIdx = oi.firstBin + i;
752 i32 real = cq[i].r;
753 i32 imag = cq[i].i;
754#ifdef FIXED_POINT
755 rawBins[binIdx] = static_cast<float>(fastMag(real, imag));
756#else
757 float r2 = float(real * real);
758 float i2 = float(imag * imag);
759 rawBins[binIdx] = sqrt(r2 + i2);
760#endif
761 }
762 }
763
764 // A full-rate Blackman-Harris FFT supplies a low-gain floor wherever
765 // the narrow CQ kernels leave a gap. Its low-frequency resolution is
766 // coarse, so do not let it override a CQ detection within its main
767 // lobe (about four FFT-bin widths).
768 s.windowed.resize(N);
769 s.fftOut.resize(N);
770 s.rawBinsI.resize(mTotalBands);
771 applyWindow(buffer.data(), mWindowBuf.data(), s.windowed.data(), N);
772 kiss_fftr(mFftrCfg, s.windowed.data(), s.fftOut.data());
773 deinterleave(s.fftOut.data(), s.re.data(), s.im.data(), numRawBins);
774 batchMag(s.re.data(), s.im.data(), s.mag.data(), numRawBins);
775 fl::memset(s.rawBinsI.data(), 0, sizeof(u32) * mTotalBands);
776 logRebinRange(s.mag.data(), N, static_cast<float>(mSampleRate), 0,
777 mTotalBands, s.rawBinsI.data(), mLogBinLut);
778 // Coverage floor = 1/50 (0.02) of the full-rate FFT magnitude; a CQ
779 // band at or above 20 counts as a detection. FIXED16 decides with
780 // detail::coverageFloorApplies(); float modes keep float compares.
781 static constexpr float kCoverageGain = 0.02f;
782 s.cqSnapshot.resize(mTotalBands);
783 s.coverageMaxQueue.resize(mTotalBands);
784 for (int i = 0; i < mTotalBands; ++i) {
785#ifdef FIXED_POINT
786 // Exact: rawBins[i] was set from an integer fastMag() above.
787 s.cqSnapshot[i] = static_cast<u32>(rawBins[i]);
788#else
789 s.cqSnapshot[i] = rawBins[i];
790#endif
791 }
792 int head = 0;
793 int tail = 0;
794 int next = 0;
795 for (int i = 0; i < mTotalBands; ++i) {
796 while (next <= mCoverageLast[i]) {
797 while (tail > head &&
798 s.cqSnapshot[s.coverageMaxQueue[tail - 1]] <=
799 s.cqSnapshot[next]) {
800 --tail;
801 }
802 s.coverageMaxQueue[tail++] = static_cast<u16>(next++);
803 }
804 while (head < tail &&
805 s.coverageMaxQueue[head] < mCoverageFirst[i]) {
806 ++head;
807 }
808#ifdef FIXED_POINT
809 // Integer form of `localCq < 20 && fft/50 > cq`; float appears
810 // only when writing the Bins output.
812 s.rawBinsI[i], s.cqSnapshot[i],
813 s.cqSnapshot[s.coverageMaxQueue[head]])) {
814 rawBins[i] = static_cast<float>(s.rawBinsI[i]) * kCoverageGain;
815 }
816#else
817 const float localCq = s.cqSnapshot[s.coverageMaxQueue[head]];
818 static constexpr float kDetectedCqMagnitude = 20.0f;
819 float coverage = static_cast<float>(s.rawBinsI[i]) * kCoverageGain;
820 if (localCq < kDetectedCqMagnitude && coverage > rawBins[i]) {
821 rawBins[i] = coverage;
822 }
823#endif
824 }
825 }
#define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE)
Stack-allocated array with automatic zero-initialization.
Definition alloca.h:32
static void batchMag(const kiss_fft_scalar *re, const kiss_fft_scalar *im, u16 *mag, int n) FL_NO_EXCEPT
void computeLinearBins(const u16 *mag, int, Bins *out) FL_NO_EXCEPT
void logRebinRange(const u16 *mag, int fftN, float fs, int binStart, int binEnd, u32 *rawBinsI, const fl::vector< u8 > &lut) FL_NO_EXCEPT
fl::vector< kiss_fft_scalar > mWorkBuf
fl::vector< alpha16 > mWindowBuf
fl::vector< u16 > mCoverageLast
static u16 fastMag(i32 re, i32 im) FL_NO_EXCEPT
static FftScratch & scratch() FL_NO_EXCEPT
static void deinterleave(const kiss_fft_cpx *cpx, kiss_fft_scalar *re, kiss_fft_scalar *im, int n) FL_NO_EXCEPT
fl::vector< u16 > mCoverageFirst
fl::vector< OctaveInfo > mOctaves
static void applyWindow(const kiss_fft_scalar *samples, const alpha16 *win, kiss_fft_scalar *out, int N) FL_NO_EXCEPT
fl::vector< u8 > mLogBinLut
fl::vector< kiss_fft_cpx > mFftOut
static void decimateBy2(kiss_fft_scalar *buf, int len) FL_NO_EXCEPT
void resize(fl::size n) FL_NO_EXCEPT
Definition vector.h:593
void apply_kernels(kiss_fft_cpx fft[], kiss_fft_cpx cq[], struct sparse_arr kernels[], struct cq_kernel_cfg cfg)
void kiss_fftr(kiss_fftr_cfg st, const kiss_fft_scalar *timedata, kiss_fft_cpx *freqdata)
bool coverageFloorApplies(u32 fftMag, u32 cqMag, u32 localCqMax) FL_NO_EXCEPT
CQ_OCTAVE coverage supplement decision (FastLED#4301/#4540), integer form used in FIXED16: the full-r...
Definition fft_impl.h:62
FASTLED_FORCE_INLINE void * memset(void *s, int c, size_t n) FL_NO_EXCEPT
Definition cstring.h:148
const oct_t oct
Definition ios.cpp.hpp:15
constexpr enable_if< is_fixed_point< T >::value, T >::type sqrt(T x) FL_NO_EXCEPT

References apply_kernels(), applyWindow(), batchMag(), fl::audio::fft::Context::OctaveInfo::cfg, computeLinearBins(), fl::audio::fft::detail::coverageFloorApplies(), fl::audio::fft::Context::FftScratch::coverageMaxQueue, fl::audio::fft::Context::FftScratch::cqSnapshot, fl::vector< T >::data(), decimateBy2(), deinterleave(), FASTLED_STACK_ARRAY, fastMag(), fl::audio::fft::Context::FftScratch::fftOut, fl::audio::fft::Context::OctaveInfo::firstBin, fl::FL_NO_EXCEPT, fl::audio::fft::Context::FftScratch::im, fl::audio::fft::Context::OctaveInfo::kernels, kiss_fftr(), logRebinRange(), fl::audio::fft::Context::FftScratch::mag, mCoverageFirst, mCoverageLast, fl::memset(), mFftOut, mFftrCfg, mFmax, mFmin, mInputSamples, mLogBinLut, mMaxBinsPerOctave, mOctaves, mSampleRate, mTotalBands, mWindowBuf, mWorkBuf, fl::audio::fft::Context::OctaveInfo::numBins, fl::oct, fl::audio::fft::Context::FftScratch::rawBinsI, fl::audio::fft::Context::FftScratch::re, fl::vector< T >::resize(), scratch(), fl::sqrt(), and fl::audio::fft::Context::FftScratch::windowed.

Referenced by run().

+ Here is the call graph for this function:
+ Here is the caller graph for this function: