Current release: v0.0 — see CHANGELOG.md.
A Linux DMX lighting player that streams MTC-synchronised DMX cues to OLA, driven over OSC.
- Source / issues: stagesoft/cuems-dmxplayer on GitHub
- Contributing: see CONTRIBUTORS.md
- Release history: see CHANGELOG.md
cuems-dmxplayer is the DMX lighting playback daemon for the CUEMS (Cue Management
System) platform. It receives lighting scenes and control commands over OSC (Open Sound
Control), interpolates per-channel DMX fades, and synchronises playback to MIDI Time Code
(MTC). DMX output is emitted through the OLA (Open Lighting Architecture) daemon, so any
DMX transport OLA supports — USB-DMX dongles, Art-Net, sACN — is available without changes to
the player.
It is composed of the player executable plus three Git submodules that are reused across the CUEMS daemon family:
oscreceiver— UDP OSC listener; the base classDmxPlayerderives from.mtcreceiver— RtMidi-based MIDI Time Code decoder and play-head estimator.cuemslogger— syslog-backed singleton logger shared by all CUEMS components.
- Overview
- Architecture
- Core Concepts
- Design Goals
- API documentation
- Installation
- Usage
- Development
- Contributors
- Release notes
- Future developments
- Copyright notice
- License
cuems-dmxplayer is a single long-running process. Lighting data and transport control enter
over OSC; a timecode source feeds MTC over MIDI; DMX frames leave through OLA. Three independent
threads cooperate around shared, mutex-protected state:
OSC bundles / commands MIDI Time Code (MTC)
(UDP, from cuems-engine) (RtMidi: ALSA / rtpmidid)
│ │
▼ ▼
┌────────────────────┐ ┌───────────────────┐
│ OscReceiver │ │ MtcReceiver │
│ (listener thread) │ │ (RtMidi callback │
└─────────┬──────────┘ │ thread) │
│ ProcessBundle/Message └─────────┬─────────┘
▼ │ estimatedCurrentHead()
┌─────────────────────────────────────────────────────────────┐
│ DmxPlayer │
│ │
│ m_scenes ──► processScenes() ──► m_activeUniverses │
│ (queued) (fetch + build) (per-channel fades) │
│ │ │
│ updateActiveUniverses() │
└─────────────────────────────────────────────┬───────────────┘
│ SendDMX()
(OLA SelectServer thread, 10 ms / 200 ms timer)
▼
┌────────────────────┐
│ olad (OLA) │
│ USB-DMX / Art-Net │
│ / sACN output │
└────────────────────┘
What each layer does:
- Ingest —
OscReceiverparses incoming UDP packets and dispatches OSC messages and bundles toDmxPlayer. A top-level bundle is assembled into one scene transition. - Timecode —
MtcReceiverdecodes MTC quarter-frames and full frames on the RtMidi thread and exposes a filtered, extrapolated play-head in milliseconds. - Scheduling —
DmxPlayerqueues scenes ordered by their MTC start time, fetches each universe's current DMX state from OLA, and converts the target values into per-channel linear fade transitions. - Output — On every OLA timer tick the player advances the play-head, interpolates each
active channel, and writes the resulting
DmxBufferto OLA.
The runtime path is built around the central DmxPlayer class; the legacy XML cue classes
(DmxCue_v0 / DmxCue_v1) are a separate, older loading path retained for reference.
DmxPlayer(dmxplayer.h/dmxplayer.cpp) — the central orchestrator. Inherits fromOscReceiverto receive OSC, owns anMtcReceiverfor timecode, and drives an OLAOlaClientWrapper+SelectServer. Responsible for scene queuing, fade interpolation, the adaptive output timer, and automatic OLA reconnection.CommandLineParser(commandlineparser.h/commandlineparser.cpp) — minimal argv tokeniser exposingoptionExists()andgetParam()lookups for the CLI flags.main(main.h/main.cpp) — process entry point. Parses the command line, installs theSIGTERM/SIGINT/SIGUSR1handlers, constructs the singleton logger and theDmxPlayer, and callsrun().CuemsConstants(cuems_constants.h) — compile-time constants: DMX/universe/channel bounds, port range, timer intervals, look-ahead and reconnection delays.cuems_errors.h— process exit codes shared across CUEMS daemons (see Exit codes).DmxCue_v0(dmxcue_v0.h/dmxcue_v0.cpp) andDmxCue_v1(dmxcue_v1.h/dmxcue_v1.cpp) — legacy XML cue parsers built on Xerces-C. They model scene → universe → channel hierarchies loaded fromdmx.xmlfiles. The current runtime path uses OSC bundles instead; these classes are not on the OSC playback path.
OscReceiver— wraps oscpack'sOscPacketListenerand aUdpListeningReceiveSocket. Spawns a dedicated listener thread (threadedRun()), holds the boundoscPortand the OSC address prefix (oscAddress), and exposes overridableProcessMessage()/ProcessBundle()hooks.DmxPlayeris constructed with an empty address prefix, so OSC addresses are matched directly (e.g./quit,/frame).
MtcReceiver— derives fromRtMidiIn. Decodes MTC quarter-frame and full-frame SysEx messages on the RtMidi callback thread and maintains a filtered, extrapolated play-head. Key public surface used by the player:isTimecodeActive()— whether timecode is currently advancing (network-tolerant timeout).estimatedCurrentHead()— current MTC head in milliseconds, extrapolated between frames.setNetworkMode(bool)— relaxes timeouts for MTC over the network (e.g.rtpmidid).- Static atomics
isTimecodeRunning,mtcHead,curFrameRate,wasLastUpdateFullFrame. setTickCallback()— optional lock-free per-quarter-frame callback (unused by the player).
MtcFrame— value type for a decodedhh:mm:ss:ffframe plus rate, with conversions to and from seconds/milliseconds.
CuemsLogger— singleton syslog logger with severity helpers (logError,logWarning,logInfo,logOK, …). Retrieved anywhere viaCuemsLogger::getLogger(). The per-process "slug" is set from the--uuid(or port) so log lines are attributable to a specific player.
| Type | Role |
|---|---|
SceneTransitionInfo |
One pending scene: target values per universe, MTC start time, fade duration. |
ChannelTransition |
Per-channel linear interpolation state: mtc0→mtc1, val0→val1. |
ActiveUniverse |
A universe currently fading: its OLA DmxBuffer, fetch state, and channel transitions. |
FrameValues |
map<channel_id, value> — channel values within a universe. |
SceneValues |
map<universe_id, FrameValues> — all universes within a scene. |
| Thread | Source | Touches | Protected by |
|---|---|---|---|
| OSC listener | oscreceiver |
parses bundles, appends to m_scenes |
m_scenesMutex |
| RtMidi callback | mtcreceiver |
decodes MTC, updates atomics | internal to MtcReceiver |
| OLA SelectServer | OLA | processScenes(), updateActiveUniverses(), SendDMX() |
m_scenesMutex, m_universesMutex |
m_scenesMutex guards the scene queue m_scenes; m_universesMutex guards
m_activeUniverses. The play-head (playHead) and connection/run flags are std::atomic.
- Scene transition — the atomic unit of playback. One top-level OSC bundle yields one
SceneTransitionInfo: a set of target channel values per universe, an MTC start time, and a fade duration. Scenes are queued ordered by start time. - Play-head — the current playback position in milliseconds. When following MTC it is
estimatedCurrentHead() + output-latency-compensation; otherwise it is held at0and scenes apply immediately ("press Go" without timecode). - Channel transition — a per-channel linear interpolation from the channel's current DMX
value to its target value over the scene's
[mtc_start, mtc_start + fade_time]window. A zero fade time is an instant set. - Universe fetch — before fading a universe, the player asks OLA for that universe's current
DMX buffer so fades start from the live on-stage value, not from zero. Fetching begins
UNIVERSE_FETCH_LOOK_AHEAD_MS(50 ms) before the scene's start time. - Output-latency compensation — a tunable look-ahead (default 35 ms, range 0–500 ms) added to the MTC play-head so DMX frames land on the wire in time with timecode despite OLA, adapter and fixture latency.
- Adaptive timer — the OLA output callback runs at 10 ms while there is active work and drops to 200 ms when idle, cutting CPU ~20×. Incoming scenes wake the timer instantly.
- Stop-on-MTC-lost — when timecode disappears, the player either freezes (default) or keeps
playing (
--ciml), so a dropout doesn't blackout the stage mid-show. - MTC following — playback only chases timecode when "following" is enabled (
--mtcfollowor OSC/mtcfollow); otherwise scenes are applied as soon as they arrive.
- Fail fast on missing infrastructure — startup probes the OLA daemon and aborts with a specific exit code if it is unreachable, rather than silently producing no output.
- Survive
oladrestarts — the run loop detects a dropped OLA connection and reconnects with exponential backoff (500 ms → 5 s), purging stale scenes so playback resumes cleanly. - Start fades from live state — universes are fetched from OLA before fading so transitions begin at the actual on-stage value, never snapping to zero.
- Be cheap when idle — the adaptive timer guarantees near-zero CPU between cues while keeping sub-frame latency once a scene is queued.
- Tolerate networked timecode — MTC timeouts are relaxed for
rtpmidid/network MIDI so normal jitter does not register as a lost signal. - Be addressable in a multi-player rig — every instance carries a
--uuidthat names its OLA client and tags its log lines, so many players can run side by side. - Never crash on bad input — out-of-range universes/channels/values are clamped or dropped with a warning; constructor failures (e.g. OSC port already bound) exit cleanly with a code.
cuems-dmxplayer exposes three interfaces: the OSC API (the primary runtime control
surface), the command-line interface, and a small C++ public API for embedding the
player. Process exit codes are part of the contract with the supervising engine.
The player listens for OSC over UDP on the port given by --port. Messages are matched against
the configured address prefix; in the shipped configuration the prefix is empty, so the
addresses below are used verbatim.
OSC traffic falls into two groups: control commands, accepted at any time, and bundle-only messages, which are only honoured while parsing an OSC bundle.
| Address | Argument | Effect |
|---|---|---|
/quit |
— | Raises SIGTERM; the player shuts down gracefully. |
/check |
— | Raises SIGUSR1; prints/logs the RUNNING! status line. |
/stoponlost |
— | Toggles the stop-on-MTC-lost flag. |
/mtcfollow |
int (optional) |
Enables (≠0) or disables (0) MTC following. With no argument, toggles the current state. |
/blackout |
— | Clears the scene queue and all active fades, then sends zeros to every active universe. |
These are processed only inside an OSC bundle and together describe a single scene
transition. A bundle is assembled into one SceneTransitionInfo and inserted into the scene
queue ordered by its MTC start time. If no /mtc_time or /start_offset is supplied, the scene
starts at the current play-head ("now").
| Address | Arguments | Meaning |
|---|---|---|
/frame |
universe_id:int, then repeating channel:int value:int pairs |
Target DMX values for a universe. universe_id must be 0–65535; an out-of-range universe makes the whole message ignored. Each channel must be 0–512 and value 0–255; out-of-range pairs are skipped with a warning. |
/fade_time |
seconds:float |
Fade duration for the scene, stored internally as round(1000 × seconds) milliseconds. |
/mtc_time |
string |
Scene start time. "now" → current play-head; "+<time>" → play-head plus <time>; otherwise max(play-head, <time>). <time> format is [[h:]m:]s (e.g. 90, 1:30, 0:01:30). |
/start_offset |
int (ms) |
Scene start as current play-head plus the given millisecond offset. |
Example (using test/send_dmx_osc.py, which builds bundles with pyliblo3):
req = DmxReq()
req.addFrame(1, "FFFFFF") # universe 1, channels 0..5 → 255
req.send("+0:02", 5.0) # start 2 s ahead of the play-head, 5 s fadeThe bundle above contains a /frame, a /mtc_time ("+0:02"), and a /fade_time (5.0).
cuems-dmxplayer --port <osc_port> [options]
| Option | Alias | Argument | Required | Default | Description |
|---|---|---|---|---|---|
--port |
-p |
<port> |
Yes | — | OSC UDP port to listen on. Must be 1–65535. |
--uuid |
-u |
<id> |
No | port number | Unique identifier used for the OLA client name and log slug. |
--ciml |
-c |
— | No | off | Continue If MTC Lost — keep playing when the MTC signal drops instead of stopping. |
--mtcfollow |
-m |
— | No | off | Start following MTC immediately, rather than waiting for an OSC /mtcfollow. |
--output-latency-ms |
— | <int> |
No | 35 |
DMX output-pipeline latency compensation in ms, clamped to 0–500. Usually fed by the engine from settings.xml. |
--show |
— | [w|c] |
No | — | Print licence disclaimers: w = warranty, c = copyright; no value prints usage. |
Running with no arguments prints the copyright banner and usage, then exits with
CUEMS_EXIT_WRONG_PARAMETERS.
The DmxPlayer class can be embedded directly. Public surface (dmxplayer.h):
DmxPlayer(int port = 8000,
const std::string oscRoute = "",
bool stopOnLostFlag = true,
bool followMTCFlag = false,
const std::string& client_name = "DMX_Player");
void run(); // Connect to OLA and block until final exit
// (handles reconnection internally).
bool IsRunning() const; // Thread-safe running flag.
void setOutputLatencyMs(long ms); // Set output-latency compensation; clamped to [0, 500].The constructor probes the OLA daemon and calls exit(CUEMS_EXIT_FAILED_OLA_SETUP) if it is
unreachable; it may also throw, which main() catches and maps to CUEMS_EXIT_INIT_FAILED.
run() owns the OLA SelectServer loop and returns only on a clean shutdown
(/quit → SIGTERM).
Defined in cuems_errors.h:
| Code | Symbol | Meaning |
|---|---|---|
0 |
CUEMS_EXIT_OK |
Success. |
-1 |
CUEMS_EXIT_FAILURE |
Generic error. |
-2 |
CUEMS_EXIT_WRONG_PARAMETERS |
Invalid/missing command-line parameters. |
-3 |
CUEMS_EXIT_WRONG_DATA_FILE |
Invalid data file. |
-4 |
CUEMS_EXIT_AUDIO_DEVICE_ERR |
Audio device error (shared code; unused here). |
-5 |
CUEMS_EXIT_FAILED_OLA_SETUP |
OLA setup failed — is olad running? |
-6 |
CUEMS_EXIT_FAILED_OLA_SEL_SERV |
OLA SelectServer failed. |
-7 |
CUEMS_EXIT_FAILED_XML_INIT |
XML parser initialisation failed (legacy cue path). |
-8 |
CUEMS_EXIT_NO_MIDI_PORTS_FOUND |
No MIDI ports available for MTC. |
-9 |
CUEMS_EXIT_INIT_FAILED |
Player constructor failed (e.g. OSC port already bound). |
A Linux system with a running OLA daemon (olad) and the following libraries:
librtmidi— MIDI input for MTClibola,libolacommon— Open Lighting Architecture clientlibxerces-c— XML parsing (legacy cue classes)liboscpack— OSC packet handling (used by theoscreceiversubmodule)libpthread,libstdc++fs— threading andstd::filesystem
On Debian/Ubuntu:
sudo apt-get install -y \
cmake g++ \
librtmidi-dev libola-dev libxerces-c-dev liboscpack-dev \
olagit clone https://github.com/stagesoft/cuems-dmxplayer.git
cd cuems-dmxplayer
git submodule update --init # fetch oscreceiver, mtcreceiver, cuemslogger
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
sudo make install # installs cuems-dmxplayer to <prefix>/binA legacy
Makefilealso exists and builds the samecuems-dmxplayerbinary (makefor a debug build with AddressSanitizer,make releasefor an optimised build). CMake is the preferred build system.
git clone --branch debian/bookworm https://github.com/stagesoft/cuems-dmxplayer.git
cd cuems-dmxplayer
dpkg-buildpackage -us -uc
sudo dpkg -i ../cuems-dmxplayer_*.debMake sure olad is running and a DMX output universe is configured (via the OLA web UI at
http://localhost:9090 or ola_dev_info). Then start a player bound to an OSC port:
# Wait for OSC /mtcfollow before chasing timecode; stop if MTC is lost (defaults)
cuems-dmxplayer --port 8000 --uuid stage-left
# Follow MTC from the start and keep playing if timecode drops out
cuems-dmxplayer --port 8000 --uuid stage-left --mtcfollow --ciml
# Override the output-latency compensation (e.g. for an Art-Net node)
cuems-dmxplayer --port 8000 --output-latency-ms 44Send test scenes with the bundled Python helper (requires pyliblo3):
python3 test/send_dmx_osc.py 8000Trigger a blackout or quit from any OSC client:
oscsend localhost 8000 /blackout
oscsend localhost 8000 /quit# Debug build with AddressSanitizer + coverage instrumentation
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="--coverage" \
-DCMAKE_EXE_LINKER_FLAGS="--coverage"
make -j$(nproc)- Language standard: C++17 (
-Wall -Wextra). - Submodules:
oscreceiver,mtcreceiver,cuemslogger. Rungit submodule update --initafter cloning and re-run it after pulling submodule bumps. - Manual testing:
test/send_dmx_osc.pysends OSC bundles for end-to-end checks. There is currently no automated test suite in this repository; contributions adding one are welcome (see CONTRIBUTORS.md). - Logging: runs through
CuemsLoggerto syslog; the slug is derived from--uuid/port.
See CONTRIBUTORS.md for the full contribution workflow (spec-first, TDD, DCO sign-off, Conventional Commits, and review requirements).
Contributions are welcome. The authoritative, step-by-step workflow lives in CONTRIBUTORS.md; the essentials are summarised here.
Workflow at a glance
- Spec first (Tier 2). Non-trivial changes start with a short written spec or issue agreed with a maintainer before code. Trivial changes (Tier 1: typos, comments, formatting) can go straight to a PR.
- Test-driven. Where automated tests are feasible, add a failing test first, then the
implementation, then refactor. This repository has no suite yet, so new features that can be
tested should bring their own (e.g. a
ctesttarget). - Branch naming.
feat/<slug>,fix/<slug>,chore/<slug>,refactor/<slug>, … - Conventional Commits v1.0. e.g.
feat(dmxplayer): add /blackout OSC command. Commit bodies should explain the why and any breaking impact. - DCO sign-off. Every commit must be signed off (
git commit -s) under the Developer Certificate of Origin. - SPDX headers. Every new source file starts with the GPL-3.0 SPDX header.
- Pull requests target
main. Keep PRs focused; include a clear description, test notes, and a CHANGELOG line.
Maintainers / reviewers
Every PR is reviewed by one of:
Authorship
cuems-dmxplayer and its submodules are developed and maintained by Stagelab Coop SCCL
(historically Stage Lab & bTactic) as part of the CUEMS platform. The mtcreceiver submodule
additionally credits Alex Ramos, Ion Reguera, and Adrià Masip.
See CHANGELOG.md for the full history. Recent highlights:
- DMX output-latency compensation. The MTC play-head is now offset by a configurable
output latency (default 35 ms, range 0–500 ms) so DMX frames land on the wire in time with
timecode. Tunable at runtime via
setOutputLatencyMs()and from the CLI with--output-latency-ms, which the engine drives fromsettings.xml. - Automatic OLA reconnection. When
oladrestarts or crashes, the player detects the lost connection and reconnects with exponential backoff (500 ms → 5 s), purging stale scenes and resetting universe state so playback resumes cleanly. - Adaptive output timer. The OLA callback runs at 10 ms while cues are active and drops to 200 ms when idle (~20× less CPU), instantly waking when a new scene arrives over OSC.
/blackoutOSC command + thread-safety. A new/blackoutclears all scenes and fades and sends zeros to every universe, guarded by a dedicatedm_universesMutex. The MTC pipeline gained network-tolerant timeouts (rtpmidid), an explicit-argument/mtcfollow, and a fix for a divide-by-zero whenfade_timeis 0.
The items below are planned but not yet implemented. They describe the intended CI/CD and DevOps integration so the work — and the badges that advertise it — can be wired in incrementally. Each subsection ends at the badge that proves the pipeline is live.
The current verification path is the manual test/send_dmx_osc.py helper. The first milestone is
a real automated suite so behaviour can be guarded in CI:
- Add a
tests/directory and actest-driven harness (GoogleTest is the intended framework, matching themtcreceiversubmodule'sMTCRECV_TESTINGhooks). - Unit-test the pure logic that does not need live hardware:
DmxPlayer::convertTime()parsing, the fade interpolation inupdateActiveUniverses()(including thefade_time = 0instant-set edge case), and OSC argument validation bounds fromCuemsConstants. - Gate hardware-dependent paths (OLA
SendDMX/FetchDMX, MTC decode) behind seams so they can be exercised withoutolador a MIDI source, mirroringmtcreceiver'sSkipPortOpenTagandinvokeTickForTesting()testing affordances. - Wire the suite into CMake:
enable_testing()
add_subdirectory(tests) # registers ctest targetsctest --test-dir build --output-on-failureA GitHub Actions workflow runs the suite on every push to main and on every pull request,
builds with coverage instrumentation, and uploads the report to Codecov:
name: Tests
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive # oscreceiver, mtcreceiver, cuemslogger
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake g++ lcov \
librtmidi-dev libola-dev libxerces-c-dev liboscpack-dev
- name: Configure with coverage
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="--coverage" \
-DCMAKE_EXE_LINKER_FLAGS="--coverage"
- name: Build
run: cmake --build build -j$(nproc)
- name: Run tests
run: ctest --test-dir build --output-on-failure
- name: Generate coverage report
run: |
lcov --capture --directory build \
--output-file coverage.info \
--ignore-errors inconsistent
lcov --remove coverage.info '/usr/*' '*/tests/*' \
--output-file coverage.info
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage.info
fail_ci_if_error: false
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}One-time manual step: activate the repository at
codecov.io/gh/stagesoft/cuems-dmxplayer
(sign in with GitHub → Activate) and add the CODECOV_TOKEN repository secret. The first
successful run populates the coverage badge.
Once tests.yml exists, append these badges below the existing badge block at the top of this
README:
[](https://github.com/stagesoft/cuems-dmxplayer/actions/workflows/tests.yml)
[](https://codecov.io/gh/stagesoft/cuems-dmxplayer)A separate workflow publishes an HTML documentation site to GitHub Pages. For this C++ daemon the intended toolchain is MkDocs (Material theme) for the hand-written architecture/API pages, optionally fed by Doxygen for the class reference extracted from the headers:
name: Deploy MkDocs site
on:
push:
branches:
- main
permissions:
contents: write # mkdocs gh-deploy pushes to the gh-pages branch
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install mkdocs mkdocs-material
- run: mkdocs gh-deploy --forceThis requires a minimal mkdocs.yml (site_name: cuems-dmxplayer,
repo_url: https://github.com/stagesoft/cuems-dmxplayer, markdown_extensions: [admonition])
and a docs/ tree seeded from the Architecture and API documentation sections of this
README. The published site lives at
stagesoft.github.io/cuems-dmxplayer.
Workflow separation: keep CI (
tests.yml) and docs (gh-pages.yml) as independent workflows.mkdocs gh-deploy --forceoverwrites thegh-pagesbranch, so no other workflow should commit to it.
Badge to add once the docs workflow is live:
[](https://github.com/stagesoft/cuems-dmxplayer/actions/workflows/gh-pages.yml)To complete the DevOps loop and match the rest of the CUEMS ecosystem:
- Maintain a
debian/bookwormbranch with packaging metadata sodpkg-buildpackage -us -ucproducescuems-dmxplayer_*.deb(already referenced under Installation). - Ship a
systemdunit so the player can be managed as a service (systemctl enable --now cuems-dmxplayer@<uuid>), with the OSC port and--output-latency-mstemplated per instance. - Optionally add a release workflow that builds the
.debon tag push and attaches it to the GitHub Release.
When all of the above is in place, the badge block at the top of the README should read:
[](https://www.gnu.org/licenses/gpl-3.0)
[](https://github.com/stagesoft/cuems-dmxplayer/actions/workflows/tests.yml)
[](https://codecov.io/gh/stagesoft/cuems-dmxplayer)
[](https://github.com/stagesoft/cuems-dmxplayer/actions/workflows/gh-pages.yml)cuems-dmxplayer — Linux DMX player with MTC sync and OSC control for the CUEMS platform.
Copyright (C) 2020-2026 Stagelab Coop SCCL
This program is free software: you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If
not, see <https://www.gnu.org/licenses/>.
cuems-dmxplayer is distributed under the terms of the
GPL v3 (GPL-3.0-or-later). The cuemslogger and
oscreceiver submodules are likewise GPL-3.0; the mtcreceiver submodule is LGPL-3.0-or-later.