#define AUTO_GAIN 0 // Auto volume adjustment (disabled for manual control)
#define VOL_THR 25 // Silence threshold (no display on matrix below this)
#define LOW_PASS 20 // Lower sensitivity threshold for noise (no jumps when no sound)
#define DEF_GAIN 80 // Default maximum threshold (ignored when GAIN_CONTROL is active)
#define FHT_N 256 // Spectrum width x2
#define LOG_OUT 1
#define PEAK_HOLD_TIME 2000 // Peak hold time in ms
// Button pins
#define BUTTON1 8
#define BUTTON2 9
#define BUTTON3 10
// Manually defined array of tones, first smooth, then steeper
byte posOffset[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; // 1500 Hz
//byte posOffset[16] = {1, 2, 3, 4, 6, 8, 10, 13, 16, 20, 25, 30, 35, 40, 45, 50}; // 4000 Hz
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#include <Wire.h>
#include <U8glib.h> // http://rcl-radio.ru/wp-content/uploads/2023/04/U8glib.zip
#include <FHT.h> // http://forum.rcl-radio.ru/misc.php?action=pan_download&item=297&download=1
#define EN 6
#define RW 5
#define CS 4
//U8GLIB_SH1106_128X64 lcd(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_FAST); // Dev 0, Fast I2C / TWI
U8GLIB_ST7920_128X64_1X lcd(EN, RW, CS); // serial use, PSB = GND
byte gain = DEF_GAIN;
unsigned long gainTimer, times;
byte maxValue, maxValue_f;
float k = 0.1;
byte ur[16], urr[16];
// Button state variables
bool button1State = false;
bool button2State = false;
bool button3State = false;
unsigned long button1Time = 0;
unsigned long button2Time = 0;
unsigned long button3Time = 0;
// Mode variables
byte displayMode = 0; // 0=normal, 1=peak hold, 2=falling dots, 3=symmetrical
byte speedMode = 0; // 0=normal, 1=fast, 2=slow
byte sensitivityMode = 0; // 0=normal, 1=high, 2=low
byte peakHold[16]; // Peak hold values for each band
unsigned long peakTimer[16]; // Timer for peak decay
void setup() {
delay(100);
sbi(ADCSRA, ADPS2);
cbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);
Serial.begin(9600);
Wire.begin();
Wire.setClock(800000L);
lcd.begin();
// lcd.setRot180();
lcd.setFont(u8g_font_profont11r);
analogReadResolution(10); // ADC 10 BIT
analogReference(INTERNAL1V024);
pinMode(A0, INPUT); // INPUT AUDIO
// Initialize button pins
pinMode(BUTTON1, INPUT_PULLUP);
pinMode(BUTTON2, INPUT_PULLUP);
pinMode(BUTTON3, INPUT_PULLUP);
// Initialize peak hold array
for(int i = 0; i < 16; i++) {
peakHold[i] = 0;
peakTimer[i] = 0;
}
}
void handleButtons() {
// Button 1 - Display Mode Cycle
if (digitalRead(BUTTON1) == LOW) {
if (millis() - button1Time > 300) { // Debounce
displayMode = (displayMode + 1) % 4; // Cycle through 4 modes
button1Time = millis();
}
}
// Button 2 - Speed Mode Cycle
if (digitalRead(BUTTON2) == LOW) {
if (millis() - button2Time > 300) {
speedMode = (speedMode + 1) % 3; // Cycle through 3 speed modes
button2Time = millis();
}
}
// Button 3 - Sensitivity Cycle
if (digitalRead(BUTTON3) == LOW) {
if (millis() - button3Time > 300) {
sensitivityMode = (sensitivityMode + 1) % 3; // Cycle through 3 sensitivity modes
button3Time = millis();
// Adjust gain based on sensitivity
switch(sensitivityMode) {
case 0: gain = DEF_GAIN; break; // Normal
case 1: gain = DEF_GAIN / 2; break; // High sensitivity
case 2: gain = DEF_GAIN * 2; break; // Low sensitivity
}
}
}
}
void updatePeakHold() {
for (int i = 0; i < 16; i++) {
int posLevel = map(fht_log_out[posOffset[i]], LOW_PASS, gain, 0, 60);
posLevel = constrain(posLevel, 0, 60);
if (posLevel > peakHold[i]) {
peakHold[i] = posLevel;
peakTimer[i] = millis();
} else if (millis() - peakTimer[i] > PEAK_HOLD_TIME) {
if (peakHold[i] > 0) peakHold[i]--;
}
}
}
void drawModeIndicators() {
// Display mode indicators at top right
lcd.setFont(u8g_font_04b_03);
// Display mode indicator (N, P, D, S)
char modeChar = 'N';
switch(displayMode) {
case 0: modeChar = 'N'; break; // Normal
case 1: modeChar = 'P'; break; // Peak
case 2: modeChar = 'D'; break; // Dot
case 3: modeChar = 'S'; break; // Symmetrical
}
// Speed mode indicator (N, F, S)
char speedChar = 'N';
switch(speedMode) {
case 0: speedChar = 'N'; break; // Normal
case 1: speedChar = 'F'; break; // Fast
case 2: speedChar = 'S'; break; // Slow
}
// Sensitivity indicator (N, H, L)
char sensChar = 'N';
switch(sensitivityMode) {
case 0: sensChar = 'N'; break; // Normal
case 1: sensChar = 'H'; break; // High
case 2: sensChar = 'L'; break; // Low
}
// Draw all three indicators at top right
lcd.drawStr(100, 5, String(modeChar).c_str());
lcd.drawStr(110, 5, String(speedChar).c_str());
lcd.drawStr(120, 5, String(sensChar).c_str());
}
void drawSpectrum() {
lcd.firstPage();
do {
for (int pos = 0; pos < 128; pos += 8) {
int band = pos / 8;
int posLevel = map(fht_log_out[posOffset[band]], LOW_PASS, gain, 0, 60);
posLevel = constrain(posLevel, 0, 60);
if(millis() - times < 2000) {
posLevel = 60; // Startup animation
}
urr[band] = posLevel;
// Apply speed mode to falling effect
int fallSpeed = 1;
switch(speedMode) {
case 0: fallSpeed = 1; break; // Normal
case 1: fallSpeed = 3; break; // Fast fall
case 2: fallSpeed = 1; if(random(2) == 0) fallSpeed = 0; break; // Slow/random
}
if(urr[band] < ur[band]) {
ur[band] = max(ur[band] - fallSpeed, 0);
} else {
ur[band] = posLevel;
}
delayMicroseconds(200);
// Draw based on display mode
switch(displayMode) {
case 0: // Normal bars
for (int v_pos = 0; v_pos < ur[band] + 4; v_pos += 4) {
lcd.drawBox(pos, 61 - v_pos, 6, 2);
}
break;
case 1: // Peak hold with bars
for (int v_pos = 0; v_pos < ur[band] + 4; v_pos += 4) {
lcd.drawBox(pos, 61 - v_pos, 6, 2);
}
// Draw peak dots
if(peakHold[band] > 0) {
lcd.drawBox(pos + 1, 61 - peakHold[band], 4, 1);
}
break;
case 2: // Falling dots
for (int v_pos = 0; v_pos < ur[band]; v_pos += 4) {
lcd.drawBox(pos + 1, 61 - v_pos, 4, 1);
}
break;
case 3: // Symmetrical mode
for (int v_pos = 0; v_pos < ur[band] + 4; v_pos += 4) {
lcd.drawBox(pos, 61 - v_pos, 6, 2);
lcd.drawBox(pos, 3 + v_pos, 6, 2); // Mirror at top
}
break;
}
}
// Draw mode indicators at top right
drawModeIndicators();
} while(lcd.nextPage());
}
void loop() {
analyzeAudio();
handleButtons();
updatePeakHold();
drawSpectrum();
if (AUTO_GAIN) {
maxValue_f = maxValue * k + maxValue_f * (1 - k);
if (millis() - gainTimer > 1500) {
if (maxValue_f > VOL_THR) gain = maxValue_f;
else gain = 100;
gainTimer = millis();
}
}
}
void analyzeAudio() {
for (int i = 0 ; i < FHT_N ; i++) {
int sample = analogRead(A0);
fht_input[i] = sample; // put real data into bins
}
fht_window(); // window the data for better frequency response
fht_reorder(); // reorder the data before doing the fht
fht_run(); // process the data in the fht
fht_mag_log(); // take the output of the fht
}
Comments