arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
AudioFFT.h
1#pragma once
2
4#include "AudioTools/CoreAudio/AudioStreams.h"
5#include "AudioTools/CoreAudio/MusicalNotes.h"
6
13namespace audio_tools {
14
15// forward declaration
16class AudioFFTBase;
17static MusicalNotes AudioFFTNotes;
18
24 int bin;
25 float magnitude;
26 float frequency;
27
28 int frequencyAsInt() { return round(frequency); }
29 const char *frequencyAsNote() { return AudioFFTNotes.note(frequency); }
30 const char *frequencyAsNote(float &diff) {
31 return AudioFFTNotes.note(frequency, diff);
32 }
33};
34
40struct AudioFFTConfig : public AudioInfo {
42 channels = 2;
43 bits_per_sample = 16;
44 sample_rate = 44100;
45 }
47 void (*callback)(AudioFFTBase &fft) = nullptr;
50 int length = 8192;
51 int stride = 0;
61 void* ref = nullptr;
62};
63
65struct FFTBin {
66 float real;
67 float img;
68
69 FFTBin() = default;
70
71 FFTBin(float r, float i) {
72 real = r;
73 img = i;
74 }
75
76 void multiply(float f) {
77 real *= f;
78 img *= f;
79 }
80
81 void conjugate() { img = -img; }
82
83 void clear() { real = img = 0.0f; }
84};
85
88 public:
90 if (size > 0) resize(size);
91 }
92
94 void resize(int size) {
95 // reset max for new scaling
96 rfft_max = 0.0;
97 // define new size
98 len = size;
99 data.resize(size);
100 for (int j = 0; j < data.size(); j++) {
101 data[j] = 0.0;
102 }
103 }
104
105 // adds the values to the array (by applying the window function)
106 void add(float value, int pos, WindowFunction *window_function) {
107 float add_value = value;
108 if (window_function != nullptr) {
109 add_value = value * window_function->factor(pos);
110 }
111 assert(pos < len);
112 data[pos] += add_value;
113 }
114
115 // gets the scaled audio data as result
116 void getStepData(float *result, int stride, float maxResult) {
117 for (int j = 0; j < stride; j++) {
118 // determine max value to scale
119 if (data[j] > rfft_max) rfft_max = data[j];
120 }
121 for (int j = 0; j < stride; j++) {
122 result[j] = data[j] / rfft_max * maxResult;
123 // clip
124 if (result[j] > maxResult) {
125 result[j] = maxResult;
126 }
127 if (result[j] < -maxResult) {
128 result[j] = -maxResult;
129 }
130 }
131 // copy data to head
132 for (int j = 0; j < len - stride; j++) {
133 data[j] = data[j + stride];
134 }
135 // clear tail
136 for (int j = len - stride; j < len; j++) {
137 data[j] = 0.0;
138 }
139 }
140
142 int size() { return data.size(); }
143
144 protected:
145 Vector<float> data{0};
146 int len = 0;
147 float rfft_max = 0;
148};
149
157 public:
158 virtual bool begin(int len) = 0;
159 virtual void end() = 0;
161 virtual void setValue(int pos, float value) = 0;
163 virtual void fft() = 0;
165 virtual float magnitude(int idx) = 0;
167 virtual float magnitudeFast(int idx) = 0;
168 virtual bool isValid() = 0;
170 virtual bool isReverseFFT() { return false; }
172 virtual void rfft() { LOGE("Not implemented"); }
174 virtual float getValue(int pos) = 0;
176 virtual bool setBin(int idx, float real, float img) { return false; }
178 bool setBin(int pos, FFTBin &bin) { return setBin(pos, bin.real, bin.img); }
180 virtual bool getBin(int pos, FFTBin &bin) { return false; }
181};
182
191class AudioFFTBase : public AudioStream {
192 public:
196
197 ~AudioFFTBase() { end(); }
198
201 AudioFFTConfig info;
202 info.rxtx_mode = mode;
203 return info;
204 }
205
208 cfg = info;
209 return begin();
210 }
211
213 bool begin() override {
214 bins = cfg.length / 2;
215 // define window functions
216 if (cfg.window_function_fft==nullptr) cfg.window_function_fft = cfg.window_function;
218 // define default stride value if not defined
219 if (cfg.stride == 0) cfg.stride = cfg.length;
220
221 if (!isPowerOfTwo(cfg.length)) {
222 LOGE("Len must be of the power of 2: %d", cfg.length);
223 return false;
224 }
225 if (!p_driver->begin(cfg.length)) {
226 LOGE("Not enough memory");
227 }
228
229 if (cfg.window_function_fft != nullptr) {
230 cfg.window_function_fft->begin(cfg.length);
231 }
232 if (cfg.window_function_ifft != nullptr
234 cfg.window_function_ifft->begin(cfg.length);
235 }
236
237 bool is_valid_rxtx = false;
238 if (cfg.rxtx_mode == TX_MODE || cfg.rxtx_mode == RXTX_MODE) {
239 // holds last N bytes that need to be reprocessed
240 stride_buffer.resize((cfg.length) * bytesPerSample());
241 is_valid_rxtx = true;
242 }
243 if (cfg.rxtx_mode == RX_MODE || cfg.rxtx_mode == RXTX_MODE) {
244 rfft_data.resize(cfg.channels * bytesPerSample() * cfg.stride);
245 rfft_add.resize(cfg.length);
246 step_data.resize(cfg.stride);
247 is_valid_rxtx = true;
248 }
249
250 if (!is_valid_rxtx){
251 LOGE("Invalid rxtx_mode");
252 return false;
253 }
254
255 current_pos = 0;
256 return p_driver->isValid();
257 }
258
260 void reset() {
261 current_pos = 0;
262 if (cfg.window_function_fft != nullptr) {
263 cfg.window_function_fft->begin(cfg.length);
264 }
265 if (cfg.window_function_ifft != nullptr) {
266 cfg.window_function_ifft->begin(cfg.length);
267 }
268 }
269
270 operator bool() { return p_driver != nullptr && p_driver->isValid(); }
271
273 void setAudioInfo(AudioInfo info) override {
275 cfg.sample_rate = info.sample_rate;
276 cfg.channels = info.channels;
277 begin(cfg);
278 }
279
281 void end() override {
282 p_driver->end();
283 l_magnitudes.resize(0);
284 rfft_data.resize(0);
285 rfft_add.resize(0);
286 step_data.resize(0);
287 }
288
290 size_t write(const uint8_t *data, size_t len) override {
291 size_t result = 0;
292 if (p_driver->isValid()) {
293 result = len;
294 switch (cfg.bits_per_sample) {
295 case 8:
296 processSamples<int8_t>(data, len);
297 break;
298 case 16:
299 processSamples<int16_t>(data, len / 2);
300 break;
301 case 24:
302 processSamples<int24_t>(data, len / 3);
303 break;
304 case 32:
305 processSamples<int32_t>(data, len / 4);
306 break;
307 default:
308 LOGE("Unsupported bits_per_sample: %d", cfg.bits_per_sample);
309 break;
310 }
311 }
312 return result;
313 }
314
316 size_t readBytes(uint8_t *data, size_t len) override {
317 TRACED();
318 if (rfft_data.size() == 0) return 0;
319
320 // get data via callback if there is no more data
321 if (cfg.rxtx_mode == RX_MODE && cfg.callback != nullptr && rfft_data.available() == 0) {
322 cfg.callback(*this);
323 }
324
325 // execute rfft when we consumed all data
326 if (has_rfft_data && rfft_data.available() == 0) {
327 rfft();
328 }
329 return rfft_data.readArray(data, len);
330 }
331
333 int availableForWrite() override {
334 return cfg.length * cfg.channels * bytesPerSample();
335 }
336
338 int available() override {
339 assert(cfg.stride != 0);
340 return cfg.stride * cfg.channels * bytesPerSample();
341 }
342
344 int size() { return bins; }
345
347 int length() { return cfg.length; }
348
351 unsigned long resultTime() { return timestamp; }
353 unsigned long resultTimeBegin() { return timestamp_begin; }
354
355
359 ret_value.magnitude = 0;
360 ret_value.bin = 0;
361 // find max value and index
362 for (int j = 0; j < size(); j++) {
363 float m = magnitude(j);
364 if (m > ret_value.magnitude) {
365 ret_value.magnitude = m;
366 ret_value.bin = j;
367 }
368 }
369 ret_value.frequency = frequency(ret_value.bin);
370 return ret_value;
371 }
372
374 template <int N>
376 // initialize to negative value
377 for (int j = 0; j < N; j++) {
378 result[j].magnitude = -1000000;
379 }
380 // find top n values
382 for (int j = 0; j < size(); j++) {
383 act.magnitude = magnitude(j);
384 act.bin = j;
385 act.frequency = frequency(j);
387 }
388 }
389
392 FFTDriver *driver() { return p_driver; }
393
395 float frequency(int bin) {
396 if (bin >= bins) {
397 LOGE("Invalid bin %d", bin);
398 return 0;
399 }
400 return static_cast<float>(bin) * cfg.sample_rate / cfg.length;
401 }
402
404 int frequencyToBin(int freq){
405 int max_freq = cfg.sample_rate / 2;
406 return map(freq, 0, max_freq, 0, size());
407 }
408
411 float magnitude(int bin) {
412 if (bin >= bins) {
413 LOGE("Invalid bin %d", bin);
414 return 0;
415 }
416 return p_driver->magnitude(bin);
417 }
418
419 float magnitudeFast(int bin) {
420 if (bin >= bins) {
421 LOGE("Invalid bin %d", bin);
422 return 0;
423 }
424 return p_driver->magnitudeFast(bin);
425 }
426
428 float phase(int bin){
430 getBin(bin, fft_bin);
431 return atan2(fft_bin.img, fft_bin.real);
432 }
433
436 float *magnitudes() {
437 if (l_magnitudes.size() == 0) {
438 l_magnitudes.resize(size());
439 }
440 for (int j = 0; j < size(); j++) {
441 l_magnitudes[j] = magnitude(j);
442 }
443 return l_magnitudes.data();
444 }
445
448 float *magnitudesFast() {
449 if (l_magnitudes.size() == 0) {
450 l_magnitudes.resize(size());
451 }
452 for (int j = 0; j < size(); j++) {
453 l_magnitudes[j] = magnitudeFast(j);
454 }
455 return l_magnitudes.data();
456 }
457
459 bool setBin(int idx, float real, float img) {
460 has_rfft_data = true;
461 if (idx < 0 || idx >= size()) return false;
462 bool rc_first_half = p_driver->setBin(idx, real, img);
463 bool rc_2nd_half = p_driver->setBin(cfg.length - idx, real, img);
464 return rc_first_half && rc_2nd_half;
465 }
467 bool setBin(int pos, FFTBin &bin) {
468 return setBin(pos, bin.real, bin.img);
469 }
471 bool getBin(int pos, FFTBin &bin) { return p_driver->getBin(pos, bin); }
472
474 void clearBins(){
475 FFTBin empty{0,0};
476 for (int j=0; j< size(); j++){
477 setBin(j, empty);
478 }
479 }
480
482 AudioFFTConfig &config() { return cfg; }
483
484 protected:
485 FFTDriver *p_driver = nullptr;
486 int current_pos = 0;
487 int bins = 0;
488 unsigned long timestamp_begin = 0l;
489 unsigned long timestamp = 0l;
490 AudioFFTConfig cfg;
491 FFTInverseOverlapAdder rfft_add{0};
492 Vector<float> l_magnitudes{0};
493 Vector<float> step_data{0};
494 SingleBuffer<uint8_t> stride_buffer{0};
495 RingBuffer<uint8_t> rfft_data{0};
496 bool has_rfft_data = false;
497
498 // Add samples to input data p_x - and process them if full
499 template <typename T>
500 void processSamples(const void *data, size_t count) {
501 T *dataT = (T *)data;
502 T sample;
503 for (int j = 0; j < count; j += cfg.channels) {
504 sample = dataT[j + cfg.channel_used];
505 if (writeStrideBuffer((uint8_t *)&sample, sizeof(T))){
506 // process data if buffer is full
507 T* samples = (T*) stride_buffer.data();
508 int sample_count = stride_buffer.size() / sizeof(T);
509 assert(sample_count == cfg.length);
510 for (int j=0; j< sample_count; j++){
511 T out_sample = samples[j];
512 p_driver->setValue(j, windowedSample(out_sample, j));
513 }
514
515 fft<T>();
516
517 // remove stride samples
518 stride_buffer.clearArray(cfg.stride * sizeof(T));
519
520 // validate available data in stride buffer
521 if (cfg.stride == cfg.length) assert(stride_buffer.available()==0);
522
523 }
524 }
525 }
526
527 template <typename T>
528 T windowedSample(T sample, int pos) {
529 T result = sample;
530 if (cfg.window_function_fft != nullptr) {
531 result = cfg.window_function_fft->factor(pos) * sample;
532 }
533 return result;
534 }
535
536 template <typename T>
537 void fft() {
538 timestamp_begin = millis();
539 p_driver->fft();
540 has_rfft_data = true;
541 timestamp = millis();
542 if (cfg.callback != nullptr) {
543 cfg.callback(*this);
544 }
545 }
546
548 void rfft() {
549 TRACED();
550 // execute reverse fft
551 p_driver->rfft();
552 has_rfft_data = false;
553 // add data to sum buffer
554 for (int j = 0; j < cfg.length; j++) {
555 float value = p_driver->getValue(j);
556 rfft_add.add(value, j, cfg.window_function_ifft);
557 }
558 // get result data from sum buffer
559 rfftWriteData(rfft_data);
560 }
561
564 // get data to result buffer
565 // for (int j = 0; j < cfg.stride; j++) {
566 // step_data[j] = 0.0;
567 // }
568 rfft_add.getStepData(step_data.data(), cfg.stride,
570
571 switch (cfg.bits_per_sample) {
572 case 8:
573 writeIFFT<int8_t>(step_data.data(), cfg.stride);
574 break;
575 case 16:
576 writeIFFT<int16_t>(step_data.data(), cfg.stride);
577 break;
578 case 24:
579 writeIFFT<int24_t>(step_data.data(), cfg.stride);
580 break;
581 case 32:
582 writeIFFT<int32_t>(step_data.data(), cfg.stride);
583 break;
584 default:
585 LOGE("Unsupported bits: %d", cfg.bits_per_sample);
586 }
587 }
588
589 template <typename T>
590 void writeIFFT(float *data, int len) {
591 for (int j = 0; j < len; j++) {
592 T sample = data[j];
593 T out_data[cfg.channels];
594 for (int ch = 0; ch < cfg.channels; ch++) {
595 out_data[ch] = sample;
596 }
597 int result = rfft_data.writeArray((uint8_t *)out_data, sizeof(out_data));
598 assert(result == sizeof(out_data));
599 }
600 }
601
602 inline int bytesPerSample() { return cfg.bits_per_sample / 8; }
603
605 template <int N>
607 // find place where we need to insert new record
608 for (int j = 0; j < N; j++) {
609 // insert when biggen then current record
610 if (tmp.magnitude > result[j].magnitude) {
611 // shift existing values right
612 for (int i = N - 2; i >= j; i--) {
613 result[i + 1] = result[i];
614 }
615 // insert new value
616 result[j] = tmp;
617 // stop after we found the correct index
618 break;
619 }
620 }
621 }
622
623 // adds samples to stride buffer, returns true if the buffer is full
624 bool writeStrideBuffer(uint8_t *buffer, size_t len) {
625 assert(stride_buffer.availableForWrite() >= len);
626 stride_buffer.writeArray(buffer, len);
627 return stride_buffer.isFull();
628 }
629
630 bool isPowerOfTwo(uint16_t x) { return (x & (x - 1)) == 0; }
631};
632
633} // namespace audio_tools
Different Window functions that can be used by FFT.
Executes FFT using audio data privded by write() and/or an inverse FFT where the samples are made ava...
Definition AudioFFT.h:191
unsigned long resultTimeBegin()
time before the fft
Definition AudioFFT.h:353
void clearBins()
clears the fft data
Definition AudioFFT.h:474
float magnitude(int bin)
Definition AudioFFT.h:411
float * magnitudesFast()
Definition AudioFFT.h:448
unsigned long resultTime()
Definition AudioFFT.h:351
int length()
The number of samples.
Definition AudioFFT.h:347
size_t readBytes(uint8_t *data, size_t len) override
Provides the result of a reverse FFT.
Definition AudioFFT.h:316
void end() override
Release the allocated memory.
Definition AudioFFT.h:281
int available() override
Data available for reverse fft.
Definition AudioFFT.h:338
AudioFFTBase(FFTDriver *driver)
Definition AudioFFT.h:195
AudioFFTConfig & config()
Provides the actual configuration.
Definition AudioFFT.h:482
size_t write(const uint8_t *data, size_t len) override
Provide the audio data as FFT input.
Definition AudioFFT.h:290
FFTDriver * driver()
Definition AudioFFT.h:392
int frequencyToBin(int freq)
Determine the bin number from the frequency.
Definition AudioFFT.h:404
bool setBin(int idx, float real, float img)
sets the value of a bin
Definition AudioFFT.h:459
int availableForWrite() override
We try to fill the buffer at once.
Definition AudioFFT.h:333
float phase(int bin)
calculates the phase
Definition AudioFFT.h:428
AudioFFTConfig defaultConfig(RxTxMode mode=TX_MODE)
Provides the default configuration.
Definition AudioFFT.h:200
bool begin(AudioFFTConfig info)
starts the processing
Definition AudioFFT.h:207
float * magnitudes()
Definition AudioFFT.h:436
bool getBin(int pos, FFTBin &bin)
gets the value of a bin
Definition AudioFFT.h:471
AudioFFTResult result()
Determines the result values in the max magnitude bin.
Definition AudioFFT.h:357
float frequency(int bin)
Determines the frequency of the indicated bin.
Definition AudioFFT.h:395
void rfft()
reverse fft
Definition AudioFFT.h:548
bool setBin(int pos, FFTBin &bin)
sets the value of a bin
Definition AudioFFT.h:467
void insertSorted(AudioFFTResult(&result)[N], AudioFFTResult tmp)
make sure that we do not reuse already found results
Definition AudioFFT.h:606
bool begin() override
starts the processing
Definition AudioFFT.h:213
void setAudioInfo(AudioInfo info) override
Notify change of audio information.
Definition AudioFFT.h:273
void reset()
Just resets the current_pos e.g. to start a new cycle.
Definition AudioFFT.h:260
void resultArray(AudioFFTResult(&result)[N])
Determines the N biggest result values.
Definition AudioFFT.h:375
int size()
The number of bins used by the FFT which are relevant for the result.
Definition AudioFFT.h:344
void rfftWriteData(BaseBuffer< uint8_t > &data)
write reverse fft result to buffer to make it available for readBytes
Definition AudioFFT.h:563
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition BaseStream.h:115
virtual int readArray(T data[], int len)
reads multiple values
Definition Buffers.h:41
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition Buffers.h:65
Abstract Class which defines the basic FFT functionality.
Definition AudioFFT.h:156
virtual float magnitude(int idx)=0
Calculate the magnitude (fft result) at index (sqr(i² + r²))
virtual bool setBin(int idx, float real, float img)
sets the value of a bin
Definition AudioFFT.h:176
virtual void fft()=0
Perform FFT.
virtual float magnitudeFast(int idx)=0
Calculate the magnitude w/o sqare root.
virtual bool isReverseFFT()
Returns true if reverse FFT is supported.
Definition AudioFFT.h:170
virtual void rfft()
Calculate reverse FFT.
Definition AudioFFT.h:172
bool setBin(int pos, FFTBin &bin)
sets the value of a bin
Definition AudioFFT.h:178
virtual void setValue(int pos, float value)=0
Sets the real value.
virtual bool getBin(int pos, FFTBin &bin)
gets the value of a bin
Definition AudioFFT.h:180
virtual float getValue(int pos)=0
Get result value from Reverse FFT.
Inverse FFT Overlapp Add.
Definition AudioFFT.h:87
void resize(int size)
Initilze data by defining new size.
Definition AudioFFT.h:94
int size()
provides the actual size
Definition AudioFFT.h:142
static int64_t maxValue(int value_bits_per_sample)
provides the biggest number for the indicated number of bits
Definition AudioTypes.h:317
virtual int available()
provides the number of entries that are available to read
Definition Buffers.h:366
virtual size_t size()
Returns the maximum capacity of the buffer.
Definition Buffers.h:383
int available() override
provides the number of entries that are available to read
Definition Buffers.h:219
int availableForWrite() override
provides the number of entries that are available to write
Definition Buffers.h:224
bool isFull() override
checks if the buffer is full
Definition Buffers.h:226
T * data()
Provides address of actual data.
Definition Buffers.h:252
int clearArray(int len) override
consumes len bytes and moves current data to the beginning
Definition Buffers.h:229
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
FFT Window Function.
Definition FFTWindows.h:24
virtual void begin(int samples)
Setup the window function providing the fft length.
Definition FFTWindows.h:29
float factor(int idx)
Definition FFTWindows.h:37
RxTxMode
The Microcontroller is the Audio Source (TX_MODE) or Audio Sink (RX_MODE). RXTX_MODE is Source and Si...
Definition AudioTypes.h:28
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioConfig.h:885
long map(long x, long in_min, long in_max, long out_min, long out_max)
Maps input to output values.
Definition NoArduino.h:186
uint32_t millis()
Returns the milliseconds since the start.
Definition Time.h:12
Configuration for AudioFFT. If there are more then 1 channel the channel_used is defining which chann...
Definition AudioFFT.h:40
void * ref
caller
Definition AudioFFT.h:61
WindowFunction * window_function_ifft
Optional window function for ifft only.
Definition AudioFFT.h:57
uint8_t channel_used
Channel which is used as input.
Definition AudioFFT.h:49
WindowFunction * window_function_fft
Optional window function for fft only.
Definition AudioFFT.h:55
WindowFunction * window_function
Optional window function for both fft and ifft.
Definition AudioFFT.h:53
void(* callback)(AudioFFTBase &fft)
Callback method which is called after we got a new result.
Definition AudioFFT.h:47
RxTxMode rxtx_mode
TX_MODE = FFT, RX_MODE = IFFT.
Definition AudioFFT.h:59
Result of the FFT.
Definition AudioFFT.h:23
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:52
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:55
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:57
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:59
And individual FFT Bin.
Definition AudioFFT.h:65