/* * melissaToques — geração de toques de término via Web Audio API * -------------------------------------------------------------- * Não usa arquivos de áudio externos. Tudo gerado em runtime com * osciladores. Mantém self-hosted, leve e sem build de assets. * * Uso: * import { TOQUES, playToque } from './melissaToques'; * playToque('sino'); */ export const TOQUES = [ { id: 'sino', label: 'Sino' }, { id: 'acorde', label: 'Acorde' }, { id: 'tic-tac', label: 'Tic-tac' }, { id: 'suave', label: 'Suave' }, { id: 'nenhum', label: 'Nenhum (silencioso)' } ]; function getCtx() { const Ctx = window.AudioContext || window.webkitAudioContext; return Ctx ? new Ctx() : null; } // Sino: clássico ding com decaimento longo function playSino(ctx) { const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.type = 'sine'; osc.frequency.value = 880; // A5 gain.gain.setValueAtTime(0.0001, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.25, ctx.currentTime + 0.01); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 1.6); osc.start(); osc.stop(ctx.currentTime + 1.7); setTimeout(() => ctx.close(), 1900); } // Acorde: C maior arpejado (C5 E5 G5) function playAcorde(ctx) { const freqs = [523.25, 659.25, 783.99]; freqs.forEach((freq, i) => { const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.type = 'sine'; osc.frequency.value = freq; const start = ctx.currentTime + i * 0.07; gain.gain.setValueAtTime(0.0001, start); gain.gain.exponentialRampToValueAtTime(0.18, start + 0.01); gain.gain.exponentialRampToValueAtTime(0.001, start + 1.5); osc.start(start); osc.stop(start + 1.6); }); setTimeout(() => ctx.close(), 2100); } // Tic-tac: dois cliques curtos e secos function playTicTac(ctx) { [0, 0.18].forEach((delay) => { const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.type = 'square'; osc.frequency.value = 1500; const start = ctx.currentTime + delay; gain.gain.setValueAtTime(0.0001, start); gain.gain.exponentialRampToValueAtTime(0.12, start + 0.005); gain.gain.exponentialRampToValueAtTime(0.001, start + 0.06); osc.start(start); osc.stop(start + 0.07); }); setTimeout(() => ctx.close(), 500); } // Suave: fade-in/fade-out lento, quase respiração function playSuave(ctx) { const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.type = 'sine'; osc.frequency.value = 660; // E5 gain.gain.setValueAtTime(0.0001, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.18, ctx.currentTime + 0.5); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 2.5); osc.start(); osc.stop(ctx.currentTime + 2.6); setTimeout(() => ctx.close(), 2800); } const PLAYERS = { sino: playSino, acorde: playAcorde, 'tic-tac': playTicTac, suave: playSuave, nenhum: () => {} }; export function playToque(id) { if (id === 'nenhum') return; const fn = PLAYERS[id] || PLAYERS.sino; try { const ctx = getCtx(); if (!ctx) return; // Web Audio em alguns browsers começa suspended até primeira interação if (ctx.state === 'suspended') ctx.resume?.(); fn(ctx); } catch { // Falha silenciosa: não há nada útil a fazer aqui } }