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

Installation
$ pnpm dlx shadcn@latest add @remocn/duotone-beat

Usage

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.

FramesMotion
0–12The silhouette rises into place, or the image settles from 112% scale
Each beatThe ink pair changes and the image punches in, decaying over about 10 frames
Each downbeatThe 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.

Props
PropTypeDefaultDescription
src
string—Portrait image URL, covering the frame. Without it a silhouette is drawn
palettes
[string, string][]4 pairs of #111111, #FFFFFF and #E5322DInk pairs [shadow, highlight], one per beat, cycled. Hex colors only
beatFrames
number15Frames per beat at 30 fps; ignored when bpm is set
bpm
number—Tempo in beats per minute; overrides beatFrames
beatsPerBar
number4Beats per bar; the first beat of each bar gets the full punch
punch
number0.07Scale added on the downbeat; other beats get a third of it
showIndicator
booleantrueShows the beat squares and BPM label in the lower left
halftone
number8Halftone dot pitch in px at 720p; 0 keeps smooth tones
speed
number1Playback multiplier, including the beat rate