#include <Arduino.h>
// Footswitch inputs (wired to GND, using internal pullups)
const uint8_t PIN_FS_MODE = 4; // Left switch: tap to toggle Sound Mode
const uint8_t PIN_FS_MANUAL = 3; // Middle switch: tap to toggle Manual On/Off
const uint8_t PIN_FS_MOMENTARY = 2; // Right switch: hold for instant 100% flash
// Status LEDs
const uint8_t PIN_STATUS_SOUND = 7; // Cyan LED (Sound mode active)
const uint8_t PIN_STATUS_MANUAL = 6; // Green LED (Manual ON active)
const uint8_t PIN_STATUS_MOMENTARY = 5; // Yellow LED (Flash active)
// PWM output to MOSFET gate (D9 = 490Hz timer PWM)
const uint8_t PIN_LED_PWM = 9;
// Analog inputs
const uint8_t PIN_MIC_IN = A0; // MAX4466 microphone output
const uint8_t PIN_SENSITIVITY_POT = A1; // 10k sensitivity adjustment knob
// Timing constants
const unsigned long AUDIO_SAMPLE_WINDOW_MS = 25; // 25ms audio sampling window (~40Hz)
const unsigned long DECAY_TIME_MS = 250; // Fade-out duration (0.25 seconds)
const float DECAY_RATE_PER_MS = 255.0f / (float)DECAY_TIME_MS; // ~1.02 PWM steps per ms
const unsigned long DEBOUNCE_DELAY_MS = 35; // Switch debounce filter time
// Brightness & timing variables
float currentBrightness = 0.0f;
unsigned long lastDecayTimestamp = 0;
unsigned long lastSampleTimestamp = 0;
// Software-latched mode states
bool isSoundModeActive = false;
bool isManualOnActive = false;
bool isMomentaryActive = false;
// Debounce and edge-detection trackers
int rawStateMode = HIGH;
int debouncedStateMode = HIGH;
int prevDebouncedMode = HIGH;
unsigned long lastDebounceMode = 0;
int rawStateManual = HIGH;
int debouncedStateManual = HIGH;
int prevDebouncedManual = HIGH;
unsigned long lastDebounceManual = 0;
int rawStateFlash = HIGH;
int debouncedStateFlash = HIGH;
unsigned long lastDebounceFlash = 0;
void readFootswitches();
uint8_t processAudioReactivity();
void updateLightingEngine(uint8_t targetBrightness);
void updateStatusLEDs();
void setup() {
// Set up footswitches with internal pull-up resistors
pinMode(PIN_FS_MODE, INPUT_PULLUP);
pinMode(PIN_FS_MANUAL, INPUT_PULLUP);
pinMode(PIN_FS_MOMENTARY, INPUT_PULLUP);
// Set up status LED indicator pins
pinMode(PIN_STATUS_SOUND, OUTPUT);
pinMode(PIN_STATUS_MANUAL, OUTPUT);
pinMode(PIN_STATUS_MOMENTARY, OUTPUT);
// Set up PWM output to MOSFET
pinMode(PIN_LED_PWM, OUTPUT);
analogWrite(PIN_LED_PWM, 0); // Start with lights off
unsigned long now = millis();
lastDecayTimestamp = now;
lastSampleTimestamp = now;
}
void loop() {
// Read inputs and handle software latching
readFootswitches();
uint8_t targetBrightness = 0;
// Mode priority evaluation
if (isMomentaryActive) {
// Flash button always overrides and forces 100% brightness
targetBrightness = 255;
}
else if (isSoundModeActive) {
// Sound-reactive mode: brightness driven by mic audio
targetBrightness = processAudioReactivity();
}
else {
// Manual mode: steady ON (255) or steady OFF (0)
targetBrightness = isManualOnActive ? 255 : 0;
}
// Update PWM output with instant 100% & 250ms decay
updateLightingEngine(targetBrightness);
// Update status LEDs
updateStatusLEDs();
}
// Reads switches and handles software-latched toggle states
void readFootswitches() {
unsigned long now = millis();
// --- Left Switch (D4): Sound Mode Toggle ---
int readMode = digitalRead(PIN_FS_MODE);
if (readMode != rawStateMode) {
lastDebounceMode = now;
rawStateMode = readMode;
}
if ((now - lastDebounceMode) > DEBOUNCE_DELAY_MS) {
if (readMode != debouncedStateMode) {
debouncedStateMode = readMode;
// Trigger toggle on button press (falling edge: HIGH to LOW)
if (debouncedStateMode == LOW && prevDebouncedMode == HIGH) {
isSoundModeActive = !isSoundModeActive;
}
prevDebouncedMode = debouncedStateMode;
}
}
// --- Middle Switch (D3): Manual On/Off Toggle ---
int readManual = digitalRead(PIN_FS_MANUAL);
if (readManual != rawStateManual) {
lastDebounceManual = now;
rawStateManual = readManual;
}
if ((now - lastDebounceManual) > DEBOUNCE_DELAY_MS) {
if (readManual != debouncedStateManual) {
debouncedStateManual = readManual;
// Trigger toggle on button press (falling edge: HIGH to LOW)
if (debouncedStateManual == LOW && prevDebouncedManual == HIGH) {
if (isSoundModeActive) {
// If coming from Sound Mode, switch back to Manual and turn lights ON
isSoundModeActive = false;
isManualOnActive = true;
} else {
isManualOnActive = !isManualOnActive;
}
}
prevDebouncedManual = debouncedStateManual;
}
}
// --- Right Switch (D2): Momentary Flash Hold ---
int readFlash = digitalRead(PIN_FS_MOMENTARY);
if (readFlash != rawStateFlash) {
lastDebounceFlash = now;
rawStateFlash = readFlash;
}
if ((now - lastDebounceFlash) > DEBOUNCE_DELAY_MS) {
debouncedStateFlash = readFlash;
// Active only while physically held down
isMomentaryActive = (debouncedStateFlash == LOW);
}
}
// Samples the microphone and calculates sound peak-to-peak brightness
uint8_t processAudioReactivity() {
static uint8_t lastSoundBrightness = 0;
unsigned long now = millis();
// Sample audio over a fixed window for clean peak detection
if (now - lastSampleTimestamp >= AUDIO_SAMPLE_WINDOW_MS) {
lastSampleTimestamp = now;
unsigned int signalMax = 0;
unsigned int signalMin = 1023;
unsigned long sampleStart = millis();
// 15ms sample burst to capture audio waveform envelope
while (millis() - sampleStart < 15) {
int sample = analogRead(PIN_MIC_IN);
if (sample < 1023) {
if (sample > (int)signalMax) signalMax = sample;
if (sample < (int)signalMin) signalMin = sample;
}
}
// Peak-to-peak amplitude represents true volume/loudness
unsigned int peakToPeak = (signalMax >= signalMin) ? (signalMax - signalMin) : 0;
// Read sensitivity pot and map to noise-gate threshold
int potValue = analogRead(PIN_SENSITIVITY_POT);
int threshold = map(potValue, 0, 1023, 350, 20);
int maxExpectedSignal = threshold + 250;
// Apply noise gate and calculate brightness
if ((int)peakToPeak > threshold) {
int calculatedBrightness = map(peakToPeak, threshold, maxExpectedSignal, 50, 255);
lastSoundBrightness = (uint8_t)constrain(calculatedBrightness, 0, 255);
} else {
lastSoundBrightness = 0;
}
}
return lastSoundBrightness;
}
// Handles instant attack and 250ms smooth decay fade-out
void updateLightingEngine(uint8_t targetBrightness) {
unsigned long now = millis();
unsigned long dt = now - lastDecayTimestamp;
lastDecayTimestamp = now;
if (targetBrightness >= (uint8_t)currentBrightness) {
// Instant 100%
currentBrightness = (float)targetBrightness;
} else {
// Smooth decay: fade out gradually over ~250ms
float decayAmount = DECAY_RATE_PER_MS * (float)dt;
currentBrightness -= decayAmount;
if (currentBrightness < (float)targetBrightness) {
currentBrightness = (float)targetBrightness;
}
}
currentBrightness = constrain(currentBrightness, 0.0f, 255.0f);
analogWrite(PIN_LED_PWM, (uint8_t)round(currentBrightness));
}
// Updates indicator LEDs on the pedal box
void updateStatusLEDs() {
digitalWrite(PIN_STATUS_SOUND, isSoundModeActive ? HIGH : LOW);
digitalWrite(PIN_STATUS_MANUAL, (!isSoundModeActive && isManualOnActive) ? HIGH : LOW);
digitalWrite(PIN_STATUS_MOMENTARY, isMomentaryActive ? HIGH : LOW);
}