Markus Loeffler
Published © MIT

LumiBand Gen3 — Wireless DMX LED Wristband with BLE

Music-reactive LED wristbands for festivals. 100 handmade units deployed. Gen3 adds wireless and BLE: the crowd becomes part of the light

AdvancedWork in progressOver 1 day29
LumiBand Gen3 — Wireless DMX LED Wristband with BLE

Things used in this project

Story

Read more

Custom parts and enclosures

The wristband

StL file

Schematics

PCB layout

Code

lumiband_demo.ino

C/C++
/*
 * LumiBand  Minimal Beat Detection Demo
 * 
 * A bare-bones demonstration of music-reactive LEDs using an ATtiny85
 * and a raw electret microphone wired directly to the ADC  no op-amp.
 * 
 * This works because festivals and clubs are loud (90dB+).
 * The venue does the amplification. Thresholds are tuned accordingly.
 * 
 * Hardware:
 *   - ATtiny85 @ 8MHz internal oscillator
 *   - 15x WS2812B NeoPixels on PB0 (pin 5)
 *   - Electret mic: VCC via 10k bias resistor, output to PB3 (ADC3, pin 2)
 *   - 100nF AC coupling cap between mic output and ADC pin
 *   - Single cell LiPo, 3.3V LDO regulator
 * 
 * Wiring:
 *   Electret mic (+) -> 10k -> VCC
 *   Electret mic (+) -> 100nF -> PB3 (ADC3)
 *   Electret mic (-) -> GND
 * 
 * Library required: Adafruit NeoPixel
 * https://github.com/adafruit/Adafruit_NeoPixel
 * 
 * WaveLights  https://wavelights.com
 * Shared as a demo sketch. Full Gen3 firmware is proprietary.
 */

#include <Adafruit_NeoPixel.h>

//  Pin definitions 
#define LED_PIN       0       // PB0  NeoPixel data line
#define MIC_PIN       A3      // PB3  ADC3  electret mic input

//  NeoPixel configuration 
#define NUM_LEDS      15
#define BRIGHTNESS    40      // Keep low  saves power, still vivid in dark environments

Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

//  Beat detection tuning 
// These thresholds assume a loud environment (festival/club, 90dB+).
// In a quiet room the mic signal will be too weak to trigger  that's expected.
#define NOISE_FLOOR       10    // Minimum ADC deviation to consider as signal
#define BEAT_THRESHOLD    40    // ADC peak above baseline to trigger a beat
#define BEAT_HOLDOFF_MS   150   // Minimum ms between beats (prevents double-triggering)
#define DECAY_MS          80    // How long the LED flash stays on after a beat

//  Runtime state 
int      baseline       = 512;  // ADC midpoint (updated with slow moving average)
int      peakLevel      = 0;
bool     beatActive     = false;
uint32_t lastBeatTime   = 0;
uint32_t beatOnTime     = 0;

//  Colour palette 
// Cycles through hues on each beat for visual variety
uint8_t  hue = 0;

//  Helper: HSV to RGB (simplified, 8-bit hue) 
uint32_t hsvToColor(uint8_t h, uint8_t s, uint8_t v) {
  uint8_t r, g, b;
  uint8_t region = h / 43;
  uint8_t remainder = (h - (region * 43)) * 6;
  uint8_t p = (v * (255 - s)) >> 8;
  uint8_t q = (v * (255 - ((s * remainder) >> 8))) >> 8;
  uint8_t t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8;
  switch (region) {
    case 0:  r = v; g = t; b = p; break;
    case 1:  r = q; g = v; b = p; break;
    case 2:  r = p; g = v; b = t; break;
    case 3:  r = p; g = q; b = v; break;
    case 4:  r = t; g = p; b = v; break;
    default: r = v; g = p; b = q; break;
  }
  return strip.Color(r, g, b);
}

//  Setup 
void setup() {
  strip.begin();
  strip.setBrightness(BRIGHTNESS);
  strip.clear();
  strip.show();

  // Warm up baseline over first 50 samples
  long sum = 0;
  for (int i = 0; i < 50; i++) {
    sum += analogRead(MIC_PIN);
    delay(2);
  }
  baseline = sum / 50;
}

//  Main loop 
void loop() {
  uint32_t now = millis();

  // Read mic and compute deviation from baseline
  int sample    = analogRead(MIC_PIN);
  int deviation = abs(sample - baseline);

  // Slow baseline drift correction (tracks DC offset changes over time)
  baseline = (baseline * 31 + sample) / 32;

  // Track peak within current window
  if (deviation > peakLevel) peakLevel = deviation;

  // Beat detection: peak exceeded threshold and holdoff has elapsed
  if (peakLevel > BEAT_THRESHOLD && (now - lastBeatTime) > BEAT_HOLDOFF_MS) {
    beatActive   = true;
    beatOnTime   = now;
    lastBeatTime = now;
    peakLevel    = 0;

    // Advance hue for next beat colour
    hue += 37;  // Prime step keeps colours varied without repeating quickly
  }

  // LED output
  if (beatActive) {
    // Flash all LEDs on beat
    uint32_t color = hsvToColor(hue, 255, 255);
    for (int i = 0; i < NUM_LEDS; i++) {
      strip.setPixelColor(i, color);
    }
    strip.show();

    // Turn off after decay period
    if ((now - beatOnTime) > DECAY_MS) {
      beatActive = false;
      strip.clear();
      strip.show();
    }
  }

  // Small delay to reduce ADC noise and save power
  delay(2);
}

Credits

Markus Loeffler
3 projects • 40 followers
I channel that passion into creating sound-reactive LED objects - blending technology and design to craft pieces that make colors dance.

Comments