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
- rms(buffer) — the frame's amplitude. Feed it time-domain samples
(
getFloatTimeDomainData); frequency data reads far too hot. - 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. - 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. - 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
}