DMXRouter 1.10.1
Universe Monitor
-
The sACN range cap is now a multi-range filter and a new Art-Net range filter has been added, both reached from a new "Universe ranges" button in the Monitor toolbar that opens a dialog with two scrollable text areas (one per protocol) — the old single-spinbox "join universes 1 through N" cap was the only way to scope the Universe Monitor and at large counts it was sending the local IGMP querier into a wall: on tight networks a tour with acts at universes 1-10, 532-540, and 1044-1060 had to either join everything from 1 up to 1060 just to see the highest range or lose the highest range entirely. The sACN field is now a free-form text input that accepts any combination of single universes and ranges separated by commas (or newlines, one per line), e.g.
1-10, 532-540, 1044-1060, so the IGMP query table only carries the universes the operator actually wants — discontiguous high-numbered ranges are reachable without joining anything below them. The same field shape is now also available for Art-Net as a pure display filter (Art-Net DMX is unicast so there are no joins to manage, but operators monitoring large multi-net rigs benefit from the same scoping for readability). Empty input means "no contiguous filter" — for sACN that disables the field's contribution to multicast joins entirely (engine inputs still join their own universes, and only those will be visible in the list), and for Art-Net it means "show every entry received". Engine inputs and outputs are guaranteed visible in the monitor regardless of how tight the user pulls the filter, so a routing engine the operator has actively configured can never quietly disappear from the list. The dialog validates each field as the operator types and disables OK until both parse cleanly, with a per-field count shown underneath ("1024 universes selected", "No filter — show all received Art-Net", and so on); the toolbar button gains a trailing dot when either filter is active and its tooltip lists the current ranges so the operator can tell at a glance whether the monitor is showing a subset. The previous numeric cap saved assACN/monitorRangemigrates automatically on first launch to the new multi-range form (1-N, identical scope) so existing setups carry over without any operator action. Reported by SirDur (GitHub #34) -
Hovering or clicking a slot in the channel grid now also surfaces the patched fixture and channel name, so the operator can read off "Ch 47: 192 (75%) — 57 MAC Aura / Dimmer" instead of having to memorise which fixture sits at which absolute address while looking at raw DMX — the monitor pulls labels straight from the loaded MVR (or restored project patch), so every patched fixture in the show contributes to the readout, not just specific products: a moving head shows up as "Pan", "Tilt", "Dimmer", "Shutter"; a colour scroller shows up as "Color1"; a 16-bit Pan splits across two adjacent slots as "Pan" and "Pan (fine)", and the rare 24-bit / 32-bit channels extend the same pattern with "(ultra)" and "(uber)". Multi-cell containers (LED bars, pixel matrices) prefix the cell name with the parent so a click on a per-pixel Dimmer reads "57 LedBar / Cell 12 / Dimmer" rather than just "Dimmer", which is what the operator wanted to know — which pixel — when they clicked. The fixture's MVR ID is included in front of the name when one is present so labels match the operator's existing fixture sheet at a glance ("57 MAC Aura"), and the same label is forwarded into the oscilloscope's header underneath so the time-domain trace also identifies its source. Slots not covered by the patch (un-addressed gaps, fixtures imported from a Capture placeholder that hasn't been resolved yet) keep the legacy "Ch N only" form silently — there's no error overlay, just no extra subtitle. Editing the patch live updates the labels everywhere the operator can see them without a reselect. Suppressed in the Priority data view because per-channel sACN priority is set by the source, not the fixture, and grafting a fixture name onto a priority readout would be misleading
Fixture Patch
-
Importing an MVR while a patch is already loaded now opens a small dialog asking whether to Replace the existing rig with the new MVR (the historical behaviour) or Merge the new MVR's fixtures into the existing patch alongside what's already there — until now, every Import MVR gesture was a destructive replace: bringing a second .mvr in dropped the first one entirely, which is right for the common case of starting fresh on a new show but wrong for the equally common case of building a combined rig from a console export plus a separate visualizer export, or stitching together an MA3 fixture sheet with a Vectorworks plot of a different department's gear, or adding a touring scenic-LED package on top of a venue's house-rig MVR for one show without losing either side. With Merge the operator picks a second .mvr and its fixtures, layer names, and addresses are appended to the current patch — fixture types shared between the two MVRs (the same Robe MAC Aura model patched in both) are deduplicated to one shared FixtureType so the rig doesn't carry two parallel copies of the same GDTF, and any manual GDTF assignments the operator had previously resolved on the first import continue to apply to incoming placeholder fixtures of the same model so they auto-resolve on the way in. Layer grouping is preserved per-source — fixtures from MVR A keep their original layer names and fixtures from MVR B keep theirs, so the patch tree visibly shows the two sides as distinct branches even after the merge. Merge can be invoked repeatedly: importing a third MVR while two are already merged stacks onto the combined patch the same way. Replace stays the default action of the dialog (so a muscle-memory Enter on Import MVR keeps the previous gesture) and Cancel aborts after the file picker without touching anything. With no patch loaded the dialog doesn't appear at all — the import goes straight through as before. Show metadata (project name, source MVR filename) belongs to the original import and isn't overwritten on subsequent merges, so the project file remains identifiable as "the original show, with these additions" rather than the most-recent fragment
-
A new "Add MA2 Fixture…" entry in the patch toolbar's Add menu lets the operator manually patch fixtures from a grandMA2 .xml fixture library, using the same cascading manufacturer → model → mode picker as "Add Fixture Manually…" but reading definitions out of
<Documents>/DMXRouter/MA2/instead of the GDTF library — opens a path for fixtures that have no GDTF available, the canonical case being Robert Juliat's SpotMe trackers, which ship today only as MA2 .xml libraries pending the GDTF working group's standardisation of tracking devices, but the entry is generic and works with any MA2 fixture .xml dropped into the folder, from a moving head to a colour scroller. The dialog cascades manufacturer → model → mode just like the GDTF version, with quantity / FID start / universe / address / spill-to-next-universe controls and the same address-range preview before the operator commits, and a name template that gets a "#1, #2, …" suffix when quantity > 1. The dialog also has an "Add .xml to library…" button at the top that opens a file picker, copies the chosen .xml file(s) directly into the local MA2 library folder, and auto-selects the just-imported entry in the cascading combos as soon as the library finishes re-indexing — operators who get an .xml file from a manufacturer or extracted from a console backup can patch from it in one continuous flow without leaving DMXRouter to drag files around. Patched fixtures land in the "Manual" layer of the tree alongside any GDTF fixtures added the same way, with full channel labelling in the Universe Monitor (Pan, Tilt, Dimmer, Shutter, Pan (fine), and so on) and home/highlight values picked up from the MA2 file when present — for moving heads with ahighlight_valuedeclared (typically 100% on Dimmer, 0% on Shutter and Gobo to mean "open / no gobo"), the Highlight button drives the fixture as expected. MA2 fixtures with attribute codes that don't map cleanly to the GDTF taxonomy (mostly tracking-specific names like X Origin, Tracking Speed, A to B Distance) still appear in Fixture Check with their MA-defined names rather than as anonymous "Ch 1, Ch 2…" sliders, so the operator can identify what they're driving. Drop a fresh .xml into the folder while the dialog is open and the combos refresh automatically without having to dismiss and reopen. The folder is auto-created on first launch; if it's empty when the dialog opens, the warning text shows the exact path the operator needs to drop files into -
Auto-Next now starts a walk even when nothing — or only one fixture — is selected — pressing the Auto button used to silently snap back off unless the operator had pre-selected two or more fixtures in the patch tree, which forced a Select-All (or a multi-pick) before the auto-tour of the rig could begin, even though the manual Next/Previous buttons have always worked without a selection by stepping through the whole patch in tree order. From this version on, Auto behaves the same way: with no selection it tours the entire patch starting from the first fixture in tree order; with a single fixture selected it tours the patch starting from that fixture (the operator can scrub the walk position by clicking another fixture in the tree mid-walk and Auto continues from there); and with a multi-selection it walks just the selected group as before. The first fixture lights up immediately when Auto is pressed so the operator sees a head come up during the first interval rather than waiting a beat for the timer to fire, and Highlight propagation and Home-on-walk-away behave identically to the multi-selection path so moving heads stay parked between steps instead of racing back to the show's Pan/Tilt every time the walk advances. The interval control, the speed presets, and the Pause/Resume gesture (toggle Auto off and on again) are unchanged
-
Right-clicking a fixture (or a multi-selection of fixtures) now offers a "Follow PSN Tracker…" submenu that binds the selected fixture(s) to a live PSN tracker so the PSN viewer can show, at a glance, which lights are aimed at which moving target — the canonical use case is a singer or performer being tracked by a PSN server (Spotme, BlackTrax, Zactrack, MA Stage Marker streams) and several follow-spot cannons configured to chase that tracker; the binding lets the operator see the relationship from both ends without having to hold the patch in their head. The submenu auto-populates with every tracker currently visible on the network — listed as "Tracker N — System Name" or "Tracker N — Tracker Name (System Name)" when the server has named the tracker via INFO heartbeats — sorted by system name then port then tracker ID so the order is stable across opens and the same submenu position always points to the same tracker. The right-clicked fixture's current binding is shown with a check-mark next to its tracker entry, so the operator can confirm "yes, this cannon is following the singer" without leaving the menu, and switching to a different tracker is one click away. Multi-select aggregates: pick five cannons in the patch tree, right-click, choose a tracker, and all five flip in one batch with a single repaint of the tree — useful when a whole rig of follow-spots needs to be repointed from one performer to another between songs. A "None (clear)" entry at the top of the submenu unbinds without having to touch the numeric ID, and an "Other tracker ID…" entry at the bottom opens a numeric input dialog covering the full PSN range (0–65535) so the operator can configure bindings ahead of time during prep — before the tracking server is online — or bind to a tracker that's silent right now but expected for the show. Bindings persist with the project file, so a once-configured rig comes back the same way next show day. The selection of fixtures the operator had set up elsewhere in the tree is preserved through the gesture so a multi-pick built up for some other workflow doesn't get stomped by the binding action. When the right-clicked fixture is bound to a tracker that's not currently broadcasting, the submenu's tooltip surfaces the bound ID directly ("Currently bound to tracker 25 (not on the network right now)") so the operator can confirm the configuration without having to dig into the numeric-entry dialog
-
A new "PSN" column in the Fixture Patch tree shows "Trk N" in cyan for every fixture bound to a PSN tracker, and stays empty for unbound fixtures (the common case), so the operator can scan the rig and see at a glance which lights are following which moving target without having to right-click each one — sits between the Mode and RDM columns at 64 pixels wide by default, with the column header itself sortable: clicking PSN groups all bound fixtures together (descending order brings them to the top, ascending puts unbound first), which matters when a tour has 30 cannons in a rig of 200 fixtures and the operator wants the bound subset isolated for a quick review. Multi-cell containers and their cells each carry their own binding state and render independently — a pixel matrix where every cell follows a different tracker shows that variation directly in the column rather than collapsing it into a container summary that would lose information. Hovering a "Trk N" cell shows a tooltip explaining the binding and pointing at the right-click menu for changes. Unresolved fixtures (red row tint) extend their warning colour over the PSN cell as well, the same way every other status column already behaves, because the binding is meaningless until the GDTF that defines the fixture's channel layout is actually loaded — surfacing a binding on a half-resolved fixture would imply the rig is configured when it isn't. The cell text is read-only; the binding gesture lives in the right-click submenu mentioned above
Test Output
-
The Ramp pattern now has Up / Down direction radios alongside synchronous / rolling style radios in the dialog, replacing the old separate "Ramp" and "Ramp Down" entries with one unified Ramp pattern that adapts to whichever combination the operator picks — synchronous Up is the historical Ramp triangle (every channel rises 0 → full and falls back to 0 in lockstep, smooth bidirectional fade with the peak at mid-cycle, comfortable to watch); synchronous Down is a sawtooth (every channel falls full → 0, snaps back to full, repeats — the per-cycle snap is the point, makes a fixture whose dimmer hangs at the bottom of a fade obvious vs neighbours that release cleanly). The two new combinations cover use cases the old grid couldn't reach: rolling Up sends a travelling triangle wavefront down the universe so adjacent channels are slightly out of phase and the wave appears to crawl through the rig, useful for verifying pixel-strip wiring direction (the wave's apparent motion direction tells the operator whether the strip's first pixel is at the lowest or highest DMX address); rolling Down does the same with a falling sawtooth shape, where the per-channel snap is staggered so the visual is a smooth travelling cliff rather than a rig-wide flash. The wavelength used by both rolling variants is the same wavelength control the rolling Sinewave already had, surfaced in the dialog only when the operator picks Rolling — pickled to one wavelength setting that applies to whichever rolling pattern is active rather than two parallel controls. Operators who never used the old "Ramp Down" entry won't notice the change; operators who did get the same shape via Down + Synchronous (the default flips for Direction so Up is preselected — a fresh Ramp picks behaves like the historical Ramp out of the box)
-
The Sinewave pattern's previous "synchronous" and "rolling" sibling entries in the pattern grid have collapsed into one Sinewave entry with a Style sub-control (Synchronous / Rolling) that mirrors the new Ramp restructuring, so the pattern grid is six radios instead of eight — the maths and the visible behaviour are unchanged from v1.10.0: synchronous is the rig-wide breathing wave (all channels in lockstep, smooth half-cosine 0 → full → 0 over the cycle, easier on the eyes than triangle Ramp for longer-duration verification), rolling is the travelling sine (per-channel phase shift so a continuous wave with multiple peaks crawls through the universe at one wavelength per cycle, distinct enough that motion direction and speed are obvious at a glance even at the fast end of the slider). Picking Sinewave + Rolling in the new dialog produces exactly the same output as the old "Sinewave (rolling)" radio did. The wavelength control is shared with the Ramp rolling variants and surfaces only when the operator picks Rolling on either pattern; Synchronous Sinewave doesn't read a wavelength because every channel shares one phase
-
Chase has a new "Hold until loop" checkbox that turns the moving lit group into a progressive fill — the universe lights up cumulatively as the chase advances, with each step adding its group to the lit set without darkening the previous, and reset only when the chase wraps back to start — useful for verifying that every channel in a strip lights AND in the right order, in one continuous gesture: the operator watches the strip fill up from the start and a missed segment shows immediately because the fill stalls visibly there. The classic chase (group of N moves cleanly through the universe leaving previous channels dark) is still the default, with the checkbox unchecked, so existing operator muscle memory is preserved. The group size control (1 for single-channel chase, 3 for RGB pixel strips, 4 for RGBW, up to 16) interacts with both modes: in classic chase a group size of 3 means "three consecutive channels light at each step, skipping forward by three"; in hold-until-loop mode the same setting means "three more channels light at each step, until all 510 are lit and the chase resets"
-
The Slow / Medium / Fast speed presets in the test dialog have been replaced with a continuous speed slider spanning 100 ms (10 Hz strobe-fast) to 30 s (slow walk through the rig), with a logarithmic mapping so small slider movements give small timing changes around the comfortable middle pace — the previous three fixed presets covered the common cases but ruled out fast-end sanity checks ("is this universe alive at all?") below the 500 ms floor and slow walks above the 4 s ceiling, both of which the new range covers with headroom: 100 ms one full cycle is at the threshold of perceptible flicker for Ramp / Sinewave (good for stress-checking how a fixture handles fast level changes) and faster than any human-readable chase rate, and 30 s gives the operator nearly half a minute to walk down a row of dimmers and notice which one releases late. The historical Slow / Medium / Fast paces (4 s, 2 s, 1 s respectively) still sit at the slider's tick marks at 25 / 50 / 75, so the operator can re-find those exact paces by feel; every pace between, faster, and slower is now reachable in one gesture instead of being unreachable
-
Slider drags on a running test preserve the pattern's visible position rather than restarting it from t=0 at every change of speed — the wave or chase head smoothly accelerates or decelerates instead of jumping back to the start as the slider crosses each step on the way to the target value, so the operator can dial in a precise pace by watching the running test rather than watching a series of restarts. The preservation is pattern-aware: Ramp and Sinewave hold their position within the cycle, and Chase holds its head position within the chase's full sweep across the universe (which is longer than one cycle when the group size is small — a 1-channel-group chase wraps after 64 cycles), so the chase doesn't jump back by multiples of its per-cycle stride every time the slider moves. The visible result across every animated pattern (Ramp synchronous and rolling, both directions; Sinewave synchronous and rolling; Chase classic and hold-until-loop) is the same head position before and after the change, with only the forward speed shifted. Static patterns (Full, Blackout, Single Channel) don't read the cycle length so they're unaffected
Routing
-
The bulk Reroute dialog (the one that opens when Reroute is invoked on a multi-engine selection) has been split into independent Inputs and Outputs sections, so the operator can rebind one side without dragging the other along when an engine has its input AND its output on the same NIC — until now a single From → To filter applied to every interface field on every selected engine in one pass, which is right for "the whole rig migrated from NIC1 to NIC2" but wrong for the more common case where a console moved to a new switch (input migration) but the rig stayed on its original NIC, or vice versa. Each section in the redesigned dialog is a checkable group with its own From / To pair and its own per-side count summary so the operator sees what's about to change on each side independently; unchecking a section greys it out and leaves that side untouched, checking both is the historical "rebind everything" behaviour. The Inputs section also covers control channels (Backup, X-fade, Switch, Master, Custom) but only the ones the engine's mode actually consumes — a Backup engine brings in its backup control alongside the regular inputs, a Forward engine has no relevant control channels so the bulk operation doesn't touch the unused fields, and Master / Limit is included on top of whichever mode-specific control applies whenever the engine has it enabled. Each side's From combo lists only the interfaces actually used on THAT side (a NIC that's only on outputs doesn't appear as an input filter and vice versa), and when exactly one NIC is in use on a side the From combo defaults to that NIC instead of "(any interface)" so the common "all engines on NIC1, want them on NIC2" case is a two-click gesture (pick destination, OK) instead of being one click from the destructive "Replace ALL" warning. When a section is unchecked the count summary explicitly reads "Section disabled — this side will not be modified" rather than continuing to count slots that won't actually change, so the operator's read of the dialog matches what's actually about to happen
-
Right-clicking a group header now offers the same multi-engine actions that the engine row's right-click menu offers when several engines are selected, applied to every member of the group in one click — until now the group header's context menu was strictly about the group as a unit (rename, recolour, expand/collapse, switch all inputs, remove the group tag) and never about the engines inside it, so an operator wanting to e.g. fire a Test Output across "FOH Wash" had to first expand the group, Ctrl-A the rows inside it, then right-click. The menu now adds Reroute, Test Output, Snapshot, Rename, Move to Group, and Remove entries, each with the engine count next to it ("Test Output (6 engines)…", "Remove (6 engines)"), so a tour-day verification sweep across a dozen groups is six right-clicks instead of six expand-and-multi-select dances. The actions sit between the existing Group Color / Expand-Collapse block and the bottom-most "Remove Group (keep engines)" entry so the muscle memory for the group-metadata operations doesn't shift; Move to Group offers every other group in the show plus "New Group…" so the gesture works equally well as a group-merge ("merge FOH Wash into FOH"). Importantly, opening the group menu and picking one of these actions does not modify the operator's existing table selection — an unrelated multi-pick the operator had set up elsewhere in the table for some other workflow stays exactly as it was at the moment of the right-click. The actions themselves go through the same paths as the engine row's multi-select equivalents, so undo / redo, snapshot capture, show-mode gating, and disabled-engine handling all behave identically
-
A new "Reset State" entry in the engine right-click menu, the group right-click menu, and a dedicated button at the bottom of the engine editor dialog wipes the engine's runtime memory and lets it start over from scratch — last held frame forgotten, data-loss flag cleared, "has ever received live data" tracking reset — useful when an engine is sitting in failsafe with a stale held frame the operator no longer wants on the wire (e.g. a console came up briefly during cabling, the engine latched a partial look, the console went away again), or when the operator wants the startup buffer to re-engage without disabling and re-enabling the engine. After the click, the engine looks at its inputs right now: if any source is alive, it resumes normal merge immediately with that live data; otherwise, if startup buffer is enabled, the boot pattern lights up; otherwise the wire goes silent (sACN sends its 3 stream-terminated packets, Art-Net stops). The engine row's status badge in the routing table updates instantly to reflect the post-reset state — "Live" if a configured input is currently alive, "Startup" if the boot pattern picked up, or "Failsafe: " if data loss persists. From a group-header right-click the action applies to every engine in the group in one shot, so a "reset every engine in FOH" sweep is a single gesture. Show-mode gating applies (Caution mode blocks the action with the same warning every other destructive engine action surfaces), and disabled engines are silently skipped because they have no runtime state to reset
-
The engine editor dialog now shows a coloured runtime-state badge at the bottom-left, mirroring the routing-table status cell so the operator can tell at a glance whether the engine they're editing is currently producing live merge, sitting on the startup buffer, or in failsafe — green "Live" when input is flowing and merging cleanly, blue "Startup buffer" while the configured boot pattern is on the wire pending the first live frame, red "Failsafe: " with the active failsafe mode spelled out (Hold last frame, Output zeros, Output full, Output scene, Stop stream) when the engine has detected data loss, grey "Disabled" if the engine is turned off. The badge updates live while the dialog is open — the operator can see the engine flip from Live to Failsafe the instant a console disconnects, or from Startup back to Live the moment the first packet arrives, without having to close the dialog and check the routing-table cell. The same "Reset State" button mentioned above sits right next to the badge, so the operator can clear a stuck failsafe and watch the badge catch up in the same gesture. Visible in edit mode only — when adding a new engine there's nothing to query yet
-
Each output of a process engine can now carry an independent transmit delay (0–5000 ms, set per-output in the engine editor's Outputs table), so a real lighting rig and a visualizer driven from the same console stay in sync on the wall — the operator delays the rig output by the visualizer's pipeline latency and the two systems land on the audience at the same instant — visualizers like Depence and Capture introduce 100–500 ms of pipeline latency between the DMX they consume and the picture they render, which on a hybrid show (real rig running alongside a virtual one for blocking, programming, or content tracking) reads as a jarring gap when both are visible to the same eye. The same gesture is also useful for syncing DMX-driven house lighting with audio playback systems that buffer their own output: dial the engine's transmit delay to match the audio buffer and the cues land together. Each output is independent — the engine that drives the rig can carry a 200 ms delay while the engine driving the visualizer carries 0, and a single engine with two outputs (one to the rig, one to the visualizer) can split the delay between them however the production wants. The default is "Off" and the cost when off is literally zero: the dispatch path's bypass branch returns immediately for any output with no delay configured, with no buffer allocated, no clock read, no hash lookup, no timer, no anything — operators who don't use the feature have an output path bit-identical to the pre-feature one. When delay > 0 is configured, the buffer that holds the in-flight frames is created on first dispatch and torn back down to nothing the moment the operator zeros the field again. Test patterns started on a delayed engine bypass the buffer and flush the queue so the operator gets immediate visual feedback ("did the rig respond?") rather than waiting two seconds for a pattern to arrive. Failsafe and stream-terminate behaviour is consistent: the buffer is left alone so the last few live frames drain naturally before the failsafe content (or the sACN release packets in Stop mode) take over, giving the receiver a smooth transition with consistent latency rather than an abrupt switch. Changing the delay value on a running engine briefly silences that output (~old delay duration) before live data resumes at the new timing, which is honest feedback that something changed; the alternative ("smoothly stretch or rush the buffer to absorb the difference") would briefly produce wrong-timed output, which on a sync-critical setup is exactly the failure mode the feature is meant to prevent. sACN sync packets are emitted alongside the data they commit (not at the moment the data is enqueued) so sync-aware receivers stay correctly latched to the delayed frames. Originally requested by RonaldBeal (GitHub #38) for a Depence visualizer sync use case
PSN Viewer
-
A dedicated servers panel now sits in the dock's left column above the trackers table, listing every detected PSN sender — including silent backups that announce themselves via INFO heartbeats but never emit tracker DATA — until now the only places a server was visible were the Server column inside the trackers table and the small status counter in the toolbar (
2 server(s), 1 tracker(s)), which meant the backup half of a redundant pair on a product like Spotme was effectively invisible: the main was sending tracker positions and showed up as a row, the backup was alive on the wire but its row never appeared because it had no trackers to populate it. The new panel is a flat table with one row per detected sender and columns for system name, address, port (annotated with the derived Spotme ID for ports above the spec default in the SpotMe range, e.g. "56566 (ID 1)"), tracker count, status, and last-seen age. The Status column flips between "Active" (in soft green, when the server is currently emitting tracker DATA) and "Silent" (in muted amber, when only INFO heartbeats are arriving — typically a backup waiting to take over from a main, but also any tracking server that has temporarily paused), so a redundant pair reads at a glance: two rows, one green, one amber, both with the same system-name prefix and adjacent addresses. The colour swatch on each server row matches the tracker dot colour on the map and the swatch on each tracker row in the table directly below, so the operator can connect a server to its trackers and to its dots on the floor plan visually without needing to read addresses. The panel and the trackers table share the column width so they read as a single tall data column to the left of the map, and the divider between them is draggable so either can grow at the other's expense; a stable name-then-address-then-port ordering means rows never shuffle on a refresh tick and main always sits above backup of the same chassis (lower port = lower Spotme ID = main), so the operator's eye stays glued to the right line even as a backup goes from silent to active during a failover. The PSN dock layout has also been reorganised at the same time: servers panel and trackers table stack vertically in the left column, and the map takes the full right half of the dock at full vertical height, replacing the previous layout where the map was squeezed into a narrow strip below the servers panel -
All Spotme units in a rig are now discovered automatically without the operator having to configure a single port number — Spotme assigns each of its main + backup processors a "PSN SpotMe ID" via a DMX channel on the server fixture, and that ID is literally an offset added to the base PSN port 56565 (ID 0 emits on 56565, ID 1 on 56566, ID 2 on 56567, and so on up to ID 255 on 56820). On a venue with four Spotme chassis the eight processors end up scattered across eight different ports — and previously DMXRouter could only listen on a single port at a time, which meant the operator had to know each chassis's ID, calculate the corresponding port, and pick exactly one stream to monitor at a time. The viewer now opens a socket on every port in the full Spotme range
56565-56820simultaneously, so every legal Spotme ID is covered out of the box: plug DMXRouter onto the tracking network, enable the receiver, and every chassis appears in the Servers panel with its derived ID labelled next to its port. The PSN spec default 56565 is part of the range so non-Spotme tracking sources (BlackTrax, Zactrack, Maestro, MA Stage Marker streams, third-party PSN tools) continue to work without any setting change. The toolbar's old single Port spinbox has been replaced with a Ports text field that accepts a comma-separated list of single ports andlow-highranges using the same syntax as the Universe Monitor's filter — operators who happen to run a tracking server on an unusual port outside56565-56820can extend the listened set with an entry like56565-56820, 60000-60010, and the field is capped at 1024 ports total to defend against typos like1-65535. The status label surfaces the partial-bind case explicitly ("Listening on 254 of 256 ports") if another PSN client on the same machine — typically a leftover MA grandMA service holding 56565 withSO_EXCLUSIVEADDRUSE— has grabbed a few ports in the range, so the operator can see at a glance that the receiver is healthy on the rest of the range rather than wondering why one specific Spotme isn't appearing. A v1.10.0-or-earlier saved single-port setting migrates automatically to a one-port expression on first launch so existing configurations carry through without any operator action -
A new "Followers" column in the trackers table shows, for every PSN tracker on the network, how many fixtures from the loaded patch are configured to follow it — so the operator can see at a glance whether the tracker that's drifting on the map has any cannons aimed at it, and how many — sits between Validity and Last seen, defaults to 80 pixels wide, shows "0" for every tracker on a freshly-imported rig and increments as the operator binds fixtures via the Fixture Patch right-click "Follow PSN Tracker…" submenu. Hovering the cell shows a tooltip with the bound fixtures, one per line, formatted as
FID Name · U<universe>/<channel>so the operator can identify each fixture by the on-paper FID, the human label, AND the wire address — three vectors of identity for the three ways operators typically scan a rig (paperwork, name, DMX address). The count refreshes immediately when bindings change in the patch widget so editing a binding produces visible feedback in the viewer without having to wait for the next PSN frame to tick in. Empty when no patch is loaded — the column is honest about what it knows, rather than synthesising a "0" that might suggest "I checked and nobody is bound" when really nobody could have bound anything yet -
A new "Selected tracker" detail panel below the trackers table surfaces the per-tracker fields the table doesn't have room for — acceleration, target position, the tracker's own timestamp, and a computed compute-to-send latency — for whichever tracker the operator has clicked on — fills the bottom of the dock's left column at ~140 pixels of default height, with a draggable divider above it so the operator can grow either the table or the detail panel at the other's expense (or drag it to zero on shows that don't need the v2.03 fields). Acceleration and Target position render the X/Y/Z triplets in metres / m·s⁻², matching the units used in the spec and on the map. Tracker timestamp is shown in seconds with millisecond precision (raw microseconds-since-server-start would be a 9-12 digit wall of numbers); Server compute lag computes the difference between the most recent packet timestamp from this server and this tracker's own timestamp, displayed in milliseconds — that's the delay between when the server computed the position and when it packed it for transmission, which an operator monitoring tracking quality wants to keep low. Network latency between server and DMXRouter is deliberately not included because there's no synchronised wall-clock between the two ends and a wrong figure would be misleading. Each field renders "—" when the underlying server doesn't emit it (older v2.02 servers don't send acceleration, target position, or per-tracker timestamps at all, and even v2.03 servers can omit fields that don't apply for a given tracker — a static prop tracker has no meaningful speed or acceleration), so the panel is honest about what's coming off the wire. A Followers row at the bottom lists bound fixtures in
FID Name · U<universe>/<channel>format so the operator can identify each by paperwork ID, name, and DMX address simultaneously — "1 fixture: 57 MAC Aura · U2/45" up to "5 fixtures: 57 MAC Aura · U2/45, 58 MAC Aura · U2/61, 12 Robe BMFL · U3/1, +2 more" with the full list still in the tooltip. When no tracker is selected the title reads "No tracker selected" and every value shows "—"; when the previously-selected tracker drops off the network the title flips to "Tracker N — gone" so the panel doesn't collapse into an indistinct empty state -
The map now draws a faint connecting line from each follower fixture's stage position to the tracker's dot, so the operator can see visually which lights are aimed at which moving target — the relationship reads as a "the cannons on the bridge follow the singer downstage centre" diagram in real time — fixtures bound via the Fixture Patch right-click submenu are placed on the map at their MVR-imported XZ coordinates (millimetres → metres conversion, height discarded) and connected to their tracker's dot with a 1-pixel line in the tracker's colour, alpha-faded so a stadium tour with 30 cannons all converging on one performer reads as a graceful star pattern rather than a saturated mess that drowns out everything else on the map. Each follower fixture also shows as a small filled square (distinguishable from the round tracker dots) so the operator can tell apart "this is a fixture position" from "this is where someone is standing right now". Hovering any fixture marker surfaces a tooltip in
FID Name · U<universe>/<channel>format so the operator can identify the marked fixture by paperwork ID, human name, and wire address all at once — picking out "the rogue cannon overshooting upstage" from a cluster of identical-looking markers becomes a single hover instead of a trip back to the patch tree. When the bound tracker is configured but not currently broadcasting (server offline, tracker timed out), the line is omitted and the fixture marker dims to grey — the binding is visible but inactive, and the operator can tell the binding from the data flow at a glance. Caveat: the visualisation assumes the MVR's coordinate origin and the PSN server's origin are co-located. If a venue places them at different points (MVR origin at downstage centre, PSN origin at the tracker calibration grid upstage), fixtures and their target trackers will appear at offset positions on the map even though the rig is correctly configured — there's no way to detect this automatically because the two systems' coordinate frames are independently calibrated. Operators who hit the offset can either re-calibrate one system to match the other, or live with the visual offset and rely on the line endpoints to read which fixture goes with which tracker. The pass is entirely skipped when no patch is loaded or no fixtures are bound, so installations that don't use the binding feature pay zero cost for it on the paint loop
View menu
- The six RDM-related entries that previously sat as a flat block in the View menu — Quick-select Recently Read PIDs, Discovery settling time, Thorough Discovery passes, Transaction timeout, Transaction retries, Background scan — now nest under a single "RDM" submenu, so the View menu stops feeling crowded and operators who don't use RDM aren't paging through six tunables they don't care about — the entries themselves are unchanged in behaviour and tooltips; the submenu is purely a grouping move. Tooltips remain visible on every entry (the submenu opens with
setToolTipsVisible(true)so each entry hovers its full explanation), and the submenu's&RDMmnemonic means keyboard navigation through the menus is one extraRkeystroke for the whole RDM block instead of having to remember which of six entries is named what. Operators upgrading from v1.10.0 will find every setting in the same logical place (under View) just nested one level deeper
Network & Interfaces
-
Newly created VLAN adapters now appear in the OS network adapter list as "VLAN<id>_DMXRouter" instead of "DMXRouter_VLAN<id>", so they sort alphabetically next to any other VLAN-named devices the host might have rather than clustering under "D" with every other DMXRouter object — purely a naming-order swap; the kernel-level interface name (
dmxr.<id>on Linux) and the Hyper-V Virtual Switch name on Windows are unchanged. Existing VLAN adapters created by v1.10.0 or earlier keep their legacy names — DMXRouter does not rename adapters in place because doing so would orphan setups where the operator has assigned the adapter a static IP, joined it to a domain, or added it to a backup script by name. Both formats are recognised everywhere DMXRouter looks for "is this one of ours?": the network monitor's compact label folds either form to "VLAN <id>", the VLAN manager dialog's coloured-VLAN detection accepts either, and the cross-platform create/list/remove paths (Windows PowerShell, Linux nmcli, macOS networksetup) accept either. Operators on long-running setups don't see anything change visibly unless they create a fresh VLAN; operators provisioning a new host see the swapped name from first boot -
Disconnected interfaces in the routing input / output combos no longer collapse to a blank line when the operator picks one — the entry now reads "VLAN 200 (no link)" or "Lighting · VLAN 200 (no link)" or "Ethernet (no link)" instead of falling through to an empty IP — the combos display the IP as the compact reading once an interface has been picked (because IP is what the operator scans by at a glance), and that worked fine for connected interfaces. For interfaces with no link the IP is empty, so the picked entry rendered as an empty white space — visually broken, and the operator couldn't tell what they had actually selected without expanding the combo again. The fallback now picks the most informative readable label available: friendly VLAN name plus VLAN ID and a "(no link)" tag for VLAN adapters, the bare adapter display name plus "(no link)" for physical NICs. The Reroute dialog combo gets the same treatment so reorganising an existing engine onto a temporarily-disconnected interface (during cabling, before patch is fully run) reads cleanly. The Interface Selector cards visible in the Network panel also surface the VLAN tag in their secondary detail line for disconnected VLAN adapters, so a card reads "Lighting" on the top line and "No link · VLAN 200 — enable now so it activates when cable is connected" on the bottom — the operator can identify which cable to plug in by VLAN rather than guessing from a list of identical-looking "No link" cards
-
The "Any" wildcard interface entry has been removed from every combo where it could mislead an operator into a misconfigured slot — input pickers, control-channel pickers, the bulk Reroute "To" combo, and the post-MVR auto-create output picker — picking "Any" had the engine accept input from every enabled NIC simultaneously, which sounds convenient but in practice caused more problems than it solved on real venue networks: a process engine wired against "Any" would unintentionally pick up a duplicate of the same console stream coming in on a different VLAN that happened to be bridged in for redundancy, or pull data from a backup network the operator didn't realise was patched in, with the symptom being a merge that "should be" reading from one source but shows two contributing on the routing table. For control channels the failure mode is sharper: a Backup / X-fade / Switch / Master / Custom control accepting from any NIC can be triggered by stray traffic on a backup or bridged network — a remote source the operator never intended to give control to silently flips the engine's behaviour. For the bulk Reroute "To" combo, "Any" as a destination meant "clear the interfaceId on every matched slot", which on outputs produces silent slots (no frames anywhere) and on control channels reopens the same trigger-by-anything problem. For the MVR auto-create dialog there's never a defensible reason to default newly-imported rig outputs to silence — the operator just imported a rig and is about to send DMX, the dialog now defaults to the first available interface and the operator picks something else if their rig is on a different one. Picking a specific NIC is the right model everywhere, and for operators who want one engine to mirror what's on every input, the better path was always to put one engine per VLAN with a single output target and let the routing table do the work. Engines saved with "Any" in v1.10.0 or earlier still function: the receiver still accepts wildcard interface IDs as a compatibility path, so existing profiles continue to merge from every NIC the way they did before. The next time the operator opens the engine editor and picks an interface, the wildcard flips to whatever specific NIC they choose, which is the migration the change is intended to encourage. The bulk Reroute dialog's per-section From combos (one for Inputs / control channels, one for Outputs — see the dual-section redesign in the Routing section above) each gain an "(unset — legacy / pre-v1.10.1)" entry, shown only when at least one of the selected engines actually has a slot with an empty interfaceId on that side, so the migration can be done across many engines in one pass without opening every engine editor individually and without the input-side filter accidentally targeting an output-side legacy slot or vice versa
PSN Viewer fixes
-
The PSN column header in the Fixture Patch tree no longer disappears after the operator clicks any column header to sort the tree — the header label list was missing the PSN entry that v1.10.0 added between Mode and RDM, so on a sort click the indicator update walked the labels in order and ended up writing the RDM label one column to the left of where it should be, which had the visible effect of the PSN header going blank and the RDM header shifting left. The labels list now matches the actual column count, so sort indicators land where they should and every header reads what it's supposed to read regardless of which column the operator clicks
-
The Followers column header in the PSN trackers table is no longer clipped to "Followe…" — the column is now wide enough to render its full header text — the column was added in v1.10.1 at 80 px which read in the dialog mockups but came back tighter than expected on the default Windows header font, where "Followers" rendered as "Followe…" with the trailing-ellipsis placeholder. Bumped to 110 px so the full word fits with breathing room, matching the visual weight of the Server / ID / Name columns to the left. Cell content (the bound-fixture count, typically a one- or two-digit number) is unaffected — those values fit comfortably within either width
Fixes
-
The Fixture Check sliders now show the correct value for 16/24/32-bit channels whose attribute name doesn't fit the standard GDTF taxonomy, instead of always reading zero while the universe was actually carrying the right bytes — the canonical case is a tracker profile (Robert Juliat SpotMe, PSN bridges, generic point-in-space metadata fixtures) where channels carry manufacturer-specific names like X Origin, Tracking Speed, A to B Distance and use 24-bit resolution to express signed coordinates around a midpoint. Pressing Home on those fixtures correctly sent the encoded mid-position bytes out the wire — verifiable in the Universe Monitor as coarse byte at 127, fine at 255, ultra at 255 (the three bytes that make up "midpoint of a 24-bit signed range") — but the slider in Fixture Check stayed pinned at zero, because the slider readback path was only reassembling the coarse byte and then scaling it as if that single byte were the full-resolution value. With a 24-bit channel that scaling produced almost-zero (127 out of 16,777,215, displayed as 0 on the 0-255 slider face) regardless of what was actually being written. The slider readback now reassembles the full coarse + fine + ultra + uber byte set into the channel's native range before scaling, so what's drawn on the slider matches what's leaving the building. 8-bit channels and the standard semantic attributes (Pan, Tilt, Dimmer, Shutter, ColourMix) were unaffected and remain unaffected — they always rendered correctly. Surfaced during testing with a SpotMe Zone V1.x library
-
Pressing Highlight on a fixture whose mode declares no channels eligible for Highlight no longer silently turns into a Release that drops the engine into data-loss failsafe — the same tracker / metadata profiles as above (SpotMe, PSN bridges, custom protocol fixtures, anything that's a "point in space" rather than a light source) have no Dimmer to drive to full, no Shutter with an Open range, and no manufacturer-declared
highlight_valueon any channel — there's literally nothing for Highlight to write. The previous behaviour was: clear the existing override state, walk every channel, find that none qualified, and leave the override empty. With nothing in the override and no live input source feeding the engine (a tracker fixture in Fixture Check is typically not being driven from a console at the same time), the engine entered data-loss failsafe and went silent on the wire, and the operator saw the stream stop on a fixture they were just trying to "light up", which read as a bug even though it was technically the consistent application of the spec. From this version on, when a Highlight pass writes nothing the override layer leaves the previous state untouched (so any slider values, any prior Home, any Highlight propagated from a sibling stays exactly where it was) and Fixture Check surfaces a short banner under the fixture info that reads "Highlight has no effect on this fixture — its mode declares no highlight-eligible channels (no Dimmer, no Shutter Open, no explicit highlight values). Tracker / metadata profiles behave this way.", which auto-hides after a few seconds so it doesn't compete with the operator's next action. Multi-selections that mix tracker fixtures with regular lights are also better behaved: the regular lights highlight as before, the trackers stay quiet without being torn down, and the banner only appears if at least one of the trackers is actually in the current Fixture Check selection. Surfaced during testing with a SpotMe Zone V1.x library -
A process engine that lost its input feed and entered failsafe no longer keeps emitting that failsafe output (last-held frame for Hold, zeros for Zero, full for Full, recorded scene for Scene) onto a new VLAN or universe after the engine is reconfigured or toggled off and back on — previously, if a process engine had received input at any point in the session and then lost it (the console disconnected, the source went offline, the network changed), the engine would correctly enter its configured failsafe and start emitting the failsafe output continuously. That part is intentional and unchanged. What was wrong is what happened next: if the operator then opened the engine editor and changed the input source, the output VLAN, the output universe, the output interface, or any other topology field — or if the operator disabled the engine and re-enabled it later — the engine quietly carried its previous failsafe state forward and kept emitting the old held frame (or zeros, depending on the failsafe mode) onto whatever the new outputs pointed at. From the operator's perspective the engine looked like it was generating fresh data on a connection that was supposed to be idle. The internal runtime memory that tracks "this engine is currently in data-loss failsafe and these are the bytes it's holding" is now wiped on engine disable, and on edits that materially change inputs or outputs while the engine is in data loss. The next live frame on a configured input repopulates everything cleanly through the normal merge path; if no input ever arrives, the engine stays silent on the wire as a fresh-config Hold engine should. Cosmetic edits (renaming an input, relabeling an output) are unaffected, and engines that are operating normally with live input flowing don't experience any output gap during an edit. Reported by SirDur (GitHub #36)
-
Switching the failsafe mode of a process engine that's already sitting in data loss away from Hold and then back to Hold no longer turns the held frame into all zeros — Hold now always re-emits the last frame the engine actually received from real input — previously, if an engine had been holding the last live frame correctly (sACN feed gone, failsafe Hold engaged, the rig staying on whatever look the console left), and the operator switched the failsafe mode to Stop (or Zero, Full, Scene) and then back to Hold, the engine would resume transmitting but with all zeros instead of the held frame. The visible symptom on the rig was a Hold that "forgot what it was holding" after a round-trip through any other failsafe mode. The cause was that the same internal frame buffer was being written by every failsafe path that produces output — Zero stamping zeros into it during data loss, Full stamping all-on, Scene stamping the recorded scene, the test-stop path stamping blackout — and Hold was re-emitting whatever happened to be in that buffer the moment the operator picked Hold, not the last live frame. From this version on, the last-live frame lives in its own dedicated buffer that's only ever written by a merge tick that saw at least one alive source, so sibling failsafe modes can't contaminate it. Hold reads from that buffer exclusively and falls through to the historical "leave silent" behaviour only when the engine has genuinely never received live input. The fix covers every transition through a non-Hold failsafe mode and back to Hold, not just Stop → Hold, so any sequence the operator might walk through on the dialog returns the held frame intact when Hold is selected
-
The "Start with Windows" toggle now works reliably for users running DMXRouter under an administrator account, where the previous implementation could silently fail to launch the application at logon — DMXRouter requests elevated privileges at launch (needed for VLAN management on Windows), and the previous autostart implementation registered the launcher under the per-user Run key (
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run). Windows blocks UAC prompts during the logon sequence as a security measure, so a Run key entry pointing at an elevation-requesting application would simply not start: no error, no prompt, no application — just nothing. Standard user accounts without admin rights weren't affected because no elevation was being requested in the first place, but admin accounts (which is the usual setup on a personal Windows install — the first user created at OS setup is an administrator) were silently broken on both Windows 10 and Windows 11. The toggle now registers a Task Scheduler entry under "DMXRouter" in the Task Scheduler Library root with the "Run with highest privileges" flag, which is the correct mechanism for elevated autostart: the elevation is pre-approved at task-creation time so the launch at logon doesn't need a prompt and Windows runs the application elevated as intended. Existing autostart entries from previous DMXRouter versions migrate automatically to a Task Scheduler entry on the next launch, with the legacy Run key entry cleared in the same step so there's no double-launch — operators who had autostart on before this version don't need to re-toggle it. Standard users without admin rights still work too: the task is created with the user's normal privilege level instead, mirroring what the previous Run key mechanism did, so single-user laptops and locked-down corporate accounts both keep autostart functional. Operators who want to inspect or remove the entry by hand will find it in Task Scheduler Library → DMXRouter — same as any other scheduled task -
VLAN isolation in the receiver is now anchored to the physical network interface the kernel actually delivered each packet on, so traffic from one VLAN can never bleed into a process engine wired against a different VLAN — even when the two VLANs share an identical IP range — DMXRouter's job for many operators is exactly to be the controlled bridge between otherwise-isolated lighting LANs (redundant console pairs, multi-tenant venues, identical preprogramming setups bridged at the routing layer), and those LANs frequently use overlapping or even identical IP plans on purpose. The previous receive path had to guess which NIC a multicast or broadcast packet had arrived on by inspecting the sender's source IP and trying to match it against the configured subnets of each enabled NIC. That guess works for the common single-NIC or distinct-subnet case, but it gets the wrong answer the moment two enabled NICs share a subnet (or have masks that overlap each other), and it fails outright when two VLANs deliberately use the same IP range — both NICs match every sender IP, both get tagged on every packet, and engines configured against either NIC receive each other's traffic. The visible symptoms ranged from a "third VLAN" mysteriously contributing data to a merge that wasn't supposed to know about it, to two competing high-priority sACN streams oscillating downstream when the operator only intended to publish one. The receiver now asks the kernel directly via IP_PKTINFO (Linux/Windows) and IP_RECVPKTINFO (macOS) to attach the receive-interface index to every datagram, and applies a strict policy against it: a packet that came in on a configured NIC routes only to that NIC's interfaceId regardless of what its source IP says it is, and a packet that came in on a NIC the operator did not enable is dropped outright instead of being re-tagged onto some other NIC by subnet match. The "drop instead of fallback" choice is deliberate — falling back here would re-introduce exactly the cross-VLAN bleed the whole mechanism exists to prevent, by silently re-routing a packet from a disabled NIC onto an enabled NIC just because their subnets happen to overlap. Engines on a configured NIC see only the traffic that physically entered through it; everything else is silently rejected at the receive boundary. If the host won't let DMXRouter enable IP_PKTINFO (an extremely restrictive container sandbox, an embedded build target where the option isn't compiled in, an OS policy that blocks it), the receiver does not silently degrade to a less-safe routing scheme and pretend nothing is wrong. It logs a critical-level message at startup naming the affected socket and the platform errno that explains why, and surfaces a permanent visible warning in two places the operator cannot miss: a red
⚠ VLAN ISOLATION DEGRADEDbadge in the status bar and a⚠ VLAN ISOLATION DEGRADED — see logsuffix on the main window title. While that warning is showing, DMXRouter is operating with subnet-match fallback — which is still safe IF every enabled NIC is on a distinct subnet AND no two VLANs share an IP range, but is not capable of enforcing isolation otherwise. The right resolution is to run DMXRouter on a host where IP_PKTINFO is permitted; the warning explicitly shifts that responsibility to the operator and the platform rather than papering over it. The cross-subnet sACN delivery path documented in github.com/#33 (SirDur: senders on a network none of the host's NICs covers, multicast routed in via a snooping switch) still works in degraded mode as the final fallback, so existing setups that depended on it don't regress. Reported by SirDur (GitHub #37) -
Art-Net rigs no longer freeze on the wire when an upstream sender briefly engages ArtSync and then stops sending sync packets — the receiver now falls back to asynchronous delivery automatically after 4 seconds of sync silence, instead of holding every subsequent ArtDmx frame in a buffer that never drains — ArtSync is the Art-Net mechanism for committing several universes' worth of DMX data to a downstream rig atomically: a sender announces "from now on, hold each ArtDmx frame I send until you also see an ArtSync packet from me, then apply them all at once". DMXRouter buffers every incoming ArtDmx packet while in synchronous mode and flushes the buffer to the merge engine when the matching sync arrives. The trouble is that the previous receive path had no way out of synchronous mode once it had been entered — there was a 4 s clock running internally to mark the exit time, but nothing ever read it. If the sender that introduced sync mode then went quiet (a console crash, a network drop, a controller reboot, a tour rig where one node briefly negotiates sync and then is power-cycled mid-show), the buffer stayed armed forever and every subsequent ArtDmx frame from any sender on that universe was added to a list waiting for a sync that was never coming. Downstream rigs froze on whatever frame had been delivered last before sync mode engaged, and the only recovery was to restart the application. The watchdog clock now actually fires: when 4 seconds elapse without any sender on the network sending an ArtSync packet, DMXRouter flushes whatever ArtDmx frames are currently buffered (those frames are valid DMX, just held for an atomic commit that will never come — delivering them is a graceful fall-back, not a corruption) and reverts to asynchronous operation, which means new ArtDmx frames go straight to the merge engine again. If a fresh sender later engages sync mode, the watchdog resets and the synchronous-buffering behaviour resumes for as long as that sender keeps its sync stream alive. Per-frame async delivery resumes within the watchdog's polling interval (~100 ms) of the timeout firing, so the visible delay between the rig appearing frozen and recovering is well under half a second once the sync timeout itself elapses. The 4-second figure is the value defined in the Art-Net 4 specification; senders that keep their sync stream alive at the spec's recommended cadence (≥ 4 Hz) never trip the watchdog, so well-behaved sources that genuinely want synchronous delivery are unaffected. A log entry at info level marks each watchdog fall-back so operators reviewing a session log can see exactly when a sync source went silent. Surfaced during multi-console testing where one of the consoles was being repeatedly power-cycled and the rig appeared to freeze on every cycle until the application was restarted
-
DMXRouter no longer risks a rare crash on Windows shutdown when a network configuration change happens at the same instant the application is closing — DMXRouter watches for network adapter changes during runtime (cable plug/unplug, IP changes, VLAN add/remove, DHCP renewals, Windows Update touching adapters, sleep/resume cycles) using a dedicated background thread and the Windows NotifyAddrChange mechanism, which registers a kernel-side request to be notified about the next adapter change. The previous shutdown path asked the thread to exit via a stop event but did not cancel the still-pending kernel request that the thread had registered just before parking on the wait. If a network change fired in the small window between "stop signalled" and "thread fully torn down", the kernel completion path would write into thread-local memory that had already been released, occasionally producing a shutdown-time crash on hosts where the network happened to be reconfiguring exactly as the operator quit DMXRouter — the canonical reproduction is a sleep/resume cycle interleaved with quitting the app, or a VPN client (re)connecting at the wrong instant. The thread now explicitly cancels the pending kernel request before exiting, so the kernel releases its reference to the thread's memory in the same gesture that the thread releases the memory itself, and the race window closes. The fix is shutdown-only — runtime monitoring behaviour is bit-identical, so operators who never hit the crash see no change
-
The "Assign GDTF manually…" dialog (right-click on a placeholder fixture in the patch tree, or any fixture you want to swap to a different GDTF) and the "Set Address From Patch" dialog in the RDM tools now both accept multi-word filters in any order, matching the behaviour the Browse GDTF Share dialog already had — typing "robe iforte" now finds the Robe Lighting iForte regardless of the column the matching tokens land in, and "martin sceptron" finds the Martin Sceptron whether the operator types manufacturer-first or model-first. Single-word filters keep working the same way (they always did). Previously the dialogs treated the whole filter string as one literal substring, so a row labelled "Robe Lighting / iForte" wouldn't match "robe iforte" because the literal substring "robe iforte" never appears in the row text — the operator had to remember the exact word order printed in the row and type it back verbatim, which on a long manufacturer name like "Mode Studios Pro Lighting Inc." was practical only with the catalogue open. Both dialogs share the same shape now: split on whitespace, AND-match each token across every searchable column, fall through gracefully when the filter is empty
-
Editing a process engine over the web API now preserves the engine's runtime memory and advanced fields the same way the desktop dialog does, so a PUT that touches one corner of the configuration (mode, inputs, outputs) no longer silently wipes everything else to defaults — the web edit endpoint used to construct a fresh engine from the request body and replace the existing one with it, so a PUT carrying just
{"mode":"Backup"}to flip a Forward engine into Backup mode would also reset the engine's failsafe mode back to Hold (losing any Output-zero / Output-full / Output-scene / Stop-stream choice the operator had set), wipe the channel patch back to identity (losing every per-channel mapping the operator had built up), zero the channel offset, drop the master / limit configuration, and clear the recorded failsafe scene buffer if one was stored. The same edit also lost a handful of per-input and per-output advanced fields: per-input "accept own data" and "accept sACN preview" toggles, per-output Auto-mode threshold, per-output sACN sync universe, and additional unicast destinations beyond the first one — these were stripped because the web's serializer didn't emit them and the parser didn't read them. From this version on, the edit endpoint inherits the existing engine's full state as the starting point and overlays only the fields the request actually carries; everything not mentioned in the body carries through untouched. Read endpoints (GET /api/engines/n) gained the missing fields too, so a web client doing a GET → modify-something-else → PUT cycle now round-trips losslessly. Unicast destinations are now array-edited correctly — a PUT that changes the destinations replaces the array with the new contents, a PUT that doesn't mention them leaves the existing array alone. Lightweight patches (PUTs that only carry name / group / enabled, with no mode or inputs or outputs) were never affected by this and continue to work the same way -
Show Mode's Caution gate now also covers LLRP fixture-network operations from the web API, closing a hole where a misclick on the wrong row could rewrite a fixture's IP address in the middle of a show without the operator being asked to confirm — Show Mode protects destructive operations during live shows by requiring the operator to leave Caution mode (or Destructive mode, depending on the operation) before the change is allowed through. The desktop UI's LLRP page (RDMNet → LLRP tab) was already gated by the operator's intent because the dialog itself is opened only when the operator is actively administering the network. The matching web API endpoints — applying a network configuration (DHCP toggle, static IP, mask, gateway, APPLY_CONFIGURATION) and the lower-level "send arbitrary RDM SET via LLRP" command, which can be used to reach the same network-config PIDs — were not gated, so a script (or a curious web client) could rewrite a fixture's network identity at any time, including during an active show. From this version on, both endpoints check Caution mode and reject with the same 403 + reason that the rest of the destructive operations already returned. Read-only LLRP operations (status, target list, GET commands, identify on/off, start / stop discovery) are unchanged because they don't alter fixture state on the wire
-
The web remote PIN now throttles repeated wrong-PIN attempts to make brute-force attacks impractical — when the web remote has a PIN configured (Settings → Web Remote), the server used to accept any number of PIN attempts at full speed from any client on the network, which meant an attacker on the venue LAN could fire thousands of attempts per second through HTTP and walk a 4-or-6-digit PIN in seconds. From this version on, each source IP gets 5 free attempts (covering legitimate typo recovery — the operator misremembering or fat-fingering a couple of digits doesn't see any delay), then a 2-second lockout kicks in on the 6th wrong attempt, doubling on each subsequent failure (4s, 8s, 16s, 32s, …) and capping at 5 minutes per source. A successful PIN entry from the same source resets the counter back to zero, so a legitimate operator who got their PIN right on attempt 3 doesn't carry any baggage forward. The lockout is per-IP, so one wrong-PIN client doesn't lock out the whole venue, and applies to both HTTP and WebSocket auth paths (an attacker can't bypass the throttle by alternating between protocols). Lockouts don't persist across application restarts — closing and reopening DMXRouter clears the attempt history — which is fine for the threat model: an attacker still can't bypass the throttle without being able to restart the application, and a legitimate operator who got locked out by accident has a clean reset path. The PIN itself is unchanged from previous versions, and configurations without a PIN (Settings → Web Remote with the field empty) are unaffected because there's no auth check to throttle
DMXRouter v1.10.1 — Built for the stage.