rox

A visualizer that lives in the player

Most players give you a spectrum analyzer and stop. rox has those panels, a Milkdrop engine for twenty years of presets, and under them a modulation layer: named signals pulled off the audio, routed into shader inputs you write yourself, saved into the same file the rest of your theme lives in.

The ordinary panels, first

A spectrum panel with bar or block styles, gradients, peak caps with their own gravity, frequency labels, and split zoning that analyses above and below a chosen frequency at different window sizes, so each end of the range trades reactivity for resolution on its own terms. A waveform strip that draws the whole track, per channel, and seeks where you click. A VU meter. An art shelf that turns covers in real 3D. None of it needs a word of code and it's what most people will use.

Pausing freezes the bars where they were, rather than letting them fall to the floor. That sounds small until you're trying to look at the exact moment you paused on.

Milkdrop, in a panel and behind everything

A Milkdrop panel renders presets through libprojectM on its own thread and draws the result through the same shader chain as everything else, so it docks, tabs and pops out like any other panel, and a surface shader goes over it the same way. Drop a preset pack into the folder named on the Presets page, Cream of the Crop or the original MilkDrop set, and hit Rescan. Textures shipped inside a pack are found on their own.

Choose Preset opens a browser in its own window: a folder tree or a grid of thumbnails, a filter, a favourites switch, and keyboard navigation. Clicking a preset loads it behind the window right away, so what you see is the real render rather than a preview. Rotation sets what Next, Previous and the timed switch cycle through, and Lock holds one preset across tracks.

The same engine runs as a backdrop behind every window, composited over the blurred cover art at a strength you set. A preset can keep its own colours, flip for the light theme, paint in the palette, or take the playing cover's colour with its own detail kept, and an album tint shifts the hues toward the art. A stopped queue fades it out and the render thread stops, so an idle visual costs nothing.

Shaders, on four surfaces

A rox shader is one WGSL fragment stage. It can run over a single panel's body, as a Shader panel of its own, as an overlay across the whole window, or as a backdrop painting under every panel, behind the cover art. Overlay and backdrop each take a switch that extends them to every window rox has open. Same language, same uniform block, same routing in all of them.

Every shader gets the clock, the resolution, the pointer, and eight meta floats describing the moment: volume, where the playhead sits in the track, whether audio is actually moving, the track's length, and how dark the current palette renders, so one shader can dress itself for light and dark instead of shipping for the theme it was written against.

A panel shader can also read what the panel actually drew. A mask binding hands it the panel's own painting replayed onto transparency, and panels publish the shape of their content, so a shadow or a glow follows a letterboxed cover rather than the empty bars either side of it.

Past one stage, a // @pass comment splits the text into a chain of up to eight, each one able to read the passes before it by name. That's what a blur pyramid or a two-stage bloom needs. A // @asset line names an image to sample, and // @asset art: @cover binds the playing track's own cover, rebinding when the track turns over. Nine examples ship with the app, each one demonstrating a different part of that contract rather than nine variations on a plasma.

Point the panel at a file on disk and rox watches it. Save in your editor and the window has it. A save that doesn't compile leaves the last good version on screen with the error in the panel, because losing the picture is a bad way to find out you typed a semicolon wrong.

Signals are the part that's different

A shader with a clock in it is a screensaver. What makes it a visualizer is what you feed it, and in rox that's a pool of named signals shared by the whole app. You build them in a window of their own, under Application, next to the equalizer, and it carries a spectrum and a transport for the same reason the equalizer does: you pick a band by eye and ear against the thing you're actually playing.

Five kinds of source. A band is the energy between two frequencies, which is the signal a swell rides. Level is the whole mix. An onset pulses when a band jumps past its own recent average, which is the signal a hit rides. A trigger pulses when a band crosses a line you drew, for material where the moving average never gets to drop and an onset goes quiet after the first bar. A total adds another signal up over time and wraps at one, so a shader can read it as a phase that advances while the music is loud and stalls while it isn't.

Each one carries response smoothing and a gate. The gate is the setting that earns its place: a band riding room tone holds every knob attached to it slightly off its rest position forever, and a threshold with a curve above it hands over silence between the hits instead. The meter draws the ungated value with the threshold marked across it, since a bar that vanished under its own gate would be no help at all in placing the gate.

A route attaches one signal to one parameter with an output span: this signal, this target, from here to there. Sixteen slots reach every shader, and the slots nothing is routed to get a plain slider on the settings page, so a look can be half performed and half set by hand.

Critters, as the argument

One of the workspaces in the box is the whole player rendered as a 1-bit print: an ordered dither over every surface, tones crushing with the sub-bass, and a noise wall behind the panels that moves with the song. It's five signals and four shaders in a single file, and none of it is a special case in the app.

The Critters rox workspace: the entire window rendered in black-and-white ordered dither, with a curved shelf of album covers over a seek bar, transport row, waveform strip and track table, all sitting on a shifting field of noise.

Load it from the welcome window, then open the settings and take it apart. The rest of them.

Running a stranger's shader

Shaders travel inside workspace files, which means an imported look arrives carrying somebody else's code.

Nothing runs until you say so. Each shader's source is fingerprinted, and the approved list is machine-local and only ever written by a direct action: you picked the file, you hit reload, you chose a preset, you pressed Approve. A look that arrived in a bundle sits there inert with a button under it until then. The examples that ship with rox are trusted by construction, because they're compiled into the binary.

Turning one on opens a confirmation window that waits for an answer instead of counting down, and that window is never itself shaded. Whatever the shader does to the rest of the screen, the way back out stays readable.

What isn't handled

  • Routes reach shader slots and the particles panel's knobs. Every other panel's settings are still knobs you set by hand.
  • Milkdrop presets, not AVS. There's no importer for Winamp's AVS. And a preset is a file on your disk, so which one is loaded stays per machine rather than travelling inside a workspace.
  • No MIDI or OSC control surface. The overlay pass and the panel passes are what exist, driven by the audio and the pointer.
  • A full-window pass is a real GPU pass every frame. On a laptop on battery, that's a choice you're making.

The distance between "I want the whole player to strobe on the kick" and having it is a band, a gate and a route, and none of the three needs a rebuild.

Try it on your own library

Nothing here phones anywhere: the analysis is the audio already going through the player. More on what a workspace carries.

Download rox

On Arch or NixOS? Install it from the AUR or the Nix flake instead.

v1.26.4 · Linux, macOS, Windows · all downloads