Files
sternboard/web/src/sound.ts
T
Millaguie f11ea58c2c
CI/CD Pipeline / test (push) Successful in 26s
CI/CD Pipeline / build-and-push (push) Successful in 44s
Sternboard: free retro board, no signup (initial)
Servidor Fastify + Socket.IO + Drizzle/Postgres; front Vite+React (EN/ES, tema claro/oscuro).
Tableros por columnas con plantillas, notas privadas/públicas, agrupación, votos
configurables (multivoto), timer compartido con alarma, fases, action items,
export Markdown/JSON e import, autodestrucción, gestión de columnas.
Imagen única (front servido por el servidor) y CI que la construye y publica.
2026-06-16 10:27:33 +02:00

45 lines
1.5 KiB
TypeScript

// Alarma corta generada con Web Audio (sin ficheros). Dos pitidos rápidos.
let ctx: AudioContext | null = null;
export function playAlarm() {
try {
const Ctor = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;
ctx = ctx ?? new Ctor();
if (ctx.state === "suspended") void ctx.resume();
const now = ctx.currentTime;
for (let i = 0; i < 2; i++) {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = "square";
osc.frequency.value = 880;
const t = now + i * 0.18;
gain.gain.setValueAtTime(0.0001, t);
gain.gain.exponentialRampToValueAtTime(0.25, t + 0.01);
gain.gain.exponentialRampToValueAtTime(0.0001, t + 0.15);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(t);
osc.stop(t + 0.16);
}
} catch {
// sin audio disponible: ignoramos
}
}
// Parsea "mm", "mm:ss" o "m.5" a segundos. Devuelve null si no es válido.
export function parseTime(input: string): number | null {
const s = input.trim();
if (!s) return null;
if (s.includes(":")) {
const [m, sec] = s.split(":");
const mm = Number(m);
const ss = Number(sec);
if (!Number.isFinite(mm) || !Number.isFinite(ss) || ss < 0 || ss >= 60) return null;
const total = Math.round(mm * 60 + ss);
return total > 0 ? total : null;
}
const mins = Number(s);
if (!Number.isFinite(mins) || mins <= 0) return null;
return Math.round(mins * 60);
}