// audio.jsx — bips (Web Audio) + voz (SpeechSynthesis) para o treino const ZAudio = (() => { let ctx = null; function ensure() { if (!ctx) { try { ctx = new (window.AudioContext || window.webkitAudioContext)(); } catch (e) { ctx = null; } } if (ctx && ctx.state === 'suspended') ctx.resume(); return ctx; } // tom genérico function tone(freq, dur = 0.15, type = 'sine', gain = 0.22) { const c = ensure(); if (!c) return; const o = c.createOscillator(); const g = c.createGain(); o.type = type; o.frequency.value = freq; o.connect(g); g.connect(c.destination); const t = c.currentTime; g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(gain, t + 0.01); g.gain.exponentialRampToValueAtTime(0.0001, t + dur); o.start(t); o.stop(t + dur + 0.02); } // bips distintos const beepPrep = () => tone(620, 0.10, 'sine', 0.16); // preparação const beepExercise = () => tone(900, 0.12, 'sine', 0.20); // contagem do exercício const beepRest = () => tone(480, 0.12, 'triangle', 0.20); // contagem do descanso const beepGo = () => { tone(1180, 0.18, 'square', 0.20); }; // início do exercício function beepDone() { // fim do treino (acorde) tone(660, 0.22, 'sawtooth', 0.18); setTimeout(() => tone(880, 0.22, 'sawtooth', 0.18), 110); setTimeout(() => tone(1175, 0.30, 'sawtooth', 0.18), 220); } // ---- voz ---- let voices = []; function loadVoices() { try { voices = window.speechSynthesis ? window.speechSynthesis.getVoices() : []; } catch (e) { voices = []; } } if (window.speechSynthesis) { loadVoices(); try { window.speechSynthesis.onvoiceschanged = loadVoices; } catch (e) {} } // target: 'f' (feminina) ou 'm' (masculina) function pickVoice(target) { loadVoices(); const pt = voices.filter(v => /pt[-_]?br/i.test(v.lang)) ; const pool = pt.length ? pt : voices.filter(v => /^pt/i.test(v.lang)); const search = (pool.length ? pool : voices); const female = /(luciana|maria|fernanda|joana|catarina|in[eê]s|vit[oó]ria|female|mulher|fem)/i; const male = /(daniel|felipe|jo[aã]o|ricardo|ant[oô]nio|hugo|male|homem|masc)/i; const re = target === 'f' ? female : male; return search.find(v => re.test(v.name)) || search[0] || null; } // userSex: 'masc' | 'fem' | 'na' → homem ouve voz feminina, mulher ouve voz masculina function speak(text, userSex) { if (!window.speechSynthesis) return; const target = userSex === 'fem' ? 'm' : 'f'; try { const u = new SpeechSynthesisUtterance(text); const v = pickVoice(target); if (v) { u.voice = v; u.lang = v.lang; } else { u.lang = 'pt-BR'; } u.pitch = target === 'f' ? 1.3 : 0.75; // reforça o timbre mesmo sem voz dedicada u.rate = 1.06; u.volume = 1; window.speechSynthesis.cancel(); window.speechSynthesis.speak(u); } catch (e) {} } function stopVoice() { try { window.speechSynthesis && window.speechSynthesis.cancel(); } catch (e) {} } const NUM = ['', 'um', 'dois', 'três', 'quatro', 'cinco']; const numWord = (n) => NUM[n] || String(n); return { ensure, beepPrep, beepExercise, beepRest, beepGo, beepDone, speak, stopVoice, numWord }; })(); window.ZAudio = ZAudio;