Repository navigation
DMXRouter 1.11.1
This release adds MVR-xchange support — DMXRouter can now discover other MVR-xchange capable applications on the local network, pull their currently-loaded MVR file directly into the patch, and (when the operator opts in) advertise its own loaded MVR back so the chain works in either direction. The standard the protocol implements is the same one grandMA3, Vectorworks Spotlight, BlenderDMX, Production Assist and Zactrack already speak, so a typical pre-show workflow becomes "designer publishes in Vectorworks → operator pulls into the console of choice → DMXRouter pulls the same file" without a USB stick or a Dropbox link changing hands. The Universe Monitor gains a per-cell channel-label band that abbreviates the GDTF or MA2 attribute name above each value cell so the operator can identify Pan, Dim, R, G, B, Zoom, Iris, etc. at a glance without going to the Fixture Check panel. Several real-world bugs reported against v1.11.0 are fixed.
MVR-xchange
-
A new "MVR-xchange…" entry in the patch panel's Add ▾ menu opens a single-page dialog that covers both halves of the standard at once — discovery and sharing. A toggle button at the top — "Activate MVR-Exchange" — switches everything on together: DMXRouter joins the mDNS multicast group as
<station-name>.<group>._mvrxchange._tcp.local.with the full record set (PTR, SRV, two TXT records carrying StationName and StationUUID, and an A record), starts a TCP listener on an OS-assigned high port and advertises that port in the SRV record, and begins browsing the network for other MVR-xchange capable stations. While the service is active, the Identity section (Station name defaults toDMXRouter on <hostname>, Group defaults toDefaultmatching every other MVR-xchange capable application — grandMA3 in particular is case-sensitive on this name, and the persistent Station UUID is read-only) is locked so a rename mid-show doesn't briefly black out joined peers — stop, edit, start again is the supported pattern. A transaction log at the bottom of the dialog scrolls every protocol event in real time (IN MVR_JOIN from gMA3,OUT MVR_COMMIT to gMA3 @ 192.168.1.50:42424,IN MVR_REQUEST file=test.mvr,✓ gMA3 acknowledged) so the operator can see at a glance who's talking and whether the handshake is going through, without reaching for Wireshark. -
An "Interface" selector picks which NIC DMXRouter advertises on, defaulting to "All interfaces (auto)" which broadcasts on every up-and-running IPv4 NIC including loopback — the right choice for the common case where DMXRouter and a peer application live on the same workstation. The default sends one announce per NIC with the A record pointing at that NIC's IP, so a peer receiving the announce over loopback gets a 127.0.0.1 address it can connect to, and a peer on the show LAN gets the show-LAN IP for the same DMXRouter — no mismatched routing that would have the peer trying to TCP-connect to an interface it can't reach. The selector also exposes "Loopback (127.0.0.1)" only for tight same-host setups where the operator wants to keep the chatter off the public LAN, and each detected IPv4 NIC by name and address for show-floor setups where traffic must be confined to one network. Selecting an interface restarts the announce burst immediately so peers pick up the new egress without waiting the next periodic re-announce tick. The choice is persistent — DMXRouter remembers the last selection across restarts and re-applies it on the next Activate.
-
Discovered stations appear in a tree as soon as they answer the mDNS query — name, IP, application provider (grandMA3, Vectorworks, BlenderDMX, …) — and a parallel TCP connection runs in the background to fetch each station's commit list, which populates as child rows under the station with the file name, size and free-form comment the publisher attached. Pick a commit, click Download & Import and the dialog requests the binary, hands the bytes to the same MVR import pipeline the file-picker side uses, with the existing Replace / Merge prompt when a patch is already loaded. The browse runs indefinitely until the operator stops or closes the dialog — there's no fixed timeout that would miss stations whose mDNS responder takes a few seconds to wake up. Once a file has been pulled successfully, MVR-xchange stops itself automatically: the dialog closes into the import flow and the operator is heading into patch edits, not waiting for more network chatter. Re-opening the dialog and clicking Activate again re-arms the service.
-
Importing or replacing the loaded MVR mid-session propagates to every currently-visible peer automatically. Whether the bytes came from the file picker, from a Download & Import in this same dialog, or from any other input path, the moment the local store gains a new commit DMXRouter opens a fresh outbound TCP to every discovered peer (and to every peer that previously joined us inbound) and pushes an
MVR_COMMITdescribing the new commit (FileUUID, file name, size, comment). The peer's UI updates to show the new entry, the operator on that peer can pick it and pull it back. Clearing the patch propagates as a "no current commit" signal. The host serves the same bytes the operator loaded, byte-for-byte — DMXRouter does not regenerate or re-export the MVR on patch edits (manual fixture additions, address changes, mode changes), so the file every peer in the chain sees is exactly the one that came in from upstream, no quality loss in the link. The push is robust against peer quirks: clients that only listen forMVR_COMMITon connections where they're the TCP server (grandMA3 falls in this group) get a fresh outbound from us regardless of which side initiated the original handshake. -
A short initial announce burst surfaces DMXRouter on the local network within milliseconds rather than waiting for the steady-state re-announce tick. When the service activates, DMXRouter emits a sequence of five mDNS announcements at 0, 250, 500, 750 and 1500 ms before settling into a 10-second re-announce cadence — comfortably below the records' published TTL so peers that join the LAN late see DMXRouter without waiting for full TTL expiry, and dense enough at the start that a peer like grandMA3's Bonjour Service catches the announcement even when its own mDNS initialisation is partway through. The cache-flush bit is set on the SRV / TXT / A records so a peer that had us cached from a previous session (different TCP port after restart, possibly different station name) replaces the old entry immediately rather than talking to a dead port for the rest of the cached TTL. Stopping the service sends a goodbye announcement with TTL=0 so well-behaved peers prune DMXRouter from their list immediately rather than waiting for the records to age out.
-
A peer that joins DMXRouter inbound while the local store is still empty is remembered, so when the operator finally loads an MVR the commit reaches that peer retroactively. This handles the very normal show-floor sequence "operator opens the dialog and activates the service → designer's laptop sees DMXRouter and connects → operator imports an MVR from a USB stick" — without the queue, the designer's app would have been stuck showing DMXRouter with no shared file and would have needed a manual refresh to notice the new commit. The same queue handles the reverse race where DMXRouter has an MVR loaded but the peer connected inbound before mDNS discovery resolved their listen address (common when the peer had a cached entry from a previous session and reconnected the instant our TCP listener came up); as soon as discovery surfaces the peer's SRV record, the queued cold push fires.
-
Service identity persists across DMXRouter restarts (Station name, Group, Station UUID — stored alongside the chosen Interface in QSettings), but the enabled state does not — the operator opts in explicitly each session. A station that was visible to a peer yesterday will be the same station UUID-wise today, so the peer's commit dedup logic recognises us; but the service does not start automatically at launch — the operator has to open the dialog and click Activate. This matches the pattern grandMA3 and Vectorworks use (an explicit toggle, off by default) and avoids surprising operators with an open mDNS responder and TCP listener on a network they may not be ready to publish to.
Universe Monitor
-
Each cell of the grid now carries a small label band above the value reading, showing an abbreviated form of the GDTF or MA2 channel name — Pan, Tilt, Dim, R, G, B, W, Zoom, Iris, Strobe, Focus, Gobo, Color, CTC, etc. — so the operator can identify what each channel does without leaving the monitor for the Fixture Check panel. Single-cell fixtures show the channel name as it appears in the GDTF or MA2 personality (compact form: "Pan", "Dim", "R/G/B/W", "Zoom", "Iris", "Strobe", "Focus", …); 16-bit and 24-bit fine channels are suffixed ".f" / ".u" so the operator can tell "Pan" (coarse 8-bit) from "Pan.f" (fine) at a glance. Channels for which the patch doesn't provide semantic information (e.g. raw DMX with no fixture mapping, an unrecognised attribute) fall back to best-effort truncation of the original name preserving casing. The band height is fixed so cell value digits remain centred and equally tall regardless of whether the label is present. Priority view, which paints the per-source priority byte instead of the merged value, suppresses the label band (the bytes shown there are priority values, not fixture data — labelling them with attribute names would be misleading). The full GDTF / MA2 attribute lexicon — ~220 entries covering both standards' common naming variants — is normalised before display so technical names from MA2 ("PAN", "GOBO1POS") and friendly names from GDTF ("Pan", "Gobo1 Pos") render with the same abbreviation regardless of source.
-
The column / row label strips around the cell grid no longer flicker or disappear when the cursor crosses from the monitor to a sibling tab and back. Previously, partial repaints along the cursor path were filling the dirty region with the background colour but skipping the label redraw — visible as a fast flicker followed by missing column or row numbers until the next full repaint (window resize, mode toggle). The labels now repaint whenever any partial dirty region overlaps their strip, with no measurable performance impact (8 column labels + 16 row labels per refresh, far below the 512 cells the monitor was already painting conditionally).
RDM
-
The device tree's Export menu gains a "Save as CSV by port…" entry that writes the discovered devices in a port-pivoted layout — one row per discovered port, fixtures laid out across as columns up to the 31-receiver RS485 ceiling. The first row carries running totals — number of nodes seen and total RDM fixtures across them — and the row count itself equals the number of active ports, so the operator can sanity-check a discovery run at a glance: confirm the fixture total against what the rig should have, and any port that looks lighter than expected stands out immediately as a row with empty fixture cells near the end. Each cell shows the fixture's UID, its operator-assigned RDM device label when present (falling back to manufacturer + model when the fixture has no label, or to whichever of the two is known otherwise), and its FID when one has been assigned. Ports that exceed the 31-device RS485 limit — rare in practice, usually a wiring or splitter fault — produce an extra "Overflow" column showing the excess count rather than silently truncating. The existing "Save as CSV" entry, which dumps the tree column-for-column for cross-machine UID-level diffing, is unchanged — the choice is now purely about what the operator wants to verify: fixture counts per port (by-port layout) versus UID reconciliation across machines (column layout). A node like Robert Juliat's self-discovering fixture, where the embedded device shows up first in the on-board TOD, appears as Fixture 1 of its port without any per-vendor handling — the layout simply preserves discovery order.
-
CSV exports — the RDM discovery log, the device tree, the port-pivoted layout above, and the RDM Linkage editor's table — now open with columns split correctly when double-clicked into Excel on every locale, including the European builds (Spain, France, Germany, Italy) that default to semicolon as the column separator. Previously, an operator on a Spanish-locale Windows install opening one of these CSVs landed the entire row in column A and had to walk through Data → Text to Columns to recover the layout, while a US-locale install opened the same file correctly first try. The exporter now prepends a one-line
sep=,hint that Excel and LibreOffice Calc both honour — the file still parses correctly in scripting tools (Pythoncsv, awk,pandas.read_csv), and on US/UK Excel the line is a no-op because comma was already the separator. UTF-8 encoding and the BOM marker are unchanged, so accented manufacturer names (Ayrton, Robe, Clay Paky) and the running-total row continue to render exactly as before.
Bug fixes
-
Process Engine outputs no longer drop to zeros mid-failsafe when the sACN sender stops the stream deliberately at the low end of E1.31's keep-alive rate. Reported on GitHub against v1.11.0 by an operator running sACNView v3.0.0 with the engine configured for failsafe Hold: the engine entered failsafe correctly with the captured frame on the wire, then ~800 ms later flipped to zeros and stayed there. Wireshark captures confirmed sACNView spaces its three OPT_STREAM_TERMINATED packets exactly 848 ms apart (sACNView's transmit rate is 1.18 Hz, at the lower edge of E1.31's keep-alive window) — each extra terminated packet was overwriting the held frame in the rate limiter with a freshly-computed zero buffer because the merge engine was producing a "no live source" output every time the cache went stale. Senders running at higher rates (grandMA3 onPC at 44 Hz, sACNView at its default rate, every console) were not affected because their three terminated packets all arrived within the 100 ms backup-timer tick window — they got coalesced into a single transition that applied failsafe once and was done. The merge logic now treats a "no live source" tick as a pure no-op on outputs already in data loss: the backup timer remains the single authority on what the rate limiter holds during a source-loss state, and a repeated or out-of-order stream-terminated packet from a slow sender no longer clobbers the chosen failsafe content. Hold, Output Full, Output Scene and Output Zeros all behave identically to before — the only path that changed is the one where data loss had already been declared and zeros would have stomped the held frame. Cable-pull scenarios (no stream-terminated, just silence past the 2.5 s timeout) remain unchanged.
-
Multi-mode Depence placeholder fixtures are now recognised correctly and flagged for upgrade against real manufacturer GDTFs. With an MVR exported from Depence containing seven different fixtures — Clay Paky K10 (Standard / Shapes modes), Martin Mac Quantum Profile (Extended / Basic), Generic 2-Lite Blinder (4 modes), Generic 4-Lite Blinder (8 modes), and three single-mode placeholders (XBeam, Pixel Strobe, etc). The single-mode placeholders were detected correctly in v1.11.0; the multi-mode placeholders fell through and imported as 1-channel dimmers with no warning that they were Depence dummies. Cause: the placeholder fingerprint required the GDTF to contain at most one
<DMXChannel>element globally, but a multi-mode Depence dummy declares one channel inside each<DMXMode>— total count equals the number of modes, not one. The fingerprint now compares the channel count against the mode count and treats an exact 1:1 ratio as the Depence signature, so K10, Quantum, both Blinders and any future multi-mode Depence export get flagged and the operator gets the same "this looks like a placeholder, consider replacing" warning that single-mode dummies have always produced. Real manufacturer multi-mode GDTFs are unaffected: a real Mac Aura with Basic (5 ch) and Extended (15 ch) modes has 20 channels across 2 modes (ratio 10:1) and the heuristic correctly leaves it alone. -
The Reroute Engine editor's input and output universe spins now honour the global Art-Net 0-based / 1-based display toggle, so they match the universe numbering the rest of the UI (routing table, status text, monitor filter) uses. Previously the spins always rendered Port-Address values directly (0-based), regardless of the toggle setting, so an engine listed as "U1" in the table opened in the editor with the spin reading 0 — and if the operator typed 1 expecting to leave the value unchanged, Apply silently shifted it to Port-Address 1 (universe 2 in 1-based mode). The spin now renders in the same numbering space the table shows, and the reader subtracts the offset before storing so a save-without-edit is a true no-op. sACN inputs / outputs are unaffected (sACN is always 1-based on the wire; the toggle only applies to Art-Net), Internal and Play inputs are unaffected (those use unrelated numbering — engine index and show id), and engines that were already correct in storage stay correct — only the displayed value and the round-trip arithmetic change.