Open source, MIT, by Calvin at ArchAstro

Clapper

Write videos in React. Render them to MP4. Review them like a studio would. Every frame is a pure function of the frame number, the music is code, and the reviewer is an agent with a contact sheet.

npx skills add ArchAstro/clapper --skill clapper --global
THE INTRO · 25 s · 1080p30 · sound onoriginal score, rendered from React

The one idea

Every frame is a function of the frame number

There is no clock. A scene reads useFrame() and draws itself. That is why the renderer can visit frame 212 without playing frames 0 to 211, why a preview can scrub in any direction, and why two renders of the same source are identical to the byte. Drag the slider. The picture below is computed from the number, nothing else.

FRAME 000 · 0.00 s
0 149

Try dragging backwards. A timeline built on wall clock time cannot do that; a pure function of the frame can.

// a scene, as Clapper would write it
function Hop() {
  const f = useFrame();            // local frame
  const x = interpolate(f, [0, 149], [130, 470]);
  const hop = Math.abs(Math.sin(f / 9)) * 120;
  const squash = f % 28 < 3 ? 0.82 : 1;
  return (
    <Box x={x} y={280 - hop} scaleY={squash} />
  );
}

// time belongs to the plan, not the scene
const SCENES = defineScenes({
  hook:  { seconds: 2.5 },
  proof: { seconds: 4, transition: { type: "fade" } },
});

How a render works

React in, MP4 out

The CLI builds the project with Vite, opens it in headless Chromium, sets the frame, takes a JPEG, and streams the frames into ffmpeg. Sounds are React elements too; they register cues that an offline synth turns into a stereo mix, which is muxed in at the end.

src/index.tsxregisterRoot, a Composition, a scene plan, a Score
vite buildthe same bundle the studio opens in a browser
Playwrightper frame: setFrame(n), screenshot as JPEG q96
ffmpeglibx264 video, cues rendered offline and mixed to one track
out/spot.mp4h264 and aac, byte identical on every run

clapper preview

The studio: live thumbnails, viewport overlays, a timeline with lanes per audio kind, an inspector with cue preview, and a scratch panel that compiles TSX in the browser.

clapper still

First, middle and last frame of a scene, or every tenth frame, into a folder. The fastest way to see whether a beat lands before spending a render.

clapper review

A contact sheet, nine tile motion strips around every cut, a spectrogram, a waveform, a lint report and a brief with a reviewer prompt. Hand the folder to a person or a subagent.

Music as code

The score is a TypeScript file

A score has a tempo map, a meter, tracks and clips. Each track names a sampled instrument from a catalog of CC0 presets, rendered with a native sampler, mixed to stems and mastered to a broadcast loudness target. The studio shows the piano roll beside the film; click a note and the video seeks there.

// videos/cat-ballet/src/score.ts, abridged
export default defineScore({
  title: "A small waltz",
  tempo: [{ at: 0, bpm: 108 }, { at: 12, bpm: 96 }],
  meter: [3, 4], seed: 17, tail: 3,
  tracks: [
    track("piano", {
      instrument: "vsupright1", gain: 0.6, reverb: 0.4,
      clips: [{ notes: phrase("D5 F#5 A5 F#5 E5 D5", { duration: 0.5 }),
               length: 3, repeat: 4 }],
      humanize: { timing: 0.006, velocity: 3 },
    }),
    track("strings", {
      instrument: "violin-ens-pizz", pan: 0.25, gain: 0.3,
      clips: [{ at: 3, notes: chord(["D4", "F#4", "A4"]), length: 3, repeat: 3 }],
    }),
  ],
});
piano, bar 1 to 23/4 at 108 bpm · D major
78catalogued CC0 presets: strings, winds, brass, keys, percussion, and three rock patches
960pulses per quarter note; musical time is independent of video frames
−18LUFS master; stems, MIDI export and import included
0backing tracks; every note in every film is in the score file

Made with it so far

Seven films and a band, all playable

Every one of these was rendered from the source in the repo, music and all. Some exist to sell something. One exists because a cat in a tutu deserved a waltz. Press play; they have sound.

Pas de Chat · 43 s

A cat dances a waltz

Cream cat, rose tutu, plum curtains. An original 24 bar waltz in D major with a B minor middle and a final ritardando, on piano, pizzicato strings, flute and glockenspiel. The choreography is driven by musical time.

After Closing · 43 s

A fox, a raccoon and a bear play garage rock

An E minor jam at 112 bpm: count in, riff, solo, breakdown, return, one unified hit. Picking, fretting and drum gestures come from actual note onsets. No backing recording, no invisible overdub.

