arduino: /* * AUDIO EQUALIZER - Arduino UNO + LCD I2C * 16-band visual equalizer * Communication: UART 9600 baud * Format: "L:0,1,2,3,4,5,6,7,0,1,2,3,4,5,6,7\n" */ #include #include // LCD I2C - address 0x27 or 0x3F LiquidCrystal_I2C lcd(0x27, 16, 2); // CUSTOM CHARACTERS - bars from bottom to top (levels 0-8) byte bar0[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B00000}; // Empty byte bar1[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B11111}; // ▁ byte bar2[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B11111, B11111}; // ▂ byte bar3[8] = {B00000, B00000, B00000, B00000, B00000, B11111, B11111, B11111}; // ▃ byte bar4[8] = {B00000, B00000, B00000, B00000, B11111, B11111, B11111, B11111}; // ▄ byte bar5[8] = {B00000, B00000, B00000, B11111, B11111, B11111, B11111, B11111}; // ▅ byte bar6[8] = {B00000, B00000, B11111, B11111, B11111, B11111, B11111, B11111}; // ▆ byte bar7[8] = {B00000, B11111, B11111, B11111, B11111, B11111, B11111, B11111}; // ▇ byte bar8[8] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111}; // Full block - NEW // EQUALIZER VARIABLES const int NUM_BANDS = 16; // 16 frequency bands int currentLevels[NUM_BANDS]; // Current displayed levels int targetLevels[NUM_BANDS]; // Target levels from Python unsigned long lastUpdate = 0; unsigned long lastDataReceived = 0; // SETTINGS const int UPDATE_INTERVAL = 40; // Refresh every 40ms (25 FPS) const int DATA_TIMEOUT = 2000; // Timeout 2 seconds const int MAX_LEVEL = 8; // Now we have 9 levels (0-8) void setup() { // Initialize LCD I2C lcd.init(); lcd.backlight(); lcd.clear(); // Load custom characters lcd.createChar(0, bar0); lcd.createChar(1, bar1); lcd.createChar(2, bar2); lcd.createChar(3, bar3); lcd.createChar(4, bar4); lcd.createChar(5, bar5); lcd.createChar(6, bar6); lcd.createChar(7, bar7); lcd.createChar(8, bar8); // New full block // Initialize Serial Serial.begin(9600); Serial.println("Equalizer Ready!"); // Initialize arrays for(int i = 0; i < NUM_BANDS; i++) { currentLevels[i] = 0; targetLevels[i] = 0; } // Welcome screen lcd.setCursor(0, 0); lcd.print("AUDIO EQUALIZER"); lcd.setCursor(0, 1); lcd.print("Ready to rock!"); delay(2000); lcd.clear(); lastDataReceived = millis(); } void loop() { unsigned long now = millis(); // Read data from Python if(Serial.available()) { if(readSerialData()) { lastDataReceived = now; } } // Check timeout - if no data, enable demo mode if(now - lastDataReceived > DATA_TIMEOUT) { generateDemoPattern(); } // Update levels (smooth transitions) if(now - lastUpdate >= UPDATE_INTERVAL) { updateLevels(); displayEqualizer(); lastUpdate = now; } } bool readSerialData() { // Read line from Python String line = Serial.readStringUntil('\n'); if(line.startsWith("L:")) { // Remove prefix "L:" line = line.substring(2); // Parse levels separated by commas int bandIndex = 0; int startPos = 0; while(bandIndex < NUM_BANDS && startPos < line.length()) { int commaPos = line.indexOf(',', startPos); String levelStr; if(commaPos != -1) { levelStr = line.substring(startPos, commaPos); startPos = commaPos + 1; } else { levelStr = line.substring(startPos); startPos = line.length(); } // Convert and constrain level int level = levelStr.toInt(); // Scale level 0-7 to 0-8 targetLevels[bandIndex] = constrain(map(level, 0, 7, 0, MAX_LEVEL), 0, MAX_LEVEL); bandIndex++; } return true; } return false; } void generateDemoPattern() { // Demo mode - different animation patterns static int demoMode = 0; static int demoCounter = 0; demoCounter++; // Change demo mode every 5 seconds if(demoCounter >= 125) { // 125 * 40ms = 5s demoCounter = 0; demoMode = (demoMode + 1) % 4; } switch(demoMode) { case 0: // Sine wave for(int i = 0; i < NUM_BANDS; i++) { float angle = (millis() / 300.0) + (i * 0.5); targetLevels[i] = map(sin(angle) * 127 + 128, 0, 255, 0, MAX_LEVEL); targetLevels[i] = constrain(targetLevels[i], 0, MAX_LEVEL); } break; case 1: // Random beats for(int i = 0; i < NUM_BANDS; i++) { if(random(100) < 25) { // 25% chance of change targetLevels[i] = random(0, MAX_LEVEL + 1); } } break; case 2: // Bass (left bands higher) for(int i = 0; i < NUM_BANDS; i++) { int baseLevel = MAX_LEVEL - (i / 3); targetLevels[i] = max(0, min(MAX_LEVEL, baseLevel + random(-2, 3))); } break; case 3: // Treble (right bands higher) for(int i = 0; i < NUM_BANDS; i++) { int baseLevel = i / 3; targetLevels[i] = max(0, min(MAX_LEVEL, baseLevel + random(-1, 2))); } break; } } void updateLevels() { // Smooth transition to target levels for(int i = 0; i < NUM_BANDS; i++) { if(currentLevels[i] < targetLevels[i]) { currentLevels[i]++; } else if(currentLevels[i] > targetLevels[i]) { currentLevels[i]--; } } } void displayEqualizer() { // Display equalizer - EACH BAR IN A SEPARATE COLUMN // BOTTOM ROW - levels 1-8 (main bars) lcd.setCursor(0, 1); for(int i = 0; i < NUM_BANDS; i++) { int level = currentLevels[i]; if(level == 0) { lcd.write(' '); // Empty when no signal } else if(level <= 8) { lcd.write((byte)level); // Custom character 1-8 } } // TOP ROW - extension for high levels lcd.setCursor(0, 0); for(int i = 0; i < NUM_BANDS; i++) { int level = currentLevels[i]; if(level <= 5) { lcd.write(' '); // No extension for low levels } else if(level == 6) { lcd.write((byte)1); // Small line at the top } else if(level == 7) { lcd.write((byte)3); // Medium line at the top } else if(level >= 8) { lcd.write((byte)8); // Full block at the top - maximum level! } } }