/** * CAPT Memory Palace - Audio Engine * * Ambient sounds and audio feedback for cognitive visualization. * Uses Web Audio API for procedural sound generation. */ const AudioEngine = { context: null, masterGain: null, isEnabled: false, sounds: {}, /** * Initialize audio context */ async init() { try { this.context = new (window.AudioContext || window.webkitAudioContext)(); this.masterGain = this.context.createGain(); this.masterGain.gain.value = 0.3; this.masterGain.connect(this.context.destination); this.isEnabled = true; // Start ambient drone this.startAmbient(); return true; } catch (e) { console.warn('Audio not available:', e); return false; } }, /** * Start ambient background drone */ startAmbient() { if (!this.isEnabled) return; // Low frequency ambient const osc1 = this.context.createOscillator(); const gain1 = this.context.createGain(); osc1.type = 'sine'; osc1.frequency.value = 60; gain1.gain.value = 0.05; osc1.connect(gain1); gain1.connect(this.masterGain); osc1.start(); // Subtle modulation const lfo = this.context.createOscillator(); const lfoGain = this.context.createGain(); lfo.frequency.value = 0.1; lfoGain.gain.value = 10; lfo.connect(lfoGain); lfoGain.connect(osc1.frequency); lfo.start(); }, /** * Play activation sound for a module */ playModuleActivation(moduleId, intensity) { if (!this.isEnabled || intensity < 0.3) return; const osc = this.context.createOscillator(); const gain = this.context.createGain(); // Frequency based on module type const freqMap = { 'PULSE': 440, 'NEDA': 523, 'HMC': 330, 'QIPC': 659, 'ECHO': 392, 'META': 587, 'IMMU': 698, 'NDS': 784 /** * Load voiceover introduction audio file */ async loadVoiceover() { if (!this.isEnabled) await this.init(); try { const response = await fetch('./assets/audio/intro.ogg'); const arrayBuffer = await response.arrayBuffer(); this.voiceoverBuffer = await this.context.decodeAudioData(arrayBuffer); return true; } catch (e) { console.warn('Failed to load voiceover:', e); return false; } }, /** * Play the voiceover introduction */ playVoiceover() { if (!this.isEnabled || !this.voiceoverBuffer) { console.warn('Audio not ready or voiceover not loaded'); return false; } const source = this.context.createBufferSource(); source.buffer = this.voiceoverBuffer; source.connect(this.masterGain); source.start(0); return true; } }; osc.type = 'sine'; osc.frequency.value = freqMap[moduleId] || 440; // Quick attack, slow decay const now = this.context.currentTime; gain.gain.setValueAtTime(0, now); gain.gain.linearRampToValueAtTime(intensity * 0.15, now + 0.05); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.5); osc.connect(gain); gain.connect(this.masterGain); osc.start(now); osc.stop(now + 0.5); }, /** * Play consensus reached sound */ playConsensusSound() { if (!this.isEnabled) return; const now = this.context.currentTime; // Ascending chime [523, 659, 784].forEach((freq, i) => { const osc = this.context.createOscillator(); const gain = this.context.createGain(); osc.type = 'sine'; osc.frequency.value = freq; gain.gain.setValueAtTime(0, now + i * 0.1); gain.gain.linearRampToValueAtTime(0.1, now + i * 0.1 + 0.05); gain.gain.exponentialRampToValueAtTime(0.001, now + i * 0.1 + 0.4); osc.connect(gain); gain.connect(this.masterGain); osc.start(now + i * 0.1); osc.stop(now + i * 0.1 + 0.4); }); }, /** * Play phase transition sound */ playPhaseSound(phase) { if (!this.isEnabled) return; const now = this.context.currentTime; const osc = this.context.createOscillator(); const gain = this.context.createGain(); const phaseFreqs = { 'receiving': 220, 'analyzing': 330, 'reasoning': 440, 'competing': 392, 'consensus': 523, 'validating': 659, 'responding': 784 }; osc.type = 'triangle'; osc.frequency.value = phaseFreqs[phase] || 440; gain.gain.setValueAtTime(0.08, now); gain.gain.exponentialRampToValueAtTime(0.001, now + 0.3); osc.connect(gain); gain.connect(this.masterGain); osc.start(now); osc.stop(now + 0.3); }, /** * Enable/disable audio */ toggle() { if (!this.context) { this.init(); } else { this.isEnabled = !this.isEnabled; if (this.isEnabled && this.context.state === 'suspended') { this.context.resume(); } } return this.isEnabled; }, /** * Set volume */ setVolume(value) { if (this.masterGain) { this.masterGain.gain.value = value; } } }; // Export window.AudioEngine = AudioEngine;