How to Build a Simple Audio Spectrum Analyzer with Adjustable Settings
Simple Audio Spectrum Analyzer with multiple adjustable display, speed, and sensitivity modes for a customizable audio visualization experience.
Components and supplies
1
Graphic LCD 128x64 STN LED Backlight
1
Arduino Nano
1
Press button
1
10k Resistor
1
Trimmer Potentiometer, 10 kohm
1
4K7 resistor
Tools and machines
1
Soldering iron (generic)
Apps and platforms
1
arduino IDE _
Project description
Code
Code
cpp
...
1#define AUTO_GAIN 0 // Auto volume adjustment (disabled for manual control) 2#define VOL_THR 25 // Silence threshold (no display on matrix below this) 3#define LOW_PASS 20 // Lower sensitivity threshold for noise (no jumps when no sound) 4#define DEF_GAIN 80 // Default maximum threshold (ignored when GAIN_CONTROL is active) 5#define FHT_N 256 // Spectrum width x2 6#define LOG_OUT 1 7#define PEAK_HOLD_TIME 2000 // Peak hold time in ms 8 9// Button pins 10#define BUTTON1 8 11#define BUTTON2 9 12#define BUTTON3 10 13 14// Manually defined array of tones, first smooth, then steeper 15byte posOffset[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; // 1500 Hz 16//byte posOffset[16] = {1, 2, 3, 4, 6, 8, 10, 13, 16, 20, 25, 30, 35, 40, 45, 50}; // 4000 Hz 17 18#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) 19#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) 20 21#include <Wire.h> 22#include <U8glib.h> // http://rcl-radio.ru/wp-content/uploads/2023/04/U8glib.zip 23#include <FHT.h> // http://forum.rcl-radio.ru/misc.php?action=pan_download&item=297&download=1 24 25#define EN 6 26#define RW 5 27#define CS 4 28 29//U8GLIB_SH1106_128X64 lcd(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_FAST); // Dev 0, Fast I2C / TWI 30U8GLIB_ST7920_128X64_1X lcd(EN, RW, CS); // serial use, PSB = GND 31 32byte gain = DEF_GAIN; 33unsigned long gainTimer, times; 34byte maxValue, maxValue_f; 35float k = 0.1; 36byte ur[16], urr[16]; 37 38// Button state variables 39bool button1State = false; 40bool button2State = false; 41bool button3State = false; 42unsigned long button1Time = 0; 43unsigned long button2Time = 0; 44unsigned long button3Time = 0; 45 46// Mode variables 47byte displayMode = 0; // 0=normal, 1=peak hold, 2=falling dots, 3=symmetrical 48byte speedMode = 0; // 0=normal, 1=fast, 2=slow 49byte sensitivityMode = 0; // 0=normal, 1=high, 2=low 50byte peakHold[16]; // Peak hold values for each band 51unsigned long peakTimer[16]; // Timer for peak decay 52 53void setup() { 54 delay(100); 55 sbi(ADCSRA, ADPS2); 56 cbi(ADCSRA, ADPS1); 57 sbi(ADCSRA, ADPS0); 58 Serial.begin(9600); 59 Wire.begin(); 60 Wire.setClock(800000L); 61 lcd.begin(); 62 // lcd.setRot180(); 63 lcd.setFont(u8g_font_profont11r); 64 analogReadResolution(10); // ADC 10 BIT 65 analogReference(INTERNAL1V024); 66 pinMode(A0, INPUT); // INPUT AUDIO 67 68 // Initialize button pins 69 pinMode(BUTTON1, INPUT_PULLUP); 70 pinMode(BUTTON2, INPUT_PULLUP); 71 pinMode(BUTTON3, INPUT_PULLUP); 72 73 // Initialize peak hold array 74 for(int i = 0; i < 16; i++) { 75 peakHold[i] = 0; 76 peakTimer[i] = 0; 77 } 78} 79 80void handleButtons() { 81 // Button 1 - Display Mode Cycle 82 if (digitalRead(BUTTON1) == LOW) { 83 if (millis() - button1Time > 300) { // Debounce 84 displayMode = (displayMode + 1) % 4; // Cycle through 4 modes 85 button1Time = millis(); 86 } 87 } 88 89 // Button 2 - Speed Mode Cycle 90 if (digitalRead(BUTTON2) == LOW) { 91 if (millis() - button2Time > 300) { 92 speedMode = (speedMode + 1) % 3; // Cycle through 3 speed modes 93 button2Time = millis(); 94 } 95 } 96 97 // Button 3 - Sensitivity Cycle 98 if (digitalRead(BUTTON3) == LOW) { 99 if (millis() - button3Time > 300) { 100 sensitivityMode = (sensitivityMode + 1) % 3; // Cycle through 3 sensitivity modes 101 button3Time = millis(); 102 103 // Adjust gain based on sensitivity 104 switch(sensitivityMode) { 105 case 0: gain = DEF_GAIN; break; // Normal 106 case 1: gain = DEF_GAIN / 2; break; // High sensitivity 107 case 2: gain = DEF_GAIN * 2; break; // Low sensitivity 108 } 109 } 110 } 111} 112 113void updatePeakHold() { 114 for (int i = 0; i < 16; i++) { 115 int posLevel = map(fht_log_out[posOffset[i]], LOW_PASS, gain, 0, 60); 116 posLevel = constrain(posLevel, 0, 60); 117 118 if (posLevel > peakHold[i]) { 119 peakHold[i] = posLevel; 120 peakTimer[i] = millis(); 121 } else if (millis() - peakTimer[i] > PEAK_HOLD_TIME) { 122 if (peakHold[i] > 0) peakHold[i]--; 123 } 124 } 125} 126 127void drawModeIndicators() { 128 // Display mode indicators at top right 129 lcd.setFont(u8g_font_04b_03); 130 131 // Display mode indicator (N, P, D, S) 132 char modeChar = 'N'; 133 switch(displayMode) { 134 case 0: modeChar = 'N'; break; // Normal 135 case 1: modeChar = 'P'; break; // Peak 136 case 2: modeChar = 'D'; break; // Dot 137 case 3: modeChar = 'S'; break; // Symmetrical 138 } 139 140 // Speed mode indicator (N, F, S) 141 char speedChar = 'N'; 142 switch(speedMode) { 143 case 0: speedChar = 'N'; break; // Normal 144 case 1: speedChar = 'F'; break; // Fast 145 case 2: speedChar = 'S'; break; // Slow 146 } 147 148 // Sensitivity indicator (N, H, L) 149 char sensChar = 'N'; 150 switch(sensitivityMode) { 151 case 0: sensChar = 'N'; break; // Normal 152 case 1: sensChar = 'H'; break; // High 153 case 2: sensChar = 'L'; break; // Low 154 } 155 156 // Draw all three indicators at top right 157 lcd.drawStr(100, 5, String(modeChar).c_str()); 158 lcd.drawStr(110, 5, String(speedChar).c_str()); 159 lcd.drawStr(120, 5, String(sensChar).c_str()); 160} 161 162void drawSpectrum() { 163 lcd.firstPage(); 164 do { 165 for (int pos = 0; pos < 128; pos += 8) { 166 int band = pos / 8; 167 int posLevel = map(fht_log_out[posOffset[band]], LOW_PASS, gain, 0, 60); 168 posLevel = constrain(posLevel, 0, 60); 169 170 if(millis() - times < 2000) { 171 posLevel = 60; // Startup animation 172 } 173 174 urr[band] = posLevel; 175 176 // Apply speed mode to falling effect 177 int fallSpeed = 1; 178 switch(speedMode) { 179 case 0: fallSpeed = 1; break; // Normal 180 case 1: fallSpeed = 3; break; // Fast fall 181 case 2: fallSpeed = 1; if(random(2) == 0) fallSpeed = 0; break; // Slow/random 182 } 183 184 if(urr[band] < ur[band]) { 185 ur[band] = max(ur[band] - fallSpeed, 0); 186 } else { 187 ur[band] = posLevel; 188 } 189 190 delayMicroseconds(200); 191 192 // Draw based on display mode 193 switch(displayMode) { 194 case 0: // Normal bars 195 for (int v_pos = 0; v_pos < ur[band] + 4; v_pos += 4) { 196 lcd.drawBox(pos, 61 - v_pos, 6, 2); 197 } 198 break; 199 200 case 1: // Peak hold with bars 201 for (int v_pos = 0; v_pos < ur[band] + 4; v_pos += 4) { 202 lcd.drawBox(pos, 61 - v_pos, 6, 2); 203 } 204 // Draw peak dots 205 if(peakHold[band] > 0) { 206 lcd.drawBox(pos + 1, 61 - peakHold[band], 4, 1); 207 } 208 break; 209 210 case 2: // Falling dots 211 for (int v_pos = 0; v_pos < ur[band]; v_pos += 4) { 212 lcd.drawBox(pos + 1, 61 - v_pos, 4, 1); 213 } 214 break; 215 216 case 3: // Symmetrical mode 217 for (int v_pos = 0; v_pos < ur[band] + 4; v_pos += 4) { 218 lcd.drawBox(pos, 61 - v_pos, 6, 2); 219 lcd.drawBox(pos, 3 + v_pos, 6, 2); // Mirror at top 220 } 221 break; 222 } 223 } 224 225 // Draw mode indicators at top right 226 drawModeIndicators(); 227 228 } while(lcd.nextPage()); 229} 230 231void loop() { 232 analyzeAudio(); 233 handleButtons(); 234 updatePeakHold(); 235 drawSpectrum(); 236 237 if (AUTO_GAIN) { 238 maxValue_f = maxValue * k + maxValue_f * (1 - k); 239 if (millis() - gainTimer > 1500) { 240 if (maxValue_f > VOL_THR) gain = maxValue_f; 241 else gain = 100; 242 gainTimer = millis(); 243 } 244 } 245} 246 247void analyzeAudio() { 248 for (int i = 0 ; i < FHT_N ; i++) { 249 int sample = analogRead(A0); 250 fht_input[i] = sample; // put real data into bins 251 } 252 fht_window(); // window the data for better frequency response 253 fht_reorder(); // reorder the data before doing the fht 254 fht_run(); // process the data in the fht 255 fht_mag_log(); // take the output of the fht 256}
Documentation
Schematic
...
Schematic.jpg

Comments
Only logged in users can leave comments