Project tutorial
FHT Audio Spectrum Visualizer

FHT Audio Spectrum Visualizer © GPL3+

This project is similar to my previous one but uses the FHT library which turns out to be at least 4 times faster than the FFT.

  • 2 views
  • 0 comments
  • 0 respects

Components and supplies

Necessary tools and machines

About this project

The Arduino FHT library

FHT does exactly the same job as the more well-known FFT but unlike this one it uses only real data, while FFT works on complex data, therefore FHT uses half processing power and half system memory.

This library uses a single array for data sampling instead of two, one for real values ​​and one for imaginary values, used by FFT lib, it also simplifies complex mathematical calculations avoiding loops with complex floating point logarithmic calculations and instead uses some numerical tables.

The downside is that it always returns half the values ​​of the size of the array used to perform the sampling, this leads to a certain loss of resolution and precision at the two ends of the audio range. However, in the specific case we do not have to develop a measuring instrument but a simple gadget, we do not need extreme precision, we just have to turn on a handful of LEDs in musical rhythm.

Of course this is not the right place to discuss the modality operation of the FHT library, anyone wishing to learn more can refer to the Open Music Lab website where they will find all the necessary documentation to understand the tricks used, the detailed explanation of the various functions and some generic examples already written for Arduino.

The FFT library is fast, so fast that it was necessary to insert a delay in the code to reduce flicker. You will immediately notice the final result, the display is much faster and more responsive than the version that uses the FFT library which often seemed to be lagging behind the music. Less use of system resources allowed us to add some lines of code to manage additional functions.

The New Version

The project is identical to the previous version, so those who have already made the previous one need only add a button and a 10K resistor and update the code.

In addition to the implementation of the FHT library, this version has the following additional functions:

  • has an additional button for brightness adjustment;
  • allows saving color and brightness settings in the EEPROM in order to reload them the next time it is turned on;
  • displays color and brightness settings level when buttons are pressed and when turned on (can be disabled).

Code

FHT Code VersionArduino
/*
  Copyright (c) 2020 Janux

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.

  Based on an original project for the MAX72xx LED matrix and FFT lib made from Shajeeb.
  Configuration settings section based on work of Ragnar Ranøyen Homb from Norvegian Creation.
*/

#define LIN_OUT 1                //FHT linear output magnitude
#define FHT_N 128                //set SAMPLES for FHT, Must be a power of 2
#include <FHT.h>

#define xres 32                 //Total number of columns in the display, must be <= SAMPLES/2
#define yres 8                  //Total number of rows in the display
#define ledPIN 6                //out pint to control Leds
#define NUM_LEDS (xres * yres)  //total leds in Matrix
#include <Adafruit_NeoPixel.h>

#define colorPIN 5        //pin to change ledcolor
#define brightnessPIN 10  //pin to change brightness

byte displaycolor = 0;    //default color value
byte brightness = 1;      //default brightness level

#include <EEPROM.h>
#define CONFIG_START 32         //Memory start location
#define CONFIG_VERSION "VER01"  //Config version configuration

typedef struct {
  char version[6];
  byte displaycolor;
  byte brightness;
} configuration_type;

configuration_type CONFIGURATION = {
  CONFIG_VERSION,
  displaycolor,
  brightness
};

byte yvalue;
int peaks[xres];
byte state = HIGH;                    // the current reading from the input pin
byte previousState = LOW;             // the previous reading from the input pin
unsigned long lastDebounceTime = 0;   // the last time the output pin was toggled
unsigned long debounceDelay = 100;    // the debounce time; increase if the output flickers

byte data_avgs[xres]; //Array for samplig

// Parameter 1 = number of leds in matrix
// Parameter 2 = pin number
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(NUM_LEDS, ledPIN, NEO_GRB + NEO_KHZ800);

// EQ filter
byte eq[32] = {
  60, 65, 70, 75, 80, 85, 90, 95,
  100, 100, 100, 100, 100, 100, 100, 100,
  100, 100, 100, 100, 100, 100, 100, 100,
  115, 125, 140, 160, 185, 200, 200, 200
};

bool EQ_ON = true; // set to false to disable eq

//Define 5 set of colors for leds, 0 for single custom color
byte colors[][8] = {
  {170, 160, 150, 140, 130, 120, 1, 1},
  {1, 5, 10, 15, 20, 25, 90, 90},
  {90, 85, 80, 75, 70, 65, 1, 1},
  {90, 90, 90, 30, 30, 30, 1, 1},
  {170, 160, 150, 140, 130, 120, 110, 0}
};

//Define chars for display settings
byte charBitmap[] = {
  0x1C, 0x10, 0x10, 0x10, 0x10, 0x1C, 0x08, 0x18, 0x08, 0x08, 0x08, 0x1C,
  0x0C, 0x12, 0x04, 0x08, 0x10, 0x1E, 0x0C, 0x12, 0x02, 0x06, 0x12, 0x0C,
  0x10, 0x10, 0x10, 0x14, 0x1E, 0x04, 0x1E, 0x10, 0x1E, 0x02, 0x12, 0x0C,
  0x1E, 0x10, 0x10, 0x1E, 0x12, 0x1E, 0x1E, 0x02, 0x04, 0x08, 0x08, 0x08,
  0x0C, 0x12, 0x0C, 0x12, 0x12, 0x0C, 0x1C, 0x12, 0x1C, 0x12, 0x12, 0x1C
};

