arduino-audio-tools
AudioFFT.h
1 #pragma once
2 
3 #include "AudioTools/CoreAudio/AudioOutput.h"
4 #include "AudioTools/CoreAudio/MusicalNotes.h"
6 
13 namespace audio_tools {
14 
15 // forward declaration
16 class AudioFFTBase;
17 static MusicalNotes AudioFFTNotes;
18 
24  int bin;
25  float magnitude;
26  float frequency;
27 
28  int frequencyAsInt(){
29  return round(frequency);
30  }
31  const char* frequencyAsNote() {
32  return AudioFFTNotes.note(frequency);
33  }
34  const char* frequencyAsNote(float &diff) {
35  return AudioFFTNotes.note(frequency, diff);
36  }
37 };
38 
44 struct AudioFFTConfig : public AudioInfo {
46  channels = 2;
47  bits_per_sample = 16;
48  sample_rate = 44100;
49  }
51  void (*callback)(AudioFFTBase &fft) = nullptr;
53  uint8_t channel_used = 0;
54  int length=8192;
55  int stride=0;
58 };
59 
61 struct FFTBin {
62  float real;
63  float img;
64 
65  FFTBin() = default;
66 
67  FFTBin(float r, float i) {
68  real = r;
69  img = i;
70  }
71 
72  void multiply(float f){
73  real *= f;
74  img *= f;
75  }
76 
77  void conjugate(){
78  img = -img;
79  }
80 
81  void clear() {
82  real = img = 0.0f;
83  }
84 };
85 
92 class FFTDriver {
93  public:
94  virtual bool begin(int len) =0;
95  virtual void end() =0;
97  virtual void setValue(int pos, float value) =0;
99  virtual void fft() = 0;
101  virtual float magnitude(int idx) = 0;
103  virtual float magnitudeFast(int idx) = 0;
104  virtual bool isValid() = 0;
106  virtual bool isReverseFFT() {return false;}
108  virtual void rfft() {LOGE("Not implemented"); }
110  virtual float getValue(int pos) = 0;
112  virtual bool setBin(int idx, float real, float img) {return false;}
114  bool setBin(int pos, FFTBin &bin) { return setBin(pos, bin.real, bin.img);}
116  virtual bool getBin(int pos, FFTBin &bin) { return false;}
117 };
118 
125 class AudioFFTBase : public AudioOutput {
126  public:
129  p_driver = driver;
130  }
131 
132  ~AudioFFTBase() {
133  end();
134  }
135 
138  AudioFFTConfig info;
139  return info;
140  }
141 
143  bool begin(AudioFFTConfig info) {
144  cfg = info;
145  return begin();
146  }
147 
149  bool begin() override {
150  bins = cfg.length/2;
151  if (!isPowerOfTwo(cfg.length)){
152  LOGE("Len must be of the power of 2: %d", cfg.length);
153  return false;
154  }
155  if (cfg.stride>0 && cfg.stride<cfg.length){
156  // holds last N bytes that need to be reprocessed
157  stride_buffer.resize((cfg.length - cfg.stride)*bytesPerSample());
158  }
159  if (!p_driver->begin(cfg.length)){
160  LOGE("Not enough memory");
161  }
162  if (cfg.window_function!=nullptr){
163  cfg.window_function->begin(length());
164  }
165 
166  current_pos = 0;
167  rfft_max = 0;
168  return p_driver->isValid();
169  }
170 
172  void reset(){
173  current_pos = 0;
174  if (cfg.window_function!=nullptr){
175  cfg.window_function->begin(length());
176  }
177  }
178 
179  operator bool() {
180  return p_driver!=nullptr && p_driver->isValid();
181  }
182 
184  void setAudioInfo(AudioInfo info) override {
185  cfg.bits_per_sample = info.bits_per_sample;
186  cfg.sample_rate = info.sample_rate;
187  cfg.channels = info.channels;
188  begin(cfg);
189  }
190 
192  void end() override {
193  p_driver->end();
194  if (p_magnitudes!=nullptr) delete []p_magnitudes;
195  }
196 
198  size_t write(const uint8_t*data, size_t len) override {
199  size_t result = 0;
200  if (p_driver->isValid()){
201  result = len;
202  switch(cfg.bits_per_sample){
203  case 16:
204  processSamples<int16_t>(data, len/2);
205  break;
206  case 24:
207  processSamples<int24_t>(data, len/3);
208  break;
209  case 32:
210  processSamples<int32_t>(data, len/4);
211  break;
212  default:
213  LOGE("Unsupported bits_per_sample: %d",cfg.bits_per_sample);
214  break;
215  }
216  }
217  return result;
218  }
219 
221  int availableForWrite() override {
222  return cfg.bits_per_sample/8*cfg.length;
223  }
224 
226  int size() {
227  return bins;
228  }
229 
231  int length() {
232  return cfg.length;
233  }
234 
236  unsigned long resultTime() {
237  return timestamp;
238  }
240  unsigned long resultTimeBegin() {
241  return timestamp_begin;
242  }
243 
245  float frequency(int bin){
246  if (bin>=bins){
247  LOGE("Invalid bin %d", bin);
248  return 0;
249  }
250  return static_cast<float>(bin) * cfg.sample_rate / cfg.length;
251  }
252 
255  AudioFFTResult ret_value;
256  ret_value.magnitude = 0;
257  ret_value.bin = 0;
258  // find max value and index
259  for (int j=0;j<size();j++){
260  float m = magnitude(j);
261  if (m>ret_value.magnitude){
262  ret_value.magnitude = m;
263  ret_value.bin = j;
264  }
265  }
266  ret_value.frequency = frequency(ret_value.bin);
267  return ret_value;
268  }
269 
270 
272  template<int N>
274  // initialize to negative value
275  for (int j=0;j<N;j++){
276  result[j].magnitude = -1000000;
277  }
278  // find top n values
279  AudioFFTResult act;
280  for (int j=0;j<size();j++){
281  act.magnitude = magnitude(j);
282  act.bin = j;
283  act.frequency = frequency(j);
284  insertSorted<N>(result, act);
285  }
286  }
287 
290  return p_driver;
291  }
292 
294  float magnitude(int bin){
295  if (bin>=bins){
296  LOGE("Invalid bin %d", bin);
297  return 0;
298  }
299  return p_driver->magnitude(bin);
300  }
301 
302  float magnitudeFast(int bin){
303  if (bin>=bins){
304  LOGE("Invalid bin %d", bin);
305  return 0;
306  }
307  return p_driver->magnitudeFast(bin);
308  }
310  float* magnitudes() {
311  if (p_magnitudes==nullptr){
312  p_magnitudes = new float[size()];
313  }
314  for (int j=0;j<size();j++){
315  p_magnitudes[j]= magnitude(j);
316  }
317  return p_magnitudes;
318  }
319 
321  float* magnitudesFast() {
322  if (p_magnitudes==nullptr){
323  p_magnitudes = new float[size()];
324  }
325  for (int j=0;j<size();j++){
326  p_magnitudes[j]= magnitudeFast(j);
327  }
328  return p_magnitudes;
329  }
330 
332  bool setBin(int idx, float real, float img) {return p_driver->setBin(idx, real, img);}
334  bool setBin(int pos, FFTBin &bin) { return p_driver->setBin(pos, bin.real, bin.img);}
336  bool getBin(int pos, FFTBin &bin) { return p_driver->getBin(pos, bin);}
337 
338 
341  return cfg;
342  }
343 
345  void setOutput(Print&out){
346  p_out = &out;
347  }
348 
350  bool isInverseFFT() {
351  return p_out != nullptr && p_driver->isReverseFFT();
352  }
353 
354  protected:
355  FFTDriver *p_driver=nullptr;
356  int current_pos = 0;
357  AudioFFTConfig cfg;
358  unsigned long timestamp_begin=0l;
359  unsigned long timestamp=0l;
360  RingBuffer<uint8_t> stride_buffer{0};
361  float *p_magnitudes = nullptr;
362  int bins = 0;
363  Print *p_out = nullptr;
364  float rfft_max = 0;
365 
366 
367  // Add samples to input data p_x - and process them if full
368  template<typename T>
369  void processSamples(const void *data, size_t samples) {
370  T *dataT = (T*) data;
371  T sample;
372  float sample_windowed;
373  for (int j=0; j<samples; j+=cfg.channels){
374  sample = dataT[j+cfg.channel_used];
375  p_driver->setValue(current_pos, windowedSample(sample));
376  writeStrideBuffer((uint8_t*)&sample, sizeof(T));
377  if (++current_pos>=cfg.length){
378  // perform FFT
379  fft<T>();
380 
381  if (isInverseFFT())
382  rfft();
383 
384  // reprocess data in stride buffer
385  if (stride_buffer.size()>0){
386  // reload data from stride buffer
387  while (stride_buffer.available()){
388  T sample;
389  stride_buffer.readArray((uint8_t*)&sample, sizeof(T));
390  p_driver->setValue(current_pos, windowedSample(sample));
391  current_pos++;
392  }
393  }
394 
395  }
396  }
397  }
398 
399  template<typename T>
400  T windowedSample(T sample){
401  T result = sample;
402  if (cfg.window_function!=nullptr){
403  result = cfg.window_function->factor(current_pos) * sample;
404  }
405  return result;
406  }
407 
408  template<typename T>
409  void fft() {
410  timestamp_begin = millis();
411  p_driver->fft();
412  timestamp = millis();
413  if (cfg.callback!=nullptr){
414  cfg.callback(*this);
415  }
416  current_pos = 0;
417  }
418 
420  void rfft() {
421  TRACED();
422  p_driver->rfft();
423  for (int j=0;j<cfg.length;j++){
424  float value = p_driver->getValue(j);
425  if (rfft_max < value ) rfft_max = value;
426  //Serial.println(value / rfft_max);
427  switch(cfg.bits_per_sample){
428  case 16:{
429  int16_t out16 = value / rfft_max * NumberConverter::maxValue(16);
430  for (int ch=0;ch<cfg.channels; ch++)
431  p_out->write((uint8_t*)&out16, sizeof(out16));
432  }break;
433  case 24:{
434  int24_t out24 = value / rfft_max * NumberConverter::maxValue(24);
435  for (int ch=0;ch<cfg.channels; ch++)
436  p_out->write((uint8_t*)&out24, sizeof(out24));
437  }break;
438  case 32: {
439  int32_t out32 = value / rfft_max * NumberConverter::maxValue(32);
440  for (int ch=0;ch<cfg.channels; ch++)
441  p_out->write((uint8_t*)&out32, sizeof(out32));
442  } break;
443  default:
444  LOGE("Unsupported bits")
445  }
446  }
447  }
448 
449  int bytesPerSample() {
450  return cfg.bits_per_sample / 8;
451  }
452 
454  template<int N>
456  // find place where we need to insert new record
457  for (int j=0;j<N;j++){
458  // insert when biggen then current record
459  if (tmp.magnitude>result[j].magnitude){
460  // shift existing values right
461  for (int i=N-2;i>=j;i--){
462  result[i+1] = result[i];
463  }
464  // insert new value
465  result[j]=tmp;
466  // stop after we found the correct index
467  break;
468  }
469  }
470  }
471 
472  void writeStrideBuffer(uint8_t* buffer, size_t len){
473  if (stride_buffer.size()>0){
474  int available = stride_buffer.availableForWrite();
475  if (len>available){
476  // clear oldest values to make space
477  int diff = len-available;
478  for(int j=0;j<diff;j++){
479  stride_buffer.read();
480  }
481  }
482  stride_buffer.writeArray(buffer, len);
483  }
484  }
485 
486  bool isPowerOfTwo(uint16_t x) {
487  return (x & (x - 1)) == 0;
488  }
489 
490 };
491 
492 
493 
494 }
Different Window functions that can be used by FFT.
Executes FFT using audio data. The Driver which is passed in the constructor selects a specifc FFT im...
Definition: AudioFFT.h:125
unsigned long resultTimeBegin()
time before the fft
Definition: AudioFFT.h:240
float magnitude(int bin)
Calculates the magnitude of the fft result to determine the max value (bin is 0 to size())
Definition: AudioFFT.h:294
bool isInverseFFT()
Returns true if we need to calculate the inverse FFT.
Definition: AudioFFT.h:350
float * magnitudes()
Provides the magnitudes as array of size size(). Please note that this method is allocating additinal...
Definition: AudioFFT.h:310
unsigned long resultTime()
time after the fft: time when the last result was provided - you can poll this to check if we have a ...
Definition: AudioFFT.h:236
int length()
The number of samples.
Definition: AudioFFT.h:231
void end() override
Release the allocated memory.
Definition: AudioFFT.h:192
AudioFFTBase(FFTDriver *driver)
Default Constructor. The len needs to be of the power of 2 (e.g. 512, 1024, 2048, 4096,...
Definition: AudioFFT.h:128
size_t write(const uint8_t *data, size_t len) override
Provide the audio data as FFT input.
Definition: AudioFFT.h:198
bool setBin(int idx, float real, float img)
sets the value of a bin
Definition: AudioFFT.h:332
int availableForWrite() override
We try to fill the buffer at once.
Definition: AudioFFT.h:221
bool begin(AudioFFTConfig info)
starts the processing
Definition: AudioFFT.h:143
bool getBin(int pos, FFTBin &bin)
gets the value of a bin
Definition: AudioFFT.h:336
AudioFFTResult result()
Determines the result values in the max magnitude bin.
Definition: AudioFFT.h:254
float frequency(int bin)
Determines the frequency of the indicated bin.
Definition: AudioFFT.h:245
void rfft()
reverse fft if necessary
Definition: AudioFFT.h:420
bool setBin(int pos, FFTBin &bin)
sets the value of a bin
Definition: AudioFFT.h:334
void insertSorted(AudioFFTResult(&result)[N], AudioFFTResult tmp)
make sure that we do not reuse already found results
Definition: AudioFFT.h:455
bool begin() override
starts the processing
Definition: AudioFFT.h:149
void setAudioInfo(AudioInfo info) override
Notify change of audio information.
Definition: AudioFFT.h:184
void reset()
Just resets the current_pos e.g. to start a new cycle.
Definition: AudioFFT.h:172
FFTDriver * driver()
provides access to the FFTDriver which implements the basic FFT functionality
Definition: AudioFFT.h:289
AudioFFTConfig & config()
Provides the actual configuration.
Definition: AudioFFT.h:340
void setOutput(Print &out)
Define final output for reverse ffft.
Definition: AudioFFT.h:345
AudioFFTConfig defaultConfig()
Provides the default configuration.
Definition: AudioFFT.h:137
void resultArray(AudioFFTResult(&result)[N])
Determines the N biggest result values.
Definition: AudioFFT.h:273
int size()
The number of bins used by the FFT which are relevant for the result.
Definition: AudioFFT.h:226
float * magnitudesFast()
Provides the magnitudes w/o calling the square root function as array of size size()....
Definition: AudioFFT.h:321
Abstract Audio Ouptut class.
Definition: AudioOutput.h:22
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:92
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:112
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:106
virtual void rfft()
Calculate reverse FFT.
Definition: AudioFFT.h:108
bool setBin(int pos, FFTBin &bin)
sets the value of a bin
Definition: AudioFFT.h:114
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:116
virtual float getValue(int pos)=0
Get result value from Reverse FFT.
const char * note(float frequency, float &diff) const
Determines the closes note for a frequency. We also return the frequency difference.
Definition: MusicalNotes.h:169
static int64_t maxValue(int value_bits_per_sample)
provides the biggest number for the indicated number of bits
Definition: AudioTypes.h:317
Definition: NoArduino.h:58
virtual T read()
reads a single value
Definition: Buffers.h:309
virtual int availableForWrite()
provides the number of entries that are available to write
Definition: Buffers.h:369
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
FFT Window Function.
Definition: FFTWindows.h:23
virtual void begin(int samples)
Setup the window function providing the fft length.
Definition: FFTWindows.h:28
float factor(int idx)
Provides the multipication factor at the indicated position. The result is symetirically mirrored aro...
Definition: FFTWindows.h:35
24bit integer which is used for I2S sound processing. The values are represented as int32_t,...
Definition: Int24_4bytes_t.h:16
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AudioConfig.h:823
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:44
uint8_t channel_used
Channel which is used as input.
Definition: AudioFFT.h:53
WindowFunction * window_function
Optional window function.
Definition: AudioFFT.h:57
void(* callback)(AudioFFTBase &fft)
Callback method which is called after we got a new result.
Definition: AudioFFT.h:51
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:61