FHT Audio Spectrum Visualizer – Bouw een indrukwekkend LED-audiodisplay
Onderdelen en benodigdheden
| Onderdeel | Aantal | Opmerking |
|---|---|---|
| Arduino Nano R3 | 1 | |
| WS2812B 8x32 RGB LED-matrix | 1 | 256 LED's, serpentine layout |
| Weerstand 4,75k ohm | 3 | |
| Weerstand 100k ohm | 2 | |
| Weerstand 10k ohm | 2 | Voor drukknoppen |
| Weerstand 390 ohm | 1 | Voor datalijn LED-matrix |
| Diode 1N4007 (of 1N4004) | 1 | Bescherming voeding |
| Condensator 100 nF | 2 | Decoupling |
| Condensator 1000 µF | 1 | Voedingstabilisatie |
| Drukknop 12mm | 2 | Kleur & helderheid |
| Prototype printplaat 4x6 cm | 1 | |
| Stereo RCA-vrouwelijke printconnector | 1 | Audio-in |
Benodigde tools en machines
- Solderbout
- 3,5mm audio jack kabel naar stereo RCA (voor aansturing)
Over dit project
De Arduino FHT-bibliotheek
FHT (Fast Hartley Transform) doet hetzelfde als de bekende FFT (Fast Fourier Transform), maar werkt alleen met reële data. FFT gebruikt complexe getallen. Daardoor is FHT tweemaal zo snel en gebruikt de helft van het geheugen.
De bibliotheek gebruikt één array voor sampling (in plaats van twee bij FFT), vereenvoudigt de wiskunde en vermijdt zware floating-point berekeningen door keertabellen te gebruiken.
Nadeel: FHT retourneert de helft van de array-grootte, wat resolutie aan de randen van het frequentiebereik vermindert. Voor een visuele gadget is dit prima – we willen LEDs op de muziek laten reageren, geen meetinstrument bouwen.
De FHT is zo snel dat een delay(10) in de loop nodig is om flikkeren te voorkomen. Het resultaat is veel vloeiender dan de FFT-versie.
De nieuwe versie
De hardware is identiek aan de vorige versie. Alleen een extra drukknop en 10k-weerstand worden toegevoegd, plus een code-update.
Nieuwe functies:
- Extra knop voor helderheidsinstelling.
- Instellingen (kleur & helderheid) worden opgeslagen in EEPROM en hersteld bij herstart.
- Visuele feedback bij indrukken knoppen en opstarten (uitschakelbaar).
Code
FHT Code Versie (Arduino)
/* Copyright (c) 2020 Janux - MIT License
Based on original MAX72xx/FFT project by Shajeeb.
Config section based on work by Ragnar Ranøyen Homb (Norwegian Creations). */
#define LIN_OUT 1
#define FHT_N 128
#include <FHT.h>
#define xres 32
#define yres 8
#define ledPIN 6
#define NUM_LEDS (xres * yres)
#include <Adafruit_NeoPixel.h>
#define colorPIN 5
#define brightnessPIN 10
byte displaycolor = 0;
byte brightness = 1;
#include <EEPROM.h>
#define CONFIG_START 32
#define CONFIG_VERSION "VER01"
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, previousState = LOW;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 100;
byte data_avgs[xres];
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(NUM_LEDS, ledPIN, NEO_GRB + NEO_KHZ800);
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;
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}
};
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();
ADCSRA = 0b11100101; // free running, prescaler 32
ADMUX = 0b00000000; // A0, externe referentie
if (loadConfig()) {
displaycolor = CONFIGURATION.displaycolor;
brightness = CONFIGURATION.brightness;
}
pixel.setBrightness(brightness * 24 + 8);
showSettings(3, true);
}
void loop() {
while (1) {
Sampling();
RearrangeFHT();
SendToDisplay();
colorChange();
brightnessChange();
delay(10);
}
}
void Sampling() {
for (int i = 0; i < FHT_N; i++) {
while (!(ADCSRA & 0x10));
ADCSRA = 0b11110101;
byte m = ADCL;
byte j = ADCH;
int value = (j << 8) | m;
value -= 0x0200;
value <<= 6;
fht_input[i] = value / 8;
}
fht_window(); fht_reorder(); fht_run(); fht_mag_lin();
}
void RearrangeFHT() {
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] += fht_lin_out[i + k];
data_avgs[c] /= step;
c++;
}
}
void SendToDisplay() {
for (int i = 0; i < xres; i++) {
if (EQ_ON) data_avgs[i] = data_avgs[i] * (float)eq[i] / 100;
data_avgs[i] = constrain(data_avgs[i], 0, 80);
data_avgs[i] = map(data_avgs[i], 0, 80, 0, yres);
yvalue = data_avgs[i];
peaks[i]--;
if (yvalue > peaks[i]) peaks[i] = yvalue;
yvalue = peaks[i];
setColumn(i, yvalue);
}
pixel.show();
}
void setColumn(byte x, byte y) {
for (byte i = 0; i < yres; i++) {
int led = GetLedFromMatrix(x, i);
if (peaks[x] > i) {
switch (displaycolor) {
case 4:
if (colors[displaycolor][i] == 0) pixel.setPixelColor(led, 255,255,255);
else pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
break;
case 5: pixel.setPixelColor(led, Wheel(x * 16)); break;
case 6: pixel.setPixelColor(led, Wheel(i * y * 3)); break;
case 7: // Vlaggenmodus
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: pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
}
} else pixel.setPixelColor(led, 0);
}
}
int GetLedFromMatrix(byte x, byte y) {
x = xres - x - 1;
int led;
if (x & 0x01) led = ((x + 1) * yres - y - 1);
else 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); 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); saveConfig(); lastDebounceTime = millis();
}
previousState = reading;
}
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);
}
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]) {
for (unsigned int i=0; i=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();
}
}
}
}
Schema's

Productieproces
- 32-band Arduino Audio Spectrum Analyzer – Lineaire & Pseudo-Log Display
- Slimme Plantenalarmer – DIY Sensor voor Optimale Hydratatie
- Bouw een Italiaanse Woordklok met Arduino Nano, NeoPixel, LCD en RTC
- Bouw een Betrouwbare Sigfox kWh Meter met Arduino MKR Fox 1200
- Bluetooth Temperatuurmeter met Arduino Nano
- Contactloos Gebaarslot: Veilige toegang met APDS-9960 en Arduino
- Arduino Nano Companion Kit – Essentiële Componenten & Hulpmiddelen voor DIY Electronica
- USB MIDI Adapter Kit voor Arduino Micro / Leonardo – Complete Onderdelenlijst
- Geïsoleerde analoge ingangsschakeling voor Arduino bouwen
- RGB 32-band audio-spectrumvisualisator – Arduino LED-matrixproject
- Precisie Reactietijd Test met Arduino UNO