export interface SnapPoint { ms: number; label: string } /** Keep magnetic selection consistent with zoom without attracting distant audio. */ export function snapTimelineTime(ms: number, points: SnapPoint[], duration: number, width: number, bypass = false) { const bounded = Math.round(Math.max(0, Math.min(duration, ms))); const radius = Math.min(500, duration / Math.max(1, width) * 8); let point: SnapPoint | null = null; let distance = radius; if (!bypass) { for (const candidate of points) { if (candidate.ms < 0 || candidate.ms > duration) continue; const delta = Math.abs(candidate.ms - bounded); if (delta <= distance && (!point || delta < distance)) { point = candidate; distance = delta; } } } return { ms: point?.ms ?? bounded, point }; } /** A resized segment must stay in the recording and retain a positive duration. */ export function resizeTranscriptRange(start: number, end: number, edge: "start" | "end", ms: number, duration: number) { const value = Math.round(ms); return edge === "start" ? { start_ms: Math.max(0, Math.min(end - 1, value)), end_ms: end } : { start_ms: start, end_ms: Math.max(start + 1, Math.min(duration, value)) }; }