Good day, everyone. Over the past couple weeks I’ve been tinkering with my Arduino Mega to make a music visualizer. You can see it in action here.
Using the FFT library, the Arduino takes in sound from an AUX cord and finds the dominant frequency. Depending on the frequency, a color is assigned; e.g. 55-100 Hz is blue, 100-175 Hz is purple, 175-250 Hz is red. Eight LEDs are set to that color and then shifted down the strip. So far I like how it looks and after posting it online, I went back and added in a feature to cycle between different color palettes.
I’m posting this because I have several things I’d like to discuss:
-
I’d like to speed up the movement of the lights. If I remove the fourier transforms, I can get the lights to travel much faster. So I know the FFT is taking a lot of computing time, which slows down the propagation of the lights. But I need to find the dominant frequency somehow. I’ve heard that autocorrelation is another method of doing so. Would it be dramatically faster?
-
After watching the video, do you have any ideas/tips/suggestions for additional features, movements, etc.?
-
Is there any way to optimize my own code so it runs faster?
I conduct 64 samples per second (sps) across 1000 Hz. It’s precise enough, but increasing this to 128 sps improves the precision for sure. Increasing any higher yields negligible results and is simply overkill. Decreasing the sps to 32 reduces the accuracy drastically. That’s not a viable option for improving performance.
If this is the wrong forum for this then I apologize.
Here’s my code if anyone’s interested:
#include "arduinoFFT.h"
#include "FastLED.h"
#define SAMPLES 64 //must be a power of 2
#define SAMPLING_FREQUENCY 1000 //Hz, must be less than 9615Hz due to ADC
#define NUM_LEDS 300
#define LED_PIN 6
#define updateLEDS 8 // How many do you want to update every millisecond?
CRGB leds[NUM_LEDS];
CRGBPalette16 currentPalette;
TBlendType currentBlending; //Optional
arduinoFFT FFT=arduinoFFT();
unsigned int sampling_period_us;
unsigned long microseconds;
double vReal[SAMPLES];
double vImag[SAMPLES];
void setup() {
//Serial.begin(115200);
sampling_period_us = round(1000000*(1.0/SAMPLING_FREQUENCY));
FastLED.addLeds<NEOPIXEL, LED_PIN>(leds, NUM_LEDS);
}
void loop() {
/*SAMPLING*/
for(int i=0; i<SAMPLES; i++){
microseconds = micros(); //Overflows after ~70 minutes
vReal[i] = analogRead(0);
vImag[i] = 0;
while(micros() < (microseconds + sampling_period_us)){
}
}
//FFT
FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
double peak = FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);
//Serial.println(peak); //Print out what frequency is the most dominant. Use this to test accurate sampling
//Rotate through different palettes so people don't get bored
ChangePalettePeriodically();
//SetupSimulationTheoryPalette(); //Or uncomment this line to get the Muse palette
// Shift all LEDs to the right by updateLEDS number each time
for(int i = NUM_LEDS - 1; i >= updateLEDS; i--) {
leds[i] = leds[i - updateLEDS];
}
// Set the left-most updateLEDs with the new color
uint8_t colorIndex=255;
if(peak>55 && peak<100){
colorIndex=0;
}
else if(peak>100 && peak<175){
colorIndex=16;
}
else if(peak>175 && peak<250){
colorIndex=32;
}
else if(peak>250 && peak<325){
colorIndex=48;
}
else if(peak>325 && peak<400){
colorIndex=64;
}
else if(peak>400 && peak<475){
colorIndex=80;
}
else if(peak>475){
colorIndex=96;
}
else{
colorIndex=255; //turn off LEDs, i.e. make color black
}
FillLEDsFromPaletteColors(colorIndex);
FastLED.show();
}
void FillLEDsFromPaletteColors(uint8_t colorIndex){
//Brightness can go up to 255, but then needs more current
uint8_t brightness=64;
for(int i=0; i<updateLEDS; i++) {
if(colorIndex==255){
leds[i]=CRGB::Black;
}
else{
leds[i]=ColorFromPalette(currentPalette, colorIndex, brightness, currentBlending);
}
}
}
void ChangePalettePeriodically(){
uint8_t secondHand = (millis() / 1000) % 60;
static uint8_t lastSecond = 99;
if(lastSecond != secondHand) {
lastSecond = secondHand;
if(secondHand == 0) { SetupSimulationTheoryPalette(); }
if(secondHand == 15) { SetupPurpleAndGreenPalette(); }
if(secondHand == 30) { SetupBlackAndWhiteStripedPalette(); }
if(secondHand == 45) { SetupBlueAndYellowPalette(); }
}
}
//CAREFUL with this one. White LEDs consume much more current
//The LEDs at the end of the strip aren't fully white due to the voltage drop along the strip
//This might occur with all color schemes, but it's very noticeable with the color white
void SetupBlackAndWhiteStripedPalette(){
// 'white out' all 16 palette entries...
fill_solid(currentPalette, 16, CRGB::White);
currentPalette[3] = CRGB::Black; //so not all 300 LEDs are white at the same time
}
void SetupPurpleAndGreenPalette(){
CRGB purple = CHSV(HUE_PURPLE, 255, 255);
CRGB green = CHSV(HUE_GREEN, 255, 255);
CRGB black = CRGB::Black;
currentPalette = CRGBPalette16(
purple, green, purple, green,
purple, green, purple, green,
green, purple, green, purple,
green, purple, black, black );
}
void SetupSimulationTheoryPalette(){
CRGB blue = 0x2F00D0;
CRGB blueViolet = 0x5F00A1;
CRGB brightBlue = 0x0007F9;
CRGB clearViolet = 0x5500AB;
CRGB violetPink = 0x84007C;
CRGB pink = 0xB5004B;
CRGB pinkViolet = 0xC2003E;
CRGB black = CRGB::Black;
currentPalette = CRGBPalette16(
blue, blueViolet, brightBlue, clearViolet,
violetPink, pink, pinkViolet, black,
black, black, black, black,
black, black, black, black);
}
void SetupBlueAndYellowPalette(){
CRGB blue = CRGB::Blue;
CRGB yellow = CRGB::Yellow;
CRGB black = CRGB::Black;
currentPalette = CRGBPalette16(
yellow, blue, yellow, blue,
yellow, blue, yellow, blue,
yellow, blue, yellow, blue,
yellow, blue, black, black );
}