(function() { 'use strict'; var audioContext = null; var audioUnlocked = false; var bgMusicActive = true; var sfxEnabled = true; var bgMusicSource = null; var bgMusicGainNode = null; function getAudioContext() { if (!audioContext) { audioContext = new (window.AudioContext || window.webkitAudioContext)(); } return audioContext; } function unlockAudio() { if (audioUnlocked) return; var ctx = getAudioContext(); if (!ctx) return; if (ctx.state === 'suspended') { ctx.resume().then(function() { audioUnlocked = true; }).catch(function() {}); } else { audioUnlocked = true; } } function canPlayAudio() { return audioUnlocked; } function playSlapSound() { if (!audioUnlocked) return; var ctx = getAudioContext(); if (!ctx) return; var buffer = ctx.createBuffer(1, ctx.sampleRate * 0.05, ctx.sampleRate); var data = buffer.getChannelData(0); for (var i = 0; i < data.length; i++) { data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / data.length, 3); } var source = ctx.createBufferSource(); source.buffer = buffer; var gain = ctx.createGain(); gain.gain.setValueAtTime(0.4, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.05); source.connect(gain); gain.connect(ctx.destination); source.start(); } function playAdminBipSound() { var ctx = getAudioContext(); if (!ctx) return; var osc = ctx.createOscillator(); osc.type = 'sine'; osc.frequency.setValueAtTime(880, ctx.currentTime); var gain = ctx.createGain(); gain.gain.setValueAtTime(0.3, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.1); osc.connect(gain); gain.connect(ctx.destination); osc.start(); osc.stop(ctx.currentTime + 0.1); } function playNukeAlarmSound() { if (!audioUnlocked) return; var ctx = getAudioContext(); if (!ctx) return; var osc = ctx.createOscillator(); osc.type = 'sawtooth'; osc.frequency.setValueAtTime(400, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(80, ctx.currentTime + 1.5); var gain = ctx.createGain(); gain.gain.setValueAtTime(0.5, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 1.5); osc.connect(gain); gain.connect(ctx.destination); osc.start(); osc.stop(ctx.currentTime + 1.5); } function loadSciFiBackgroundLoop() { var ctx = getAudioContext(); if (!ctx) return null; var audioUrl = '/custom_assets/sound/menu_ambient.mp3'; // Use HTML5 Audio element for streaming with volume control var audioEl = new Audio(); audioEl.src = audioUrl; audioEl.loop = true; audioEl.preload = 'auto'; // Create a MediaElementAudioSourceNode for gain control bgMusicSource = ctx.createMediaElementSource(audioEl); bgMusicGainNode = ctx.createGain(); var savedVolume = localStorage.getItem('dw_volume'); var masterPct = savedVolume ? parseInt(savedVolume, 10) / 100 : 0.55; bgMusicGainNode.gain.value = masterPct; bgMusicSource.connect(bgMusicGainNode); bgMusicGainNode.connect(ctx.destination); // Handle load errors - try fallback procedural loop audioEl.onerror = function(e) { console.warn('[AudioEngine] menu_ambient.mp3 failed to load:', e); // Fallback: generate procedural ambient loop generateProceduralAmbient(); }; audioEl.onloadedmetadata = function() { if (bgMusicActive) { audioEl.play().catch(function(err) { console.warn('[AudioEngine] Autoplay blocked:', err); }); } }; // Store reference for volume updates window._bgAudioElement = audioEl; // Start playing if music is active if (bgMusicActive) { audioEl.play().catch(function(err) { console.warn('[AudioEngine] Autoplay blocked:', err); }); } } function generateProceduralAmbient() { var ctx = getAudioContext(); if (!ctx) return; if (bgMusicSource) return; var bufferSize = ctx.sampleRate * 2; // 2 second loop var buffer = ctx.createBuffer(2, bufferSize, ctx.sampleRate); var t = 0; for (var ch = 0; ch < 2; ch++) { var data = buffer.getChannelData(ch); for (var i = 0; i < bufferSize; i++) { t = i / ctx.sampleRate; // Low ambient drone with slow detuning var freq = 80 + 20 * Math.sin(t * 0.05); var noise = Math.random() * 2 - 1; var envelope = Math.exp(-t * 0.5); data[i] = (Math.sin(2 * Math.PI * freq * t) * 0.1 + noise * 0.02) * envelope; } } bgMusicSource = ctx.createBufferSource(); bgMusicSource.buffer = buffer; bgMusicSource.loop = true; bgMusicGainNode = ctx.createGain(); var savedVolume = localStorage.getItem('dw_volume'); var masterPct = savedVolume ? parseInt(savedVolume, 10) / 100 : 0.55; bgMusicGainNode.gain.value = masterPct; bgMusicSource.connect(bgMusicGainNode); bgMusicGainNode.connect(ctx.destination); bgMusicSource.start(0); } function startBackgroundMusic() { if (!bgMusicActive) return; if (!audioUnlocked) return; var ctx = getAudioContext(); if (!ctx || bgMusicSource) return; loadSciFiBackgroundLoop(); } function stopBackgroundMusic() { if (bgMusicSource) { try { bgMusicSource.stop(); } catch(e) {} bgMusicSource = null; } if (bgMusicGainNode) { bgMusicGainNode.disconnect(); bgMusicGainNode = null; } } function applyMasterVolume() { // Scale background music by master volume * music volume slider if (bgMusicGainNode) { var savedVolume = localStorage.getItem('dw_volume'); var masterPct = savedVolume ? parseInt(savedVolume, 10) / 100 : 0.8; var savedMusicVol = localStorage.getItem('dw_music_vol'); var musicPct = savedMusicVol ? parseInt(savedMusicVol, 10) / 100 : 0.20; var targetVol = masterPct * musicPct; bgMusicGainNode.gain.setTargetAtTime(Math.max(0, targetVol), getAudioContext().currentTime, 0.02); } } function setMusicVolume(volPct) { localStorage.setItem('dw_music_vol', String(volPct)); localStorage.setItem('dw_volume', String(volPct)); if (bgMusicGainNode && bgMusicGainNode.gain) { var ctx = getAudioContext(); if (ctx) { var targetVol = volPct / 100; bgMusicGainNode.gain.setTargetAtTime(targetVol, ctx.currentTime, 0.02); } } // Also update the master volume slider UI if present var masterSlider = document.getElementById('volume-slider'); if (masterSlider) masterSlider.value = volPct; var masterLabel = document.getElementById('volume-value-label'); if (masterLabel) masterLabel.innerText = volPct + '%'; } function toggleBackgroundMusic(enabled) { bgMusicActive = enabled; var label = document.getElementById('music-label'); if (label) label.textContent = enabled ? 'ON' : 'OFF'; localStorage.setItem('dw_music', enabled ? '1' : '0'); if (enabled) { startBackgroundMusic(); } else { stopBackgroundMusic(); } } function toggleSfx(enabled) { sfxEnabled = enabled; var label = document.getElementById('sfx-label'); if (label) label.textContent = enabled ? 'ON' : 'OFF'; localStorage.setItem('dw_sfx', enabled ? '1' : '0'); } function playHoverSfx() { if (!sfxEnabled || !audioUnlocked) return; var ctx = getAudioContext(); if (!ctx) return; var osc = ctx.createOscillator(); osc.type = 'sine'; osc.frequency.setValueAtTime(660, ctx.currentTime); var gain = ctx.createGain(); gain.gain.setValueAtTime(0.1, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.15); osc.connect(gain); gain.connect(ctx.destination); osc.start(); osc.stop(ctx.currentTime + 0.15); } function playClickSfx() { if (!sfxEnabled || !audioUnlocked) return; var ctx = getAudioContext(); if (!ctx) return; var osc = ctx.createOscillator(); osc.type = 'sawtooth'; osc.frequency.setValueAtTime(220, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(55, ctx.currentTime + 0.2); var gain = ctx.createGain(); gain.gain.setValueAtTime(0.15, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.2); osc.connect(gain); gain.connect(ctx.destination); osc.start(); osc.stop(ctx.currentTime + 0.2); } function playExplosionSound() { if (!sfxEnabled || !audioUnlocked) return; var ctx = getAudioContext(); if (!ctx) return; var osc = ctx.createOscillator(); osc.type = 'sawtooth'; osc.frequency.setValueAtTime(150, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(30, ctx.currentTime + 0.5); var gain = ctx.createGain(); gain.gain.setValueAtTime(0.4, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.5); osc.connect(gain); gain.connect(ctx.destination); osc.start(); osc.stop(ctx.currentTime + 0.5); } function loadAudioPreferences() { var savedMusic = localStorage.getItem('dw_music'); bgMusicActive = savedMusic !== '0'; var musicToggle = document.getElementById('music-toggle'); if (musicToggle) musicToggle.checked = bgMusicActive; var musicLabel = document.getElementById('music-label'); if (musicLabel) musicLabel.textContent = bgMusicActive ? 'ON' : 'OFF'; var savedSfx = localStorage.getItem('dw_sfx'); sfxEnabled = savedSfx !== '0'; var sfxToggle = document.getElementById('sfx-toggle'); if (sfxToggle) sfxToggle.checked = sfxEnabled; var sfxLabel = document.getElementById('sfx-label'); if (sfxLabel) sfxLabel.textContent = sfxEnabled ? 'ON' : 'OFF'; // Restore music volume slider var savedMusicVol = localStorage.getItem('dw_music_vol'); var musicVol = savedMusicVol ? parseInt(savedMusicVol, 10) : 55; var musicVolSlider = document.getElementById('music-volume'); if (musicVolSlider) musicVolSlider.value = musicVol; var musicVolLabel = document.getElementById('music-volume-value-label'); if (musicVolLabel) musicVolLabel.textContent = musicVol + '%'; // Also set master volume slider to 55% by default var savedMasterVol = localStorage.getItem('dw_volume'); var masterVol = savedMasterVol ? parseInt(savedMasterVol, 10) : 55; var masterSlider = document.getElementById('volume-slider'); if (masterSlider) masterSlider.value = masterVol; var masterLabel = document.getElementById('volume-value-label'); if (masterLabel) masterLabel.textContent = masterVol + '%'; // Apply master volume to background loop if already playing applyMasterVolume(); } window.AudioEngine = { getAudioContext: getAudioContext, unlockAudio: unlockAudio, canPlayAudio: canPlayAudio, playSlapSound: playSlapSound, playAdminBipSound: playAdminBipSound, playNukeAlarmSound: playNukeAlarmSound, startBackgroundMusic: startBackgroundMusic, stopBackgroundMusic: stopBackgroundMusic, toggleBackgroundMusic: toggleBackgroundMusic, toggleSfx: toggleSfx, playHoverSfx: playHoverSfx, playClickSfx: playClickSfx, playExplosionSound: playExplosionSound, loadAudioPreferences: loadAudioPreferences, applyMasterVolume: applyMasterVolume, setMusicVolume: setMusicVolume, isMusicActive: function() { return bgMusicActive; }, isSfxEnabled: function() { return sfxEnabled; } }; })();