How to decrease size of bins using FFT.h

I'm working on a spectrum analyzer and it works great, except for the fact that my bins are 150Hz in size, so the first three bins (I skipped the first bin, so 150-600Hz) combine all of the beat/bass frequencies, so my display isn't as impressive. The LEDs for these three bins don't even fluctuate with the beat that well, so I was hoping for some help diagnosing that.

Anyways, I was wondering how I could decrease the sampling rate using FFT.h such that my bins will be a much smaller size so that I will have more LEDs to display the lower bass frequencies.

Below is my code relevant to the FFT.

#define LIN_OUT 1 // use the log output function
#define FFT_N 256 // set to 256 point fft
#define DC_OFFSET 511

#include <FFT.h>  //FFT library

void setup() {
  delay(1000);  //soft startup to ease flow of electrons

  LEDS.addLeds<LED_TYPE, LED_DT, COLOR_ORDER>(leds, NUM_LEDS);
  FastLED.setBrightness(100);

  TIMSK0 = 0; // turn off timer0 for lower jitter - delay() and millis() killed
  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 < 512 ; i += 2) { // 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 -= DC_OFFSET;
      adc_val = k<<6;
      fft_input[i] = adc_val; // put real data into even bins
      fft_input[i + 1] = 0; // set odd bins to 0
    }
    // window data, then reorder, then run, then take output
    fft_window(); // window the data for better frequency response
    fft_reorder(); // reorder the data before doing the fft
    fft_run(); // process the data in the fft
    fft_mag_lin(); // take the output of the fft
    sei(); // turn interrupts back on

    fft_lin_out[1] -= 10; //adjust for values in bin when silent (unknown cause)
}

Do you have room to double the number of bins in your FFT?

Another choice might be to throw out every other sample when sampling the audio. I think that will cut your sample frequency in half, making each bin half as wide (75 hz). You could alternate between full sampling and half sampling, using full for upper frequencies and half for lower frequencies.

johnwasser:
Do you have room to double the number of bins in your FFT?

That library supports a maximum of 256 bins.

With this line you are calculating the amplitude of the transform, which is not simply related to audio volume.

    fft_mag_lin(); // take the output of the fft

Change the above to

    fft_mag_log();

to get audio volume bins. You may have to rescale the output for your display.

You can reduce the ADC sample frequency (38.4 kHz in the posted code), which will reduce the highest frequency bin to (sample frequency)/2, thus narrowing the bin width.

The next lowest sample rate requires you to change ADCSRA to

  ADCSRA = 0xe6; // set the adc to free running mode, 19.2 kHz

However, you must make certain that no frequencies higher than (sample frequency)/2 =9.6 kHz are present in the input, or the resulting spectrum may become uninterpretable due to aliasing. With music input, you will need a good low pass filter on the input to accomplish that.

jremington:
With this line you are calculating the amplitude of the transform, which is not simply related to audio volume.

    fft_mag_lin(); // take the output of the fft

Change the above to

    fft_mag_log();

to get audio volume bins. You may have to rescale the output for your display.

What is the difference between amplitude and volume in the context of the spectrum analyzer? Does that make it easier for beat detection if I get audio volume bins?

In any case, I tested fft_mag_log, but there was a lot of noise present in the bins, so I'm not sure if I can use it (output data attached).

jremington:
You can reduce the ADC sample frequency (38.4 kHz in the posted code), which will reduce the highest frequency bin to (sample frequency)/2, thus narrowing the bin width.

The next lowest sample rate requires you to change ADCSRA to

  ADCSRA = 0xe6; // set the adc to free running mode, 19.2 kHz

However, you must make certain that no frequencies higher than (sample frequency)/2 =9.6 kHz are present in the input, or the resulting spectrum may become uninterpretable due to aliasing. With music input, you will need a good low pass filter on the input to accomplish that.

Lowering the sampling rate would definitely help. However, I'm getting strange outputs. When I play a 1000Hz tone, the frequency output says 500Hz. This wasn't a problem before I halved the sampling rate. Also, some of the bins near the frequency bin read 0. This problem didn't show up with a sampling rate of 38.4kHz either. (output below)

FFTlog.txt (4.46 KB)

FFTlin.txt (4.4 KB)

Honestly, even with a sampling rate of 19.2kHz the bass beats only occupy the first 2-3 columns, so it's not really worth displaying, unless it is possible to have the bin size be non-constant (eg low frequency bin sizes are 10Hz, higher freq bin size 75Hz), or have small bin sizes but over a wide frequency range (eg all bin sizes 10Hz, but that would mean a range of 256Hz).

If there are any other ways to show more bass on the spectrum analyzer, I am very open to suggestions.

Human hearing has logarithmic response in volume, a signal with amplitude of 10X sounds "twice as loud". The log output scale allows you to visualize volume of signal rather than amplitude.

Does that make it easier for beat detection if I get audio volume bins?

Experiment to find out, but I would guess not. I think beat detection is not simple.

When I play a 1000Hz tone, the frequency output says 500Hz.

You forgot to change the code to reflect the new sample frequency.

If you need more bins, use a different FFT package, and a faster Arduino with more memory. The PJRC Teensy series comes to mind, but is more challenging to use.

jremington:
Human hearing has logarithmic response in volume, a signal with amplitude of 10X sounds "twice as loud". The log output scale allows you to visualize volume of signal rather than amplitude.

Assuming I can get rid of the large noise values in the log bins, what would this mean in terms of the display? D Do the heights of the columns increase logarithmically with the volume?

Do the heights of the columns increase logarithmically with the volume?

No. When you use fft_mag_log() the heights of the columns are proportional to volume, which increases logarithmically with amplitude.

jremington:
No. When you use fft_mag_log() the heights of the columns are proportional to volume, which increases logarithmically with amplitude.

Ok, so it sounds like fft_mag_log() is the way to go. Do you know why there are such large noise values when silent? Perhaps that is why all of the FFT spectrum analyzer examples use fft_mag_lin().

I did not say that using log is "the way to go", I suggested that it is an option to try. Do whatever you like!

The noise level has not changed, but the volume representation of the various levels gives a different visual impression.