Core utility

Lipsync

An amplitude envelope, four pure functions wide.

Mouth weight
Raw RMS
0.000
Smoothed
0.000
Target
0.000
Weight
0.000

Source: synthetic. Nothing is recorded.

The chain, in the order it runs

  1. rms(buffer) — the frame's amplitude. Feed it time-domain samples (getFloatTimeDomainData); frequency data reads far too hot.
  2. smoothRms(previous, raw) — speech is spiky, and a mouth driven by raw RMS chatters. SMOOTH_FACTOR (0.5) is how much of the previous frame survives.
  3. mouthTarget(smoothed) — divides by RMS_CEILING (0.06), the amplitude treated as fully open, and clamps. That constant is what makes a quiet speaker and a loud one reach the same maximum.
  4. approach(current, target) — moves a fraction (INTERP, 0.35) of the remaining distance each frame, so the mouth has weight instead of snapping.

Drive it yourself

import { rms, smoothRms, mouthTarget, approach }
  from '@whykusanagi/corrupted-theme/lipsync';

let smoothed = 0, weight = 0;
function frame(buffer) {           // Float32Array from an AnalyserNode
  smoothed = smoothRms(smoothed, rms(buffer));
  weight   = approach(weight, mouthTarget(smoothed));
  // weight is 0..1 — drive a mouth, a meter, a corruption intensity
}