Skip to content
Русский

The flags

FlagValueWhat it is for
--help—Print the flag roster and exit
--console—Open the operator console at launch, on a display other than the show’s
--list-devices—Enumerate audio capture endpoints and exit (Windows-only)
--list-adapters—Enumerate graphics adapters and exit, from both rosters
--list-presets—List the presets this launch would load, with each file’s status, and exit
--schema—Print the preset schema as JSON on stdout and exit
--check<path>Check a preset file, or a directory of them, and exit
--strict—Make a --check warning cost the same exit code an error does. Needs --check
--events—Report as JSON lines on stderr, for a parent process
--previewstdout | stdout@WxHMirror the windowed show to a parent process, as a fixed-size copy
--inputloopback | line-inWhere audio comes from (Windows-only)
--device"<friendly name>"Which capture endpoint to open (Windows-only)
--tierfloor | richPin the quality tier instead of letting the engine pick
--grid-scale0.25–1 | autoDraw the internal grids at this fraction of the frame — the window and --stream both. Overrides [quality] grid_scale for one run
--osc<host:port>Publish analyzer telemetry as OSC over UDP, and turn the sink on
--control<host:port>Listen for studio control messages as OSC over UDP, and turn the listener on
--soak[path]Write a long-run frame-time trace; bare, a default path
--downbeat-log[path]Write the per-beat downbeat decomposition; bare, a default path
--stream—Run headless and publish every frame to a sink
--sinkspout | stdoutWhere the published frames go, default spout. Needs --stream
--sizeWxHPublished frame size; default 1280x720 on --sink spout, 640x360 on --sink stdout. Needs --stream
--fps<n>Published frame rate; default 60 on --sink spout, 30 on --sink stdout. Needs --stream
--sender<name>The published Spout sender name, default ritmolux. Needs --stream
--frames<n>Stop after this many frames. Needs --stream
--gpu<name|index>Which graphics adapter to render on — the window and --stream both. Overrides [output] gpu for one run
--preset<name>Hold one scene and disable rotation

A flag marked Needs --stream passed without it is a startup error naming both flags, rather than silence.

The ones that need a paragraph

--help writes to stdout and creates no window, no GPU device and no capture client, so a script can probe the flag surface without starting a show.

--events turns on the structured report a parent process reads (the control protocol). One JSON object per line on standard error: hello with this build’s version, the schema hash and the control port actually bound; preset and roster as the show moves; preset_error and preset_warning with the file, the message and — when the TOML parser gives a position — the line and column; health once a second while frames are being drawn; and pong answering a ctl/ping.

preset_error also covers a ctl/preset the player declined to select: there its file holds the name that was asked for rather than a path, and the message says the selection did not take. It is the same event because it is the same fact to whoever asked — the preset you clicked is not on screen — and without it a click on a name the roster does not hold produced nothing at all.

health reports the control listener alongside the frame timings: ctl_rejected, ctl_dropped and ctl_refused are what the path discarded, and ctl_received, ctl_recv_errors and ctl_listening are the listener’s own state — datagrams the socket handed it, receive failures that were not the ordinary read timeout, and whether the receive loop is still running. Read together they say which of “nothing was sent”, “something arrived and was discarded”, “the socket is failing” and “the listener has gone” a silent control path is. A run without a listener reports zeros and "ctl_listening":false.