Clapper intro · 25 s

The mascot explains the whole thing muted

A mischievous clapperboard slams, bows, and walks a reader from a React expression to a groove to a scrubbed preview. Two rounds with an independent visual reviewer before the README got it.

tryintern.dev · 38 s

Chatter becomes a wall of live sites

Four people ask for four sites and each request materializes as a durable company URL, scored with bowed strings and tuned wooden mallets. Brand tokens and copy are lifted straight from the live landing page.

ArchDev · rage comic cut

A developer, twelve swarming agents, 2 AM

Boiling ink on a sketched laptop, a camera pull back into the story, and a score whose risers land exactly on the cut. One of five ArchDev cuts built on a monoline character rig.

Ledger · 30 s

Editorial type that counts

An agency spot for a product that does not exist, built to find out what the review loop catches. Three rounds: a blank tail, cuts landing on empty frames, a number roll clipping the type.

Orbit · 22 s

Springs, and things that move in space

Playful spring motion and spatial staging. The reviewer caught a block sliding through another with no elevation, and a banner held under 1.2 seconds.

Nimbus · 36 s

A terminal, a graph and a lot of grain

Camera push, tiled grain and live log panels. This is the spot that proved PNG capture, not the page, was the render bottleneck.

Reviewed like a studio

The scoreboard

A finished render is not a finished film. Each spot went to an independent reviewer briefed as a creative director, sometimes with an audio director and a feed strategist alongside, who read the contact sheet, rendered their own stills, and returned a verdict with frame referenced fixes. These are the real rounds from the showcase notes.

SpotRound 1Round 2Round 3What the reviewers caught
LedgerREVISE 6REVISE 6SHIP 9A blank tail because transition overlaps were never subtracted, hard cuts landing on empty frames, empty quadrants, a number roll clipping the type
OrbitREVISE 7SHIP 8A block sliding through another with no elevation, a banner held under 1.2 seconds, a mechanical waveform
NimbusREVISE 6REVISE 6SHIP 9A graph edge crossing the headline, a camera push cropping the log panel, a hot pad under quiet holds
ArchDevREVISE 6SHIP 8SHIP 9A hands to head beat hidden behind windows, generic swivel arms, no hit on a cut, the character missing from the end card
ArchDev v3CD 7 · X 7 · audio 7SHIP 8 · 9 · 8Three reviewers in parallel named the same three defects; the audio director found a duck swallowing a paper flip by measuring peaks around every cut

What the lint catches on its own

  • Blank or flat frames in the first eight frames after a cut
  • Copy colliding with copy, or sitting outside the safe area
  • Wall clock time, timers or unseeded randomness in a source file
  • Loudness around every cut, so a duck cannot hide a sound effect

What it will not claim

  • An agent cannot hear. Audio review is score, sample map and signal based, and the notes say so every time.
  • A verdict names the frames and time ranges it looked at, and what stayed unverified.
  • The intro's approved export is pinned by hash. A changed render needs a new hash and a new review.

Four rules

What keeps a render deterministic

  1. Everything on screen is a function of useFrame(). No wall clock, no timers, no Math.random. Use the seeded helpers.
  2. A composition that throws fails the render, and the error names the scene and the local frame.
  3. Every hard cut scene puts something on screen at local frame 0. An eyebrow, a rule, a caption box.
  4. Sounds register while their sequence is mounted, and the renderer visits every frame, so a cue is never missed.

From the friction log

Things that hurt, then got fixed

  • PNG was the bottleneck, not the page. Screenshotting a grainy 1080p frame as PNG cost about 250 ms; as JPEG q96 about 40 ms. A 40 frame probe went from 13 s to 2.5 s. JPEG is now the default intermediate.
  • Compositions were invisible to the renderer. A composition registered itself when rendered, and nothing rendered the root. The harness now mounts the root before listing.
  • Films ended on blank frames. Every transition subtracts its overlap from the total and nobody did the arithmetic. Now the scene plan owns time and the lint flags an empty tail.
  • Cuts landed on masked type. The DSL made "everything animates in" the default. Now every scene anchors something at frame 0.

Try it

Your agent, a whole production crew

Install the skill for your coding agent. It installs Clapper, prepares a managed runtime with Node, Chromium and ffmpeg, creates a project, and walks the visual and audio review with you. No separate setup.

npx skills add ArchAstro/clapper --skill clapper --global

Then, in a new session: "Use the Clapper skill to make a video with original music. Set up everything needed."