/** * audio-robot.js — Sintetizador procedural de efeitos sonoros robóticos (Web Audio API) * Gera bips, assobios, trinados alegres e sons estilo WALL-E / Eilik diretamente no navegador. * Sem arquivos de áudio externos, sem dependências, 100% autônomo e instantâneo. */ (function(window) { 'use strict'; let audioCtx = null; let enabled = localStorage.getItem('carinha_sfx') !== 'false'; let volume = parseFloat(localStorage.getItem('carinha_volume') || '0.7'); function getContext() { if (!audioCtx) { const AudioContext = window.AudioContext || window.webkitAudioContext; if (AudioContext) { audioCtx = new AudioContext(); } } if (audioCtx && audioCtx.state === 'suspended') { audioCtx.resume(); } return audioCtx; } const RobotAudio = { get enabled() { return enabled; }, set enabled(val) { enabled = Boolean(val); localStorage.setItem('carinha_sfx', enabled ? 'true' : 'false'); window.dispatchEvent(new CustomEvent('carinha:audio-toggle', { detail: { enabled } })); }, get volume() { return volume; }, set volume(val) { volume = Math.max(0, Math.min(1, parseFloat(val) || 0.7)); localStorage.setItem('carinha_volume', volume.toString()); }, toggle() { this.enabled = !this.enabled; if (this.enabled) this.bleep(); return this.enabled; }, // Bip simples de confirmação / clique click() { if (!enabled) return; const ctx = getContext(); if (!ctx) return; const osc = ctx.createOscillator(); const gain = ctx.createGain(); const now = ctx.currentTime; osc.type = 'sine'; osc.frequency.setValueAtTime(800, now); osc.frequency.exponentialRampToValueAtTime(1400, now + 0.04); gain.gain.setValueAtTime(volume * 0.15, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.05); osc.connect(gain); gain.connect(ctx.destination); osc.start(now); osc.stop(now + 0.05); }, // Bip robótico positivo bleep() { if (!enabled) return; const ctx = getContext(); if (!ctx) return; const osc = ctx.createOscillator(); const gain = ctx.createGain(); const now = ctx.currentTime; osc.type = 'sine'; osc.frequency.setValueAtTime(659.25, now); // E5 osc.frequency.setValueAtTime(880, now + 0.06); // A5 gain.gain.setValueAtTime(volume * 0.25, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.16); osc.connect(gain); gain.connect(ctx.destination); osc.start(now); osc.stop(now + 0.16); }, // Trinado alegre (estilo WALL-E / Eilik feliz) happy() { if (!enabled) return; const ctx = getContext(); if (!ctx) return; const notes = [523.25, 659.25, 783.99, 1046.50]; // C5, E5, G5, C6 const now = ctx.currentTime; notes.forEach((freq, idx) => { const osc = ctx.createOscillator(); const gain = ctx.createGain(); const start = now + idx * 0.07; osc.type = 'triangle'; osc.frequency.setValueAtTime(freq, start); osc.frequency.exponentialRampToValueAtTime(freq * 1.08, start + 0.06); gain.gain.setValueAtTime(0.001, start); gain.gain.linearRampToValueAtTime(volume * 0.3, start + 0.02); gain.gain.exponentialRampToValueAtTime(0.001, start + 0.1); osc.connect(gain); gain.connect(ctx.destination); osc.start(start); osc.stop(start + 0.1); }); }, // Curiosidade / interrogação curious() { if (!enabled) return; const ctx = getContext(); if (!ctx) return; const osc = ctx.createOscillator(); const gain = ctx.createGain(); const now = ctx.currentTime; osc.type = 'sine'; osc.frequency.setValueAtTime(440, now); osc.frequency.exponentialRampToValueAtTime(950, now + 0.22); gain.gain.setValueAtTime(volume * 0.22, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.25); osc.connect(gain); gain.connect(ctx.destination); osc.start(now); osc.stop(now + 0.25); }, // Surpreso / Alerta surprise() { if (!enabled) return; const ctx = getContext(); if (!ctx) return; const osc = ctx.createOscillator(); const gain = ctx.createGain(); const now = ctx.currentTime; osc.type = 'square'; osc.frequency.setValueAtTime(800, now); osc.frequency.linearRampToValueAtTime(1400, now + 0.08); osc.frequency.linearRampToValueAtTime(1800, now + 0.15); gain.gain.setValueAtTime(volume * 0.2, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.18); osc.connect(gain); gain.connect(ctx.destination); osc.start(now); osc.stop(now + 0.18); }, // Triste / Melancólico sad() { if (!enabled) return; const ctx = getContext(); if (!ctx) return; const osc = ctx.createOscillator(); const gain = ctx.createGain(); const now = ctx.currentTime; osc.type = 'sine'; osc.frequency.setValueAtTime(600, now); osc.frequency.linearRampToValueAtTime(320, now + 0.35); gain.gain.setValueAtTime(volume * 0.2, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.4); osc.connect(gain); gain.connect(ctx.destination); osc.start(now); osc.stop(now + 0.4); }, // Acordando (Power on) wake() { if (!enabled) return; const ctx = getContext(); if (!ctx) return; const freqs = [350, 440, 587.33, 880]; const now = ctx.currentTime; freqs.forEach((f, i) => { const osc = ctx.createOscillator(); const gain = ctx.createGain(); const t = now + i * 0.06; osc.type = 'triangle'; osc.frequency.setValueAtTime(f, t); gain.gain.setValueAtTime(volume * 0.25, t); gain.gain.exponentialRampToValueAtTime(0.001, t + 0.12); osc.connect(gain); gain.connect(ctx.destination); osc.start(t); osc.stop(t + 0.12); }); }, // Dormindo (Power down) sleep() { if (!enabled) return; const ctx = getContext(); if (!ctx) return; const freqs = [700, 520, 380, 220]; const now = ctx.currentTime; freqs.forEach((f, i) => { const osc = ctx.createOscillator(); const gain = ctx.createGain(); const t = now + i * 0.08; osc.type = 'sine'; osc.frequency.setValueAtTime(f, t); gain.gain.setValueAtTime(volume * 0.2, t); gain.gain.exponentialRampToValueAtTime(0.001, t + 0.14); osc.connect(gain); gain.connect(ctx.destination); osc.start(t); osc.stop(t + 0.14); }); }, // Tocar som de acordo com o gesto/emoção tocarGesto(gesto) { if (!enabled) return; switch ((gesto || '').toLowerCase()) { case 'feliz': case 'alegre': case 'coracao': this.happy(); break; case 'surpreso': this.surprise(); break; case 'triste': this.sad(); break; case 'dormir': this.sleep(); break; case 'acordar': this.wake(); break; case 'olhar_esq': case 'olhar_dir': case 'olhar_baixo': this.curious(); break; case 'piscar': case 'piscar2': case 'piscadinha': this.click(); break; default: this.bleep(); } } }; window.RobotAudio = RobotAudio; })(window);