export class Sound { enabled = true; private context?: AudioContext; unlock(): void { if (!this.enabled) return; try { this.context ||= new AudioContext(); if (this.context.state === 'suspended') void this.context.resume().catch(() => {}); } catch { /* 无音频设备仍能玩 */ } } note(n: number, merge = false): void { if (!this.enabled || !this.context) return; try { const c = this.context, time = c.currentTime, gain = c.createGain(), oscillator = c.createOscillator(); oscillator.type = 'sine'; oscillator.frequency.value = 220 * Math.pow(2, (n + (merge ? 5 : 0)) / 12); gain.gain.setValueAtTime(0, time); gain.gain.linearRampToValueAtTime(0.09, time + 0.015); gain.gain.exponentialRampToValueAtTime(0.001, time + (merge ? 0.32 : 0.13)); oscillator.connect(gain); gain.connect(c.destination); oscillator.start(); oscillator.stop(time + 0.36); } catch { /* 音效不是游戏运行前提 */ } } pop(n: number, combo: number, fever: boolean): void { this.note(n + combo * 2 + (fever ? 3 : 0), true); if (combo > 1) window.setTimeout(() => this.note(n + combo * 2 + 5, true), 45); } }