Cómo hacer un analizador de espectro DIY
Ya sabes que vamos a hacer un analizador de espectro de audio, pero realmente ¿qué es y para qué sirve? Vamos a tener un micrófono al que le van a llegar las ondas de sonido. Cada una de estas ondas tiene una frecuencia diferente. Si la frecuencia es baja, el sonido es grave, y si la frecuencia es alta, el sonido es agudo.
Sin embargo, el micrófono recibe todas estas ondas con frecuencias diferentes y las une en una sola señal. El analizador de espectro lo que va a hacer es coger esta señal generada por el micro y obtener la frecuencia y la amplitud de cada onda de sonido que le ha llegado al micrófono.
Aunque un analizador de espectro tiene muchas aplicaciones, esto por ejemplo lo utilizan los ingenieros de sonido a la hora de hacer la mezcla cuando un cantante graba un disco.
Resumen del proyecto
Para poder obtener la frecuencia y amplitud de las ondas de sonido a partir de la señal eléctrica generada por el micrófono, es necesario aplicar la transformada rápida de Fourier (FFT). Esto requiere velocidad y un mínimo espacio de memoria, por lo que en vez de utilizar un Arduino Uno o Nano, vamos a utilizar una ESP8266.
En cuanto al micrófono, es el modelo MAX9814. Es capaz de detectar ondas de sonido en un rango de 20Hz a 20KHz, lo cual está muy bien. Nosotros vamos a dividir nuestro analizador de espectro en 10 columnas. Todas las columnas tienen 8 LEDs por lado, modelo WS2812B.
Cada columna comprenderá un rango de frecuencias. Se iluminarán más o menos LEDs dependiendo de la amplitud de las ondas de sonido cuya frecuencia se encuentre en el rango de frecuencias de la columna.
Además, no se si alguna vez has visto algún proyecto de espejo infinito, pero nuestro analizador de espectro también va a tener este efecto. Para conseguirlo ponemos un cristal con una película de espejo unidireccional delante de las columnas y un espejo detrás.
Lista de materiales
- Micrófono MAX9814
- NodeMCU ESP8266
- Tira LED WS2812B de 60 leds/m
- Cristal de 60×15 cm
- Espejo de 60×15 cm
- Película de espejo unidireccional
- Listo de madera de 60 cm.
- Perfil plano de aluminio para hacer las columnas
Hay algunos materiales a los que no les hemos puesto enlace. Eso significa que lo hemos comprado en una tienda local, como por ejemplo en una tienda de bricolaje y construcción.
Esquema eléctrico del analizador de espectro

