airwaves / web /gestures.js
AndresCarreon's picture
AIRWAVES v0 — air-DJ (MediaPipe + Web Audio) + VoxCPM2 hype-man
860eb59 verified
Raw
History Blame Contribute Delete
1.88 kB
// Landmarks -> performance controls. Every control is normalized [0,1] and
// derived from in-plane geometry normalized by palm size, so it is invariant to
// how close the hand is to the lens. Four INDEPENDENT controls per hand chosen so
// they don't false-trigger each other:
// HEIGHT (wrist y) -> the hero filter sweep
// ROLL (wrist->middle_mcp tilt) -> turntable warp (tempo/pitch)
// PINCH (thumb<->index) -> reverb / delay space [ignores m/r/pinky]
// FIST (middle+ring+pinky curl) -> stutter buildup [ignores thumb/index]
import { H, dist, norm, clamp } from "./math.js";
const palmSize = (lm) => dist(lm[H.WRIST], lm[H.MIDDLE_MCP]) || 1e-6;
export function controls(lm) {
const ps = palmSize(lm);
// HEIGHT: 1 - wrist.y, mapped so a comfortable raise spans the full range.
const height = norm(1 - lm[H.WRIST].y, 0.18, 0.82);
// PINCH: thumb tip to index tip over palm. ~0.25 pinched … ~1.1 spread.
// Tight pinch => high "space" send.
const pinchRaw = dist(lm[H.THUMB_TIP], lm[H.INDEX_TIP]) / ps;
const pinch = 1 - norm(pinchRaw, 0.28, 0.95);
// FIST: how curled the middle/ring/pinky are (tip distance to wrist over palm).
// open hand ~2.0, tight fist ~1.05 -> fist amount near 1.
const curl = (dist(lm[H.MIDDLE_TIP], lm[H.WRIST]) +
dist(lm[H.RING_TIP], lm[H.WRIST]) +
dist(lm[H.PINKY_TIP], lm[H.WRIST])) / (3 * ps);
const fist = 1 - norm(curl, 1.15, 1.95);
// ROLL: tilt of the wrist->middle_mcp vector. 0=straight up; +right / -left.
const dx = lm[H.MIDDLE_MCP].x - lm[H.WRIST].x;
const dy = lm[H.MIDDLE_MCP].y - lm[H.WRIST].y;
const rollAngle = Math.atan2(dx, -dy); // radians
const roll = norm(rollAngle, -0.7, 0.7); // 0.5 = upright
return { height, pinch, fist, roll, cx: clamp(lm[H.WRIST].x, 0, 1) };
}