Duotone Beat
A two-ink halftone portrait that swaps its ink pair on every beat, punches in on the downbeat and shows a bar indicator
$ pnpm dlx shadcn@latest add @remocn/duotone-beatUsage
DuotoneBeat is a full-frame scene. It takes a portrait, drops it to
grayscale, optionally screens it into halftone dots, and maps the tones onto
two inks: shadows to the first ink, highlights to the second. On every beat it
switches to the next ink pair in palettes and punches the image in with a
small scale kick that decays over a few frames. The first beat of each bar
gets the full punch, the others a third of it. A row of squares in the lower
left marks the beat within the bar, next to a BPM label.
import { staticFile } from "remotion";
import { DuotoneBeat } from "@/components/remocn/duotone-beat";
export const Speaker = () => (
<DuotoneBeat
src={staticFile("speaker.jpg")}
bpm={124}
palettes={[
["#0B0B45", "#FFD23F"],
["#0B0B45", "#FF5D8F"],
["#FF5D8F", "#FFFFFF"],
]}
/>
);Without src it draws a head-and-shoulders silhouette that rises into frame.
With src the image covers the frame and settles from 112% to 100% scale over
the first 12 frames. Palette colors must be hex (#rgb or #rrggbb), since
they are parsed into a lookup table for an SVG filter.
Timing
The beat length is 1800 / bpm frames at 30 fps when bpm is set, and
beatFrames otherwise. The default of 15 frames is 120 BPM. Beats are counted
in real time, so bpm stays in sync with the music at any composition frame
rate.
| Frames | Motion |
|---|---|
| 0–12 | The silhouette rises into place, or the image settles from 112% scale |
| Each beat | The ink pair changes and the image punches in, decaying over about 10 frames |
| Each downbeat | The punch is three times stronger |
The look has no end: it keeps cycling for as long as the sequence runs. Start
the sequence on a downbeat of your track so the punches land on the music.
speed also speeds up the beat, while the BPM label keeps showing the nominal
tempo, so prefer bpm to sync with a track.
Notes
halftone is the dot pitch in px at 720p. Larger values give coarser, more
graphic dots, and 0 keeps smooth duotone gradients. The whole frame,
including the white ground, passes through the duotone filter, so the ground
takes the highlight ink.
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | Portrait image URL, covering the frame. Without it a silhouette is drawn |
palettes | [string, string][] | 4 pairs of #111111, #FFFFFF and #E5322D | Ink pairs [shadow, highlight], one per beat, cycled. Hex colors only |
beatFrames | number | 15 | Frames per beat at 30 fps; ignored when bpm is set |
bpm | number | — | Tempo in beats per minute; overrides beatFrames |
beatsPerBar | number | 4 | Beats per bar; the first beat of each bar gets the full punch |
punch | number | 0.07 | Scale added on the downbeat; other beats get a third of it |
showIndicator | boolean | true | Shows the beat squares and BPM label in the lower left |
halftone | number | 8 | Halftone dot pitch in px at 720p; 0 keeps smooth tones |
speed | number | 1 | Playback multiplier, including the beat rate |