Every event line begins with { and no human diagnostic does, so a parent splits the two on the first byte and needs no framing. The flag is purely additive: without it standard error carries exactly the lines it always did, and with it those same lines are still there, unchanged, beside the events.

--preview stdout mirrors a windowed run’s frames to whatever spawned it, as raw 8-bit frames announced by the same stream event --stream --sink stdout announces. It is how a studio watches the show that is actually on the projector, rather than a second headless one.

The mirror is a fixed-size copy, and the size is the mirror’s own. --preview stdout sends 640x360; --preview stdout@WIDTHxHEIGHT asks for something else. The show keeps the window’s resolution and the mirror is a scaled copy of it, letterboxed so a window of any shape keeps its aspect. The size is answered once, before the first frame, and a resize, a maximize or a fullscreen toggle does not move it — which is what lets a reader cut the byte stream into frames by a number it was told at the start. That is the one place --preview and --sink stdout differ in kind: the latter is an exact feed at the size named on the command line, for ffmpeg, and it is unchanged by any of this.

Off by default, and the default matters: with the flag absent nothing is allocated, no frame is copied twice, and the show draws exactly what it drew before the flag existed. With it on, the frame is routed through the operator console’s own intermediate, blitted into the mirror’s fixed target, copied to a staging buffer, and read back one frame late with a non-blocking poll — so the display loop never waits for it.

It drops rather than stalls. A reader slower than the show loses frames and the show carries on; the count of what was dropped is written to diagnostics.log beside the frame-time figures, so the cost of the mirror is measurable rather than assumed. That is the opposite of the headless --sink stdout policy, which blocks — a headless loop has no present deadline and a windowed one does. See Capturing.

--schema answers the other question a program asks before it starts driving the player: what a preset may contain. It prints one JSON object on stdout — every system and engine stage with its parameters (name, default, range, and the line that says what each does), every structural table with its keys and the closed rosters they draw from, the expression grammar’s identifier rosters, and a hash of all of it. Like --help it creates nothing and exits, and it moves no files on the way.

The grammar object carries three arrays — variables, functions and constants — which is what an expression editor colours from. variables is what the parser’s identifier lookup accepts, so the reserved [latch] placeholders are absent: an author reaches a latch through the name they declared for it, and a preset’s own latch names are in the preset rather than here.

The parameter half is rendered from the same declarations the reference table in the preset library’s README is, so the two cannot disagree. The hash changes when and only when the document does: a studio that cached the schema compares it against the one the player reports on startup to know whether its panels are stale.

--check <path> is the verdict on a preset before it is rendered. It runs the engine’s own loader — the same one a show runs — and prints what that loader said, one line per diagnostic on stdout:

presets/fragment_tunnel.toml:34:1: error[engine]: parameter 'glow' has an invalid expression: unexpected end of expression

The shape is path:line:col: severity[rule]: message, which an editor’s problem matcher reads directly. Line and column are 1-based and the column counts characters rather than bytes. A summary line — files checked, errors, warnings — goes to stderr, so a caller piping stdout into a matcher gets diagnostics alone on the stream it parses.

<path> is a file, or a directory whose *.toml files are checked non-recursively: a subdirectory such as presets/pending/ is held back by construction, which is the same convention the shipped set is embedded under. An empty directory is not an error — the summary says checked 0 files, so a run over nothing reports itself rather than passing silently.

Exit codes: 0 clean, 1 a preset failed, 2 the command was wrong. A path naming nothing is 2, because a missing file is the spelling rather than the content. --strict moves a warning into the failing class, which is what a gate wants and what an author mid-edit does not: the class the loader forgives on purpose is a parameter name it does not recognise, where the binding is kept and nothing reads it — a silent typo, and the one --strict exists for.

Like --help and --schema it creates no window, no GPU device and no capture client, so an author’s “does this compile” loop costs one process start. Nothing about it writes: there is no --fix and no formatter, and ADR-0190 records the measurement that decided it.

--console is a presence flag with no value: it turns the console on for this run and never off, and it does not write itself into config.toml (the same shape --input, --device and --osc follow). [console] enabled = true is the persistent form, and the C hotkey and the settings menu’s Console row are the same path — no two of the four can disagree about whether the console is open.

--list-adapters prints both rosters: the one the renderer selects through and the one the Spout sender selects through. They are separate enumerations and are not assumed to agree on order, so both are printed with their own indices.

--list-presets prints one row per preset this launch would load — the display name, the file it came from, and how that file stands against the set this build ships — then exits without a window. It reads and never writes: it does not seed, and nothing in the preset directory is ever deleted, overwritten or migrated by it or by anything else.

The status column has three values. shipped is a file byte-identical to the copy this build carries. differs is a file whose name this build ships under different bytes: your edit, or a copy from an older release that seeding left alone — nothing on disk records which, so the two cannot be told apart. not shipped is a file this build’s set does not have at all, either your own preset or one a later release retired.

A row also says when two files claim one display name. Only the first in filename order is reachable by name, so the second is loaded and rotated through but cannot be selected by --preset, by the studio, or by the browse overlay. The same drift is summarised in one startup line, printed only when something has drifted, so an untouched install says nothing.

That startup line is for the seeded per-user directory only — the one the app writes the curated set into on first run. With RLX_PRESET_DIR set, the app neither seeds nor reports: that directory is yours, differing from the shipped set is usually the point of it, and --list-presets is the only reading of its drift.

--gpu <name|index> works for both the window and --stream. On a machine with one GPU you will never need it; on a hybrid laptop it is what decides which GPU the show runs on. A Spout sender shares a D3D11 texture by handle and the receiver opens it on its own device, which works only when both are the same physical GPU — and Windows hands a plain console process the power-saving GPU while the receiving application runs on the discrete one. One flag moves both halves: the renderer and the sender each resolve the name against their own roster. Unset, --stream’s renderer asks for the high-performance adapter and the sender follows it by name, printing what both resolved to.

The window asks for the same thing unset: the high-performance adapter, which on a hybrid laptop is the discrete GPU rather than the power-saving one the graphics layer would otherwise hand a window. The startup line in diagnostics.log names the adapter and which carrier chose it — (default: high performance), (pinned by --gpu) or (from config.toml [output] gpu). For the window the flag is the per-run override of a stored choice: [output] gpu holds the adapter by name in config.toml, the settings menu’s Adapter row writes it, and --gpu wins over the key for one launch without writing it — the precedence --tier has over [quality] tier. The two carriers fail differently on purpose. A --gpu that names an adapter this machine does not have, or one that cannot drive the window, is a startup error rather than a quiet fall-back to another GPU. A stored [output] gpu that no longer resolves — a laptop undocked from its eGPU, a driver that renamed the card — starts anyway on the default preference, with one line on stderr and a startup note naming both what the file asked for and what was taken: a file outlives the machine state that made it valid, and a show that will not start is the wrong failure for a persisted preference.

--preset <name> takes the preset’s display name — Clifford, Rose Window — as the browse overlay and --preset’s own error listing spell it, not the .toml filename, so most of them need quoting. An unknown name is a startup error that lists the roster, and no window opens. Hotkeys still browse, so this pins where a run starts and turns the dwell timer off.

--input loopback|line-in overrides [input] mode. loopback taps whatever the system is playing; line-in captures an input endpoint (an audio interface, a mixer feed). A value that is neither is a usage error and the app exits, the same way a bad --tier does. The input also moves while the app is running — the settings menu’s Input mode and Input device rows swap the capture stream in place and write the choice to config.toml, so --input pins the launch rather than the session. Expect a brief hitch on the swap (the old stream is stopped and the new one opened synchronously), and, when the two endpoints negotiate different sample rates, a second or two of re-adaptation while the level tracking rebuilds.

--device "<friendly name>" overrides [input] device. Copy a name out of --list-devices; a substring is enough. The two flags override independently — --device alone keeps the configured mode, --input alone keeps the configured device name. A name that matches no active endpoint of the selected mode is not an error: capture falls back to that mode’s default endpoint and says so on stderr, because the interface being unplugged is a fact about the world rather than a typo in the flag. Giving no name at all — a trailing --device, or --device= — is an error: an empty value selects the default endpoint, which is the opposite of what naming a device asks for. What happens when an endpoint disappears mid-show is in Running the app.

--stream runs headless as a live video source and publishes every frame as a Spout sender, for TouchDesigner (or any Spout receiver) on the same machine. No window, no swapchain, no codec. Windows-only, and present only in a build with the spout feature — the release ritmolux.exe has it; a plain cargo build does not, and --stream there fails with a named error saying so. Presets rotate on the operator config’s dwell timer exactly as they do in the window (rotation is on here even where [rotate] auto is off, since a headless source has nobody to press Space). Ctrl-C stops it and prints the run’s frames, wall clock and scene clock. See Headless capture and video for the TouchDesigner side.

--tier floor|rich pins the quality tier. Unpinned, the app starts on rich and a frame-time governor demotes it to floor once if the display’s frame budget is not being held. A pin is never demoted, so this is also how you keep rich on a machine a transient stall demoted. The tier also moves while the app is running — see Quality tiers.

--grid-scale <0.25..1|auto> sets the fraction of the frame the internal grids are drawn at — the post stages’ grid (trails, kaleidoscope, bloom) and the attractor’s trail field. Those grids are where a heavy preset spends its frame, and their cost follows their area, so 0.5 draws a quarter of the texels at a visible loss of sharpness and nothing else: the picture’s shape does not move. auto lets the engine pick one for the tier and the kind of GPU from the table under [quality]: 0.75 for rich on an integrated GPU, 1 everywhere else. Under --stream it is the only way to draw at less than full size: a headless run takes 1 on any GPU, so two machines of different kinds capture the same grids, and neither [quality] grid_scale nor RLX_GRID_SCALE reaches it. A value outside 0.25–1 is a usage error naming the range, and the app exits, the same way a bad --tier does. The scale in force is printed beside the tier in the F3 overlay, and in the header of --stream’s pass-cost table together with both grids’ sizes.

--osc <host:port> both aims the sink and turns it on, so enabled = false in config.toml cannot veto a target typed for this run. Off unless you ask for it. A target that will not resolve is a usage error and the app exits — the same way a bad --tier does — whereas a stale target in config.toml degrades to no sink and says so, because a config file must not stop a show. Sends are non-blocking and dropped on failure: a broken link costs the telemetry, never a frame, and the app prints one line when it starts failing and one when it recovers rather than a line per frame.

The one flag --help does not print

--thumb <name> renders one preset’s browser thumbnail into the cache and exits. It is deliberately absent from the roster, because the process that passes it is the player itself: the app re-invokes its own executable one preset at a time to fill the thumbnail cache below (ADR-0230). Like --schema and --check it opens no window, binds no socket and starts no capture client.

It is documented here rather than hidden outright so that a ritmolux seen in a process list with an argument nothing explains is not a mystery. Running it by hand is harmless — it renders the still for one preset of whatever library this launch resolves, writes the cache entry, and says on standard error either what it wrote or that the preset has not changed since last time. A name no library holds is refused and nothing is written.

Built from 13c7582 at version 0.158.0. This site tracks main and is not versioned per release.