Light Storm 289
Difficulty: ● ● ● ● ●
-
Make Sure You have:
-
Did you know ?
Parts Required
No products found in parts_required metafield
How It Works ?
GENESIS
Connection Guide
Snap your modules as shown above, copy the code into the Arduino IDE and Click upload
#include <WiFi.h>
#include <WebServer.h>
#include <arduinoFFT.h>
#include <FastLED.h>
#include <math.h>
// ======= WIFI & WEB SERVER =======
const char* ssid = "NOS-EA84";
const char* password = "M3FMPC3E31";
IPAddress local_IP(192,168,1,27);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);
WebServer server(80);
// ======= AUDIO & FFT SETUP =======
#define SAMPLES 256
#define SAMPLING_FREQUENCY 16000
const int micPin = 4; // MEMS Mic on GPIO4
double vReal[SAMPLES];
double vImag[SAMPLES];
ArduinoFFT<double> FFT(vReal, vImag, SAMPLES, SAMPLING_FREQUENCY);
const int noiseThresholdBass = 350;
const int noiseThresholdMid = 80;
const int noiseThresholdHigh = 50;
int bassLevel = 100, midsLevel = 100, highsLevel = 100;
double peakAmplitudeBass = 300, peakAmplitudeMid = 300, peakAmplitudeHigh = 300;
unsigned long lastLoudBass = 0, lastLoudMid = 0, lastLoudHigh = 0;
const unsigned long resetDelay = 2000;
// ======= LED MATRIX SETUP =======
#define MATRIX_PIN 10
#define NUM_LEDS 289
#define BRIGHTNESS 40
CRGB leds[NUM_LEDS];
// Matrix mapping constants from your original code
#define NUM_ROWS 21
const uint8_t rowCount[NUM_ROWS] = { 5, 9, 11, 13, 15, 15, 17, 17, 17, 17, 17, 17, 17, 17, 17, 15, 15, 13, 11, 9, 5 };
const uint8_t rowOffset[NUM_ROWS] = { 6, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 6 };
const uint16_t prefix[NUM_ROWS] PROGMEM = { 0, 5, 14, 25, 38, 53, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 236, 251, 264, 275, 284 };
#define INVALID 0xFFFF
uint16_t XY(uint8_t x, uint8_t y) {
if (y >= NUM_ROWS || x < rowOffset[y] || x >= rowOffset[y] + rowCount[y]) return INVALID;
return pgm_read_word_near(prefix + y) + (x - rowOffset[y]);
}
const float CENTER_X = 8.0;
const float CENTER_Y = 10.0;
// ======= ANIMATION MODES =======
enum Mode { AMBIENT, SWIRL, RIPPLE };
Mode currentMode = AMBIENT;
// ======= WEB CONTROL VARIABLES =======
bool patternChanged = false;
// ======= HTML PAGE with sliders + pattern buttons =======
String htmlPage() {
String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head><meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: Arial; text-align:center; background:#111; color:#fff; }
.container { width:90%; max-width:500px; margin:auto; }
h2 { margin-bottom:20px; font-weight:bold; font-size:24px; }
.slider-row, .button-row { display:flex; justify-content:center; align-items:center; margin:15px 0; }
.label { width:120px; text-align:right; margin-right:10px; font-size:18px; }
input[type=range] {
-webkit-appearance:none; width:100%; height:15px; border-radius:5px;
background:#444; outline:none; opacity:0.8; transition:opacity .2s;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance:none; width:25px; height:25px; border-radius:50%;
background:#fff; cursor:pointer;
}
input[type=range].bass::-webkit-slider-runnable-track { background: linear-gradient(to right, red 0%, red 100%); }
input[type=range].mids::-webkit-slider-runnable-track { background: linear-gradient(to right, green 0%, green 100%); }
input[type=range].highs::-webkit-slider-runnable-track { background: linear-gradient(to right, blue 0%, blue 100%); }
button {
background:#222; border:none; color:#fff; padding:10px 20px; margin: 0 10px;
font-size:18px; border-radius:8px; cursor:pointer;
transition: background-color 0.3s ease;
}
button:hover { background:#555; }
button.selected { background:#0f0; color:#000; font-weight:bold; }
</style>
</head>
<body>
<div class="container">
<h2>Light Music on GENESIS</h2>
<div class="slider-row">
<div class="label">Bass</div>
<input type="range" min="0" max="100" value="%BASS%" class="bass" oninput="updateSlider('bass',this.value)">
</div>
<div class="slider-row">
<div class="label">Mids</div>
<input type="range" min="0" max="100" value="%MIDS%" class="mids" oninput="updateSlider('mids',this.value)">
</div>
<div class="slider-row">
<div class="label">Highs</div>
<input type="range" min="0" max="100" value="%HIGHS%" class="highs" oninput="updateSlider('highs',this.value)">
</div>
<div class="button-row">
<button id="btnAmbient" onclick="setPattern('0')">Ambient</button>
<button id="btnSwirl" onclick="setPattern('1')">Swirl</button>
<button id="btnRipple" onclick="setPattern('2')">Ripple</button>
</div>
</div>
<script>
function updateSlider(type, value) {
fetch('/set?type='+type+'&value='+value);
}
function setPattern(mode) {
fetch('/setpattern?mode='+mode).then(() => {
updateButtons(mode);
});
}
function updateButtons(selectedMode) {
document.getElementById('btnAmbient').classList.remove('selected');
document.getElementById('btnSwirl').classList.remove('selected');
document.getElementById('btnRipple').classList.remove('selected');
if(selectedMode == '0') document.getElementById('btnAmbient').classList.add('selected');
else if(selectedMode == '1') document.getElementById('btnSwirl').classList.add('selected');
else if(selectedMode == '2') document.getElementById('btnRipple').classList.add('selected');
}
// Initialize selected button on page load
window.onload = () => {
updateButtons("%MODE%");
};
</script>
</body>
</html>
)rawliteral";
html.replace("%BASS%", String(bassLevel));
html.replace("%MIDS%", String(midsLevel));
html.replace("%HIGHS%", String(highsLevel));
html.replace("%MODE%", String((int)currentMode));
return html;
}
// ======= WEB HANDLERS =======
void handleRoot() { server.send(200, "text/html", htmlPage()); }
void handleSet() {
if (server.hasArg("type") && server.hasArg("value")) {
String type = server.arg("type");
int value = server.arg("value").toInt();
if (type == "bass") bassLevel = value;
else if (type == "mids") midsLevel = value;
else if (type == "highs") highsLevel = value;
}
server.send(200, "text/plain", "OK");
}
void handleSetPattern() {
if (server.hasArg("mode")) {
int mode = server.arg("mode").toInt();
if (mode >= 0 && mode <= 2) {
currentMode = (Mode)mode;
patternChanged = true;
}
}
server.send(200, "text/plain", "OK");
}
// ======= UTILITY =======
int getEffectiveThreshold(int sliderValue, int baseThreshold) {
return baseThreshold + (sliderValue * baseThreshold * 2) / 100;
}
// ======= MATRIX PATTERNS =======
void drawAmbient() {
static uint8_t hue = 0;
hue += 2;
CHSV color(hue, 255, 255);
for (int y = 0; y < NUM_ROWS; y++) {
for (int x = rowOffset[y]; x < rowOffset[y] + rowCount[y]; x++) {
uint16_t i = XY(x, y);
if (i != INVALID) leds[i] = color;
}
}
}
void drawSwirl() {
static float swirlAngle = 0;
swirlAngle += 2.0;
if (swirlAngle >= 360.0) swirlAngle -= 360.0;
for (int y = 0; y < NUM_ROWS; y++) {
for (int x = rowOffset[y]; x < rowOffset[y] + rowCount[y]; x++) {
uint16_t i = XY(x, y);
if (i == INVALID) continue;
float dx = x - CENTER_X;
float dy = y - CENTER_Y;
float angle = atan2(dy, dx) * 180.0 / PI;
if (angle < 0) angle += 360.0;
float dist = sqrt(dx * dx + dy * dy);
float h = fmod(angle + swirlAngle + dist * 10, 360.0);
leds[i] = CHSV(h / 360.0 * 255, 255, 255);
}
}
}
void drawRipple() {
static float rippleTime = 0;
rippleTime += 0.05;
for (int y = 0; y < NUM_ROWS; y++) {
for (int x = rowOffset[y]; x < rowOffset[y] + rowCount[y]; x++) {
uint16_t i = XY(x, y);
if (i == INVALID) continue;
float dx = x - CENTER_X;
float dy = y - CENTER_Y;
float dist = sqrt(dx * dx + dy * dy);
float h = fmod((dist * 30.0) - (rippleTime * 100), 360.0);
if (h < 0) h += 360.0;
leds[i] = CHSV(h / 360.0 * 255, 255, 255);
}
}
}
// ======= FFT TASK PINNED TO CORE 0 =======
void sampleAndProcessFFT(void * parameter) {
for (;;) {
unsigned long startMicros = micros();
for (int i = 0; i < SAMPLES; i++) {
vReal[i] = analogRead(micPin) - 2048;
vImag[i] = 0;
while (micros() - startMicros < (i + 1) * (1000000UL / SAMPLING_FREQUENCY)) {}
}
FFT.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
FFT.compute(FFT_FORWARD);
FFT.complexToMagnitude();
double bassEnergy = 0, midEnergy = 0, highEnergy = 0;
int bassBins = 0, midBins = 0, highBins = 0;
for (int i = 1; i < (SAMPLES / 2); i++) {
double freq = (i * SAMPLING_FREQUENCY) / SAMPLES;
if (freq >= 150 && freq <= 250) {
bassEnergy += vReal[i];
bassBins++;
}
else if (freq > 250 && freq <= 2000) {
midEnergy += vReal[i];
midBins++;
}
else if (freq > 2000 && freq <= 18000) {
highEnergy += vReal[i];
highBins++;
}
}
int effBassThresh = getEffectiveThreshold(bassLevel, noiseThresholdBass);
int effMidThresh = getEffectiveThreshold(midsLevel, noiseThresholdMid);
int effHighThresh = getEffectiveThreshold(highsLevel, noiseThresholdHigh);
double avgBass = bassBins ? bassEnergy / bassBins : 0;
double avgMid = midBins ? midEnergy / midBins : 0;
double avgHigh = highBins ? highEnergy / highBins : 0;
double signalBass = max(avgBass - effBassThresh, 0.0);
double signalMid = max(avgMid - effMidThresh, 0.0);
double signalHigh = max(avgHigh - effHighThresh, 0.0);
signalHigh *= 2.0;
if (signalBass < 20) signalBass = 0;
if (signalMid < 20) signalMid = 0;
if (signalHigh < 20) signalHigh = 0;
unsigned long now = millis();
if (signalBass > peakAmplitudeBass) peakAmplitudeBass = signalBass;
if (signalMid > peakAmplitudeMid) peakAmplitudeMid = signalMid;
if (signalHigh > peakAmplitudeHigh) peakAmplitudeHigh = signalHigh;
if (now - lastLoudBass > resetDelay) peakAmplitudeBass = 300;
if (now - lastLoudMid > resetDelay) peakAmplitudeMid = 300;
if (now - lastLoudHigh > resetDelay) peakAmplitudeHigh = 300;
if (signalBass > 0) lastLoudBass = now;
if (signalMid > 0) lastLoudMid = now;
if (signalHigh > 0) lastLoudHigh = now;
// Calculate combined signal presence for lighting matrix ON/OFF
double combinedSignal = signalBass + signalMid + signalHigh;
if (combinedSignal > 0) {
// Light ON - run selected pattern
switch (currentMode) {
case AMBIENT: drawAmbient(); break;
case SWIRL: drawSwirl(); break;
case RIPPLE: drawRipple(); break;
}
} else {
// No sound - clear LEDs
fill_solid(leds, NUM_LEDS, CRGB::Black);
}
FastLED.setBrightness(BRIGHTNESS);
FastLED.show();
vTaskDelay(1);
}
}
void setup() {
Serial.begin(115200);
WiFi.config(local_IP, gateway, subnet);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected: " + WiFi.localIP().toString());
server.on("/", handleRoot);
server.on("/set", handleSet);
server.on("/setpattern", handleSetPattern);
server.begin();
pinMode(micPin, INPUT);
FastLED.addLeds<WS2812B, MATRIX_PIN, GRB>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
FastLED.setBrightness(BRIGHTNESS);
// Create FFT task pinned to core 0
xTaskCreatePinnedToCore(sampleAndProcessFFT, "FFT", 8192, NULL, 1, NULL, 0);
}
void loop() {
server.handleClient();
}