FastLED 3.9.15
Loading...
Searching...
No Matches
audio_context.cpp.hpp
Go to the documentation of this file.
2#include "fl/stl/noexcept.h"
3
4namespace fl {
5namespace audio {
6
8 // Create a hash from fft::Args for O(1) cache lookup
9 // Use simple hash combining of the integer fields
10 fl::size hash = 0;
11 hash = (hash * 31) ^ static_cast<fl::size>(args.samples);
12 hash = (hash * 31) ^ static_cast<fl::size>(args.bands);
13 hash = (hash * 31) ^ static_cast<fl::size>(args.sample_rate);
14
15 // For floats, use bit representation via memcpy (safer than reinterpret_cast)
16 u32 fmin_bits, fmax_bits;
17 fl::memcpy(&fmin_bits, &args.fmin, sizeof(fmin_bits));
18 fl::memcpy(&fmax_bits, &args.fmax, sizeof(fmax_bits));
19 hash = (hash * 31) ^ static_cast<fl::size>(fmin_bits);
20 hash = (hash * 31) ^ static_cast<fl::size>(fmax_bits);
21 hash = (hash * 31) ^ static_cast<fl::size>(args.mode);
22 return hash;
23}
24
32
34
35shared_ptr<const fft::Bins> Context::getFFT(int bands, float fmin, float fmax, fft::Mode mode, fft::Window window) {
36 fft::Args args(mSample.size(), bands, fmin, fmax, mSampleRate, mode, window);
37
38 // O(1) cache lookup using hash map
39 fl::size argsHash = hashFFTArgs(args);
40 auto it = mFFTCacheMap.find(argsHash);
41 if (it != mFFTCacheMap.end()) {
42 int idx = it->second;
43 if (idx >= 0 && idx < static_cast<int>(mFFTCache.size())) {
44 // Double-check args match in case of hash collision
45 if (mFFTCache[idx].args == args) {
46 return mFFTCache[idx].bins;
47 }
48 }
49 }
50
51 // Not cached — try to recycle a previously-used fft::Bins with matching band count
53 for (size i = 0; i < mRecyclePool.size(); i++) {
54 if (static_cast<int>(mRecyclePool[i]->bands()) == bands) {
55 bins = fl::move(mRecyclePool[i]);
56 mRecyclePool.erase(mRecyclePool.begin() + i);
57 bins->clear(); // Vectors keep capacity — zero allocs
58 break;
59 }
60 }
61 if (!bins) {
62 bins = fl::make_shared<fft::Bins>(bands);
63 }
64
66 mFFT.run(sample, bins.get(), args);
67
68 // Evict oldest if at capacity
69 if (static_cast<int>(mFFTCache.size()) >= MAX_FFT_CACHE_ENTRIES) {
70 // Remove the oldest entry's hash from hash map
71 fl::size oldHash = hashFFTArgs(mFFTCache[0].args);
72 mFFTCacheMap.erase(oldHash);
73
74 // Shift all remaining entries and update map indices
75 for (size i = 1; i < mFFTCache.size(); i++) {
76 fl::size key = hashFFTArgs(mFFTCache[i].args);
77 mFFTCacheMap[key] = static_cast<int>(i - 1);
78 }
79 mFFTCache.erase(mFFTCache.begin());
80 }
81
82 FFTCacheEntry entry;
83 entry.args = args;
84 entry.bins = bins;
85 int newIndex = static_cast<int>(mFFTCache.size());
86 mFFTCache.push_back(fl::move(entry));
87
88 // Add to hash map for O(1) future lookups
89 mFFTCacheMap[argsHash] = newIndex;
90
91 return bins;
92}
93
95 auto fft = getFFT(3, 20.0f, 11025.0f);
96 BandEnergy out;
97 span<const float> lin = fft->linear();
98 if (lin.size() >= 3) {
99 out.bass = lin[0];
100 out.mid = lin[1];
101 out.treb = lin[2];
102 }
103 return out;
104}
105
110
112 if (mFFTHistoryDepth != depth) {
113 mFFTHistory.clear();
114 mFFTHistory.reserve(depth);
115 mFFTHistoryDepth = depth;
117 }
118}
119
120const fft::Bins* Context::getHistoricalFFT(int framesBack) const {
121 if (framesBack < 0 || framesBack >= static_cast<int>(mFFTHistory.size())) {
122 return nullptr;
123 }
124 // Ring buffer lookup: walk backwards from the most-recently-written slot.
125 // Adding history.size() before the modulo avoids negative values from
126 // the subtraction, since C++ modulo of negative ints is implementation-defined.
127 const int n = static_cast<int>(mFFTHistory.size());
128 int index = (mFFTHistoryIndex - 1 - framesBack + n) % n;
129 return &mFFTHistory[index];
130}
131
133 // Save current fft::FFT to history (use first cached entry if available)
134 if (!mFFTCache.empty() && mFFTHistoryDepth > 0) {
135 const shared_ptr<fft::Bins>& first = mFFTCache[0].bins;
136 if (first) {
137 if (static_cast<int>(mFFTHistory.size()) < mFFTHistoryDepth) {
138 mFFTHistory.push_back(*first);
139 // When the history fills up, wrap index to 0 for ring buffer mode
140 mFFTHistoryIndex = static_cast<int>(mFFTHistory.size()) % mFFTHistoryDepth;
141 } else {
144 }
145 }
146 }
147
148 // Recycle bins that only the cache holds (use_count == 1).
149 // These can be reused next frame without allocation.
150 mRecyclePool.clear();
151 for (size i = 0; i < mFFTCache.size(); i++) {
152 if (mFFTCache[i].bins.use_count() == 1) {
153 mRecyclePool.push_back(fl::move(mFFTCache[i].bins));
154 }
155 }
156
157 mSample = sample;
158 // Clear per-frame fft::FFT cache (new sample = new data)
159 mFFTCache.clear();
160 // Reset silence flag — pipeline must re-populate after NFT update this frame.
161 mIsSilent = false;
162}
163
165 mFFTCache.clear();
166 mFFTCacheMap.clear();
167 mRecyclePool.clear();
168 mFFTHistory.clear();
171}
172
173} // namespace audio
174} // namespace fl
vector< fft::Bins > mFFTHistory
void setFFTHistoryDepth(int depth) FL_NOEXCEPT
flat_map< fl::size, int > mFFTCacheMap
vector< FFTCacheEntry > mFFTCache
~Context() FL_NOEXCEPT
shared_ptr< const fft::Bins > getFFT(int bands=16, float fmin=fft::Args::DefaultMinFrequency(), float fmax=fft::Args::DefaultMaxFrequency(), fft::Mode mode=fft::Mode::AUTO, fft::Window window=fft::Window::BLACKMAN_HARRIS) FL_NOEXCEPT
void setSample(const Sample &sample) FL_NOEXCEPT
shared_ptr< const fft::Bins > getFFT16(fft::Mode mode=fft::Mode::LOG_REBIN, fft::Window window=fft::Window::BLACKMAN_HARRIS) FL_NOEXCEPT
void clearCache() FL_NOEXCEPT
const fft::Bins * getHistoricalFFT(int framesBack) const FL_NOEXCEPT
static constexpr int MAX_FFT_CACHE_ENTRIES
vector< shared_ptr< fft::Bins > > mRecyclePool
BandEnergy getBandEnergy() FL_NOEXCEPT
Context(const Sample &sample) FL_NOEXCEPT
static fl::size hashFFTArgs(const fft::Args &args) FL_NOEXCEPT
shared_ptr< fft::Bins > bins
T * get() const FL_NOEXCEPT
Definition shared_ptr.h:334
constexpr fl::size size() const FL_NOEXCEPT
Definition span.h:458
constexpr remove_reference< T >::type && move(T &&t) FL_NOEXCEPT
Definition s16x16x4.h:28
void * memcpy(void *dest, const void *src, size_t n) FL_NOEXCEPT
CRGB sample(const CRGB *grid, const XYMap &xyMap, float x, float y, SampleMode mode)
Sample a pixel from a 2D CRGB grid at floating-point coordinates.
Definition sample.cpp.hpp:9
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
Base definition for an LED controller.
Definition crgb.hpp:179
corkscrew_args args
Definition old.h:149
#define FL_NOEXCEPT
Context() FL_NOEXCEPT=default
static float DefaultMaxFrequency() FL_NOEXCEPT
Definition fft.h:128
static float DefaultMinFrequency() FL_NOEXCEPT
Definition fft.h:127