//v1
/*
Parámetros Clave para Ajustar:
NUM_LEDS: Lo más importante. Pon aquí el número exacto de LEDs que tiene tu tira.
AMPLITUDE: Este es el ajuste de "sensibilidad".
Si los LEDs están siempre al máximo brillo y blancos, aumenta este valor (ej. a 600, 800...).
Si los LEDs apenas se encienden, disminuye este valor (ej. a 200, 150...).
BRIGHTNESS: Limita el brillo general para proteger tus ojos y tu fuente de alimentación. 200 es un buen punto de partida.
SAMPLES: He aumentado las muestras a 256 para tener una mejor resolución de frecuencias para los LEDs. Puedes probar con
128 si el ESP32 se ralentiza (aunque no debería).
Efectos:
Centro de Energía: La visualización nace en el centro de la tira de LEDs.
Espejo Estéreo: El canal de audio izquierdo se expande hacia la izquierda desde el centro, y el canal derecho se expande
hacia la derecha.
El Color es la Frecuencia: Las frecuencias bajas (graves) se representan con colores cálidos (rojo, naranja) cerca del
centro. A medida que la frecuencia aumenta (medios y agudos), el color se desplaza por el arcoíris (amarillo, verde, azul)
hacia los extremos de la tira.
El Brillo es la Amplitud: La intensidad o "volumen" de cada banda de frecuencia determina el brillo de su LED correspondiente.
¡Los beats fuertes harán que los colores brillen intensamente!
*/
/*
#include <arduinoFFT.h>
#include <Adafruit_NeoPixel.h>
// --- Configuraciones de Hardware ---
// --- Pines de entrada de Audio para ESP32 ---
#define audioInL 36 // Canal Izquierdo (ADC1_CH0) VP
#define audioInR 39 // Canal Derecho (ADC1_CH3) VN
// --- Configuraciones de la Tira LED WS2812 (NeoPixel) ---
#define LED_PIN 5 // Pin de datos de la tira LED
#define NUM_LEDS 30 // ¡IMPORTANTE! Cambia esto al número de LEDs de tu tira
#define BRIGHTNESS 200 // Brillo máximo (0-255). ¡Cuidado, los LEDs pueden ser muy brillantes!
// --- Parámetros de la FFT ---
#define SAMPLES 256 // Aumentamos las muestras para más resolución en los LEDs
#define SAMPLING_FREQUENCY 40000.0 // Frecuencia de muestreo
#define AMPLITUDE 400.0 // Sensibilidad. AJUSTA ESTE VALOR para que las barras no saturen o sean muy bajas.
// --- Crear objeto NeoPixel ---
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
// --- Arrays para la FFT ---
double vRealL[SAMPLES], vImagL[SAMPLES];
double vRealR[SAMPLES], vImagR[SAMPLES];
// --- Crear objetos FFT para cada canal ---
ArduinoFFT<double> fftL = ArduinoFFT<double>(vRealL, vImagL, SAMPLES, SAMPLING_FREQUENCY);
ArduinoFFT<double> fftR = ArduinoFFT<double>(vRealR, vImagR, SAMPLES, SAMPLING_FREQUENCY);
void setup() {
strip.begin();
strip.setBrightness(BRIGHTNESS);
strip.show(); // Inicializa todos los píxeles a 'off'
}
void loop() {
// --- 1. Muestreo de los dos canales de audio ---
for (int i = 0; i < SAMPLES; i++) {
// Leemos el valor del ADC, lo centramos en cero y aplicamos un pequeño filtro pasa-altas simple
vRealL[i] = (double)analogRead(audioInL) - 2048.0;
vImagL[i] = 0.0;
vRealR[i] = (double)analogRead(audioInR) - 2048.0;
vImagR[i] = 0.0;
// Pequeña espera para mejorar la estabilidad del muestreo
delayMicroseconds(20);
}
// --- 2. Procesamiento FFT para ambos canales ---
fftL.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
fftL.compute(FFT_FORWARD);
fftL.complexToMagnitude();
fftR.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
fftR.compute(FFT_FORWARD);
fftR.complexToMagnitude();
// --- 3. Visualización en la Tira LED ---
strip.clear(); // Limpiamos la tira para el nuevo frame
int center = NUM_LEDS / 2;
int numDisplayLeds = NUM_LEDS / 2;
// Recorremos los LEDs de una mitad de la tira
for (int i = 0; i < numDisplayLeds; i++) {
// Mapeamos el LED actual a una banda de frecuencia de la FFT
// Ignoramos las primeras 2-3 bandas que suelen tener ruido de DC
int fftBin = map(i, 0, numDisplayLeds - 1, 3, SAMPLES / 2);
// --- Canal Izquierdo ---
double magnitudeL = vRealL[fftBin] / AMPLITUDE;
int brightnessL = constrain((int)magnitudeL, 0, 255);
// --- Canal Derecho ---
double magnitudeR = vRealR[fftBin] / AMPLITUDE;
int brightnessR = constrain((int)magnitudeR, 0, 255);
// Mapeamos la banda de frecuencia a un color (Hue)
// 0 = Rojo, 15000 = Verde, 43000 = Azul, 65535 = Rojo de nuevo
uint16_t hue = map(fftBin, 3, SAMPLES / 2, 0, 48000);
// Calculamos el color final usando el modelo HSV (Hue, Saturation, Value/Brightness)
// Usamos el mismo Hue para ambos lados para mantener la simetría de color
uint32_t colorL = strip.ColorHSV(hue, 255, brightnessL);
uint32_t colorR = strip.ColorHSV(hue, 255, brightnessR);
// Asignamos el color al LED correspondiente en la tira
strip.setPixelColor(center - 1 - i, colorL); // Lado izquierdo
strip.setPixelColor(center + i, colorR); // Lado derecho
}
strip.show(); // Enviamos los datos a la tira para que se muestren los colores
}
*/
/*
Modo 0: Analizador de Espectro Clásico. Dos analizadores de 15 LEDs, uno para cada canal. Las frecuencias bajas
están a la izquierda de cada segmento y las altas a la derecha. El color representa la frecuencia y el brillo la amplitud.
Modo 1: Medidor VU Estéreo (VU Meter). Simula los clásicos medidores de volumen. Las barras crecen desde la izquierda de
cada segmento, con un degradado de verde a rojo. Mide el volumen general de cada canal.
Modo 2: Espejo de Energía Central. Este es mi favorito. Todo el conjunto de 30 LEDs actúa como una sola unidad. El audio
se expande desde el centro hacia afuera: el canal izquierdo va hacia la izquierda y el derecho hacia la derecha. ¡Es hipnótico!
Modo 3: Pulso de Graves Estéreo. Cada segmento de 15 LEDs se ilumina con un solo color (azul para la izquierda, rojo para la derecha).
El brillo de todo el segmento "respira" al ritmo de los graves de la música. Ideal para un ambiente más sutil.
Ajusta la Sensibilidad (AMPLITUDE): Este es el paso más importante. Pon algo de música:
Si las luces están siempre a tope o blancas, aumenta el valor de AMPLITUDE en el código (por ejemplo, a 1200 o 1500).
Si las luces casi no reaccionan, disminuye el valor de AMPLITUDE (por ejemplo, a 500 o 300).
Vuelve a subir el código con el nuevo valor hasta que la reacción sea la que te gusta.
*/
//v2
/*
#include <arduinoFFT.h>
#include <Adafruit_NeoPixel.h>
// --- Configuraciones de Hardware ---
#define audioInL 36 // Canal Izquierdo
#define audioInR 39 // Canal Derecho
// --- Configuraciones de la Tira LED WS2812 ---
#define LED_PIN 5 // ¡Pin de datos cambiado a 5!
#define NUM_LEDS 30 // ¡Total de LEDs ajustado a 30!
#define BRIGHTNESS 180 // Brillo (0-255)
// --- Configuraciones del Botón ---
#define BUTTON_PIN 0 // Pin para el botón de cambio de modo
#define NUM_MODES 7 // El número total de modos de visualización (4 visualizadores + 3 efectos)
// --- Parámetros de la FFT ---
#define SAMPLES 256
#define SAMPLING_FREQUENCY 40000.0
// --- ¡AJUSTE DE SENSIBILIDAD! ---
// Si las luces están muy altas, aumenta este valor (ej. 1200).
// Si apenas se encienden, disminúyelo (ej. 400).
#define AMPLITUDE 900.0
// --- Objetos y Variables Globales ---
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
// ******** CORRECCIÓN AQUÍ ********
// Primero, declaramos los arrays para almacenar los datos de la FFT.
double vRealL[SAMPLES], vImagL[SAMPLES], vRealR[SAMPLES], vImagR[SAMPLES];
// Ahora, creamos los objetos FFT, pasándoles los arrays que ya existen.
ArduinoFFT<double> fftL = ArduinoFFT<double>(vRealL, vImagL, SAMPLES, SAMPLING_FREQUENCY);
ArduinoFFT<double> fftR = ArduinoFFT<double>(vRealR, vImagR, SAMPLES, SAMPLING_FREQUENCY);
// ******** FIN DE LA CORRECCIÓN ********
// Variables para el control de modo y botón
int currentMode = 0;
volatile bool modeChanged = false;
unsigned long lastButtonPress = 0;
// --- Función de Interrupción del Botón ---
// Se ejecuta instantáneamente cuando se presiona el botón
void IRAM_ATTR handleButtonInterrupt() {
if (millis() - lastButtonPress > 200) { // Sencillo antirrebote (debounce)
modeChanged = true;
lastButtonPress = millis();
}
}
void setup() {
Serial.begin(115200);
// Configuración del botón con una interrupción
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), handleButtonInterrupt, FALLING);
// Inicialización de la tira de LEDs
strip.begin();
strip.setBrightness(BRIGHTNESS);
strip.show();
Serial.println("Setup completo. Modo inicial: 0");
}
void loop() {
// --- Comprobar si se ha pulsado el botón ---
if (modeChanged) {
currentMode = (currentMode + 1) % NUM_MODES;
Serial.print("Cambiando a Modo: ");
Serial.println(currentMode);
strip.clear(); // Limpia la tira al cambiar de modo
strip.show();
delay(50); // Pequeña pausa
modeChanged = false; // Resetea la bandera
}
// --- Selector de Modo de Visualización ---
switch (currentMode) {
case 0:
visualizerSpectrum();
break;
case 1:
visualizerVUMeter();
break;
case 2:
visualizerMirror();
break;
case 3:
visualizerBassPulse();
break;
case 4:
effectRainbow(20);
break;
case 5:
effectColorWipe();
break;
case 6:
effectTheaterChaseRainbow(50);
break;
}
}
// ======================================================
// --- FUNCIONES DE PROCESAMIENTO DE AUDIO Y FFT ---
// ======================================================
// Función para muestrear y procesar la FFT
void performFFT() {
// --- Muestreo de audio ---
for (int i = 0; i < SAMPLES; i++) {
vRealL[i] = (double)analogRead(audioInL) - 2048.0;
vImagL[i] = 0.0;
vRealR[i] = (double)analogRead(audioInR) - 2048.0;
vImagR[i] = 0.0;
delayMicroseconds(20);
}
// --- Procesamiento FFT ---
fftL.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
fftL.compute(FFT_FORWARD);
fftL.complexToMagnitude();
fftR.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
fftR.compute(FFT_FORWARD);
fftR.complexToMagnitude();
}
// ======================================================
// --- MODOS DE VISUALIZACIÓN DE AUDIO ---
// ======================================================
// --- MODO 0: Analizador de Espectro Clásico ---
void visualizerSpectrum() {
performFFT(); // Obtener datos de audio
strip.clear();
int leds_per_channel = NUM_LEDS / 2; // 15
for (int i = 0; i < leds_per_channel; i++) {
int bin = map(i, 0, leds_per_channel - 1, 2, SAMPLES / 4);
// Canal Izquierdo (LEDs 0-14)
int brightnessL = constrain((int)(vRealL[bin] / AMPLITUDE * 255.0), 0, 255);
uint16_t hueL = map(i, 0, leds_per_channel - 1, 22000, 0); // Verde a Rojo
strip.setPixelColor(i, strip.ColorHSV(hueL, 255, brightnessL));
// Canal Derecho (LEDs 15-29)
int brightnessR = constrain((int)(vRealR[bin] / AMPLITUDE * 255.0), 0, 255);
uint16_t hueR = map(i, 0, leds_per_channel - 1, 22000, 0); // Verde a Rojo
strip.setPixelColor(i + leds_per_channel, strip.ColorHSV(hueR, 255, brightnessR));
}
strip.show();
}
// --- MODO 1: Medidor VU Estéreo ---
void visualizerVUMeter() {
performFFT(); // Obtener datos de audio
strip.clear();
int leds_per_channel = NUM_LEDS / 2; // 15
double totalMagL = 0;
double totalMagR = 0;
for(int i = 2; i < SAMPLES / 2; i++) {
totalMagL += vRealL[i];
totalMagR += vRealR[i];
}
int heightL = constrain(map((long)totalMagL, 0, AMPLITUDE * 40, 0, leds_per_channel), 0, leds_per_channel);
int heightR = constrain(map((long)totalMagR, 0, AMPLITUDE * 40, 0, leds_per_channel), 0, leds_per_channel);
for(int i = 0; i < leds_per_channel; i++) {
if (i < heightL) {
uint16_t hue = map(i, 0, leds_per_channel - 1, 22000, 0); // Verde a Rojo
strip.setPixelColor(i, strip.ColorHSV(hue, 255, 255));
}
if (i < heightR) {
uint16_t hue = map(i, 0, leds_per_channel - 1, 22000, 0); // Verde a Rojo
strip.setPixelColor(i + leds_per_channel, strip.ColorHSV(hue, 255, 255));
}
}
strip.show();
}
// --- MODO 2: Espejo de Energía Central ---
void visualizerMirror() {
performFFT(); // Obtener datos de audio
strip.clear();
int center = NUM_LEDS / 2; // 15
for (int i = 0; i < center; i++) {
int bin = map(i, 0, center - 1, 2, SAMPLES / 3);
int brightnessL = constrain((int)(vRealL[bin] / AMPLITUDE * 255.0), 0, 255);
uint16_t hue = map(i, 0, center - 1, 0, 40000); // Rojo -> Verde -> Azul
strip.setPixelColor(center - 1 - i, strip.ColorHSV(hue, 255, brightnessL));
int brightnessR = constrain((int)(vRealR[bin] / AMPLITUDE * 255.0), 0, 255);
strip.setPixelColor(center + i, strip.ColorHSV(hue, 255, brightnessR));
}
strip.show();
}
// --- MODO 3: Pulso de Graves Estéreo ---
void visualizerBassPulse() {
performFFT(); // Obtener datos de audio
strip.clear();
double bassL = 0;
double bassR = 0;
for (int i = 2; i < 7; i++) { // Sumamos solo las frecuencias graves
bassL += vRealL[i];
bassR += vRealR[i];
}
int brightnessL = constrain(map((long)bassL, 0, AMPLITUDE * 5, 0, 255), 0, 255);
int brightnessR = constrain(map((long)bassR, 0, AMPLITUDE * 5, 0, 255), 0, 255);
uint32_t colorL = strip.Color(0, 0, brightnessL); // Azul
uint32_t colorR = strip.Color(brightnessR, 0, 0); // Rojo
for(int i = 0; i < 15; i++) {
strip.setPixelColor(i, colorL);
strip.setPixelColor(i + 15, colorR);
}
strip.show();
}
// ======================================================
// --- MODOS DE EFECTOS DE ANIMACIÓN ---
// ======================================================
// Función auxiliar para colores de arcoíris
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if (WheelPos < 85) {
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if (WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
// --- MODO 4: Arcoíris Cíclico ---
void effectRainbow(int wait) {
for (long firstPixelHue = 0; firstPixelHue < 65536; firstPixelHue += 256) {
if (modeChanged) return; // Salir si se presiona el botón
strip.rainbow(firstPixelHue);
strip.show();
delay(wait);
}
}
// --- MODO 5: Limpieza de Color (Color Wipe) ---
void effectColorWipe() {
uint32_t colors[] = {strip.Color(255, 0, 0), strip.Color(0, 255, 0), strip.Color(0, 0, 255)};
for(int c=0; c<3; c++) {
for (int i = 0; i < strip.numPixels(); i++) {
if (modeChanged) return; // Salir si se presiona el botón
strip.setPixelColor(i, colors[c]);
strip.show();
delay(50);
}
if (modeChanged) return;
}
}
// --- MODO 6: Marquesina de Arcoíris (Theater Chase Rainbow) ---
void effectTheaterChaseRainbow(int wait) {
for (int j = 0; j < 256; j++) { // Un ciclo de todos los colores
if (modeChanged) return; // Salir si se presiona el botón
for (int q = 0; q < 3; q++) {
if (modeChanged) return;
strip.clear();
for (int i = 0; i < strip.numPixels(); i = i + 3) {
strip.setPixelColor(i + q, Wheel((i + j) % 255));
}
strip.show();
delay(wait);
}
}
}
*/
//v3
/*
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <arduinoFFT.h>
#include <Adafruit_NeoPixel.h>
// --- Configuraciones de Hardware ---
#define audioInL 36 // Canal Izquierdo
#define audioInR 39 // Canal Derecho
// --- Configuraciones de la Tira LED WS2812 ---
#define LED_PIN 5 // Pin de datos
#define NUM_LEDS 30 // Total de LEDs
#define BRIGHTNESS 180 // Brillo (0-255)
// --- Configuraciones de la Pantalla OLED SSD1306 ---
#define SCREEN_WIDTH 128 // Ancho en píxeles
#define SCREEN_HEIGHT 64 // Alto en píxeles
#define OLED_RESET -1 // Pin de reset (-1 si comparte el reset del Arduino)
#define SCREEN_ADDRESS 0x3C // Dirección I2C, puede ser 0x3D en algunos modelos
// --- Configuraciones del Botón ---
#define BUTTON_PIN 0 // Pin para el botón de cambio de modo
#define NUM_MODES 7 // El número total de modos de visualización
// --- Parámetros de la FFT ---
#define SAMPLES 256
#define SAMPLING_FREQUENCY 40000.0
#define AMPLITUDE 900.0
// --- Objetos y Variables Globales ---
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Primero, declaramos los arrays para almacenar los datos de la FFT.
double vRealL[SAMPLES], vImagL[SAMPLES], vRealR[SAMPLES], vImagR[SAMPLES];
// Ahora, creamos los objetos FFT, pasándoles los arrays que ya existen.
ArduinoFFT<double> fftL = ArduinoFFT<double>(vRealL, vImagL, SAMPLES, SAMPLING_FREQUENCY);
ArduinoFFT<double> fftR = ArduinoFFT<double>(vRealR, vImagR, SAMPLES, SAMPLING_FREQUENCY);
// Variables para el control de modo y botón
int currentMode = 0; // Se inicializa en 0 para arrancar en el Modo 0
volatile bool modeChanged = false;
unsigned long lastButtonPress = 0;
// --- Función de Interrupción del Botón ---
void IRAM_ATTR handleButtonInterrupt() {
if (millis() - lastButtonPress > 200) { // Sencillo antirrebote (debounce)
modeChanged = true;
lastButtonPress = millis();
}
}
void setup() {
// Configuración del botón con una interrupción
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), handleButtonInterrupt, FALLING);
// Inicialización de la pantalla OLED
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
// Si la inicialización falla, el programa se detiene aquí.
for(;;);
}
// Muestra el modo inicial en la pantalla OLED al arrancar
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(22, 25); // Centrado para "Modo: X"
display.print("Modo: ");
display.print(currentMode); // Muestra el modo inicial (0)
display.display();
// Inicialización de la tira de LEDs
strip.begin();
strip.setBrightness(BRIGHTNESS);
strip.show();
}
void loop() {
// --- Comprobar si se ha pulsado el botón ---
if (modeChanged) {
currentMode = (currentMode + 1) % NUM_MODES;
// --- Mostrar el cambio de modo en la pantalla OLED ---
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(22, 25);
display.print("Modo: ");
display.print(currentMode);
display.display();
strip.clear(); // Limpia la tira al cambiar de modo
strip.show();
delay(50); // Pequeña pausa
modeChanged = false; // Resetea la bandera
}
// --- Selector de Modo de Visualización ---
switch (currentMode) {
case 0:
visualizerSpectrum();
break;
case 1:
visualizerVUMeter();
break;
case 2:
visualizerMirror();
break;
case 3:
visualizerBassPulse();
break;
case 4:
effectRainbow(20);
break;
case 5:
effectColorWipe();
break;
case 6:
effectTheaterChaseRainbow(50);
break;
}
}
// ======================================================
// --- FUNCIONES DE PROCESAMIENTO DE AUDIO Y FFT ---
// ======================================================
void performFFT() {
for (int i = 0; i < SAMPLES; i++) {
vRealL[i] = (double)analogRead(audioInL) - 2048.0;
vImagL[i] = 0.0;
vRealR[i] = (double)analogRead(audioInR) - 2048.0;
vImagR[i] = 0.0;
delayMicroseconds(20);
}
fftL.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
fftL.compute(FFT_FORWARD);
fftL.complexToMagnitude();
fftR.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
fftR.compute(FFT_FORWARD);
fftR.complexToMagnitude();
}
// ======================================================
// --- MODOS DE VISUALIZACIÓN DE AUDIO ---
// ======================================================
void visualizerSpectrum() {
performFFT();
strip.clear();
int leds_per_channel = NUM_LEDS / 2;
for (int i = 0; i < leds_per_channel; i++) {
int bin = map(i, 0, leds_per_channel - 1, 2, SAMPLES / 4);
int brightnessL = constrain((int)(vRealL[bin] / AMPLITUDE * 255.0), 0, 255);
uint16_t hueL = map(i, 0, leds_per_channel - 1, 22000, 0);
strip.setPixelColor(i, strip.ColorHSV(hueL, 255, brightnessL));
int brightnessR = constrain((int)(vRealR[bin] / AMPLITUDE * 255.0), 0, 255);
uint16_t hueR = map(i, 0, leds_per_channel - 1, 22000, 0);
strip.setPixelColor(i + leds_per_channel, strip.ColorHSV(hueR, 255, brightnessR));
}
strip.show();
}
void visualizerVUMeter() {
performFFT();
strip.clear();
int leds_per_channel = NUM_LEDS / 2;
double totalMagL = 0, totalMagR = 0;
for(int i = 2; i < SAMPLES / 2; i++) { totalMagL += vRealL[i]; totalMagR += vRealR[i]; }
int heightL = constrain(map((long)totalMagL, 0, AMPLITUDE * 40, 0, leds_per_channel), 0, leds_per_channel);
int heightR = constrain(map((long)totalMagR, 0, AMPLITUDE * 40, 0, leds_per_channel), 0, leds_per_channel);
for(int i = 0; i < leds_per_channel; i++) {
if (i < heightL) strip.setPixelColor(i, strip.ColorHSV(map(i, 0, leds_per_channel - 1, 22000, 0), 255, 255));
if (i < heightR) strip.setPixelColor(i + leds_per_channel, strip.ColorHSV(map(i, 0, leds_per_channel - 1, 22000, 0), 255, 255));
}
strip.show();
}
void visualizerMirror() {
performFFT();
strip.clear();
int center = NUM_LEDS / 2;
for (int i = 0; i < center; i++) {
int bin = map(i, 0, center - 1, 2, SAMPLES / 3);
uint16_t hue = map(i, 0, center - 1, 0, 40000);
int brightnessL = constrain((int)(vRealL[bin] / AMPLITUDE * 255.0), 0, 255);
strip.setPixelColor(center - 1 - i, strip.ColorHSV(hue, 255, brightnessL));
int brightnessR = constrain((int)(vRealR[bin] / AMPLITUDE * 255.0), 0, 255);
strip.setPixelColor(center + i, strip.ColorHSV(hue, 255, brightnessR));
}
strip.show();
}
void visualizerBassPulse() {
performFFT();
strip.clear();
double bassL = 0, bassR = 0;
for (int i = 2; i < 7; i++) { bassL += vRealL[i]; bassR += vRealR[i]; }
int brightnessL = constrain(map((long)bassL, 0, AMPLITUDE * 5, 0, 255), 0, 255);
int brightnessR = constrain(map((long)bassR, 0, AMPLITUDE * 5, 0, 255), 0, 255);
uint32_t colorL = strip.Color(0, 0, brightnessL);
uint32_t colorR = strip.Color(brightnessR, 0, 0);
for(int i = 0; i < 15; i++) {
strip.setPixelColor(i, colorL);
strip.setPixelColor(i + 15, colorR);
}
strip.show();
}
// ======================================================
// --- MODOS DE EFECTOS DE ANIMACIÓN ---
// ======================================================
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if (WheelPos < 85) return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
if (WheelPos < 170) { WheelPos -= 85; return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); }
WheelPos -= 170; return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
void effectRainbow(int wait) {
for (long firstPixelHue = 0; firstPixelHue < 65536; firstPixelHue += 256) {
if (modeChanged) return;
strip.rainbow(firstPixelHue);
strip.show();
delay(wait);
}
}
void effectColorWipe() {
uint32_t colors[] = {strip.Color(255, 0, 0), strip.Color(0, 255, 0), strip.Color(0, 0, 255)};
for(int c=0; c<3; c++) {
for (int i = 0; i < strip.numPixels(); i++) {
if (modeChanged) return;
strip.setPixelColor(i, colors[c]);
strip.show();
delay(50);
}
if (modeChanged) return;
}
}
void effectTheaterChaseRainbow(int wait) {
for (int j = 0; j < 256; j++) {
if (modeChanged) return;
for (int q = 0; q < 3; q++) {
if (modeChanged) return;
strip.clear();
for (int i = 0; i < strip.numPixels(); i = i + 3) {
strip.setPixelColor(i + q, Wheel((i + j) % 255));
}
strip.show();
delay(wait);
}
}
}
*/
//v4
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <arduinoFFT.h>
#include <Adafruit_NeoPixel.h>
// --- Configuraciones de Hardware ---
#define audioInL 36 // Canal Izquierdo
#define audioInR 39 // Canal Derecho
// --- Configuraciones de la Tira LED WS2812 ---
#define LED_PIN 5 // Pin de datos
#define NUM_LEDS 30 // Total de LEDs (2 barras de 15)
#define BRIGHTNESS 180 // Brillo (0-255)
// --- Configuraciones de la Pantalla OLED SSD1306 ---
#define SCREEN_WIDTH 128 // Ancho en píxeles
#define SCREEN_HEIGHT 64 // Alto en píxeles
#define OLED_RESET -1 // Pin de reset (-1 si comparte el reset del Arduino)
#define SCREEN_ADDRESS 0x3C // Dirección I2C, puede ser 0x3D en algunos modelos
// --- Configuraciones del Botón ---
#define BUTTON_PIN 0 // Pin para el botón de cambio de modo
#define NUM_MODES 4 // El número total de modos de visualización (actualizado a 4)
// --- Parámetros de la FFT ---
#define SAMPLES 256
#define SAMPLING_FREQUENCY 40000.0
#define AMPLITUDE 900.0
// --- Parámetros para el nuevo modo "Peak Meter" ---
const int PEAK_FALL_DELAY = 50; // Tiempo en ms para que el píxel de pico caiga un nivel
// --- Objetos y Variables Globales ---
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Arrays para almacenar los datos de la FFT.
double vRealL[SAMPLES], vImagL[SAMPLES], vRealR[SAMPLES], vImagR[SAMPLES];
// Objetos FFT
ArduinoFFT<double> fftL = ArduinoFFT<double>(vRealL, vImagL, SAMPLES, SAMPLING_FREQUENCY);
ArduinoFFT<double> fftR = ArduinoFFT<double>(vRealR, vImagR, SAMPLES, SAMPLING_FREQUENCY);
// Variables para el control de modo y botón
int currentMode = 0; // Se inicializa en 0 para arrancar en el Modo 0
volatile bool modeChanged = false;
unsigned long lastButtonPress = 0;
// Variables para el nuevo modo "Peak Meter"
int peakL = 0, peakR = 0; // Almacena la posición del píxel de pico para cada canal
unsigned long lastPeakTimeL = 0, lastPeakTimeR = 0; // Almacena el tiempo del último pico
// --- Función de Interrupción del Botón ---
void IRAM_ATTR handleButtonInterrupt() {
if (millis() - lastButtonPress > 200) { // Sencillo antirrebote (debounce)
modeChanged = true;
lastButtonPress = millis();
}
}
void setup() {
// Configuración del botón con una interrupción
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), handleButtonInterrupt, FALLING);
// Inicialización de la pantalla OLED
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
// Si la inicialización falla, el programa se detiene aquí.
for(;;);
}
// Muestra el modo inicial en la pantalla OLED al arrancar
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(22, 25); // Centrado para "Modo: X"
display.print("Modo: ");
display.print(currentMode); // Muestra el modo inicial (0)
display.display();
// Inicialización de la tira de LEDs
strip.begin();
strip.setBrightness(BRIGHTNESS);
strip.show();
}
void loop() {
// --- Comprobar si se ha pulsado el botón ---
if (modeChanged) {
currentMode = (currentMode + 1) % NUM_MODES;
// --- Mostrar el cambio de modo en la pantalla OLED ---
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(22, 25);
display.print("Modo: ");
display.print(currentMode);
display.display();
// Limpia la tira y resetea los picos al cambiar de modo
strip.clear();
strip.show();
peakL = 0;
peakR = 0;
delay(50); // Pequeña pausa
modeChanged = false; // Resetea la bandera
}
// --- Selector de Modo de Visualización ---
switch (currentMode) {
case 0:
visualizerSpectrum();
break;
case 1:
visualizerVUMeter();
break;
case 2:
visualizerBassPulse();
break;
case 3:
visualizerPeakMeter(); // Nuevo modo espectacular!
break;
}
}
// ======================================================
// --- FUNCIONES DE PROCESAMIENTO DE AUDIO Y FFT ---
// ======================================================
void performFFT() {
for (int i = 0; i < SAMPLES; i++) {
vRealL[i] = (double)analogRead(audioInL) - 2048.0;
vImagL[i] = 0.0;
vRealR[i] = (double)analogRead(audioInR) - 2048.0;
vImagR[i] = 0.0;
delayMicroseconds(20);
}
fftL.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
fftL.compute(FFT_FORWARD);
fftL.complexToMagnitude();
fftR.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
fftR.compute(FFT_FORWARD);
fftR.complexToMagnitude();
}
// ======================================================
// --- MODOS DE VISUALIZACIÓN DE AUDIO ---
// ======================================================
// --- MODO 0: ANALIZADOR DE ESPECTRO ---
void visualizerSpectrum() {
performFFT();
strip.clear();
int leds_per_channel = NUM_LEDS / 2;
for (int i = 0; i < leds_per_channel; i++) {
// Mapea el LED actual a un "bin" o rango de frecuencia de la FFT
int bin = map(i, 0, leds_per_channel - 1, 2, SAMPLES / 4);
// Canal Izquierdo (LEDs 0-14)
int brightnessL = constrain((int)(vRealL[bin] / AMPLITUDE * 255.0), 0, 255);
uint16_t hueL = map(i, 0, leds_per_channel - 1, 22000, 0); // Degradado de Verde a Rojo
strip.setPixelColor(i, strip.ColorHSV(hueL, 255, brightnessL));
// Canal Derecho (LEDs 15-29)
int brightnessR = constrain((int)(vRealR[bin] / AMPLITUDE * 255.0), 0, 255);
uint16_t hueR = map(i, 0, leds_per_channel - 1, 22000, 0); // Mismo degradado
strip.setPixelColor(i + leds_per_channel, strip.ColorHSV(hueR, 255, brightnessR));
}
strip.show();
}
// --- MODO 1: MEDIDOR DE VOLUMEN (VU METER) ---
void visualizerVUMeter() {
performFFT();
strip.clear();
int leds_per_channel = NUM_LEDS / 2;
// Suma la magnitud de todas las frecuencias para obtener el volumen general
double totalMagL = 0, totalMagR = 0;
for(int i = 2; i < SAMPLES / 2; i++) { totalMagL += vRealL[i]; totalMagR += vRealR[i]; }
// Mapea el volumen a la altura de la barra (0 a 15 LEDs)
int heightL = constrain(map((long)totalMagL, 0, AMPLITUDE * 40, 0, leds_per_channel), 0, leds_per_channel);
int heightR = constrain(map((long)totalMagR, 0, AMPLITUDE * 40, 0, leds_per_channel), 0, leds_per_channel);
// Dibuja las barras con un degradado de color
for(int i = 0; i < leds_per_channel; i++) {
if (i < heightL) strip.setPixelColor(i, strip.ColorHSV(map(i, 0, leds_per_channel - 1, 22000, 0), 255, 255));
if (i < heightR) strip.setPixelColor(i + leds_per_channel, strip.ColorHSV(map(i, 0, leds_per_channel - 1, 22000, 0), 255, 255));
}
strip.show();
}
// --- MODO 2: PULSO DE GRAVES ---
void visualizerBassPulse() {
performFFT();
strip.clear();
// Suma la magnitud de las frecuencias bajas (graves)
double bassL = 0, bassR = 0;
for (int i = 2; i < 7; i++) { bassL += vRealL[i]; bassR += vRealR[i]; }
// Mapea la intensidad de los graves al brillo de cada barra
int brightnessL = constrain(map((long)bassL, 0, AMPLITUDE * 5, 0, 255), 0, 255);
int brightnessR = constrain(map((long)bassR, 0, AMPLITUDE * 5, 0, 255), 0, 255);
// Asigna un color diferente a cada canal para el pulso
uint32_t colorL = strip.Color(0, 0, brightnessL); // Izquierda en Azul
uint32_t colorR = strip.Color(brightnessR, 0, 0); // Derecha en Rojo
// Enciende toda la barra con el brillo calculado
for(int i = 0; i < 15; i++) {
strip.setPixelColor(i, colorL);
strip.setPixelColor(i + 15, colorR);
}
strip.show();
}
// --- MODO 3: ¡NUEVO! MEDIDOR DE PICOS CON CAÍDA ---
void visualizerPeakMeter() {
performFFT();
strip.clear();
int leds_per_channel = NUM_LEDS / 2;
// Calcula la altura de la barra (igual que en el VU Meter)
double totalMagL = 0, totalMagR = 0;
for(int i = 2; i < SAMPLES / 2; i++) { totalMagL += vRealL[i]; totalMagR += vRealR[i]; }
int heightL = constrain(map((long)totalMagL, 0, AMPLITUDE * 40, 0, leds_per_channel), 0, leds_per_channel);
int heightR = constrain(map((long)totalMagR, 0, AMPLITUDE * 40, 0, leds_per_channel), 0, leds_per_channel);
// Si la altura actual supera al pico guardado, actualiza el pico
if (heightL > peakL) {
peakL = heightL;
lastPeakTimeL = millis();
}
if (heightR > peakR) {
peakR = heightR;
lastPeakTimeR = millis();
}
// Dibuja las barras principales
for(int i = 0; i < leds_per_channel; i++) {
if (i < heightL) strip.setPixelColor(i, strip.ColorHSV(map(i, 0, leds_per_channel - 1, 22000, 0), 255, 255));
if (i < heightR) strip.setPixelColor(i + leds_per_channel, strip.ColorHSV(map(i, 0, leds_per_channel - 1, 22000, 0), 255, 255));
}
// Dibuja el píxel de pico (si es mayor que 0)
// Lo dibujamos después para que se superponga si coincide con la altura de la barra
if (peakL > 0) {
strip.setPixelColor(peakL - 1, strip.Color(255, 255, 255)); // Pico izquierdo en blanco
}
if (peakR > 0) {
strip.setPixelColor((peakR - 1) + leds_per_channel, strip.Color(255, 255, 255)); // Pico derecho en blanco
}
// Lógica para hacer caer el pico
if (millis() - lastPeakTimeL > PEAK_FALL_DELAY) {
if (peakL > 0) peakL--;
lastPeakTimeL = millis();
}
if (millis() - lastPeakTimeR > PEAK_FALL_DELAY) {
if (peakR > 0) peakR--;
lastPeakTimeR = millis();
}
strip.show();
}