/* ============================================================ * pb_audio.js · 物理弹球 Web 复刻版 · WebAudio 合成音效 * 无外部资源:发射/击中/碎裂/道具/结束 均为振荡器合成。 * 设置页可开关(PBStorage.soundOn)。 * ============================================================ */ var PBAudio = (function () { 'use strict'; var ctx = null; var lastHit = 0, lastWall = 0; // 分开节流,防砖块命中音被墙壁音吃掉 function ac() { if (!ctx) { try { ctx = new (window.AudioContext || window.webkitAudioContext)(); } catch (e) { ctx = null; } } if (ctx && ctx.state === 'suspended') { try { ctx.resume(); } catch (e) {} } return ctx; } function enabled() { try { return PBStorage.soundOn(); } catch (e) { return true; } } function tone(freq, dur, type, gain, slideTo) { var c = ac(); if (!c || !enabled()) return; try { var o = c.createOscillator(); var g = c.createGain(); o.type = type || 'sine'; o.frequency.setValueAtTime(freq, c.currentTime); if (slideTo) o.frequency.exponentialRampToValueAtTime(slideTo, c.currentTime + dur); g.gain.setValueAtTime(gain || 0.08, c.currentTime); g.gain.exponentialRampToValueAtTime(0.0001, c.currentTime + dur); o.connect(g); g.connect(c.destination); o.start(); o.stop(c.currentTime + dur + 0.02); } catch (e) {} } return { unlock: function () { ac(); }, fire: function () { tone(520, 0.12, 'triangle', 0.06, 260); }, hit: function (speed) { var now = Date.now(); if (now - lastHit < 45) return; // 限流防炸音 lastHit = now; tone(300 + Math.min(500, speed * 0.15), 0.06, 'square', 0.035); }, wall: function () { var now = Date.now(); if (now - lastWall < 45) return; lastWall = now; tone(180, 0.05, 'sine', 0.03); }, breakBlock: function () { tone(760, 0.16, 'sawtooth', 0.06, 180); }, item: function () { tone(660, 0.1, 'sine', 0.07, 990); tone(990, 0.16, 'sine', 0.06, 1320); }, over: function () { tone(392, 0.25, 'triangle', 0.08, 196); }, claim: function () { tone(523, 0.09, 'sine', 0.07); tone(659, 0.09, 'sine', 0.07); tone(784, 0.16, 'sine', 0.07); } }; })();