/*
============================================================
16 x 16 WS2812B NeoPixel Matrix — Art Gallery Firmware
16x16 निओपिक्सेल मॅट्रिक्स — आर्ट गॅलरी फर्मवेअर
12 animation modes matching the HTML simulator:
1 Plasma
2 Fire
3 Matrix Rain
4 Rainbow Spiral
5 Ripple
6 Starfield Warp
7 Fireworks
8 DNA Helix
9 Aurora
10 Checker Wave
11 Kaleidoscope
12 Conway's Life
Board: works on ESP32 / ESP8266 / Arduino Uno (any FastLED target)
Library: FastLED (install via Library Manager)
Arvind Patil / AI Centre Nandurbar / Ysz4 / HansaScript
============================================================
*/
#include <FastLED.h>
// ---------------- कॉन्फिगरेशन / CONFIG ----------------
#define LED_PIN 5 // डेटा पिन बदला — change to your wiring (ESP32: e.g. 5, 15, 27; Uno: 6)
#define MATRIX_W 16
#define MATRIX_H 16
#define NUM_LEDS (MATRIX_W * MATRIX_H)
#define BRIGHTNESS 90 // 0-255
#define BTN_PIN 4 // पुश बटण (मोड बदलण्यासाठी) — push button to change mode, wire to GND, uses INPUT_PULLUP
// Serpentine wiring: row 0 left->right, row 1 right->left, etc.
// तुमची मॅट्रिक्स सरळ (progressive) वायरिंग असल्यास SERPENTINE false करा
#define SERPENTINE true
CRGB leds[NUM_LEDS];
uint8_t currentMode = 0;
const uint8_t NUM_MODES = 12;
unsigned long lastFrame = 0;
unsigned long lastAutoSwitch = 0;
const unsigned long FRAME_MS = 33; // ~30 fps
const unsigned long AUTO_SWITCH_MS = 15000; // auto-advance mode every 15s (set 0 to disable)
uint32_t t = 0; // animation clock
// button debounce
int lastBtnState = HIGH;
unsigned long lastBtnTime = 0;
// ---------------- XY मॅपिंग / XY MAPPING ----------------
uint16_t XY(uint8_t x, uint8_t y) {
if (x >= MATRIX_W || y >= MATRIX_H) return 0;
uint16_t i;
if (SERPENTINE) {
if (y & 1) {
i = y * MATRIX_W + (MATRIX_W - 1 - x);
} else {
i = y * MATRIX_W + x;
}
} else {
i = y * MATRIX_W + x;
}
return i;
}
void setPixel(int x, int y, CRGB c) {
if (x < 0 || x >= MATRIX_W || y < 0 || y >= MATRIX_H) return;
leds[XY(x, y)] = c;
}
void clearAll() {
fill_solid(leds, NUM_LEDS, CRGB::Black);
}
// ============================================================
// MODE 1 — PLASMA
// ============================================================
void modePlasma() {
for (uint8_t y = 0; y < MATRIX_H; y++) {
for (uint8_t x = 0; x < MATRIX_W; x++) {
int v = sin8(x * 24 + t / 3) + sin8(y * 24 + t / 4) +
sin8((x + y) * 18 + t / 2) +
sin8((uint8_t)(sqrt((x - 8) * (x - 8) + (y - 8) * (y - 8)) * 20) - t / 3);
uint8_t hue = (v / 4) + t / 2;
setPixel(x, y, CHSV(hue, 255, 255));
}
}
}
// ============================================================
// MODE 2 — FIRE (classic fire2012 style, per column)
// ============================================================
byte heat[MATRIX_W][MATRIX_H];
void modeFire() {
for (uint8_t x = 0; x < MATRIX_W; x++) {
// cool down every cell
for (uint8_t y = 0; y < MATRIX_H; y++) {
heat[x][y] = qsub8(heat[x][y], random8(0, 40));
}
// heat rises & diffuses upward (y=0 is top, fire rises toward y=0)
for (int y = 0; y < MATRIX_H - 1; y++) {
heat[x][y] = (heat[x][y + 1] + heat[x][y + 1] + (y + 2 < MATRIX_H ? heat[x][y + 2] : 0)) / 3;
}
// ignite new sparks at the bottom row
if (random8() < 160) {
heat[x][MATRIX_H - 1] = qadd8(heat[x][MATRIX_H - 1], random8(160, 255));
}
for (uint8_t y = 0; y < MATRIX_H; y++) {
CRGB c = HeatColor(heat[x][y]);
setPixel(x, y, c);
}
}
}
// ============================================================
// MODE 3 — MATRIX RAIN
// ============================================================
float dropY[MATRIX_W];
float dropSpeed[MATRIX_W];
uint8_t dropLen[MATRIX_W];
bool matrixInit = false;
void modeMatrixRain() {
if (!matrixInit) {
for (uint8_t x = 0; x < MATRIX_W; x++) {
dropY[x] = -random8(0, MATRIX_H);
dropSpeed[x] = 0.3 + random8(0, 40) / 100.0;
dropLen[x] = 4 + random8(0, 8);
}
matrixInit = true;
}
clearAll();
for (uint8_t x = 0; x < MATRIX_W; x++) {
dropY[x] += dropSpeed[x];
if (dropY[x] - dropLen[x] > MATRIX_H) {
dropY[x] = -random8(0, 10);
dropSpeed[x] = 0.3 + random8(0, 40) / 100.0;
dropLen[x] = 4 + random8(0, 8);
}
for (uint8_t i = 0; i < dropLen[x]; i++) {
int yy = (int)dropY[x] - i;
if (yy >= 0 && yy < MATRIX_H) {
if (i == 0) setPixel(x, yy, CRGB(180, 255, 180)); // bright head
else {
uint8_t fade = 255 - (255 * i / dropLen[x]);
setPixel(x, yy, CRGB(0, fade, 0));
}
}
}
}
}
// ============================================================
// MODE 4 — RAINBOW SPIRAL
// ============================================================
void modeRainbowSpiral() {
for (uint8_t y = 0; y < MATRIX_H; y++) {
for (uint8_t x = 0; x < MATRIX_W; x++) {
float dx = x - 7.5, dy = y - 7.5;
float ang = atan2(dy, dx) * 180.0 / PI;
float dist = sqrt(dx * dx + dy * dy);
uint8_t hue = (uint8_t)(ang + dist * 14 - t * 1.2);
setPixel(x, y, CHSV(hue, 255, 255));
}
}
}
// ============================================================
// MODE 5 — RIPPLE
// ============================================================
struct Ripple { float x, y, r; uint8_t hue; bool active; };
Ripple ripples[6];
void modeRipple() {
clearAll();
for (uint8_t y = 0; y < MATRIX_H; y++)
for (uint8_t x = 0; x < MATRIX_W; x++)
setPixel(x, y, CRGB(0, 4, 10));
if (random8() < 12) {
for (uint8_t i = 0; i < 6; i++) {
if (!ripples[i].active) {
ripples[i] = {(float)random8(0, MATRIX_W), (float)random8(0, MATRIX_H), 0, random8(), true};
break;
}
}
}
for (uint8_t i = 0; i < 6; i++) {
if (!ripples[i].active) continue;
ripples[i].r += 0.35;
for (uint8_t a = 0; a < 48; a++) {
float ang = a / 48.0 * TWO_PI;
int px = round(ripples[i].x + cos(ang) * ripples[i].r);
int py = round(ripples[i].y + sin(ang) * ripples[i].r);
if (px >= 0 && px < MATRIX_W && py >= 0 && py < MATRIX_H) {
uint8_t fade = max(0.0f, 255.0f - ripples[i].r * 25.0f);
setPixel(px, py, CHSV(ripples[i].hue, 180, fade));
}
}
if (ripples[i].r > 10) ripples[i].active = false;
}
}
// ============================================================
// MODE 6 — STARFIELD WARP
// ============================================================
struct Star { float x, y, z; };
Star stars[36];
bool starInit = false;
void modeStarfield() {
if (!starInit) {
for (uint8_t i = 0; i < 36; i++) {
stars[i] = {(float)random8(0, MATRIX_W) - 8, (float)random8(0, MATRIX_H) - 8, (float)random8(1, 16)};
}
starInit = true;
}
clearAll();
for (uint8_t i = 0; i < 36; i++) {
stars[i].z -= 0.18;
if (stars[i].z <= 0.5) {
stars[i].x = random8(0, MATRIX_W) - 8;
stars[i].y = random8(0, MATRIX_H) - 8;
stars[i].z = 16;
}
int px = round(8 + stars[i].x / stars[i].z * 4);
int py = round(8 + stars[i].y / stars[i].z * 4);
uint8_t b = min(255.0f, (16 - stars[i].z) * 18);
setPixel(px, py, CRGB(b, b, 255));
}
}
// ============================================================
// MODE 7 — FIREWORKS
// ============================================================
struct Particle { float x, y, vx, vy, life; uint8_t hue; bool active; };
Particle particles[40];
void modeFireworks() {
clearAll();
if (random8() < 12) {
float cx = 2 + random8(0, 12), cy = 2 + random8(0, 10);
uint8_t hue = random8();
uint8_t count = 12 + random8(0, 8);
for (uint8_t i = 0; i < count && i < 40; i++) {
float ang = i / (float)count * TWO_PI;
particles[i] = {cx, cy, (float)cos(ang) * (0.6 + random8(0,60)/100.0),
(float)sin(ang) * (0.6 + random8(0,60)/100.0), 1.0, hue, true};
}
}
for (uint8_t i = 0; i < 40; i++) {
if (!particles[i].active) continue;
particles[i].x += particles[i].vx;
particles[i].y += particles[i].vy;
particles[i].vy += 0.03;
particles[i].life -= 0.035;
if (particles[i].life <= 0) { particles[i].active = false; continue; }
int px = round(particles[i].x), py = round(particles[i].y);
setPixel(px, py, CHSV(particles[i].hue, 255, particles[i].life * 255));
}
}
// ============================================================
// MODE 8 — DNA HELIX
// ============================================================
void modeDNA() {
clearAll();
for (uint8_t y = 0; y < MATRIX_H; y++) {
float phase = y * 0.55 + t * 0.08;
int x1 = round(7.5 + sin(phase) * 6);
int x2 = round(7.5 + sin(phase + PI) * 6);
setPixel(x1, y, CRGB(255, 60, 180));
setPixel(x2, y, CRGB(60, 180, 255));
if (y % 3 == 0) {
int lo = min(x1, x2), hi = max(x1, x2);
for (int x = lo; x <= hi; x++) setPixel(x, y, CRGB(30, 90, 50));
}
}
}
// ============================================================
// MODE 9 — AURORA
// ============================================================
void modeAurora() {
for (uint8_t x = 0; x < MATRIX_W; x++) {
float wave = sin(x * 0.35 + t * 0.04) * 3 + sin(x * 0.15 - t * 0.02) * 2;
float centerY = 5 + wave;
for (uint8_t y = 0; y < MATRIX_H; y++) {
float dist = abs(y - centerY);
float intensity = max(0.0f, 1.0f - dist / 5.0f);
uint8_t hue = 100 + (uint8_t)(sin(x * 0.3 + t * 0.03) * 40);
setPixel(x, y, CHSV(hue, 200, intensity * 255));
}
}
}
// ============================================================
// MODE 10 — CHECKER WAVE
// ============================================================
void modeCheckerWave() {
for (uint8_t y = 0; y < MATRIX_H; y++) {
for (uint8_t x = 0; x < MATRIX_W; x++) {
int v = sin8((x + y) * 16 - t * 3);
uint8_t hue = (t * 2 + (x + y) * 10) % 256;
if (v > 128) setPixel(x, y, CHSV(hue, 255, 255));
else setPixel(x, y, CRGB(2, 2, 6));
}
}
}
// ============================================================
// MODE 11 — KALEIDOSCOPE
// ============================================================
void modeKaleidoscope() {
for (uint8_t y = 0; y < MATRIX_H; y++) {
for (uint8_t x = 0; x < MATRIX_W; x++) {
float dx = x - 7.5, dy = y - 7.5;
float ang = atan2(dy, dx);
float seg = PI / 4;
ang = abs(fmod(ang, seg) - seg / 2);
float dist = sqrt(dx * dx + dy * dy);
uint8_t hue = (uint8_t)(ang * 180.0 / PI * 6 + dist * 12 + t * 1.5);
uint8_t val = 150 + 100 * sin(dist * 0.8 - t * 0.1);
setPixel(x, y, CHSV(hue, 255, val));
}
}
}
// ============================================================
// MODE 12 — CONWAY'S LIFE (colored)
// ============================================================
bool lifeGrid[MATRIX_W][MATRIX_H];
uint8_t lifeHue[MATRIX_W][MATRIX_H];
bool lifeInit = false;
uint8_t lifeTick = 0;
void seedLife() {
for (uint8_t x = 0; x < MATRIX_W; x++)
for (uint8_t y = 0; y < MATRIX_H; y++)
lifeGrid[x][y] = random8() > 190;
}
void modeLife() {
if (!lifeInit) { seedLife(); lifeInit = true; }
lifeTick++;
if (lifeTick % 4 == 0) {
bool next[MATRIX_W][MATRIX_H];
uint8_t alive = 0;
for (uint8_t x = 0; x < MATRIX_W; x++) {
for (uint8_t y = 0; y < MATRIX_H; y++) {
uint8_t n = 0;
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
if (dx == 0 && dy == 0) continue;
uint8_t xx = (x + dx + MATRIX_W) % MATRIX_W;
uint8_t yy = (y + dy + MATRIX_H) % MATRIX_H;
if (lifeGrid[xx][yy]) n++;
}
}
if (lifeGrid[x][y]) next[x][y] = (n == 2 || n == 3);
else next[x][y] = (n == 3);
if (next[x][y] && !lifeGrid[x][y]) lifeHue[x][y] = t * 3;
if (next[x][y]) alive++;
}
}
memcpy(lifeGrid, next, sizeof(lifeGrid));
if (alive < 6) seedLife();
}
clearAll();
for (uint8_t x = 0; x < MATRIX_W; x++)
for (uint8_t y = 0; y < MATRIX_H; y++)
if (lifeGrid[x][y]) setPixel(x, y, CHSV(lifeHue[x][y], 230, 255));
}
// ============================================================
// MODE डिस्पॅच / DISPATCH
// ============================================================
void runMode(uint8_t m) {
switch (m) {
case 0: modePlasma(); break;
case 1: modeFire(); break;
case 2: modeMatrixRain(); break;
case 3: modeRainbowSpiral(); break;
case 4: modeRipple(); break;
case 5: modeStarfield(); break;
case 6: modeFireworks(); break;
case 7: modeDNA(); break;
case 8: modeAurora(); break;
case 9: modeCheckerWave(); break;
case 10: modeKaleidoscope(); break;
case 11: modeLife(); break;
}
}
void resetModeState(uint8_t m) {
// प्रत्येक मोड बदलताना बफर रीसेट — clear per-mode buffers on switch
if (m == 2) matrixInit = false;
if (m == 5) starInit = false;
if (m == 11) lifeInit = false;
memset(heat, 0, sizeof(heat));
for (uint8_t i = 0; i < 6; i++) ripples[i].active = false;
for (uint8_t i = 0; i < 40; i++) particles[i].active = false;
}
void nextMode() {
currentMode = (currentMode + 1) % NUM_MODES;
resetModeState(currentMode);
lastAutoSwitch = millis();
}
// ============================================================
// SETUP / LOOP
// ============================================================
void setup() {
Serial.begin(115200);
FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
FastLED.clear();
FastLED.show();
pinMode(BTN_PIN, INPUT_PULLUP);
randomSeed(analogRead(A0));
Serial.println(F("16x16 NeoPixel Matrix — Art Gallery"));
Serial.println(F("Arvind Patil / AI Centre Nandurbar"));
}
void loop() {
unsigned long now = millis();
// बटण तपासा / check button (debounced)
int btnState = digitalRead(BTN_PIN);
if (btnState == LOW && lastBtnState == HIGH && (now - lastBtnTime) > 250) {
nextMode();
lastBtnTime = now;
}
lastBtnState = btnState;
// ऑटो मोड बदल / auto-advance mode
if (AUTO_SWITCH_MS > 0 && now - lastAutoSwitch > AUTO_SWITCH_MS) {
nextMode();
}
// फ्रेम रेंडर / render frame
if (now - lastFrame >= FRAME_MS) {
lastFrame = now;
t++;
runMode(currentMode);
FastLED.show();
}
}