readout
A CMOS rolling shutter for Resolume

What it does
An FFGL effect for Resolume Arena and Avenue. A CMOS sensor does not expose a frame — it exposes and reads its rows, one after another. Row r of H is read Readout × (1 − r / H) before the frame's timestamp, and was integrating light for Exposure before that. So every pixel has a sample window, and the picture is sampled there. That is the whole plugin.
None of what follows is drawn. A vertical bar moving at v px/frame leans by v × Readout, because the top of the frame was read earlier and shows the bar further back. Shake the camera and every row sees it in a different position, so a straight edge comes out as a wave, one full cycle every 1/f of readout. A strobe shorter than the readout lights only the rows whose exposure window overlapped it, so the band is (Exposure + Length) / Readout of the frame high, its edges ramp over the shorter of the two, and where it sits is the flash's phase. A mains lamp is brightest twice a cycle, so at 50 Hz it pulses at 100 Hz and beats against the readout into rolling dark bands T_light / Readout apart — the reason phone video of a stage looks striped. The propeller, and partial frames, and every other rolling-shutter photograph anybody has posted, need no code at all: they are the first item on that list applied to content that happens to be rotating.
Global sets the readout to zero — a global shutter, for comparison — and with nothing else switched on it returns the picture untouched, to the byte. That null is checked with a negative control, so the check can fail.
The shake is a camera, not a wobble generator: it has an audio input, because a speaker near a camera moves the camera, so the audio drives the pose and the jello follows on its own. Audio Drive defaults to zero, so with nothing routed the camera sits still. The flash fires from a button, from Resolume's own beat or bar, from an onset in the routed audio, or on an interval.
The honest limit is that a real sensor samples a continuous scene and this one has the frames the host gave it. Between two of them it either interpolates or holds the older one, so the lean of a moving bar is right and the sub-frame detail of anything moving unevenly is the interpolation's invention rather than an observation. The shake is sampled once per row rather than integrated across the exposure: rows are the axis that makes jello and that part is exact, but a real sensor would also blur the shake and this does not.
- Readout time and exposure in real milliseconds, with the row's sample window as the one rule underneath everything
- Four readout directions — top-down, bottom-up, left-right, right-left
- Global shutter for comparison, which returns the input bit-exactly
- Flash banding whose height, edge ramp and position come from exposure, length and phase rather than a drawn band
- Mains flicker at 50 or 60 Hz, with the band spacing the beat against readout gives
- Camera shake driven by audio level, because a speaker near a camera moves the camera
- Flash triggering from a button, Resolume's beat or bar, an audio onset, or an interval
- Blend or Hold between the host's frames, so the interpolation is a choice rather than a hidden assumption
- Universal macOS bundle and a Windows x64 DLL
Where it stands
v0.1.0, and honestly early. Every claim is checked against its closed form by an offline harness that drives the real plugin class in a headless GL context, and tools/verify.sh runs the lot against a fresh universal Release build: a bar at 24 px/frame under a one-frame readout leans 24.000 px against 23.967 expected, an 8 ms flash at 4 ms exposure over a 40 ms readout bands 215.0 rows of 720 against 216.0 and centres 252.5 against 252.0, the 50 and 60 Hz mains bands come out at 120.00 and 100.00 rows against 120.00 and 100.00, a 40 Hz shake over a 60 ms readout gives a 300.00-row period and a 28.80 px swing against exactly those, and Global deviates 0/255 from the input twice over while the negative control differs by 164/255 — so that check can fail. All 20 swept controls measurably change the picture and all three shaders compile through glslc rather than merely through Apple's driver. Render cost on macOS is 0.278 ms/frame at 720p, 0.517 at 1080p, 0.902 at 1440p and 2.274 at 4K. On 21 September 2026 the x64 DLL was registered, loaded and instantiated in Resolume Arena 7.27.1 on win-lab: SW Readout is listed under idstring RO01 among 112 video effects, the shaders compiled, and Arena drew its inspector. That VM has no GPU — OpenGL came from Mesa llvmpipe, a software rasteriser — so it has never run on a GPU in Resolume, it has never been instantiated in Arena on macOS, and nothing was timed on Windows at all. This is where the float-overflow trap in Resolume's clock was found, and Arena is the first real host the unit detection has met. No real audio has reached it in a host: the audio path has only ever seen the harness's synthetic spectrum, so what Resolume's 64 FFT bins mean is still assumed rather than measured, and Beat and Bar have only seen a synthetic 120 bpm transport. No long session, composition save and reload or preset recall in the host; nothing built for Linux; no OpenFX port, no user guide and no factory presets. The browser demo is live, but it is a port and not evidence: the GLSL from source/Shaders.cpp copied across unedited and run in WebGL2 over clips generated in the page, without the audio side or Trigger's Beat and Bar, because a browser has no equivalent of Resolume's FFT parameter or its transport. Nothing on that page measures anything — the numbers above come from the harness in the repository. Check it in your own rig before trusting it in a show.
Watch it running
Nothing loads from YouTube until you press play.Watch it there instead