// Shared utilities for mq_editor // Evaluate cubic bezier curve at time t (robust: handles dt<=0) function evalBezier(curve, t) { const dt = curve.t3 - curve.t0; if (dt <= 0) return curve.v0; let u = (t - curve.t0) / dt; u = Math.max(0, Math.min(1, u)); const u1 = 1.0 - u; return u1*u1*u1 * curve.v0 + 3*u1*u1*u * curve.v1 + 3*u1*u*u * curve.v2 + u*u*u * curve.v3; } // Get canvas-relative {x, y} from a mouse event function getCanvasCoords(e, canvas) { const rect = canvas.getBoundingClientRect(); return { x: e.clientX - rect.left, y: e.clientY - rect.top }; } // Build upper/lower band point arrays for a frequency curve. // factorAbove/factorBelow are fractional offsets (e.g. 0.02 = ±2%). // Returns { upper: [[x,y],...], lower: [[x,y],...] } function buildBandPoints(viewer, curve, factorAbove, factorBelow) { const STEPS = 60; const upper = [], lower = []; for (let i = 0; i <= STEPS; ++i) { const t = curve.t0 + (curve.t3 - curve.t0) * i / STEPS; if (t < viewer.t_view_min - 0.01 || t > viewer.t_view_max + 0.01) continue; const f = evalBezier(curve, t); upper.push([viewer.timeToX(t), viewer.freqToY(f * (1 + factorAbove))]); lower.push([viewer.timeToX(t), viewer.freqToY(f * (1 - factorBelow))]); } return { upper, lower }; }