// Pure fact primitives — no model, no state. (her-pattern: code owns the facts.) // MediaPipe Hands 21-landmark indices we care about. export const H = { WRIST: 0, THUMB_TIP: 4, INDEX_MCP: 5, INDEX_TIP: 8, MIDDLE_MCP: 9, MIDDLE_TIP: 12, RING_MCP: 13, RING_TIP: 16, PINKY_MCP: 17, PINKY_TIP: 20, }; export const dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y); export const mid = (a, b) => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }); // Exponential moving average — kills landmark jitter before it hits the audio. export const ema = (prev, next, a = 0.5) => (prev == null ? next : prev * (1 - a) + next * a); export const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v)); // Linear normalize v in [lo,hi] -> [0,1], clamped. export const norm = (v, lo, hi) => clamp((v - lo) / (hi - lo), 0, 1); export const lerp = (a, b, t) => a + (b - a) * t; // Exponential map t in [0,1] -> [lo,hi] (for perceptually-correct cutoff/freq). export const expmap = (t, lo, hi) => lo * Math.pow(hi / lo, clamp(t, 0, 1));