Skip to content

Add the DMX planner: looks, library, positions, drop guess, Art-Net and a favicon - #40

Open
strikeout wants to merge 65 commits into
mainfrom
port-louis/controller-interface
Open

strikeout wants to merge 65 commits into
mainfrom
port-louis/controller-interface

Conversation

@strikeout

@strikeout strikeout commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

This branch adds the first part of the DMX planner (plan 1 of 5) and a new favicon. Version 1.24.0.

A preset of the DMX scenes can now be a look. The ESP32 sends the look on its own DMX output (IO4), in time with the beat. The rows, the steps and the gates of the DMX scenes stay the same.

  • The new crate autoblink-core-dmx holds the show model, the look renderer, a beat clock, the intensity effects and the Lights engine. A late WiFi beat packet does not move the beat clock. The idle look is always under the other looks, so the rig does not go dark.
  • The scene engine reports each preset that a row fires (Scenes::drain_fired).
  • The firmware has a new 64 KB flash partition show. A box needs one USB flash to get it, because OTA does not change the partition table.
  • The dmx thread starts before the network. It gets the scene notes and the beats through a FreeRTOS queue. dmx is now a default feature.
  • GET /show and POST /show read and write the show. The POST route checks the Origin, refuses a body above 60 000 bytes and a body above half the largest free heap block, and bounds each list while it reads it.
  • The WebSocket commands dmx_live and dmx_fog drive the master, the strobe hold and the fog. The strobe hold ends when the active page closes, when a send to it fails, or when another page takes over.
  • The page has a Lights panel: patch, groups, look editor, idle look and a live strip.
  • The favicon is a light cone "A". The tab icon blinks on each master beat.

The design is in docs/superpowers/specs/2026-10-27-dmx-planner-design.md. The plan is in docs/superpowers/plans/2026-10-27-dmx-planner-1-looks.md.

Tests

  • cargo test --locked: 460 tests pass. The new crate has 42 tests.
  • sh scripts/check.sh --esp32 passes: format, clippy, tests, and the firmware with and without dmx.
  • The firmware image is 1 612 640 bytes (82 % of the slot).

Update: scene holds and the new Lights layout (1.25.0)

  • Each preset in a scene row can have its own hold in beats. While a held preset is on, the triggers of the row wait, and the next preset starts when the hold ends. This applies to the MIDI notes and to the DMX looks.
  • The Lights panel is now part of the scenes panel ("DMX Scenes & Looks"). The ✎ on a palette chip opens the look editor of that preset. The editor has one card for each group.
  • The master fader, the STROBE button and the FOG button are in a bar at the top of the page.
  • The patch, the groups and the idle look are in the collapsed section "Lights setup".
  • The top-left corner of a chip shows the colour of its look, or else a colour from its name (English and German colour words).

Update: plans 2 to 5 (1.26.0 to 1.29.0)

  • Light library (1.26.0): "+ from library" in the Lights setup searches the Open Fixture Library through the box. The page maps the channels of the chosen mode. Moving heads with a combined dimmer and strobe channel and pixel bars in their master modes light up. A mode of unknown size or above 32 channels cannot be added.
  • Positions and shapes (1.27.0): pan and tilt channel kinds, named positions that the owner aims live from the page, and circle, figure 8 and sweep shapes in time with the beat. A shape of 4, 8 or 16 beats starts on beat 1. A head that no look aims stays at the centre.
  • Drop guess (1.28.0): a track without phrase analysis gets guessed buckets from the waveform energy. They fire on 16-beat blocks for the coming block. The page shows "(guess)".
  • Art-Net and the host (1.29.0): the box merges Art-Net from a desk (universe 0, HTP for intensity and colour, LTP for the rest, 3 s timeout). The host page edits the box's show and keeps a copy. Without a box, the host engine sends Art-Net to a node ([dmx] artnet_target).

Each task had a code review, and each plan had a whole-plan review with one fix wave. The rulings and the open follow-ups are in the commit history and in AGENTS.md.

Box test (WT32-ETH01, USB flash of 1.24.0)

  • The log shows "DMX output is on IO4" before the WiFi lines.
  • /mem with the page open: dmx 3 836 bytes free of 6 KB, midi 3 820, httpd 5 432 of 8 KB after all the POST tests. The 8-bit heap has 104 KB minimum and a 110 KB largest block.
  • GET /show on a new partition gives the empty show. A POST with a foreign Origin gives 403. A valid show gives 204, and a GET returns it, also after a restart.
  • A body above 60 000 bytes gives 413. A list of 7 000 looks is cut to 32 while the box reads it. A 120-deep nested body gives 204 and no restart.

Not tested yet

These steps need a player, a DMX monitor and a phone:

  1. Stop the deck with and without a Stop look. After 2 s, the correct look shows.
  2. Make sure that the flash looks are on the beat on Ethernet and on WiFi.
  3. Hold the strobe on a phone, then lock the phone. The strobe stops.
  4. Measure the DMX frame stall and the MIDI clock jitter while a show saves.

