arduino-audio-tools
AudioFFT.h
1 #pragma once
2 
3 #include "AudioTools/AudioOutput.h"
5 
12 namespace audio_tools {
13 
14 // forward declaration
15 class AudioFFTBase;
16 static MusicalNotes AudioFFTNotes;
17 
23  int bin;
24  float magnitude;
25  float frequency;
26 
27  int frequencyAsInt(){
28  return round(frequency);
29  }
30  const char* frequencyAsNote() {
31  return AudioFFTNotes.note(frequency);
32  }
33  const char* frequencyAsNote(float &diff) {
34  return AudioFFTNotes.note(frequency, diff);
35  }
36 };
37 
43 struct AudioFFTConfig : public AudioInfo {
45  channels = 2;
46  bits_per_sample = 16;
47  sample_rate = 44100;
48  }
50  void (*callback)(AudioFFTBase &fft) = nullptr;
52  uint8_t channel_used = 0;
53  int length=8192;
54  int stride=0;
57 };
58 
65 class FFTDriver {
66  public:
67  virtual bool begin(int len) =0;
68  virtual void end() =0;
69  virtual void setValue(int pos, int value) =0;
70  virtual void fft() = 0;
71  virtual float magnitude(int idx) = 0;
72  virtual float magnitudeFast(int idx) = 0;
73  virtual bool isValid() = 0;
74 };
75 
82 class AudioFFTBase : public AudioOutput {
83  public:
86  p_driver = driver;
87  }
88 
89  ~AudioFFTBase() {
90  end();
91  }
92 
95  AudioFFTConfig info;
96  return info;
97  }
98 
100  bool begin(AudioFFTConfig info) {
101  cfg = info;
102  bins = cfg.length/2;
103  if (!isPowerOfTwo(cfg.length)){
104  LOGE("Len must be of the power of 2: %d", cfg.length);
105  return false;
106  }
107  if (cfg.stride>0 && cfg.stride<cfg.length){
108  // holds last N bytes that need to be reprocessed
109  stride_buffer.resize((cfg.length - cfg.stride)*bytesPerSample());
110  }
111  if (!p_driver->begin(cfg.length)){
112  LOGE("Not enough memory");
113  }
114  if (cfg.window_function!=nullptr){
115  cfg.window_function->begin(length());
116  }
117 
118  current_pos = 0;
119  return p_driver->isValid();
120  }
121 
123  void reset(){
124  current_pos = 0;
125  if (cfg.window_function!=nullptr){
126  cfg.window_function->begin(length());
127  }
128  }
129 
130  operator bool() {
131  return p_driver!=nullptr && p_driver->isValid();
132  }
133 
135  void setAudioInfo(AudioInfo info) override {
136  cfg.bits_per_sample = info.bits_per_sample;
137  cfg.sample_rate = info.sample_rate;
138  cfg.channels = info.channels;
139  begin(cfg);
140  }
141 
143  void end() override {
144  p_driver->end();
145  if (p_magnitudes!=nullptr) delete []p_magnitudes;
146  }
147 
149  size_t write(const uint8_t*data, size_t len) override {
150  size_t result = 0;
151  if (p_driver->isValid()){
152  result = len;
153  switch(cfg.bits_per_sample){
154  case 16:
155  processSamples<int16_t>(data, len/2);
156  break;
157  case 24:
158  processSamples<int24_t>(data, len/3);
159  break;
160  case 32:
161  processSamples<int32_t>(data, len/4);
162  break;
163  default:
164  LOGE("Unsupported bits_per_sample: %d",cfg.bits_per_sample);
165  break;
166  }
167  }
168  return result;
169  }
170 
172  int availableForWrite() override {
173  return cfg.bits_per_sample/8*cfg.length;
174  }
175 
177  int size() {
178  return bins;
179  }
180 
182  int length() {
183  return cfg.length;
184  }
185 
187  unsigned long resultTime() {
188  return timestamp;
189  }
191  unsigned long resultTimeBegin() {
192  return timestamp_begin;
193  }
194 
196  float frequency(int bin){
197  if (bin>=bins){
198  LOGE("Invalid bin %d", bin);
199  return 0;
200  }
201  return static_cast<float>(bin) * cfg.sample_rate / cfg.length;
202  }
203 
206  AudioFFTResult ret_value;
207  ret_value.magnitude = 0;
208  ret_value.bin = 0;
209  // find max value and index
210  for (int j=0;j<size();j++){
211  float m = magnitude(j);
212  if (m>ret_value.magnitude){
213  ret_value.magnitude = m;
214  ret_value.bin = j;
215  }
216  }
217  ret_value.frequency = frequency(ret_value.bin);
218  return ret_value;
219  }
220 
221 
223  template<int N>
225  // initialize to negative value
226  for (int j=0;j<N;j++){
227  result[j].magnitude = -1000000;
228  }
229  // find top n values
230  AudioFFTResult act;
231  for (int j=0;j<size();j++){
232  act.magnitude = magnitude(j);
233  act.bin = j;
234  act.frequency = frequency(j);
235  insertSorted<N>(result, act);
236  }
237  }
238 
241  return p_driver;
242  }
243 
245  float magnitude(int bin){
246  if (bin>=bins){
247  LOGE("Invalid bin %d", bin);
248  return 0;
249  }
250  return p_driver->magnitude(bin);
251  }
252 
253  float magnitudeFast(int bin){
254  if (bin>=bins){
255  LOGE("Invalid bin %d", bin);
256  return 0;
257  }
258  return p_driver->magnitudeFast(bin);
259  }
261  float* magnitudes() {
262  if (p_magnitudes==nullptr){
263  p_magnitudes = new float[size()];
264  }
265  for (int j=0;j<size();j++){
266  p_magnitudes[j]= magnitude(j);
267  }
268  return p_magnitudes;
269  }
270 
272  float* magnitudesFast() {
273  if (p_magnitudes==nullptr){
274  p_magnitudes = new float[size()];
275  }
276  for (int j=0;j<size();j++){
277  p_magnitudes[j]= magnitudeFast(j);
278  }
279  return p_magnitudes;
280  }
281 
284  return cfg;
285  }
286 
287  protected:
288  FFTDriver *p_driver=nullptr;
289  int current_pos = 0;
290  AudioFFTConfig cfg;
291  unsigned long timestamp_begin=0l;
292  unsigned long timestamp=0l;
293  RingBuffer<uint8_t> stride_buffer{0};
294  float *p_magnitudes = nullptr;
295  int bins = 0;
296 
297 
298  // Add samples to input data p_x - and process them if full
299  template<typename T>
300  void processSamples(const void *data, size_t samples) {
301  T *dataT = (T*) data;
302  T sample;
303  float sample_windowed;
304  for (int j=0; j<samples; j+=cfg.channels){
305  sample = dataT[j+cfg.channel_used];
306  p_driver->setValue(current_pos, windowedSample(sample));
307  writeStrideBuffer((uint8_t*)&sample, sizeof(T));
308  if (++current_pos>=cfg.length){
309  // perform FFT
310  fft<T>();
311 
312  // reprocess data in stride buffer
313  if (stride_buffer.size()>0){
314  // reload data from stride buffer
315  while (stride_buffer.available()){
316  T sample;
317  stride_buffer.readArray((uint8_t*)&sample, sizeof(T));
318  p_driver->setValue(current_pos, windowedSample(sample));
319  current_pos++;
320  }
321  }
322 
323  }
324  }
325  }
326 
327  template<typename T>
328  T windowedSample(T sample){
329  T result = sample;
330  if (cfg.window_function!=nullptr){
331  result = cfg.window_function->factor(current_pos) * sample;
332  }
333  return result;
334  }
335 
336  template<typename T>
337  void fft() {
338  timestamp_begin = millis();
339  p_driver->fft();
340  timestamp = millis();
341  if (cfg.callback!=nullptr){
342  cfg.callback(*this);
343  }
344  current_pos = 0;
345  }
346 
347  int bytesPerSample() {
348  return cfg.bits_per_sample / 8;
349  }
350 
352  template<int N>
354  // find place where we need to insert new record
355  for (int j=0;j<N;j++){
356  // insert when biggen then current record
357  if (tmp.magnitude>result[j].magnitude){
358  // shift existing values right
359  for (int i=N-2;i>=j;i--){
360  result[i+1] = result[i];
361  }
362  // insert new value
363  result[j]=tmp;
364  // stop after we found the correct index
365  break;
366  }
367  }
368  }
369 
370  void writeStrideBuffer(uint8_t* buffer, size_t len){
371  if (stride_buffer.size()>0){
372  int available = stride_buffer.availableForWrite();
373  if (len>available){
374  // clear oldest values to make space
375  int diff = len-available;
376  for(int j=0;j<diff;j++){
377  stride_buffer.read();
378  }
379  }
380  stride_buffer.writeArray(buffer, len);
381  }
382  }
383 
384  bool isPowerOfTwo(uint16_t x) {
385  return (x & (x - 1)) == 0;
386  }
387 
388 };
389 
390 
391 
392 }
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:82
unsigned long resultTimeBegin()
time before the fft
Definition: AudioFFT.h:191
float magnitude(int bin)
Calculates the magnitude of the fft result to determine the max value (bin is 0 to size())
Definition: AudioFFT.h:245
float * magnitudes()
Provides the magnitudes as array of size size(). Please note that this method is allocating additinal...
Definition: AudioFFT.h:261
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:187
int length()
The number of samples.
Definition: AudioFFT.h:182
void end() override
Release the allocated memory.
Definition: AudioFFT.h:143
AudioFFTBase(FFTDriver *driver)
Default Constructor. The len needs to be of the power of 2 (e.g. 512, 1024, 2048, 4096,...
Definition: AudioFFT.h:85
size_t write(const uint8_t *data, size_t len) override
Provide the audio data as FFT input.
Definition: AudioFFT.h:149
int availableForWrite() override
We try to fill the buffer at once.
Definition: AudioFFT.h:172
bool begin(AudioFFTConfig info)
starts the processing
Definition: AudioFFT.h:100
AudioFFTResult result()
Determines the result values in the max magnitude bin.
Definition: AudioFFT.h:205
float frequency(int bin)
Determines the frequency of the indicated bin.
Definition: AudioFFT.h:196
void insertSorted(AudioFFTResult(&result)[N], AudioFFTResult tmp)
make sure that we do not reuse already found results
Definition: AudioFFT.h:353
void setAudioInfo(AudioInfo info) override
Notify change of audio information.
Definition: AudioFFT.h:135
void reset()
Just resets the current_pos e.g. to start a new cycle.
Definition: AudioFFT.h:123
FFTDriver * driver()
provides access to the FFTDriver which implements the basic FFT functionality
Definition: AudioFFT.h:240
AudioFFTConfig & config()
Provides the actual configuration.
Definition: AudioFFT.h:283
AudioFFTConfig defaultConfig()
Provides the default configuration.
Definition: AudioFFT.h:94
void resultArray(AudioFFTResult(&result)[N])
Determines the N biggest result values.
Definition: AudioFFT.h:224
int size()
The number of bins used by the FFT which are relevant for the result.
Definition: AudioFFT.h:177
float * magnitudesFast()
Provides the magnitudes w/o calling the square root function as array of size size()....
Definition: AudioFFT.h:272
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:65
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
virtual T read()
reads a single value
Definition: Buffers.h:305
virtual int availableForWrite()
provides the number of entries that are available to write
Definition: Buffers.h:365
virtual int available()
provides the number of entries that are available to read
Definition: Buffers.h:362
virtual size_t size()
Returns the maximum capacity of the buffer.
Definition: Buffers.h:379
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
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AnalogAudio.h:10
uint32_t millis()
Returns the milliseconds since the start.
Definition: Millis.h:18
Configuration for AudioFFT. If there are more then 1 channel the channel_used is defining which chann...
Definition: AudioFFT.h:43
uint8_t channel_used
Channel which is used as input.
Definition: AudioFFT.h:52
WindowFunction * window_function
Optional window function.
Definition: AudioFFT.h:56
void(* callback)(AudioFFTBase &fft)
Callback method which is called after we got a new result.
Definition: AudioFFT.h:50
Result of the FFT.
Definition: AudioFFT.h:22
Basic Audio information which drives e.g. I2S.
Definition: AudioTypes.h:50
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition: AudioTypes.h:53
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition: AudioTypes.h:55
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition: AudioTypes.h:57