El esquema eléctrico del analizador de espectro es muy sencillo. Por un lado tienes que conectar el pin «Out» a una entrada analógica. En nuestro caso hemos elegido la A0. Para alimentarlo puedes conectar el pin «V+» del micrófono a los 3.3V de la NodeMCU ESP8266 o a los 5V de la fuente de alimentación externa. «GND» a tierra, del microcontrolador o de la fuente, y los dos pines que sobran los dejas libres.
Por otro lado tienes que conectar la tira LED. Aparte de alimentarla a través de los pines «Vcc» y «GND», que se conectan a la fuente de alimentación externa, tienes que conectar el pin de datos a un pin digital. En este caso al pin D5 de la ESP8266.
Código del analizador de espectro
En muchos proyectos, aunque no sepas programar, tratamos de dar indicaciones para que puedas hacer ciertos cambios y personalizar el proyecto a tu gusto. Sin embargo, el código del analizador de espectro es muy complicado. Por ello te recomendamos que hagas la estructura con la misma cantidad de LEDs que nosotros y colocándolos de la misma forma, además de seguir nuestro esquema eléctrico.
Con este código analizamos frecuencias en un rango de 300Hz a 20Khz. Pese a que el micro puede captar ondas con frecuencias a partir de los 20Hz, los altavoces de nuestros ordenadores y móviles no son capaces de emitir sonido con frecuencias tan bajas, por lo que las hemos excluido.
#include //Librería para calcular la transformada de Fourier
#include
//SEÑAL DE AUDIO
#define SAMPLES 256 //Tiene que ser potencia de 2
#define SAMPLING_FREQ 40000 //Determina la frecuencia máxima que se puede analizar con FFT. Freq_max = sampling_freq/2
#define AMPLITUDE 1000 //Can be used as a "sensitivity" control (1000)
#define AUDIO_IN_PIN A0 //Entrada analógica a la que se ha conectado el microfono
#define NOISE 500 //Los valores que estén por debajo de NOISE se ignoran
#define NUM_BANDS 10
#define MAX_BAR_HEIGHT 8 //Número de leds por columna
#define LEDS_BETWEEN_BANDS 3
//LEDS
#define NUM_LEDS 2*NUM_BANDS*MAX_BAR_HEIGHT + NUM_BANDS*LEDS_BETWEEN_BANDS + 2*NUM_BANDS
#define LEDS_PIN 14 //D5 en la NodeMCU ESP8266
#define COLOR_ORDER GRB
#define MAX_BRIGHTNESS 30
#define LED_TYPE WS2812B
CRGB leds[NUM_LEDS];
byte state = 0;
bool base_leds_on = false;
int state_cont = 0;
unsigned int sampling_period_us;
unsigned long newTime;
double vReal[SAMPLES];
double vImag[SAMPLES];
arduinoFFT FFT = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);
//***********************************************************
int bandValues[] = {0,0,0,0,0,0,0,0,0,0}; //Tenemos 10 bandas
//***********************************************************
int oldBarHeights[] = {0,0,0,0,0,0,0,0,0,0};
//***********************************************************
int peak[] = {0,0,0,0,0,0,0,0,0,0};
//HSV
byte base_hue = 0;
//Palettes
DEFINE_GRADIENT_PALETTE (ocean_palette){
0, 13, 4, 156,
75, 39, 142, 228,
145, 40, 160, 255,
255, 0, 255, 255
};
DEFINE_GRADIENT_PALETTE (fire_palette){
0, 255, 0, 0,
75, 255, 149, 0,
150, 250, 255, 100,
255, 255, 255, 255
};
CRGBPalette16 myPal;
void setup() {
// put your setup code here, to run once:
//Configuramos el ADC para obtener 58.6 KHz de velocidad de muestreo
Serial.begin(115200);
sampling_period_us = round(1000000 * (1.0/SAMPLING_FREQ));
FastLED.addLeds(leds, NUM_LEDS);
FastLED.setBrightness(MAX_BRIGHTNESS);
}
void loop() {
FastLED.clear();
Serial.println("0");
// Reset bandValues[]
for (int i = 0; i NOISE) { //Filtro de ruido
//10 bands, 300Hz a 20000KHz, 256 muestras
if (i<=2 ) bandValues[0] += (int)vReal[i];
if (i>2 && i<=4 ) bandValues[1] += (int)vReal[i];
if (i>4 && i<=6 ) bandValues[2] += (int)vReal[i];
if (i>6 && i<=10 ) bandValues[3] += (int)vReal[i];
if (i>10 && i<=16 ) bandValues[4] += (int)vReal[i];
if (i>16 && i<=26) bandValues[5] += (int)vReal[i];
if (i>26 && i<=41 ) bandValues[6] += (int)vReal[i];
if (i>41 && i<=65 ) bandValues[7] += (int)vReal[i];
if (i>65 && i<=104 ) bandValues[8] += (int)vReal[i];
if (i>104 ) bandValues[9] += (int)vReal[i];
}
}
for(int band=0; band MAX_BAR_HEIGHT) barHeight = MAX_BAR_HEIGHT;
//Hacemos promedio con el valor anterior
barHeight = ((oldBarHeights[band] * 1) + barHeight) / 2;
//Movemos la "cima"
if(barHeight > peak[band]){
peak[band] = min(MAX_BAR_HEIGHT, barHeight);
}
//DIBUJAMOS LAS BANDAS
switch(state){
case 0:
drawBarsWithStaticColor(band, barHeight, 255, 0, 0, base_leds_on);
break;
case 1:
myPal = ocean_palette;
drawBarsWithPalette(band, barHeight, myPal, base_leds_on);
break;
case 2:
myPal = fire_palette;
drawBarsWithPalette(band, barHeight, myPal, base_leds_on);
break;
case 3:
drawBarsWithCHSV(band, barHeight, base_hue, base_leds_on);
break;
}
drawPeak(band, 255, 255, 255);
oldBarHeights[band] = barHeight;
}
//Bajamos la cima
EVERY_N_MILLISECONDS(120){
for (byte band = 0; band < NUM_BANDS; band++){
if (peak[band] > 0) peak[band] -= 1;
}
//19200 milisegundos
if(state_cont < 320){
state_cont++;
}
else{
state_cont = 0;
if(state<3){
state++;
}
else{
state=0;
if(base_leds_on==true)
base_leds_on=false;
else
base_leds_on=true;
}
}
}
if(base_hue>245) base_hue = 0;
else base_hue += 10;
FastLED.show();
}
void drawBarsWithStaticColor(int num_band, int barHeight, byte R, byte G, byte B, bool base_leds_on){
int start_point = (num_band+1)*(2*MAX_BAR_HEIGHT) - 1 + num_band*LEDS_BETWEEN_BANDS;
int j = start_point + LEDS_BETWEEN_BANDS + 1;
for(int i=start_point; i>(start_point-barHeight); i--){
if(base_leds_on && i==start_point){
leds[i+1].setRGB(R,G,B);
leds[i+2].setRGB(R,G,B);
leds[i+3].setRGB(R,G,B);
}
leds[i].setRGB(R,G,B);
leds[j].setRGB(R,G,B);
j++;
}
}
void drawBarsWithPalette(int num_band, int barHeight, CRGBPalette16 palette, bool base_leds_on){
int start_point = (num_band+1)*(2*MAX_BAR_HEIGHT) - 1 + num_band*LEDS_BETWEEN_BANDS;
int j = start_point + LEDS_BETWEEN_BANDS + 1;
int num_led_band = 0;
for(int i=start_point; i>(start_point-barHeight); i--){
if(base_leds_on && i==start_point){
leds[i+1]= ColorFromPalette(palette, round(255*num_led_band/MAX_BAR_HEIGHT), 255, NOBLEND);
leds[i+2]= ColorFromPalette(palette, round(255*num_led_band/MAX_BAR_HEIGHT), 255, NOBLEND);
leds[i+3]= ColorFromPalette(palette, round(255*num_led_band/MAX_BAR_HEIGHT), 255, NOBLEND);
}
leds[i]= ColorFromPalette(palette, round(255*num_led_band/MAX_BAR_HEIGHT), 255, NOBLEND);
leds[j]= ColorFromPalette(palette, round(255*num_led_band/MAX_BAR_HEIGHT), 255, NOBLEND);
num_led_band++;
j++;
}
}
void drawBarsWithCHSV(int num_band, int barHeight, byte hue, bool base_leds_on){
int start_point = (num_band+1)*(2*MAX_BAR_HEIGHT) - 1 + num_band*LEDS_BETWEEN_BANDS;
int j = start_point + LEDS_BETWEEN_BANDS + 1;
for(int i=start_point; i>(start_point-barHeight); i--){
if(base_leds_on==true && i==start_point){
leds[i+1] = CHSV(hue, 255, 255);;
leds[i+2] = CHSV(hue, 255, 255);
leds[i+3] = CHSV(hue, 255, 255);
}
leds[i] = CHSV(hue, 255, 255);
leds[j] = CHSV(hue, 255, 255);
j++;
}
}
void drawPeak(int num_band, byte R, byte G, byte B){
int start_point1 = (num_band+1)*(2*MAX_BAR_HEIGHT) - 1 + num_band*LEDS_BETWEEN_BANDS;
int start_point2 = start_point1 + LEDS_BETWEEN_BANDS + 1;
int point1 = start_point1 - peak[num_band];
int point2 = start_point2 + peak[num_band];
leds[point1].setRGB(R, G, B);
leds[point2].setRGB(R, G, B);
}