The Disco 240 desk has no MIDI input. The DMX planner replaces the desk
as the light source. The design keeps the song events of the MIDI
mapping, and adds looks, an effect layer and phrase triggers.
A preset of the DMX scenes can now be a look on the DMX output of the box.
The events, rows, overlays, steps and gates of the scenes stay the same.
The scenes must be on main before the work on the planner starts.
The plan is the first of five plans for the DMX planner. It adds the
crate autoblink-core-dmx, the show partition, the DMX thread that renders
the looks of the scenes, the show routes and the Lights panel.
The icon is an inline SVG in the page. Thus the host, the emulator and
the ESP32 need no icon route.
The A is a magenta light cone below a spot. It stays readable at 16 px.
The light cone of the icon turns yellow for 100 ms, at the same time as
the BPM flash of the page.
The scene engine now keeps the presets that its rows sent. The new
method drain_fired gives them to the caller in order and clears the
list. The list keeps the newest 16 entries.
The new crate autoblink-core-dmx holds the fixtures, the groups and the looks. The sanitize method cuts a show to the limits and removes each wrong reference. The from_json function always returns a valid show.
The engine stacks the scene layers, fades them in, keeps each look for at least 80 ms, and starts the strobe and fog bursts of a phrase event. It shows the idle look when no layer plays. The engine allocates no memory in the frame function.
The idle look is now the bottom layer of each frame, so a layer without a look never blacks out the rig. A scene note on turns a manual layer of the same preset into a held layer.
The partition table gets the 64 KB partition "show" at 0x3F0000. OTA does
not change the partition table, so a box needs one flash over USB to get it.

The new module show.rs keeps the DMX show as JSON in this partition. Bytes
0 to 3 hold the length of the JSON. The save writes the length last, so a
power loss gives "no show". The save counts the bytes first and erases only
the sectors that the new data needs. A show that is too large does not erase
the stored show. The load reads through a 256-byte buffer and calls
sanitize.

The DMX output is now a default feature.
espflash 4.6.0 stops with a panic on the data subtype 0x40. The runner and
the release workflow give this table to espflash, so each flash failed. The
show partition now has the subtype "undefined".

The firmware has the DMX output by default. Thus the first firmware step of
scripts/check.sh now builds without the DMX output, and the second step
builds the default configuration.
The dmx thread runs the Lights engine and sends one DMX frame after the
other on IO4. It replaces the beat flash on channel 1. The midi thread
queues the scene notes, the fired events and the preset tests. The
receive thread queues the master beats. The firmware loads the show from
the show partition before the network starts. Thus the maintenance start
of a firmware update also sends the idle look.
GET /show sends a copy of the show in the memory as JSON. POST /show
reads the body as a stream, cuts the show to the limits, writes it to
the show partition and then replaces the show of the dmx thread. The
dmx_live command sets the master and the strobe hold. The dmx_fog
command queues the fog. The hello message gets the capability dmx, and
the status gets the field dmx. ShowStore::write_json is removed,
because no route uses it.
A long list in the JSON of a show can use all of the heap of the
firmware before sanitize runs, and the box then restarts. The reader
now keeps only the first entries of each list up to its limit and skips
the other entries. A look has a new limit of 8 rows (MAX_ROWS), and
sanitize applies it too. POST /show replies 503 when the body is larger
than half of the largest free heap block.
The version is 1.24.0. AGENTS.md describes the DMX crate, the show partition, the show routes and the dmx thread. Three comments use clearer wording.
A row entry can have a hold time in beats. The row stays busy until the hold ends.
A trigger during the hold waits, and the next entry fires when the hold ends.
A hold entry of a scene row comes back after an overlay note off. A queued fire of a scene row
that is no longer the current scene is dropped. A row without holds leaves out the holds key.
A hold below 0.0625 beats rises to 0.0625.
AGENTS.md describes the hold of each scene entry, the pending trigger
and the restore of a held entry after a flash or a pulse.
@strikeout strikeout changed the title Add DMX looks on the box, and a favicon Add DMX looks on the box, scene holds and a favicon Oct 28, 2026
The plan is the second of five plans for the DMX planner. The box
fetches the fixture data, and the page maps the channels.
A fixture of the show now has an optional ofl field. It holds
manufacturer/model#mode name. The sanitize step cuts the field to 96
characters and removes control characters.
A combined dimmer and strobe channel maps to the dimmer. A shutter that is closed at 0 is kept open. Template channels of pixel bars map to the colours and the dimmer. The ofl routes need the feature dmx. A show with an empty ofl key drops the key. The STROBE button waits for a library fetch.
The show model gets the channel kinds pan, pan_fine, tilt and tilt_fine. It also gets up to 8 positions with one aim for each fixture. A look row can point to a position and carry a shape. An old show loads and saves unchanged. The render code does not use the new fields yet.
Render the pan and tilt channels from the position of a row. Add the circle, figure 8 and sweep shapes with a phase shift for each fixture. Add Lights::aim, which holds the pan and tilt of a fixture for 10 s over every layer.
The WebSocket command dmx_aim queues DmxMsg::Aim to the dmx thread.
The firmware build without the dmx feature no longer warns about dead code in ofl and query_value.
The page maps the Pan and Tilt channels of a library light, and their fine channels, to the pan and tilt channel types. Lights setup has a Positions section with one pan slider and one tilt slider for each moving head. A look row of a group with heads gets a position and a shape. The pixel keys that OFL makes from a pixel count now resolve the explicit pixel channel names.
The generated pixel keys use the pixel coordinate on each axis with more than one pixel, as OFL does. The aim throttle sends a pending aim of another head at once. The fine channel type uses the same capability as the coarse channel. The idle look row no longer shows the text false.
The DMX show section of AGENTS.md describes the pan and tilt kinds, the positions, the shapes and the aim override. The OFL mapping sentence now says that a pixel channel gets the kind of its template channel.
The shape index counts the beats from a bar line. A period of 4, 8 or 16 beats now starts on beat 1, whatever beat the clock saw first.
A beat with beat_in_bar 0 uses the beat count for the shape index and for the bar effects.
The scene engine takes a guessed bucket in SceneInput. It fires the bucket
row at a block start when no phrase is present and the guess differs from
the last guess that fired for the track. The status shows the guessed bucket
and sets the field guessed.
The firmware builds a DropGuess when the overview of a track without
phrases arrives. TrackData keeps it next to the overview. The midi loop
calculates the position from the playhead and the track length of the
master deck, and gives the guess to the scenes.

