The Manual as Test Suite
The Panasonic WJ-MX50 was a desktop digital A/V mixer from the early 1990s. Two buses, four sources, 287 wipe patterns, a chroma keyer, downstream keyer, eight event memories, and a joystick. Its 40-page operating manual is unusually precise: which button blinks when, which effect excludes which, how many frames an auto-fade takes (0 to 510, in steps of 2).
Sebastian (sebs) took that manual and turned it into a WebGPU implementation called web-mx-50. The core insight: the manual's behavior descriptions read like Gherkin scenarios. So he wrote 536 Gherkin scenarios (3,921 steps) in 26 .feature files, one per hardware block. Each scenario is ground-truthed against a section of the manual. Alongside run 208 conventional unit tests. All execute headlessly against the real domain code — no GPU, no DOM, no browser.
Single Reducer Architecture
The entire panel state — both buses, matte generator, wipe pattern selection, DSK sliders, fade enables, all eight event memories — is a single plain JSON-serializable value. Every change goes through one pure reducer as a typed command. No classes, no observables, no GPU handles inside the state. Device bindings are referenced by id and resolved outside the store.
This design choice was forced by two hardware features. First, Event Memory: eight snapshots of the complete panel, storable and recallable at a button press. With a single value, Event Memory is structuredClone and an array of eight slots. Second, the unit holds settings across power cycles, which maps to schema-versioned browser storage — again trivial with one value.
The same store made the control layer thin. The original unit had a GPI contact input and an RS-422 port. The browser equivalent is a mapping layer that normalizes keyboard, gamepad, MIDI, and a small automation API onto the same command vocabulary. A MIDI fader and the on-screen lever end up as the same command with a different origin.
The Wipe Engine
The MX50's 287 wipe patterns are not a list; they are a small algebra. Seven pattern families, each cycling four variants, times a set of stackable modifiers (Compression, Slide, Multi, Pairing, Blinds), with a legality table for which combinations exist and a numbering scheme where pattern n and pattern n+128 are the same wipe reversed. Numbers above 255 exist on the panel but cannot be addressed over RS-422, and the external edit controller could only call 01–99, with 99 meaning "whatever is currently set up".
All of that is pure arithmetic and table lookups, implemented in one GPU-free module and specified exhaustively in Gherkin. The shader consumes its output; it does not reimplement it. The wipe shader treats each family as an analytic signed distance field f(uv, progress, variant). The A/B mask is smoothstep(-w, +w, f); the Soft button sets the feather width w; the Border button draws a colored band where |f| is small. Reverse mirrors a coordinate, Aspect scales one axis, Multi tiles, Pairing mirrors before evaluation. One shader, one uniform block, seven fields.
Timing: Fixed-Timestep Clock
Auto Take and Auto Fade run 0–510 frames in 2-frame steps, pausable mid-flight. In a club, this is how you land a transition on a phrase boundary. If driven from requestAnimationFrame deltas, a 300-frame fade takes different wall time on 60 Hz vs 144 Hz monitors. Instead, there is a fixed-timestep logical clock: an accumulator converts real elapsed time into whole video-frame ticks. All time-dependent logic reads ticks only; the present loop uses the sub-tick fraction purely for interpolation. Tests step the clock directly. A 300-frame fade is 300 ticks everywhere.
What Was Left Out
Two deliberate fidelity bends. First, no NTSC. Browser sources arrive as progressive full-resolution RGBA frames on a compositor clock. Emulating chroma subsampling, field parity, and genlock drift would mean synthesizing artifacts the input never had. The working representation is linear-light RGBA with sRGB output, compositing math done in linear space to avoid gamma-darkening wipe edges. The Frame button's behavior is documented as moot.
Second, the frame synchronizer is not built because the problem it solved no longer exists. What survives is per-bus frame memory, which Still, Strobe, Multi, and Trail consume. Those four effects share one GPU frame-store per bus, and the hardware's odd exclusion rules (Still and Strobe mutually exclusive; Trail rides on Still) fall out of the design naturally.
Next Steps
The prototype source is published at github.com/sebs/webgpu-mx-50. Next: a web component so the mixer can be dropped into any website, a demo setup with Source A/B preview and program-out channels, a UI reproducing the original panel layout, multi-tab and multi-window operation, and further exploration of the mixer as a tool for visual art.

