This fork was largely vibe-coded with AI pair-programming. It has NOT been through upstream code review or formal QA. Do NOT submit PRs to upstream from this fork.
For the official QLC+ project, go to: mcallegari/qlcplus
This fork adds an MCP (Model Context Protocol) server that lets AI agents (Copilot, Claude, Cursor, etc.) design and control lighting shows via natural language.
mcp/directory: Self-contained MCP server — 47 tools, 3 prompts, 230 unit tests- Streamable HTTP transport on
http://localhost:9696/mcp(auto-starts with app)autolight/directory: Iterative LED effect research loop (Python)- Function Wizard for QML UI
- Launchpad controller integration support
- Audio capture / BPM detection for scripts
- 22 audio-reactive RGB scripts (LedFX-ported atmospheric effects, strobes, motion, EQ visualizers)
- RGB Matrix rotation & mirroring (engine-level, all algorithm types)
- Blend mode ordering fix for Mask/Subtractive blend modes
- Enhanced OS2L plugin — Bonjour/mDNS auto-discovery, song metadata, connection status LED
- Auto-reload last workspace on startup (no
--openlastflag needed)- Theme preset infrastructure — switchable UI themes (includes "VS Code Dark")
Rotation and mirroring are now engine-level properties on
RGBMatrix, available for all algorithm types (Plain, Script, Text, Image, Audio).
Property Values Description Rotation 0°, 90°, 180°, 270° Rotates the rendered pattern. 90°/270° swap the algorithm's input dimensions so the pattern renders in rotated coordinate space. Mirror Off, Horizontal, Vertical, Both Places a mirror at the midpoint of the axis. Rotation is applied first, then mirroring. Mirror Blend Flip, Max, Average, Additive How mirrored halves are combined. Flip = pure reflection (default). Max = brighter pixel wins. Average = (a+b)/2. Additive =min(255, a+b).Previously, rotation/mirroring was only available for audio-reactive scripts via auto-injected script properties. The 90° rotation had a bug (
swvsshcoordinate index) that caused most pixels to be black on non-square grids. That bug is now fixed.Mask and Subtractive blend modes are order-dependent — they read the current universe value and transform it, so the base layer must write before the overlay. Previously, the write order depended on fader insertion order (essentially random from the user's perspective), which meant Mask/Subtractive often had no visible effect.
Fix: A new
BlendOverlayfader priority ensures Mask/Subtractive faders always write after Normal/Additive faders. This makes blend modes work correctly regardless of function start order or collection function list order.How blend modes work:
Mode Formula Use case Normal HTP: max(current, new)Default — highest value wins Additive min(current + new, 255)Layer effects on top of each other Mask current × (new / 255)Function output = brightness multiplier. White=pass, Black=block. Subtractive max(current − new, 0)Subtract function's values from existing output Example — RGB Matrix as a mask: Set the RGB Matrix's blend mode to Mask. Run it alongside a chaser in a collection. The matrix's pixel pattern acts as a stencil — white areas show the chaser's colors, dark areas are blocked.
The OS2L (Open Sound 2 Light) plugin now supports Bonjour/mDNS service discovery on macOS. VirtualDJ's OS2L "Auto" mode finds QLC+ automatically — no manual IP configuration needed.
Feature Description Bonjour discovery Registers _os2l._tcpservice via native macOSdns_sd.hAPI. VirtualDJ discovers QLC+ automatically.Song metadata Parses songevents — title, artist, BPM, key, duration available for scripting.Connection status LED Input patch shows orange (advertising) / green (connected) status indicator. Bonjour checkbox Enable/disable Bonjour from the OS2L config dialog (default: ON). Single-client enforcement Tracks TCP connection state; only one DJ app connects at a time. Quick setup: Enable OS2L on a universe → set VirtualDJ OS2L to Auto → done. See
plugins/os2l/README.mdfor details.A complete library of audio-reactive RGB Matrix algorithms, including ports from the LedFX project. All scripts accept audio frequency data via
Engine.getAudioFrequency()and support customizable parameters (palette, speed, sensitivity, trigger mode).
Category Scripts Atmospheric aurora, lava, plasma, fire, water, soap, melt Motion crawler, chaser, scroll, tunnel, vortex Visualizers spectrum, equalizer, wavelength, scan Beat-reactive strobe, shot, energy, power, glitch, blocks All effects support engine-level rotation (0°/90°/180°/270°), mirroring, and customizable color palettes.
QLC+ now automatically loads the most recent workspace file on startup when no file is specified on the command line. Skips gracefully if the file no longer exists on disk.
New theme preset infrastructure in UiManager allows switching between UI color schemes. Ships with a "VS Code Dark" preset. Presets control toolbar, frame header, and panel colors.
The
autolight/directory is a Python CLI tool that uses the MCP server to run structured A/B-style experiments on LED effects. It automates the create → preview → rate → iterate loop for finding the best-looking effects for your fixture setup.How it works:
- Setup — builds a rating UI in QLC+ Virtual Console (star buttons, dimension ratings)
- Briefing — interactive CLI questionnaire (genre, energy, palette, BPM)
- Rounds — each round generates 3–4 experiments (different algorithms, colors, timing)
- Rate — preview each experiment live, rate it 1–5 stars + per-dimension feedback
- Iterate — analysis picks winners, next round refines based on feedback
Each round creates a git branch for safe rollback. State is persisted in
autolight-state.json.Prerequisites: QLC+ running with MCP enabled, fixtures patched, Python 3.10+.
Quick start:
# Install dependencies (one-time) python3 -m venv .venv && source .venv/bin/activate pip install -r autolight/requirements.txt # Create rating UI in QLC+ Virtual Console python3 -m autolight setup # Start the research loop (briefing → experiments → rating → iterate) python3 -m autolight # Smoke test (verify setup end-to-end, no manual interaction) python3 autolight/test_loop.pySee
autolight/README.mdfor full documentation (custom dimensions, architecture, color palettes, state file format).Download the latest DMG from Actions artifacts. After mounting the DMG and dragging QLC+ to
/Applications:sudo xattr -cr /Applications/QLC+.app # clear quarantine (ad-hoc signed) open /Applications/QLC+.app# Configure (one-time, from repo root) mkdir -p build && cd build cmake .. -Dqmlui=ON -Dmcp_server=ON # Build cmake --build . -j$(sysctl -n hw.ncpu) # Run (MCP auto-starts on port 9696) ./qmlui/qlcplus-qmlRuntime flags:
Flag Description --no-mcpDisable MCP server --mcp-http <port>Change MCP port (default: 9696) -dEnable debug output to stderr -gLog debug output to ~/QLC+.logDev cycle — after code changes:
# Rebuild only what changed cd build && cmake --build . --target qlcplus-qml -j$(sysctl -n hw.ncpu) # If only MCP server code changed: cmake --build . --target qlcplusmcp -j$(sysctl -n hw.ncpu) # Run tests cmake --build . --target mcp_vc_query_filter_test -j8 && ./mcp/test/mcp_vc_query_filter_testCopilot CLI / VS Code — add to
~/.copilot/mcp.json:{ "servers": { "qlcplus": { "url": "http://localhost:9696/mcp" } } }Claude Code — run:
claude mcp add qlcplus --transport http http://localhost:9696/mcpClaude Desktop — add to
claude_desktop_config.json:{ "mcpServers": { "qlcplus": { "url": "http://localhost:9696/mcp" } } }Cursor — add to
.cursor/mcp.json:{ "mcpServers": { "qlcplus": { "url": "http://localhost:9696/mcp" } } }
Category Tools Query query_fixtures,query_available_fixtures,query_functions,query_fixture_channels,query_palettes,query_universes,query_input_profiles,query_midi_devices,query_osc_status,query_channel_modifiers,query_feedback_profilePatch patch_fixturesFunctions create_scenes,create_chasers,create_sequences,create_efxs,create_collections,create_rgb_matrices,create_scripts,create_fixture_groups,update_scene_from_dmx,delete_functionsPalettes create_palettes,delete_palettesChannels configure_channels,read_dmx_values,set_channel_modifiers,convert_degrees_to_dmx,set_grand_masterI/O configure_universes,configure_plugin_params,configure_osc,configure_beat_source,configure_launchpad,set_input_profile,vc_configure_feedbackVirtual Console vc_create_pages,vc_create_widgets,vc_query_pages,vc_query_widgets,vc_update_widgets,vc_delete_widgets,vc_reparent_widgetsVC Input vc_map_inputs,vc_set_key_sequences,vc_detect_overlapsVC Layout vc_reflow_framePrompts:
design_dj_show,debug_channel_conflict,setup_launchpadAll tools are batch-based (arrays in, arrays out) and idempotent (upsert by name). See
mcp/MCP-ARCHITECTURE.mdfor full documentation.
create_scriptsaccepts raw JavaScript executed by QJSEngine in a dedicated thread. Scripts are validated before saving — syntax errors are rejected with line numbers.Full Engine API (25 methods)
Function Control:
Method Returns Description Engine.startFunction(id)bool Start any QLC+ function Engine.stopFunction(id)bool Stop a running function Engine.isFunctionRunning(id)bool Check if function is active Engine.waitFunctionStart(id)bool Block until function starts Engine.waitFunctionStop(id)bool Block until function stops Engine.stopOnExit(bool)bool Auto-stop started functions on script exit Fixture Control:
Method Returns Description Engine.setFixture(fxID, ch, val)bool Set fixture channel (0-255) Engine.setFixture(fxID, ch, val, fadeMs)bool Set with fade time Engine.getChannelValue(universe, ch)int Read live pre-GM DMX value Timing:
Method Returns Description Engine.waitTime(ms)bool Pause execution (ms) Engine.waitTime("2s.500")bool Pause using time string Engine.random(min, max)int Random integer in [min,max] Engine.random("1s.0", "5s.0")int Random ms from time strings BPM & Beat:
Method Returns Description Engine.setBPM(bpm)bool Set beat generator BPM Engine.getBPM()int Current BPM (internal/MIDI/audio) Engine.getBeatDuration()int Beat period in ms Engine.isBeat()bool True if current tick is on a beat Audio Input:
Method Returns Description Engine.getAudioLevel()int Overall volume 0-255 Engine.getAudioFrequency(band, numBands)int Frequency band 0-255 (3=bass/mid/high, 16=detailed) Envelope (from parent Chaser/Collection):
Method Returns Description Engine.getOwnID()int This script's function ID Engine.getElapsed()int Ms since script started Engine.getEnvelopeDuration()int Allocated duration from parent (ms, 0 if standalone) Engine.getEnvelopeFadeIn()int Fade-in from parent (ms, 0 if not set) Engine.getEnvelopeFadeOut()int Fade-out from parent (ms, 0 if not set) Function Attributes:
Method Returns Description Engine.getFunctionAttribute(id, idx)float Read function attribute Engine.setFunctionAttribute(id, idx, val)bool Modify running function attribute Engine.setFunctionAttribute(id, "Name", val)bool By name (e.g. "Width", "Intensity") System:
Method Returns Description Engine.setBlackout(bool)bool Toggle global blackout Engine.systemCommand("prog args")bool Run external process (detached) Example patterns
// Candle flicker — Gaussian random, warm colors function gaussRand(mean, std) { var u1 = Math.random(), u2 = Math.random(); return mean + std * Math.sqrt(-2*Math.log(u1)) * Math.cos(2*Math.PI*u2); } for (var tick = 0; tick < 200; tick++) { for (var c = 0; c < 6; c++) { Engine.setFixture(c, 0, Math.max(100, Math.min(255, Math.round(gaussRand(210, 25))))); } Engine.waitTime(Engine.random(30, 120)); } // Envelope-adaptive buildup — reusable across different chaser step durations var totalMs = Engine.getEnvelopeDuration(); if (totalMs <= 0) totalMs = 5000; var steps = Math.round(totalMs / 25); for (var i = 0; i <= steps; i++) { Engine.setFixture(0, 0, Math.round(255 * i / steps)); Engine.waitTime(25); } // Audio-reactive — bass drives brightness, mid drives color for (var tick = 0; tick < 500; tick++) { var bass = Engine.getAudioFrequency(0, 3); var mid = Engine.getAudioFrequency(1, 3); Engine.setFixture(0, 0, bass); Engine.setFixture(0, 1, mid); Engine.waitTime(25); }
(Often abbreviated as "QLC+")
Open-source lighting control for DMX, Art-Net, sACN and more.
Designed for live shows, theatre, architectural installations, and venues.
QLC+ is powerful and user-friendly software to control lighting. QLC+ supports a huge amount of hardware, runs on Linux, Windows (10+), macOS (10.12+), and Raspberry Pi. Whether you're an experienced lighting professional or just getting started, QLC+ empowers you to take control of your lighting fixtures with ease. The primary goal of this project is to bring QLC+ to the level of available commercial software.
We have a dedicated page to help you find support, please check out SUPPORT.md. To learn about a specific feature of QLC+, take a look at the official documentation. To give feedback, submit new fixtures and get new ideas, go to the forum
Click the badge below to see the currently confirmed issues with QLC+. Perhaps you can find a solution?
Compilation guides and platform-specific instructions are available in our GitHub Wiki.
If you're regularly updating QLC+ sources with git pull, you may encounter compiler warnings, errors, or unresolved symbols. We strive to keep the master branch free of critical errors; however, dependencies between objects can sometimes cause issues, requiring a full package recompilation rather than just updating recent changes.
We welcome contributions from the community to help make QLC+ even better. If you're working on something major, start a thread in the Development Forum first. Make sure you read the CONTRIBUTING.md document for more.
If you're reading this we already appreciate you. If you're just getting started with lighting you have absolutely no obligation to give us money. When QLC+ opens up revenue opportunities for you, we'd be very thankful for your support. GitHub sponsors is the preferred option.
If you're interested, QLC+ also has an official store where you can purchase clothing, themes, the Raspberry Pi image or one-on-one consultation with an expert.
QLC+ owes its success to the dedication and expertise of numerous individuals who have generously contributed their time and skills. The following list recognizes those whose remarkable contributions have played a pivotal role in building QLC+.
QLC+ 5
- Eric Arnebäck (3D preview features)
- Santiago Benejam Torres (Catalan translation)
- Luis García Tornel (Spanish translation)
- Nils Van Zuijlen, Jérôme Lebleu (French translation)
- Felix Edelmann, Florian Edelmann (fixture definitions, German translation)
- Jannis Achstetter (German translation)
- Dai Suetake (Japanese translation)
- Hannes Bossuyt (Dutch translation)
- Aleksandr Gusarov (Russian translation)
- Vadim Syniuhin (Ukrainian translation)
- Mateusz Kędzierski + smaks6 (Polish translation)
QLC+ 4
- Jano Svitok (bugfix, new features and improvements)
- David Garyga (bugfix, new features and improvements)
- Lukas Jähn (bugfix, new features)
- Robert Box (fixtures review)
- Thomas Achtner (ENTTEC wing improvements)
- Joep Admiraal (MIDI SysEx init messages, Dutch translation)
- Florian Euchner (FX5 USB DMX support)
- Stefan Riemens (new features)
- Bartosz Grabias (new features)
- Simon Newton, Peter Newman (OLA plugin)
- Janosch Frank (webaccess improvements)
- Karri Kaksonen (DMX USB Eurolite USB DMX512 Pro support)
- Stefan Krupop (HID DMXControl Projects e.V. Nodle U1 support)
- Nathan Durnan (RGB scripts, new features)
- Giorgio Rebecchi (new features)
- Florian Edelmann (code cleanup, German translation)
- Heiko Fanieng, Jannis Achstetter (German translation)
- NiKoyes, Jérôme Lebleu, Olivier Humbert, Nils Van Zuijlen (French translation)
- Raymond Van Laake (Dutch translation)
- Luis García Tornel (Spanish translation)
- Jan Lachman (Czech translation)
- Nuno Almeida, Carlos Eduardo Porto de Oliveira (Portuguese translation)
- Santiago Benejam Torres (Catalan translation)
- Koichiro Saito, Dai Suetake (Japanese translation)
Q Light Controller
- Stefan Krumm (Bugfixes, new features)
- Christian Suehs (Bugfixes, new features)
- Christopher Staite (Bugfixes)
- Klaus Weidenbach (Bugfixes, German translation)
- Lutz Hillebrand (uDMX plugin)
- Matthew Jaggard (Velleman plugin)
- Ptit Vachon (French translation)
Licensed under the Apache 2.0 License. See COPYING for details.
Copyright © Heikki Junnila, Massimo Callegari