This was built because I didn't want to buy a massive lighting controller just to run one DMX derby/laser fixture. This is a lightweight UI that runs over a USB-DMX adapter so you can bypass confusing remote control auto-modes and actually control the lights yourself. I also added experimental live music analysis -- though obviously it's not the same experience as a real human preparing before a show starts.
- One slider per DMX channel (9-channel mode), each showing a description of the current value
- Blackout
- Three selectable color themes via
COLOR_SCHEMEinsrc/config.py - Music Mode: analyzes your system's audio in real time and drives the fixture from it
- Live spectrum ring (Sub / Bass / Mids / Highs) + waveform display
- Hit detection per band, tempo tracking (BPM, beat clock, bars)
- Rules: "when this happens -> do this" (band hit / level / every N beats -> LED, Derby, Laser)
- Ready-made looks as starting points, adjustable sensitivity
- Shows the currently playing track's title, artist, and cover art
DMX-RDC/
βββ main.py # entry point -- run this
βββ requirements.txt
βββ ...manual.pdf # usermanual for the used DMX Derby Laser
βββ presets/ # created automatically, holds saved channel presets .json
βββ nowplaying_cache/ # just the cache for powershell
βββ _old/ # superseded files (old Music Mode, old config) -- reference only, safe to delete
βββ src/
βββ __init__.py
βββ app.py # main window, dialogs
βββ config.py # main window / DMX / theme constants
βββ controller.py # DMX serial link, preset persistence, platform helpers
βββ theme.py # every ttk style
βββ musicmode/
βββ __init__.py
βββ config.py # every Music Mode tunable
βββ audio_source.py # loopback capture (WASAPI / PulseAudio)
βββ analysis.py # band levels, hits, tempo, beat clock
βββ devices.py # LED / Derby / Laser: states -> DMX values
βββ engine.py # rules + looks -> DMX frame
βββ music_app.py # the Music Mode window
βββ nowplaying.py # title / artist / cover reader
βββ NowPlayingBridge.ps1 # Windows: title/artist/album/cover bridge
-
Cross-platform:
- Python 3.10+
- pyserial 3.5+
- numpy 1.26+
- pillow 10.0+
-
Windows only:
- PyAudioWPatch>=0.2.12
- pywin32>=306
-
Linux only (not tested yet):
- PyAudio>=0.2.14
- jeepney>=0.8.0
-
A USB-DMX adapter that is recognized as a serial (COM) port
-
See requirements.txt for Python packages -- installation differs slightly by platform, see below
pip install -r requirements.txtAnd thats basically it, just run main:
python main.pyand you can go on to Instructions.
Well, of course, we (fortunately) have far more options and possibilities available to us for solving all these problems. I recommend setting up a development environment first:
# 1. System packages
sudo apt update
sudo apt install python3-venv python3-dev python3-tk build-essential portaudio19-dev
# 2. Virtual environment + Python packages
cd <.../DMX-RDC-root>
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtBy default only root may open /dev/ttyUSB* ("Permission denied"). Install a udev rule once. The example is for FTDI adapters (0403:6001) -- check your adapter's IDs with lsusb.
sudo tee /etc/udev/rules.d/99-dmx.rules <<'EOF'
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666"
EOF
sudo udevadm control --reload-rules
sudo udevadm triggerUnplug and replug the adapter. ls -l /dev/ttyUSB0 should now show crw-rw-rw-.
Use the
teeform instead ofecho '...' | sudo tee: quotes insideechoeasily get mangled when pasted, which leaves the port with no permissions (c---------).
source .venv/bin/activate # once per new terminal
python main.pyOn Linux the adapter shows up as /dev/ttyUSB0 (or /dev/ttyACM0). Ignore the /dev/ttyS* entries, those are unused built-in ports.
- Select the serial port your USB-DMX adapter is connected to (
COM3etc. on Windows,/dev/ttyUSB0or/dev/ttyACM0on Linux). - Click Connect.
- Move the sliders -- changes are sent continuously while connected.
- BLACKOUT sets all channels to 0 immediately.
- π΅ Music Mode opens a dedicated window for audio-reactive lighting (see below). The main window hides itself while Music Mode is open but keeps sending in the background; closing Music Mode brings it back.
- Disconnect stops sending and closes the port.
Click π΅ Music Mode to open it. It analyzes whatever is currently playing through your system's audio output and turns that into DMX values. The track title and artist are shown top left (Windows: via the bundled NowPlayingBridge.ps1; Linux: via MPRIS over D-Bus), with cover art where available. Without cover art, the disc shows a small rotating pixel-art animation instead.
Spectrum ring (left): a live frequency view, 20 Hz to 20 kHz on a logarithmic scale, with level rings from 0 dB down to -40 dB. Hover over it to read the exact frequency and level at that point (e.g. 50 Hz -3 dB). The four bands (Sub / Bass / Mids / Highs) light up on a hit.
Live
- BPM with tempo lock and four beat/bar indicators. Re-check BPM restarts the tempo detection, e.g. after a tempo change.
- A level meter and hit lamp per band (Sub / Bass / Mids / Highs).
- A small preview of what LED, Derby and Laser are doing right now.
Look: one-click starting points (Kick Flash, Colour Pulse, Sub Swing, Ambient) with a short description of what each does. Editing the mapping below switches the look to "Custom".
When this hits -> do this: the mapping matrix. Each row is a source (Sub / Bass / Mids / Highs band hit, or the Beat), each column is something to trigger:
- HIT = FLASH: LED, Derby, Laser
- HIT = CHANGE: LED pattern, Derby colour, Laser colour, Derby swing, Laser spin
Click a cell to cycle through its states: β (nothing) β ON (flash on hit) β OFF (dark on hit); for CHANGE columns the cell instead steps to the next pattern / colour / position on every hit. Right-click goes back. In the Beat row, the dropdown picks how often it fires (every N bars/beats).
Extras
- Bouncy Bass: the Derby motor jumps between position 10 and 110 on every kick. When kicks come faster than the motor can follow, it only jumps on every 2nd (3rd, ...) kick (
BOUNCE_TRAVEL_S, default 0.45 s, insrc/musicmode/config.py). - Learn kick & thresholds per song: the analysis learns each track's kick frequency, band thresholds and BPM while it plays, and caches them in
src/musicmode/song_profiles.json(keyed by "artist - title"), so a song you've played before is detected better right from the start.
Tuning
- Sensitivity: how strongly quiet parts count (1.0x = default).
- Smoothness: how slowly the band levels fall off after a hit. Higher values give calmer, longer lights; lower values react snappier.
Mappings and tuning can be changed while Music Mode is running. Blackout sets all channels to 0, Back to Manual Control returns to the sliders. The status line at the bottom warns if no DMX adapter is connected (go back to manual control and click Connect).
Channel setups can be saved and reloaded as presets, stored as individual JSON files in the presets/ folder (created automatically on first run).
- Save As... -- stores the current slider values under a name you choose
- Load -- applies the selected preset's values to all sliders
- Delete -- removes the selected preset
Each preset is a plain JSON file: presets/<name>.json:
{
"1": 44,
"2": 180,
"3": 216,
"4": 0,
"5": 128,
"6": 60,
"7": 0,
"8": 254,
"9": 127
}DMX512 is a unidirectional protocol: The controller has no way of verifying whether a device is actually receiving data; it can only check whether the USB-DMX adapter itself is accessible via the serial interface. And anyway, I don't think you can use this script for other devices because of the different channels; that might be the biggest limitation :/
Distributed under the MIT License. See LICENSE for more information.