Lamp-Da 0.1
A compact lantern project
Loading...
Searching...
No Matches
utils.hpp
Go to the documentation of this file.
1
5#ifndef MODES_INCLUDE_AUDIO_UTILS_HPP
6#define MODES_INCLUDE_AUDIO_UTILS_HPP
7
9
10#include "src/system/ext/math8.h"
11#include <cmath>
12#include <cstdint>
13#include <deque>
14#include <string>
15
18
23{
25 static constexpr bool useBeatTracking = false;
26};
27
85template<typename ConfigTy = MicrophoneConfig,
86 int eventCutoff = 500,
87 int eventNorm = 500,
88 int nbEventSample = 10,
89 int windowSize = 100,
90 int windowShort = 64>
92{
94 static constexpr float _eventCutoff = eventCutoff / 100.0;
95
97 static constexpr float _eventNorm = eventNorm / 100.0;
98
100 static constexpr float _windowSize = windowSize / 4.0;
101
103 static constexpr float _windowShort = windowShort / 4.0;
104
111
115 static constexpr size_t _FFThistory_MaxSize = round(fftResolutionHz);
116
117 using FFTContainer = std::array<float, _dataLenght / 2>;
118 using FFTLogContainer = std::array<float, _fftChannels>;
119 using FFTHistoryContainer = std::array<FFTLogContainer, _FFThistory_MaxSize>;
120
122 void reset(auto& ctx)
123 {
124 level = 10.0;
125 avgLevel = level;
126 avgMax = level;
127 delta = 0;
128 avgDelta = 0;
129 hasEvent = false;
130 eventScale = 0;
131 _eventCount = 0;
132 _eventScale = 0;
133
134 historyIndex = 0;
135 historySize = 0;
136
137 data.fill(0);
138 dataAutoGained.fill(0);
139 fft_log.fill(0);
140 fft_raw.fill(0);
141 beatDetected.fill(false);
142 }
143
145 void update(auto& ctx)
146 {
147 const physical::microphone::SoundStruct& soundObject = ctx.lamp.get_sound_struct();
148
149 // copy microphone data
150 data = soundObject.data;
151 dataAutoGained = soundObject.rectifiedData;
152 fft_log = soundObject.fft_log;
153 fft_raw = soundObject.fft_raw;
154 fft_log_end_frequencies = soundObject.fft_log_end_frequencies;
155 maxAmplitude = soundObject.maxAmplitude;
157
158 // average input sound over a second-long window (approx)
159 const auto soundLevel = soundObject.sound_level_Db;
160 level = (not std::isinf(soundLevel) and not std::isnan(soundLevel) and soundLevel > -70) ? soundLevel : -70;
162
163 // average maximum over the same long window (approx)
164 avgMax = (avgLevel + std::max<float>(level, avgMax) * _windowSize) / (_windowSize + 1.0);
165 avgMax = std::max<float>(_eventCutoff, avgMax);
166
167 // delta between instantaneous level & average, squashed by its max
168 delta = std::max<float>(level - avgLevel, 0) / avgMax;
169
170 // average of delta square (1.0 split around _eventCutoff)
172
173 // if avgDelta looks triggered, start sampling event
174 if (avgDelta > 1.0)
175 {
176 _eventCount += 1;
177 _eventScale = std::max<float>(_eventScale, 256.0 * (avgMax / _eventNorm) * (1.0 + delta));
178
179 // if no trigger, decay event
180 }
181 else
182 {
183 _eventCount = std::max<float>(1, _eventCount - 1);
184 }
185
186 // if event is active, and event decayed, reset eventScale & hasEvent
187 if (hasEvent)
188 {
189 _eventCount = 0;
190 _eventScale = 0;
191 eventScale = 0;
192 hasEvent = false;
193 }
194
195 // if no event is active, and enough samples, set eventScale & hasEvent
196 if (!hasEvent && _eventCount > nbEventSample)
197 {
198 eventScale = _eventScale;
199 hasEvent = true;
200 }
201
202 // beat tracking is heavy on performances
203 if constexpr (ConfigTy::useBeatTracking)
204 {
205 // update history size
206 if (historySize < _FFThistory_MaxSize)
207 historySize++;
208 else
209 beatDetected = track_beat_events(_FFTHistory_beatDetector, fft_log);
210
211 // add sample to history
212 _FFTHistory_beatDetector[historyIndex] = fft_log;
213 historyIndex++;
214 if (historyIndex >= _FFThistory_MaxSize)
215 historyIndex = 0;
216 }
217 }
218
220 bool is_beat_on_freq_range(const float minFreq, const float maxFreq)
221 {
222 if constexpr (not ConfigTy::useBeatTracking)
223 {
224 return false;
225 }
226
227 size_t nbDataPoints = 0;
228 size_t beatCnt = 0;
229
230 size_t i = 0;
231 // climb to min freq bin
232 for (; i < fft_log_end_frequencies.size(); ++i)
233 {
234 const float maxFrequencyForBin = fft_log_end_frequencies[i];
235 if (minFreq < maxFrequencyForBin)
236 break;
237 }
238 // register beat events
239 for (; i < fft_log_end_frequencies.size(); ++i)
240 {
241 nbDataPoints++;
242 if (beatDetected[i])
243 beatCnt++;
244
245 const float maxFrequencyForBin = fft_log_end_frequencies[i];
246 if (maxFrequencyForBin >= maxFreq)
247 break;
248 }
249 return beatCnt > 0 && beatCnt >= ceil(0.5f * nbDataPoints);
250 }
251
252 float level = 10;
253 float avgLevel = 10;
254 float avgMax = 10;
255 float delta = 0;
256 float avgDelta = 0;
257 bool hasEvent;
258 uint8_t eventScale;
261
263 std::array<int16_t, _dataLenght> data;
265 std::array<int16_t, _dataLenght> dataAutoGained;
269 std::array<bool, _fftChannels> beatDetected;
271 std::array<float, _dataLenght / 2> fft_raw;
272
273private:
274 uint8_t _eventCount = 0;
275 uint8_t _eventScale = 0;
276
278 FFTHistoryContainer _FFTHistory_beatDetector;
279 size_t historyIndex;
280 size_t historySize;
281
282 std::array<float, _fftChannels> fft_log_end_frequencies;
283
285 template<size_t T> static inline std::array<bool, T> track_beat_events(const FFTHistoryContainer& dataHistory,
286 const std::array<float, T>& data)
287 {
288 std::array<bool, T> beats;
289 beats.fill(false);
290
291 // beat detection starts after a bit
292 const size_t dataHistoryCnt = dataHistory.size();
293 const float oneOverdataHistory = 1.0f / dataHistoryCnt;
294
295 std::array<float, T> averageFft;
296 averageFft.fill(0.0f);
297 for (const auto& fft: dataHistory)
298 {
299 for (size_t i = 0; i < T; i++)
300 averageFft[i] += fft[i] * oneOverdataHistory;
301 }
302
303 std::array<float, T> varianceFft;
304 varianceFft.fill(0.0f);
305 for (const auto& fft: dataHistory)
306 {
307 for (size_t i = 0; i < T; i++)
308 {
309 const float val = fft[i] - averageFft[i];
310 varianceFft[i] += val * val;
311 }
312 }
313
314 for (size_t i = 0; i < T; i++)
315 {
316 varianceFft[i] *= oneOverdataHistory;
317 const float stdDev = sqrtf(varianceFft[i]);
318 static constexpr float N = 1.5f;
319 // beat if > med + N * variance
320 // N=1 : greater than 68.0% of values
321 // N=2 : greater than 95.4% of values
322 // N=3 : greater than 99.6% of values
323 // N=4 : greater than 99.8% of values
324 beats[i] = data[i] > (averageFft[i] + N * stdDev);
325 }
326
327 return beats;
328 }
329};
330
331} // namespace lampda::modes::audio
332
333#endif
User modes audio utilities.
Definition: utils.hpp:17
constexpr int16_t gainedSignalTarget
Desired output of the auto gain (0-INT16_MAX)
Definition: sound.h:26
Specific configuration for the sound object.
Definition: utils.hpp:23
static constexpr bool useBeatTracking
Set to true to activate the beat tracking algorithm.
Definition: utils.hpp:25
Sound processor able to detect sound level events.
Definition: utils.hpp:92
void reset(auto &ctx)
Call this once inside the mode on_enter_mode callback.
Definition: utils.hpp:122
float maxAmplitude
max detected frequency amplitude
Definition: utils.hpp:259
std::array< FFTLogContainer, _FFThistory_MaxSize > FFTHistoryContainer
history of the FFT
Definition: utils.hpp:119
bool is_beat_on_freq_range(const float minFreq, const float maxFreq)
After the update, given a range, will return a boolean for beat detection.
Definition: utils.hpp:220
static constexpr float _windowShort
Window size used for avgDelta
Definition: utils.hpp:103
float avgMax
Decaying sound "peak" level average.
Definition: utils.hpp:254
static constexpr int16_t _autoGainTargetValue
target value reached by the auto gain
Definition: utils.hpp:108
std::array< bool, _fftChannels > beatDetected
set to true when the corresponding frequency range registers a beat
Definition: utils.hpp:269
std::array< int16_t, _dataLenght > dataAutoGained
dynamically sound adjusted data
Definition: utils.hpp:265
static constexpr size_t _fftChannels
number of channels in the log fft
Definition: utils.hpp:110
std::array< float, _dataLenght/2 > fft_raw
fast fourrier transform raw results
Definition: utils.hpp:271
static constexpr float _windowSize
Window size used for avgLevel and avgMax
Definition: utils.hpp:100
float level
Last sound level measured.
Definition: utils.hpp:252
float maxAmplitudeFrequency
max amplitude frequency, in Hertz
Definition: utils.hpp:260
std::array< float, _dataLenght/2 > FFTContainer
Container for the fast fourrier linear results.
Definition: utils.hpp:117
static constexpr size_t _dataLenght
number of sample in a microphone run
Definition: utils.hpp:106
void update(auto &ctx)
Call this once every tick inside the mode loop callback.
Definition: utils.hpp:145
bool hasEvent
Did an event happened last tick? (reset each loop)
Definition: utils.hpp:257
static constexpr float _eventNorm
Inverse scaling factor for eventScale
Definition: utils.hpp:97
float avgDelta
Rolling sound "contrast" average (squared)
Definition: utils.hpp:256
static constexpr float _eventCutoff
Floor level for computations using AvgMax (lowest peak average)
Definition: utils.hpp:94
float delta
Last sound "contrast" computed (centered around 1.0)
Definition: utils.hpp:255
FFTLogContainer fft_log
fast fourrier transform as a log scale (closer to human perception)
Definition: utils.hpp:267
std::array< int16_t, _dataLenght > data
raw microphone data
Definition: utils.hpp:263
static constexpr size_t _FFThistory_MaxSize
Size of the fourrier transform history.
Definition: utils.hpp:115
uint8_t eventScale
Event scale (0-255)
Definition: utils.hpp:258
static constexpr float fftResolutionHz
Frequency resolution of the raw fft result.
Definition: utils.hpp:113
float avgLevel
Rolling sound level average.
Definition: utils.hpp:253
std::array< float, _fftChannels > FFTLogContainer
Container for the fast fourrier logarithmic results.
Definition: utils.hpp:118
Handle the analysis of a sound sample. This structure handled the FastFourrier analysis of a sound sa...
Definition: sound.h:33
std::array< float, SAMPLE_SIZE/2 > fft_raw
Results of the FFT process, in raw Hertz bins.
Definition: sound.h:72
std::array< int16_t, SAMPLE_SIZE > data
raw audio data
Definition: sound.h:45
std::array< float, numberOfFFtChanels > fft_log_end_frequencies
Results of the FFT process, in maximum frequency for every bin.
Definition: sound.h:76
static constexpr float get_fft_resolution_Hz()
Return the FFT resolution of a single FFT bin in Hertz.
Definition: sound.h:67
float maxAmplitudeFrequency
Maximum detected amplitude frequency of this sample, in Hertz.
Definition: sound.h:54
static constexpr auto SAMPLE_SIZE
Size fo the audio sample.
Definition: sound.h:42
float maxAmplitude
Maximum detected amplitude of this sample.
Definition: sound.h:52
std::array< int16_t, SAMPLE_SIZE > rectifiedData
audio data with auto gain enabled
Definition: sound.h:47
static constexpr uint8_t numberOfFFtChanels
Define the number of FFt bins to use. You should set this number close to the lamp max X coordinates.
Definition: sound.h:64
std::array< float, numberOfFFtChanels > fft_log
Results of the FFT process, in scaled logarithmic bins. This is closer to the sound sensitivity of th...
Definition: sound.h:74
float sound_level_Db
Sound level of this sample, in Decibels A.
Definition: sound.h:50