FastLED 3.10.8
Loading...
Searching...
No Matches
mic_response_data.h
Go to the documentation of this file.
1#pragma once
2
10
13#include "fl/math/math.h"
14#include "fastled_progmem.h"
15#include "fl/stl/noexcept.h"
16
17namespace fl {
18namespace audio {
19
20// ============================================================================
21// PROGMEM float reader — portable across AVR and non-AVR platforms
22// ============================================================================
23
26inline float fl_progmem_read_float(const float* addr) FL_NO_EXCEPT {
27 u32 raw = FL_PGM_READ_DWORD_ALIGNED(addr);
28 float result;
29 FL_BUILTIN_MEMCPY(&result, &raw, sizeof(float));
30 return result;
31}
32
33// ============================================================================
34// 1/6-octave frequency points: 61 points from 20 Hz to 20 kHz
35// ============================================================================
36
37static constexpr int kMicResponsePoints = 61;
38
39// 1/6-octave spaced frequencies from 20 Hz to 20 kHz.
40// Generated via: f[i] = 20 * 2^(i/6), i = 0..60
41// f[0] = 20.0, f[60] = 20159.4 ≈ 20 kHz
43 20.0f, 22.4f, 25.2f, 28.3f, 31.7f, 35.6f, // 0- 5
44 40.0f, 44.9f, 50.4f, 56.6f, 63.5f, 71.3f, // 6-11
45 80.0f, 89.8f, 100.8f, 113.1f, 127.0f, 142.5f, // 12-17
46 160.0f, 179.6f, 201.6f, 226.3f, 254.0f, 285.1f, // 18-23
47 320.0f, 359.1f, 403.1f, 452.5f, 508.0f, 570.2f, // 24-29
48 640.0f, 718.3f, 806.3f, 905.1f, 1016.0f, 1140.4f, // 30-35
49 1280.0f, 1436.5f, 1612.5f, 1810.2f, 2032.0f, 2280.8f, // 36-41
50 2560.0f, 2873.1f, 3225.0f, 3620.4f, 4064.0f, 4561.6f, // 42-47
51 5120.0f, 5746.1f, 6450.0f, 7240.8f, 8128.0f, 9123.2f, // 48-53
52 10240.0f, 11492.2f, 12900.0f, 14481.6f, 16256.1f, 18246.5f, // 54-59
53 20480.0f // 60
54};
55
56// ============================================================================
57// Per-mic response curves (gain at each 1/6-octave point)
58// Values > 1.0 = mic is weak → boost; < 1.0 = mic is hot → cut
59// ============================================================================
60
61// INMP441 (TDK InvenSense)
62// Derived from ikostoski/esp32-i2s-slm IIR equalization filter analysis.
63// Flat 100 Hz–8 kHz, bass rolloff below 100 Hz, resonance peak ~12-15 kHz.
65 // 20-36 Hz: significant bass rolloff (f[0..5])
66 3.50f, 3.20f, 2.90f, 2.60f, 2.30f, 2.00f,
67 // 40-71 Hz: moderate rolloff (f[6..11])
68 1.74f, 1.55f, 1.40f, 1.28f, 1.18f, 1.12f,
69 // 80-143 Hz: transitioning to flat (f[12..17])
70 1.07f, 1.04f, 1.02f, 1.01f, 1.00f, 1.00f,
71 // 160-285 Hz: essentially flat (f[18..23])
72 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
73 // 320-570 Hz: flat (f[24..29])
74 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
75 // 640-1140 Hz: flat (f[30..35])
76 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
77 // 1280-2281 Hz: flat (f[36..41])
78 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
79 // 2560-4562 Hz: beginning of rise (f[42..47])
80 1.00f, 0.99f, 0.98f, 0.97f, 0.96f, 0.95f,
81 // 5120-9123 Hz: resonance approach (f[48..53])
82 0.93f, 0.90f, 0.87f, 0.83f, 0.80f, 0.78f,
83 // 10240-18247 Hz: resonance peak region (f[54..59])
84 0.76f, 0.75f, 0.76f, 0.80f, 0.88f, 0.95f,
85 // 20480 Hz (f[60])
86 1.00f
87};
88
89// ICS-43434 (TDK InvenSense)
90// Derived from ikostoski/esp32-i2s-slm measured response.
91// Flat 200 Hz–2.5 kHz, rising response above 3 kHz.
93 // 20-36 Hz: significant bass rolloff (f[0..5])
94 3.80f, 3.50f, 3.10f, 2.80f, 2.50f, 2.20f,
95 // 40-71 Hz: moderate rolloff (f[6..11])
96 1.90f, 1.65f, 1.45f, 1.30f, 1.20f, 1.13f,
97 // 80-143 Hz: transitioning to flat (f[12..17])
98 1.08f, 1.04f, 1.02f, 1.01f, 1.00f, 1.00f,
99 // 160-285 Hz (f[18..23])
100 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
101 // 320-570 Hz (f[24..29])
102 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
103 // 640-1140 Hz (f[30..35])
104 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
105 // 1280-2281 Hz: flat (f[36..41])
106 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
107 // 2560-4562 Hz: beginning of HF rise (f[42..47])
108 0.98f, 0.96f, 0.93f, 0.91f, 0.89f, 0.87f,
109 // 5120-9123 Hz: strong HF rise (f[48..53])
110 0.85f, 0.83f, 0.81f, 0.80f, 0.79f, 0.78f,
111 // 10240-18247 Hz: very hot at HF (f[54..59])
112 0.77f, 0.77f, 0.78f, 0.80f, 0.83f, 0.87f,
113 // 20480 Hz (f[60])
114 0.90f
115};
116
117// SPM1423 (Knowles)
118// Estimated from SPM1423HM4H-B datasheet frequency response.
119// Flat 100 Hz–10 kHz, moderate HF rise.
121 // 20-36 Hz: bass rolloff (f[0..5])
122 2.80f, 2.60f, 2.40f, 2.20f, 2.00f, 1.80f,
123 // 40-71 Hz: moderate rolloff (f[6..11])
124 1.60f, 1.45f, 1.32f, 1.22f, 1.15f, 1.10f,
125 // 80-143 Hz: transitioning to flat (f[12..17])
126 1.06f, 1.03f, 1.01f, 1.00f, 1.00f, 1.00f,
127 // 160-285 Hz (f[18..23])
128 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
129 // 320-570 Hz (f[24..29])
130 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
131 // 640-1140 Hz (f[30..35])
132 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
133 // 1280-2281 Hz: flat (f[36..41])
134 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
135 // 2560-4562 Hz: slight HF rise (f[42..47])
136 0.98f, 0.96f, 0.94f, 0.93f, 0.92f, 0.91f,
137 // 5120-9123 Hz: moderate rise (f[48..53])
138 0.90f, 0.89f, 0.88f, 0.87f, 0.86f, 0.85f,
139 // 10240-18247 Hz: strong rise (f[54..59])
140 0.84f, 0.84f, 0.85f, 0.87f, 0.90f, 0.93f,
141 // 20480 Hz (f[60])
142 0.95f
143};
144
145// Generic MEMS: average of INMP441 and ICS-43434 responses
147 // 20-36 Hz (f[0..5])
148 3.65f, 3.35f, 3.00f, 2.70f, 2.40f, 2.10f,
149 // 40-71 Hz (f[6..11])
150 1.82f, 1.60f, 1.42f, 1.29f, 1.19f, 1.12f,
151 // 80-143 Hz (f[12..17])
152 1.07f, 1.04f, 1.02f, 1.01f, 1.00f, 1.00f,
153 // 160-285 Hz (f[18..23])
154 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
155 // 320-570 Hz (f[24..29])
156 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
157 // 640-1140 Hz (f[30..35])
158 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
159 // 1280-2281 Hz (f[36..41])
160 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f,
161 // 2560-4562 Hz (f[42..47])
162 0.99f, 0.97f, 0.95f, 0.94f, 0.92f, 0.91f,
163 // 5120-9123 Hz (f[48..53])
164 0.89f, 0.86f, 0.84f, 0.81f, 0.79f, 0.78f,
165 // 10240-18247 Hz (f[54..59])
166 0.76f, 0.76f, 0.77f, 0.80f, 0.85f, 0.91f,
167 // 20480 Hz (f[60])
168 0.95f
169};
170
171// Line-In: flat response, no correction needed
173 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
174 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
175 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
176 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
177 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
178 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
179 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
180 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
181 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
182 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
183 1.0f
184};
185
186// ============================================================================
187// MicResponseCurve — lightweight handle to a mic's response data
188// ============================================================================
189
191 const float* freqs;
192 const float* gains;
193 int count;
194};
195
199 MicResponseCurve curve = {nullptr, nullptr, 0};
200 switch (profile) {
203 break;
206 break;
209 break;
212 break;
215 break;
216 case MicProfile::None:
217 default:
218 break;
219 }
220 return curve;
221}
222
223// ============================================================================
224// Interpolation and downsampling
225// ============================================================================
226
230inline float interpolateMicResponse(const MicResponseCurve& curve, float freq_hz) FL_NO_EXCEPT {
231 if (curve.count <= 0 || !curve.freqs || !curve.gains) return 1.0f;
232
233 float f0 = fl_progmem_read_float(&curve.freqs[0]);
234 float fN = fl_progmem_read_float(&curve.freqs[curve.count - 1]);
235
236 // Clamp to endpoints
237 if (freq_hz <= f0) return fl_progmem_read_float(&curve.gains[0]);
238 if (freq_hz >= fN) return fl_progmem_read_float(&curve.gains[curve.count - 1]);
239
240 // Binary search for bracketing interval
241 int lo = 0, hi = curve.count - 1;
242 while (hi - lo > 1) {
243 int mid = (lo + hi) / 2;
244 float fMid = fl_progmem_read_float(&curve.freqs[mid]);
245 if (freq_hz < fMid) {
246 hi = mid;
247 } else {
248 lo = mid;
249 }
250 }
251
252 float fLo = fl_progmem_read_float(&curve.freqs[lo]);
253 float fHi = fl_progmem_read_float(&curve.freqs[hi]);
254 float gLo = fl_progmem_read_float(&curve.gains[lo]);
255 float gHi = fl_progmem_read_float(&curve.gains[hi]);
256
257 // Log-frequency linear interpolation
258 float logFrac = fl::logf(freq_hz / fLo) / fl::logf(fHi / fLo);
259 return gLo + (gHi - gLo) * logFrac;
260}
261
268 const float* binCenters, int numBins,
269 float* out) FL_NO_EXCEPT {
270 for (int i = 0; i < numBins; ++i) {
271 out[i] = interpolateMicResponse(curve, binCenters[i]);
272 }
273}
274
275// ============================================================================
276// Pink noise compensation (derived from bin geometry)
277// ============================================================================
278
285inline float computePinkNoiseGain(float freq_hz, float f_ref) FL_NO_EXCEPT {
286 if (freq_hz <= 0.0f || f_ref <= 0.0f) return 1.0f;
287 return fl::sqrtf(freq_hz / f_ref);
288}
289
295inline void computePinkNoiseGains(const float* binCenters, int numBins, float* out) FL_NO_EXCEPT {
296 if (numBins <= 0) return;
297
298 // Compute geometric mean of bin centers for f_ref
299 float logSum = 0.0f;
300 int validCount = 0;
301 for (int i = 0; i < numBins; ++i) {
302 if (binCenters[i] > 0.0f) {
303 logSum += fl::logf(binCenters[i]);
304 ++validCount;
305 }
306 }
307 float f_ref = (validCount > 0)
308 ? fl::expf(logSum / static_cast<float>(validCount))
309 : 1000.0f;
310
311 // Compute raw gains
312 for (int i = 0; i < numBins; ++i) {
313 out[i] = computePinkNoiseGain(binCenters[i], f_ref);
314 }
315
316 // Cap individual gains to prevent extreme boosting of high-frequency bins
317 static constexpr float kMaxPinkNoiseGain = 3.0f;
318 for (int i = 0; i < numBins; ++i) {
319 if (out[i] > kMaxPinkNoiseGain) {
320 out[i] = kMaxPinkNoiseGain;
321 }
322 }
323
324 // Normalize so geometric mean of output gains = 1.0
325 float outLogSum = 0.0f;
326 for (int i = 0; i < numBins; ++i) {
327 if (out[i] > 0.0f) {
328 outLogSum += fl::logf(out[i]);
329 }
330 }
331 float geoMean = fl::expf(outLogSum / static_cast<float>(numBins));
332 if (geoMean > 0.001f) {
333 for (int i = 0; i < numBins; ++i) {
334 out[i] /= geoMean;
335 }
336 }
337}
338
339} // namespace audio
340} // namespace fl
#define FL_BUILTIN_MEMCPY(dest, src, n)
#define FL_PGM_READ_DWORD_ALIGNED(addr)
#define FL_PROGMEM
PROGMEM keyword for storage.
Wrapper definitions to allow seamless use of PROGMEM in environments that have it.
void downsampleMicResponse(const MicResponseCurve &curve, const float *binCenters, int numBins, float *out) FL_NO_EXCEPT
Downsample a high-resolution mic response curve to N output bins.
MicResponseCurve getMicResponseCurve(MicProfile profile) FL_NO_EXCEPT
Get the high-resolution response curve for a given mic profile.
float computePinkNoiseGain(float freq_hz, float f_ref) FL_NO_EXCEPT
Compute pink noise compensation gain for a single frequency.
void computePinkNoiseGains(const float *binCenters, int numBins, float *out) FL_NO_EXCEPT
Compute pink noise compensation gains for all bins.
float interpolateMicResponse(const MicResponseCurve &curve, float freq_hz) FL_NO_EXCEPT
Interpolate mic response at an arbitrary frequency (Hz).
MicProfile
Microphone frequency response correction profile.
@ LineIn
Line-in input (relatively flat, minor HF rolloff)
@ GenericMEMS
Generic MEMS microphone (moderate bass rolloff)
@ None
No correction (flat response assumed)
@ INMP441
InvenSense INMP441 MEMS mic (most common)
@ ICS43434
InvenSense ICS-43434 MEMS mic.
@ SPM1423
Knowles SPM1423 MEMS mic.
float fl_progmem_read_float(const float *addr) FL_NO_EXCEPT
Read a float from PROGMEM.
static const float kMicResponse_GenericMEMS[kMicResponsePoints]
static const float kMicResponse_ICS43434[kMicResponsePoints]
static const float kMicResponseFreqs[kMicResponsePoints]
static const float kMicResponse_SPM1423[kMicResponsePoints]
static constexpr int kMicResponsePoints
static const float kMicResponse_INMP441[kMicResponsePoints]
static const float kMicResponse_LineIn[kMicResponsePoints]
const float * freqs
Pointer to frequency array (PROGMEM)
int count
Number of data points.
const float * gains
Pointer to gain array (PROGMEM)
float logf(float value) FL_NO_EXCEPT
Definition math.h:426
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
float sqrtf(float value) FL_NO_EXCEPT
Definition math.h:461
float expf(float value) FL_NO_EXCEPT
Definition math.h:406
Base definition for an LED controller.
Definition crgb.hpp:179