Electronics, music instruments
| Home |
|---|
| One-shot |
| Microled |
| Moduled Nano |
| Moduled Rack |
| Lunt |
| Lunt Preview |
| Resources |
| PO-24 Cheat Sheet |
MIDI-enabled controller for dimmable AC light bulbs, with on-device manual control.
Lunt drives up to four 110/220 V dimmable bulbs through an AC dimmer module. A toggle switch selects between MANUAL (set each bulb’s brightness by hand, or over MIDI) and ANIMATION (pick from self-running light shows). An 8-LED NeoPixel strip is the on-device display — warm yellow in MANUAL, red in ANIMATION.
▶ Live animation preview — an interactive simulation of all 8 light shows on four Edison-style bulbs, no hardware needed.
An Arduino Nano controls the four channels of an AC Light Dimmer Module via phase-cut dimming (the Dimmable Light library, synced to the AC zero-cross on D2), and reads MIDI on its hardware serial port.
The toggle switch picks the mode:
| Control | Action | | — | — | | Turn encoder right / left | Brighter / dimmer (the selected target) | | Press encoder | Cycle the target: ALL → 1 → 2 → 3 → 4 → STRIP → TEMPO → SUBDIV (defaults to ALL) |
After the four bulbs there are three setting targets (turn the encoder to adjust each):
STRIP_BRIGHTNESS_MIN.No MIDI/clock info is shown on the strip in MANUAL mode itself — only these toggles.
MIDI also drives brightness here (channel 15 by default, MIDI_CHANNEL in the sketch):
| Message | Mapping |
|---|---|
Note On / Off C4–D#4 (60–63) |
Turn bulbs 1–4 on/off (Note On velocity sets the on-brightness, Note Off turns it fully off) |
| CC 22 / 23 / 24 / 25 | Brightness of bulb 1 / 2 / 3 / 4 |
| CC 27 | Brightness of all bulbs at once |
The strip is warm yellow: ALL lights every pixel, Light N lights the Nth pixel (counted from the left). While you turn the encoder it shows a brightness bar, then reverts to the pixel number.
Entering MANUAL mode resets all bulbs to mid brightness (MANUAL_RESET_BRIGHTNESS),
so nothing stays stuck at whatever level an animation left it.
The strip is red. Each of the 8 animations sits on one of the 8 pixels. See them all in the live animation preview.
| Control | Action |
|---|---|
| Turn encoder (in menu) | Move the cursor over the 8 animations (the lit pixel = selection) |
| Press encoder (in menu) | Enter the highlighted animation |
| Turn encoder (running) | Adjust the animation (speed, or — with a MIDI clock — the beat subdivision) |
| Short press (running) | Re-sync: restart the animation at that instant — tap on the beat to line it up with the music |
| Long hold (running) | Exit back to the selection menu (LONG_PRESS_MS) |
| # | Animation | What it does |
|---|---|---|
| 1 | Comet | A bright spot sweeps 1→2→3→4 with a fading tail |
| 2 | Larson | A single bright bulb bounces 1→4→1 (Knight Rider) |
| 3 | Sine sweep | A phase-offset sine wave rolls smoothly across the bulbs |
| 4 | Breathe | All four fade up and down together |
| 5 | Twinkle | Random bulbs sparkle bright then fade |
| 6 | Build & drop | Fills 1→12→123→1234, blackout, repeat |
| 7 | Alternate | 1&3 ↔ 2&4 swap back and forth |
| 8 | 6/8 Pulse | For a 6/8 feel: bulbs 1 & 2 flash-and-fade on beats 1–3 (strong→medium→weak), then 3 & 4 on beats 4–6, repeating |
Here each of the six beats is one count of the 6/8 measure, so one free-run beat = one flash; 6/8 Pulse defaults to 120 BPM (the other animations default to ~135).
MIDI clock: when a clock is running, the animation beat-locks and the encoder selects the subdivision — 1 bar · 1/2 · 1/4 · 1/8 · 1/16 (shown as 1–5 red pixels). With no clock, the encoder sets a free speed of 20–250 BPM (shown as a red bar). MIDI notes/CC are ignored in ANIMATION mode.
Clock indicators (right two pixels, while an animation is running):
Each can be turned off via the MANUAL TEMPO / SUBDIV targets. If the tempo pixel never turns purple while you send clock, the Arduino isn’t receiving it (see Troubleshooting).
⚠️ The dimmer module switches mains AC. Wire and enclose the AC side carefully.
All control pins are on the Arduino Nano:
| Arduino pin | Connected to | Notes |
|---|---|---|
| D0 (RX) | MIDI input circuit output | Hardware serial; disconnect while uploading (see below) |
| D2 | Dimmer SYNC (zero-cross) | Must be an interrupt pin; required by the dimmer library |
| D9 | Dimmer CH1 | Bulb 1 gate |
| D7 | Dimmer CH2 | Bulb 2 gate |
| D5 | Dimmer CH3 | Bulb 3 gate |
| D6 | Dimmer CH4 | Bulb 4 gate |
| D3 | Encoder CLK (A) | |
| D8 | Encoder DT (B) | |
| D12 | Encoder button | INPUT_PULLUP |
| A1 (D15) | NeoPixel DIN | 8 pixels |
| A0 (D14) | Mode switch | INPUT_PULLUP; other switch terminal to GND |
Power: dimmer module and NeoPixel VCC/GND to the Nano’s 5V/GND; encoder and
switch grounds to GND.
MIDI input is an opto-isolated circuit (6N138 + 1N914 + 4.7k + 220 ohm) feeding the Nano’s RX (D0). See the SparkFun MIDI tutorial for the standard input schematic.
Mode switch logic: with the internal pull-up, the switch idles HIGH (= MANUAL) and
reads LOW when closed to GND (= ANIMATION). Flip ANIM_MODE_LEVEL in the sketch if your
two modes come out reversed.
Adjustable knobs are grouped in the CONFIG / KNOBS section at the top of the sketch.
See Tunable parameters for the full list with values and ranges.
Install via the Arduino Library Manager:
Build with the Arduino AVR Boards core 1.8.3 — see Troubleshooting for why.
All are const (or #define) in the CONFIG / KNOBS block at the top of lunt.ino,
except where noted. Pins are in the Wiring table. “Range” is the sensible
working range, not the data-type limit.
| Variable | Value | Range | Description |
| — | — | — | — |
| ANIM_MODE_LEVEL | LOW | LOW / HIGH | Switch level that selects ANIMATION (the other position is MANUAL). |
| ENCODER_STEP | 8 | 1–32 | Brightness / free-speed change per encoder detent. |
| BUTTON_DEBOUNCE_MS | 200 | 50–500 | Minimum ms between accepted encoder-button presses. |
| LONG_PRESS_MS | 600 | 300–1500 | Hold time to exit an animation (shorter = a re-sync). |
| MANUAL_RESET_BRIGHTNESS | 128 | 0–255 | All bulbs reset to this level when MANUAL mode is entered. |
| Variable | Value | Range | Description |
| — | — | — | — |
| MIDI_CHANNEL | 15 | 1–16 | Channel the controller listens on. |
| NOTE_LIGHT[4] | 60, 61, 62, 63 | 0–127 each | Note numbers that turn bulbs 1–4 on/off. |
| NOTE_MIN_BRIGHT | 40 | 0–255 | Brightness of a note played at the softest velocity. |
| CC_LIGHT[4] | 22, 23, 24, 25 | 0–127 each | CC numbers that set bulbs 1–4 brightness. |
| CC_ALL_BRIGHT | 27 | 0–127 | CC number that sets all bulbs at once. |
| Variable | Value | Range | Description |
| — | — | — | — |
| NUM_PIXELS | 8 | 1–~60 | Number of LEDs on the strip; set to match yours. |
| STRIP_BRIGHTNESS_DEFAULT | 60 | 0–255 | Indicator brightness at boot (live-adjustable via the MANUAL STRIP target). |
| STRIP_BRIGHTNESS_MIN | 5 | 0–60 | Floor for the STRIP brightness control so the indicator stays visible. |
| COLOR_WARM | (255,130,15) | any RGB | MANUAL-mode strip color (set in setup()). |
| COLOR_RED | (255,0,0) | any RGB | ANIMATION-mode strip color (set in setup()). |
| MANUAL_BRIGHT_SHOW_MS | 1200 | 500–3000 | How long the brightness bar shows after a turn before reverting. |
| Variable | Value | Range | Description |
| — | — | — | — |
| ANIM_BPM_MIN | 20 | 5–120 | Free-run speed (BPM) at encoder fully left. One beat = one step / one cycle. |
| ANIM_BPM_MAX | 250 | 120–600 | Free-run speed (BPM) at encoder fully right. The knob is linear in BPM. |
| TAIL_SHIFT | 2 | 1–4 | Comet/larson/twinkle tail fade: b -= b >> this (smaller = longer tail). |
| CLOCK_TIMEOUT_MS | 600 | 300–2000 | Clock is treated as absent (→ free speed) after this gap. |
| CLOCK_BLINK_MS | 90 | 30–250 | How long the purple clock indicator stays bright on each beat. |
Beat-lock subdivisions are fixed in SUBDIV_BEATS (1 bar, 1/2, 1/4, 1/8, 1/16).
multiple definition of 'std::nothrow'The full error looks like new.cpp.o ... multiple definition of 'std::nothrow' ...
ArduinoSTL/new_handler.cpp.o ... first defined here.
Cause: the Dimmable Light library depends on ArduinoSTL, which redefines
operator new / std::nothrow already provided by the Arduino AVR Boards core
1.8.4 and newer, so the linker sees two definitions.
Fix: downgrade the core to 1.8.3, the last version that links cleanly with ArduinoSTL: Tools → Board → Boards Manager → search “Arduino AVR Boards” → pick version 1.8.3 → Install. Re-select the Nano afterwards and recompile.
avrdude: stk500_recv(): programmer is not respondingCause #1 — the MIDI input is on RX. MIDI uses the Nano’s hardware serial (D0/D1), the same UART the bootloader uses to receive the program. If the MIDI input circuit is connected to RX (D0), it blocks the upload. Fix: disconnect whatever is wired to D0 (RX) before uploading, then reconnect it afterward. (This is inherent to using hardware serial for MIDI — you’ll do it every flash.)
Cause #2 — wrong bootloader. Most CH340-based Nano clones ship with the old bootloader. Fix: select Tools → Processor → ATmega328P (Old Bootloader) and upload again.
Also check the Serial Monitor is closed (it holds the port) and that the correct CH340/USB-serial port is selected.
Mechanical rotary encoders bounce, and EMI from the nearby AC dimmer makes it worse. The sketch already decodes the encoder with a quadrature state table that rejects bounce. If it still twitches, add a 100 nF capacitor from each encoder pin (CLK, DT) to GND for hardware debouncing.
Flip the right test in handleEncoder() to reverse the knob direction; the strip’s
left/right mapping is in fillPixels() / lightOnePixel().
First check the clock indicator: enter an animation (ANIMATION mode → press the encoder) and watch the far-right pixel — it turns purple and flashes on the beat when a clock is arriving. If it stays blue (or dark):
If the indicator is purple but tempo seems to do nothing: make sure you’ve entered an animation (pressed the encoder), since the clock only drives a running animation, and that you’re watching a tempo-sensitive one (e.g. comet or 6/8 pulse).