// 纯 WebAudio 合成音效,无外部资源 let ctx = null, master = null; export const Audio2 = { enabled: true, volume: 0.5 }; function ac() { if (!ctx) { const AC = window.AudioContext || window.webkitAudioContext; if (!AC) return null; ctx = new AC(); master = ctx.createGain(); master.gain.value = Audio2.volume; master.connect(ctx.destination); } if (ctx.state === 'suspended') ctx.resume(); return ctx; } export function unlockAudio() { ac(); } function tone({ f = 440, f2 = null, d = 0.1, type = 'square', vol = 0.25, delay = 0, decay = 0 }) { if (!Audio2.enabled) return; const c = ac(); if (!c) return; const t0 = c.currentTime + delay; const o = c.createOscillator(); const g = c.createGain(); o.type = type; o.frequency.setValueAtTime(f, t0); if (f2 !== null) o.frequency.exponentialRampToValueAtTime(Math.max(20, f2), t0 + d); g.gain.setValueAtTime(0.0001, t0); g.gain.linearRampToValueAtTime(vol, t0 + 0.008); const end = t0 + d + decay; g.gain.exponentialRampToValueAtTime(0.0001, end); o.connect(g); g.connect(master); o.start(t0); o.stop(end + 0.02); } function noise({ d = 0.2, vol = 0.25, delay = 0, hp = 400 }) { if (!Audio2.enabled) return; const c = ac(); if (!c) return; const t0 = c.currentTime + delay; const len = Math.floor(c.sampleRate * d); const buf = c.createBuffer(1, len, c.sampleRate); const data = buf.getChannelData(0); for (let i = 0; i < len; i++) data[i] = (Math.random() * 2 - 1) * (1 - i / len); const src = c.createBufferSource(); src.buffer = buf; const f = c.createBiquadFilter(); f.type = 'highpass'; f.frequency.value = hp; const g = c.createGain(); g.gain.value = vol; src.connect(f); f.connect(g); g.connect(master); src.start(t0); } export const sfx = { jump() { tone({ f: 380, f2: 700, d: 0.09, type: 'square', vol: 0.16 }); }, djump() { tone({ f: 520, f2: 900, d: 0.09, type: 'square', vol: 0.14 }); }, shoot() { tone({ f: 900, f2: 260, d: 0.07, type: 'square', vol: 0.14 }); noise({ d: 0.05, vol: 0.06, hp: 2000 }); }, death() { tone({ f: 620, f2: 60, d: 0.55, type: 'sawtooth', vol: 0.26 }); noise({ d: 0.35, vol: 0.16, hp: 300, delay: 0.02 }); }, save() { tone({ f: 700, d: 0.08, type: 'triangle', vol: 0.2 }); tone({ f: 1050, d: 0.13, type: 'triangle', vol: 0.18, delay: 0.07 }); }, goal() { [523, 659, 784, 1047].forEach((f, i) => tone({ f, d: 0.16, type: 'triangle', vol: 0.2, delay: i * 0.1 })); }, warp() { tone({ f: 300, f2: 1400, d: 0.2, type: 'sine', vol: 0.18 }); }, hit() { tone({ f: 200, f2: 90, d: 0.08, type: 'square', vol: 0.16 }); }, bossHit() { tone({ f: 260, f2: 140, d: 0.1, type: 'sawtooth', vol: 0.14 }); }, bossDie() { [400, 330, 260, 180, 110].forEach((f, i) => tone({ f, d: 0.22, type: 'sawtooth', vol: 0.22, delay: i * 0.11 })); noise({ d: 0.8, vol: 0.2, hp: 150, delay: 0.1 }); }, menu() { tone({ f: 620, d: 0.05, type: 'square', vol: 0.12 }); }, vanish() { tone({ f: 700, f2: 300, d: 0.12, type: 'sine', vol: 0.1 }); }, troll() { tone({ f: 180, f2: 900, d: 0.3, type: 'sine', vol: 0.18 }); }, splash() { noise({ d: 0.18, vol: 0.12, hp: 800 }); }, };