// Web Audio API Synthesizer for Children's Storybook class SoundEffectsManager { private ctx: AudioContext | null = null; public enabled: boolean = true; private initCtx() { if (!this.ctx && typeof window !== 'undefined') { const AudioCtx = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext; if (AudioCtx) { this.ctx = new AudioCtx(); } } if (this.ctx && this.ctx.state === 'suspended') { this.ctx.resume(); } } // Soft cheerful pop playPop() { if (!this.enabled) return; this.initCtx(); if (!this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'sine'; const now = this.ctx.currentTime; osc.frequency.setValueAtTime(400, now); osc.frequency.exponentialRampToValueAtTime(800, now + 0.08); gain.gain.setValueAtTime(0.18, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.09); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now); osc.stop(now + 0.1); } // Pentatonic magical chime playChime() { if (!this.enabled) return; this.initCtx(); if (!this.ctx) return; const notes = [523.25, 659.25, 783.99, 1046.5]; // C5, E5, G5, C6 notes.forEach((freq, idx) => { if (!this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = 'triangle'; const now = this.ctx.currentTime + idx * 0.07; osc.frequency.setValueAtTime(freq, now); gain.gain.setValueAtTime(0.12, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.35); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now); osc.stop(now + 0.4); }); } // Soft gentle raindrop plop playRain() { if (!this.enabled) return; this.initCtx(); if (!this.ctx) return; const drops = [700, 1100, 850]; drops.forEach((freq, i) => { if (!this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); const now = this.ctx.currentTime + i * 0.08; osc.type = 'sine'; osc.frequency.setValueAtTime(freq, now); osc.frequency.exponentialRampToValueAtTime(freq * 0.5, now + 0.07); gain.gain.setValueAtTime(0.14, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.12); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now); osc.stop(now + 0.15); }); } // Harp strum playHarp() { if (!this.enabled) return; this.initCtx(); if (!this.ctx) return; const notes = [392.00, 523.25, 659.25, 783.99, 1046.5]; // G4, C5, E5, G5, C6 notes.forEach((freq, i) => { if (!this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); const now = this.ctx.currentTime + i * 0.05; osc.type = 'sine'; osc.frequency.setValueAtTime(freq, now); gain.gain.setValueAtTime(0.1, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.6); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now); osc.stop(now + 0.65); }); } // Flutter / whoosh playFlutter() { if (!this.enabled) return; this.initCtx(); if (!this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); const now = this.ctx.currentTime; osc.type = 'sine'; osc.frequency.setValueAtTime(220, now); osc.frequency.linearRampToValueAtTime(440, now + 0.1); osc.frequency.linearRampToValueAtTime(260, now + 0.2); gain.gain.setValueAtTime(0.08, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.22); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now); osc.stop(now + 0.25); } // Playful giggle / chirp playGiggle() { if (!this.enabled) return; this.initCtx(); if (!this.ctx) return; const notes = [587.33, 739.99, 587.33, 880.00]; notes.forEach((freq, i) => { if (!this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); const now = this.ctx.currentTime + i * 0.06; osc.type = 'triangle'; osc.frequency.setValueAtTime(freq, now); gain.gain.setValueAtTime(0.09, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.1); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now); osc.stop(now + 0.12); }); } // Page turn sound playPageTurn() { if (!this.enabled) return; this.initCtx(); if (!this.ctx) return; const bufferSize = this.ctx.sampleRate * 0.15; const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate); const data = buffer.getChannelData(0); for (let i = 0; i < bufferSize; i++) { data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (bufferSize * 0.3)); } const noise = this.ctx.createBufferSource(); noise.buffer = buffer; const filter = this.ctx.createBiquadFilter(); filter.type = 'lowpass'; filter.frequency.value = 1200; const gain = this.ctx.createGain(); const now = this.ctx.currentTime; gain.gain.setValueAtTime(0.12, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.14); noise.connect(filter); filter.connect(gain); gain.connect(this.ctx.destination); noise.start(now); } // Star Celebration fanfare playStarCelebration() { if (!this.enabled) return; this.initCtx(); if (!this.ctx) return; const notes = [523.25, 659.25, 783.99, 1046.50, 1318.51]; notes.forEach((freq, i) => { if (!this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); const now = this.ctx.currentTime + i * 0.08; osc.type = 'sine'; osc.frequency.setValueAtTime(freq, now); gain.gain.setValueAtTime(0.15, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.45); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(now); osc.stop(now + 0.5); }); } playSound(type: 'pop' | 'chime' | 'rain' | 'harp' | 'flutter' | 'giggle') { switch (type) { case 'pop': this.playPop(); break; case 'chime': this.playChime(); break; case 'rain': this.playRain(); break; case 'harp': this.playHarp(); break; case 'flutter': this.playFlutter(); break; case 'giggle': this.playGiggle(); break; default: this.playPop(); } } } export const soundManager = new SoundEffectsManager();