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