void setup() {

  pixel.begin();           //initialize Led Matrix

  //Begin FFT operations
  ADCSRA = 0b11100101;    // set ADC to free running mode and set pre-scaler to 32 (0xe5)
  ADMUX =  0b00000000;    // use pin A0 and external voltage reference

  // Read config data from EEPROM
  if (loadConfig()) {
    displaycolor = CONFIGURATION.displaycolor;
    brightness = CONFIGURATION.brightness;
  }

  //Set brightness loaded from EEPROM
  pixel.setBrightness(brightness * 24 + 8);

  //Show current config on start
  //change true to false if you don't want this
  showSettings(3, true);
}

void loop() {
  while (1) {            // reduces jitter
    Sampling();          // FHT Library use only one data array
    RearrangeFHT();      // re-arrange FHT result to match with no. of display columns
    SendToDisplay();     // send to display according measured value
    colorChange();       // check if button pressed to change color
    brightnessChange();  // check if button pressed to change brightness
    delay(10);           // delay to reduce flickering (FHT is too fast :D)
  }
}

void Sampling() {
  for (int i = 0; i < FHT_N; i++) {
    while (!(ADCSRA & 0x10));   // wait for ADC to complete current conversion ie ADIF bit set
    ADCSRA = 0b11110101 ;       // clear ADIF bit so that ADC can do next operation (0xf5)
    //ADLAR bit is 0, so the 10 bits of ADC Data registers are right aligned
    byte m = ADCL;              // fetch adc data
    byte j = ADCH;
    int value = (j << 8) | m;   // form into an int
    value -= 0x0200;            // form into a signed int
    value <<= 6;                // form into a 16b signed int
    fht_input[i] = value / 8;   // copy to fht input array after compressing
  }
  // ++ begin FHT data process -+-+--+-+--+-+--+-+--+-+--+-+--+-+-
  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_lin();   // take the output of the fht
}

void RearrangeFHT() {
  // FHT return real value unsing only one array
  // after fht_mag_lin() calling the samples value are in
  // the first FHT_N/2 position of the array fht_lin_out[]
  int step = (FHT_N / 2) / xres;
  int c = 0;
  for (int i = 0; i < (FHT_N / 2); i += step) {
    data_avgs[c] = 0;
    for (int k = 0 ; k < step ; k++) {
      data_avgs[c] = data_avgs[c] + fht_lin_out[i + k];  // linear output magnitude
    }
    data_avgs[c] = data_avgs[c] / step ; // save avgs value
    c++;
  }
}

void SendToDisplay() {
  for (int i = 0; i < xres; i++) {
    if (EQ_ON)
      data_avgs[i] = data_avgs[i] * (float)(eq[i]) / 100; // apply eq filter
    data_avgs[i] = constrain(data_avgs[i], 0, 80);        // set max & min values for buckets to 0-80
    data_avgs[i] = map(data_avgs[i], 0, 80, 0, yres);     // remap averaged values to yres 0-8
    yvalue = data_avgs[i];
    peaks[i] = peaks[i] - 1;                              // decay by one light
    if (yvalue > peaks[i]) peaks[i] = yvalue;             // save peak if > previuos peak
    yvalue = peaks[i];                                    // pick peak to display
    setColumn(i, yvalue);                                 // draw columns
  }
  pixel.show();                                           // show column
}

// Light up leds of x column according to y value
void setColumn(byte x, byte y) {
  int led, i;

  for (i = 0; i < yres; i++) {
    led = GetLedFromMatrix(x, i); //retrieve current led by x,y coordinates
    if (peaks[x] > i) {

      switch (displaycolor) {
        case 4:
          if (colors[displaycolor][i] == 0) {
            // show custom color with zero value in array
            pixel.setPixelColor(led, 255, 255, 255); //withe
          }
          else {
            // standard color defined in colors array
            pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
          }
          break;

        case 5:
          //change color by column
          pixel.setPixelColor(led, Wheel(x * 16));
          break;

        case 6:
          //change color by row
          pixel.setPixelColor(led, Wheel(i * y * 3));
          break;

        case 7:
          //change color by... country :D

          //Italy flagh
          //if (x < 11) pixel.setPixelColor(led, 0, 255, 0);
          //if (x > 10 && x < 21) pixel.setPixelColor(led, 255, 255, 255);
          //if (x > 20) pixel.setPixelColor(led, 255, 0, 0);

          //stars and stripes
          if (i < yres - 2) {
            if (x & 0x01) {
              pixel.setPixelColor(led, 0, 0, 255);
            }
            else {
              pixel.setPixelColor(led, 255, 0, 0);
            }
          }
          else {
            pixel.setPixelColor(led, 255, 255, 255);
          }

          break;

        default:
          //display colors defined in color array
          pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
      }   //END SWITCH
    }
    else {
      //Light off leds
      pixel.setPixelColor(led, pixel.Color(0, 0, 0));
    }
  }
}