The host builds a DropGuess in set_overview and keeps it in DeviceState.
scene_input gives the guess only for a track without phrases. It also
gives phrase16 from the 16-beat counter of the master.

The scenes panel shows "(guess)" next to the current bucket while the
bucket comes from the guess.
A new master deck starts its first guess without the previous bucket of the old deck. Thus the hysteresis of the old deck does not hold a Drop or a Break on the new deck. The Every::Bar effects use the same no-bar-information fallback as the shapes.
The guess of the phrase buckets from the waveform is in AGENTS.md. The image of the firmware uses 85 % of the slot.
The new `artnet` thread receives ArtDmx on UDP port 6454 on all
interfaces. It keeps the last frame of universe 0 and its time in
`State::artnet`. The `dmx` thread copies that frame with `try_lock`
and merges it into each frame with `Merge::apply`. The status field
`dmx.artnet` is true while the last frame is not older than 3 s.

The merge state and the frame copy are on the heap. The `dmx` stack
increases from 6 KB to 7 KB. This size is an estimate from the stack
frames of the build. The firmware starts the `artnet` thread after the
network, and the OTA maintenance start does not start it.
The config section [dmx] sets artnet_target (an IP or IP:port, the default
port is 6454) and universe (default 0). A bad target logs a warning, and the
host starts without the engine.

With a target, a supervised task runs the Lights engine and the beat clock
like the dmx thread of the firmware. It sends one ArtDmx frame each 25 ms. The
MIDI mapper sends the scene notes, the fired presets and the master beats to
the task through a bounded channel with try_send. While an AUTOBLINK box is on
the Pro DJ Link network, the task sends nothing.

The host serves GET /show and POST /show with the limits of the box. It
writes the show to show.json next to the config file on a blocking thread.
The hello has the capability dmx only with a target. The status field box
gives the IP address of the box.
The host handles the WebSocket commands dmx_live, dmx_fog and dmx_aim. The
engine reads the live master and the strobe hold for each frame, and it
starts the fog and the aims. scene_test also shows the look of the preset on
the engine, as on the box. Without an engine, these commands give an error.

The status field dmx gives the layers, the live master and the strobe hold of
the engine. The engine writes the field only after a change, with try_lock.

The mapper stamps each DMX beat with the receive time of the beat. The doc of
DmxConfig tells that two hosts with the default device name see each other as
a box.
A phone that locks during a strobe hold, or a tab that closes, sends no
release. The whole rig then strobes until another page releases the hold.

Each WebSocket client now has a guard. The guard records whether the last
dmx_live of this page set the strobe hold. When the socket closes, the guard
releases the hold. A page that did not set the hold releases nothing.

The hello has the capability dmx only when the DMX engine runs.
The host page reads and writes the show of the box when the host finds one. It posts the same show to the host copy after each save. The live strip moves to the page of the box.
The page loads the show once, after the first status. A late load of another endpoint cannot replace the show. A failed load clears the show. A pending save goes to the old endpoint before the switch. The look editor hides Preview when a box is the target.
The look editor closes when the show fails to load, so a click on a stale editor cannot throw. The drop guess documents the length limit of its look-ahead.
The version is 1.29.0. AGENTS.md describes Art-Net in on the box, the DMX engine on the host and the new image size.
The page posts the show that it loads from the box to the host. The host warns when the default device number hides the box. The 413 reply of POST /show carries a CORS header.
@strikeout strikeout changed the title Add DMX looks on the box, scene holds and a favicon Add the DMX planner: looks, library, positions, drop guess, Art-Net and a favicon Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant