FastLED 3.10.8
Loading...
Searching...
No Matches

◆ runOctaveWise()

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

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

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