Using FFT/FHT for a visualizer

Hi everyone,
I’ve been looking for ways to create a (single channel) multi-band audio visualizer on an I2C OLED display that I bought, and I came across this IC from sparkfun that essentially takes an analog audio input and converts it to a DC representation of a set of particular frequencies.
I was wondering if I could essentially recreate this in code, using the A0 input on the arduino that I have. I’ve looked into using things like FFT and FHT, and while useful, seemed very complicated and a bit out of my understanding. I attempted to create a demo visualizer using the FHT library, however the set of outputs that I received from FHT just seemed to be completely inaccurate, possibly meaning that I’m doing something wrong in my code. If someone would take a look at it, that’d be great:

#define LOG_OUT 1 // use the log output function
#define FHT_N 256 // set to 256 point fht

#include <FHT.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_SSD1306 display(6);
int freqs[8] = {0,0,0,0,0,0,0,0};

void setup() {
  Serial.begin(115200)
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  TIMSK0 = 0; // turn off timer0 for lower jitter
  ADCSRA = 0xe5; // set the adc to free running mode
  ADMUX = 0x40; // use adc0
  DIDR0 = 0x01; // turn off the digital input for adc0
}

void loop() {
  
  while(1) { // reduces jitter
    cli();  // UDRE interrupt slows this way down on arduino1.0
    for (int i = 0 ; i < FHT_N ; i++) { // save 256 samples
      while(!(ADCSRA & 0x10)); // wait for adc to be ready
      ADCSRA = 0xf5; // restart adc
      byte m = ADCL; // fetch adc data
      byte j = ADCH;
      int k = (j << 8) | m; // form into an int
      k -= 0x0200; // form into a signed int
      k <<= 6; // form into a 16b signed int
      fht_input[i] = k; // put real data into bins
    }
    fht_window(); // window the data for better frequency response
    fht_reorder(); // reorder the data before doing the fht
    fht_run(); // process the data in the fht
    fht_mag_log(); // take the output of the fht
    sei();

    display.clearDisplay(); 
    
    memset(freqs, 0, sizeof(freqs));
    
    for(int i = 0; i< sizeof(fht_log_out); i++){ //add freq to range pool
      Serial.print(String(fht_log_out[i]) + " ");
     
      freqs[i/16] += fht_log_out[i];
    }
    for(int i = 0; i< 8; i++){
      display.fillRect(i*16,0,16,freqs[i]/16,1); //take avg freq of each pool
    }
    display.display();
  }
}

The FHT from OpenMusicLabs works perfectly. How would you know from your example that the output “seemed to be completely inaccurate”, unless you have a standard to compare it to?

When testing unfamiliar code, you need to prepare an example where you know what to expect, and make sure that the results are as expected. For example, to test the FFT routines, I wrote the following, and it works fine. Try it.

/*
 fft_test_sine
 example sketch for testing the fft library.
 This generates a simple sine wave data set consisting
 of two sine waves of frequency f1 and f2, transforms it, calculates 
 and prints the amplitude of the transform.
 */

// do #defines BEFORE #includes
#define LIN_OUT 1 // use the lin output function
#define FFT_N 64 // set to 64 point fft

#include <FFT.h> // include the library

void setup() {
  Serial.begin(9600); // output on the serial port
}

void loop() {
  int i,k;
  float f1=2.0,f2=5.0;  //the two input frequencies (bin values)
  for (i = 0 ; i < FFT_N ; i++) { // create samples
    // amplitudes are 1000 for f1 and 500 for f2
    k=1000*sin(2*PI*f1*i/FFT_N)+500.*sin(2*PI*f2*i/FFT_N);
    fft_input[2*i] = k; // put real data into even bins
    fft_input[2*i+1] = 0; // set odd bins to 0
  }
  
  fft_window();  //Try with and without this line, it smears

  fft_reorder(); // reorder the data before doing the fft
  fft_run(); // process the data using the fft
  fft_mag_lin(); // calculate the magnitude of the output

  // print the frequency index and amplitudes

  Serial.println("bin  amplitude");
  for (i=0; i<FFT_N/2; i++) {
    Serial.print(i);
    Serial.print("       ");
    Serial.println(2*fft_lin_out[i]); //*2 for "negative frequency" amplitude
  }
  Serial.println("Done");
  while(1); //wait here
}

PS: don’t forget the Sampling Theorem – ignoring the rules can make the results nearly uninterpretable.

Finally, what is the following code supposed to do?

memset(freqs, 0, sizeof(freqs));
   
    for(int i = 0; i< sizeof(fht_log_out); i++){ //add freq to range pool
      Serial.print(String(fht_log_out[i]) + " ");
     
      freqs[i/16] += fht_log_out[i];
    }

That lower code block there is used to firstly reset the freqs array back to a set of zeroes, then fit all of the (128, I believe) sets of outputs from FHT into a set of 8 different pools of bands. Later on in the code I divide to basically get the average value of each of the outputs added together before.

As for the FHT library itself, I'm sure it works perfectly - I'm just not sure what I'm doing wrong with either the setup or the way I'm reading the outputs. What I'm getting so far (on my display, and by extension, my array) is a set of bands that move independently, whether or not an audio input is connected. I'm not using any amplification yet, but the source I'm using should be strong enough to at least produce some visible change, which it isn't. Perhaps I'm missing some use of that sampling theorem you mentioned, but I'm not completely sure.

Looking over at some examples, would the solution be to use fht_mag_octave() at this point?