//================================================================
// Calculate a led number by x,y coordinates
// valid for WS2812B with serpentine layout placed in horizzontal
// and zero led at bottom right (DIN connector on the right side)
// input value: x= 0 to xres-1 , y= 0 to yres-1
// return a led number from 0 to NUM_LED
//================================================================
int GetLedFromMatrix(byte x, byte y) {
  int led;
  x = xres - x - 1;
  if (x & 0x01) {
    //Odd columns increase backwards
    led = ((x + 1) * yres - y - 1);
  }
  else {
    //Even columns increase normally
    led = ((x + 1) * yres - yres + y);
  }
  return constrain(led, 0, NUM_LEDS);
}
//================================================================

void colorChange() {
  int reading = digitalRead(colorPIN);
  if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) {
    displaycolor++;
    if (displaycolor > 7) displaycolor = 0;
    showSettings(1, true); //set to false if you don't want this
    saveConfig();
    lastDebounceTime = millis();
  }
  previousState = reading;
}

void brightnessChange() {
  int reading = digitalRead(brightnessPIN);
  if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) {
    brightness++;
    if (brightness > 7) brightness = 0;
    pixel.setBrightness(brightness * 24 + 8);
    showSettings(2, true); //set to false if you don't want this
    saveConfig();
    lastDebounceTime = millis();
  }
  previousState = reading;
}

// Utility from Adafruit Neopixel demo sketch
// Input a value 0 to 255 to get a color value.
// The colours are a transition R - G - B - back to R.
unsigned long Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return pixel.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return pixel.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return pixel.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

// load whats in EEPROM in to the local CONFIGURATION if it is a valid setting
int loadConfig() {
  if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] &&
      EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] &&
      EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2] &&
      EEPROM.read(CONFIG_START + 3) == CONFIG_VERSION[3] &&
      EEPROM.read(CONFIG_START + 4) == CONFIG_VERSION[4]) {

    // load (overwrite) the local configuration struct
    for (unsigned int i = 0; i < sizeof(CONFIGURATION); i++) {
      *((char*)&CONFIGURATION + i) = EEPROM.read(CONFIG_START + i);
    }
    return 1; // return 1 if config loaded
  }
  return 0; // return 0 if config NOT loaded
}

// save the CONFIGURATION in to EEPROM
void saveConfig() {
  CONFIGURATION.displaycolor = displaycolor;
  CONFIGURATION.brightness = brightness;
  for (unsigned int i = 0; i < sizeof(CONFIGURATION); i++)
    EEPROM.write(CONFIG_START + i, *((char*)&CONFIGURATION + i));
}

// 1 display color level, 2 display brightness level, 3 both
void showSettings(byte num, bool show) {
  if (show) {
    pixel.clear();
    if (num == 1 || num == 3) {
      drawChar(0, 0);
      drawChar(displaycolor + 1, 5);
    }
    if (num == 2 || num == 3) {
      drawChar(9, xres - 9);
      drawChar(brightness + 1, xres - 4);
    }
    delay(1000);
    pixel.clear();
  }
}

// Draw custom chars
void drawChar(byte val, byte pos) {
  for (int x = 4; x >= 0; x--) {
    for (int y = 5; y >= 0; y--) {
      if ((charBitmap[val * 6 + 5 - y] >> x) & 0x01) {
        pixel.setPixelColor(GetLedFromMatrix(4 - x + pos, y + 1), Wheel((pos > 10) * 170));
        pixel.show();
      }
    }
  }
}
//by Janux®, Last version on 28/06/2020.

Schematics

Schematic
Schematic idrixz6jtw
Breadboard
Breadboard 8ei5s70wbo

Comments

Similar projects you might like

RGB 32-Band Audio Spectrum Visualizer

Project tutorial by janux

  • 888 views
  • 0 comments
  • 2 respects

32-Band Audio Spectrum Visualizer Analyzer

Project showcase by shajeeb

  • 64,936 views
  • 122 comments
  • 191 respects

DIY FFT Audio Spectrum Analyzer

Project tutorial by Mirko Pavleski

  • 5,315 views
  • 1 comment
  • 8 respects

2 x 16-Band Audio Spectrum Analyzer with LCD

by shajeeb and ThomAce

  • 6,510 views
  • 0 comments
  • 10 respects

RGB Backlight + MSGEQ7 Audio Visualizer

Project tutorial by PhilKey

  • 15,829 views
  • 8 comments
  • 27 respects

RGB Matrix Audio Visualizer with Arduino

Project tutorial by Sridhar Rajagopal

  • 7,388 views
  • 9 comments
  • 27 respects
Add projectSign up / Login