diff options
Diffstat (limited to 'src/game/AudioEngine.ts')
| -rw-r--r-- | src/game/AudioEngine.ts | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/src/game/AudioEngine.ts b/src/game/AudioEngine.ts new file mode 100644 index 0000000..1a6f907 --- /dev/null +++ b/src/game/AudioEngine.ts @@ -0,0 +1,100 @@ +type AudioContextCtor = typeof AudioContext; + +export class AudioEngine { + private context?: AudioContext; + private master?: GainNode; + private drone?: OscillatorNode; + private shimmer?: OscillatorNode; + private active = false; + + activate(): void { + if (this.active) { + void this.context?.resume(); + return; + } + + const ctor = window.AudioContext ?? (window as Window & { webkitAudioContext?: AudioContextCtor }).webkitAudioContext; + if (!ctor) { + return; + } + + this.context = new ctor(); + this.master = this.context.createGain(); + this.master.gain.value = 0.07; + this.master.connect(this.context.destination); + + this.drone = this.context.createOscillator(); + this.drone.type = 'sawtooth'; + this.drone.frequency.value = 82; + const droneGain = this.context.createGain(); + droneGain.gain.value = 0.18; + this.drone.connect(droneGain); + droneGain.connect(this.master); + this.drone.start(); + + this.shimmer = this.context.createOscillator(); + this.shimmer.type = 'triangle'; + this.shimmer.frequency.value = 164; + const shimmerGain = this.context.createGain(); + shimmerGain.gain.value = 0.08; + this.shimmer.connect(shimmerGain); + shimmerGain.connect(this.master); + this.shimmer.start(); + + this.active = true; + } + + setMood(fidelity: number, cycle: number, layerIndex: number): void { + if (!this.context || !this.master || !this.drone || !this.shimmer) { + return; + } + + const now = this.context.currentTime; + const base = 82 + cycle * 8 + layerIndex * 3; + this.drone.frequency.linearRampToValueAtTime(base + fidelity * 18, now + 0.18); + this.shimmer.frequency.linearRampToValueAtTime(base * 2 + (1 - fidelity) * 70, now + 0.22); + this.master.gain.linearRampToValueAtTime(0.045 + fidelity * 0.04, now + 0.25); + } + + pulseJump(boost = false): void { + if (!this.context || !this.master) { + return; + } + + const osc = this.context.createOscillator(); + const gain = this.context.createGain(); + osc.type = boost ? 'square' : 'sine'; + osc.frequency.value = boost ? 420 : 260; + gain.gain.value = 0.0001; + osc.connect(gain); + gain.connect(this.master); + + const now = this.context.currentTime; + gain.gain.exponentialRampToValueAtTime(0.06, now + 0.01); + gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.24); + osc.frequency.exponentialRampToValueAtTime(boost ? 170 : 120, now + 0.24); + osc.start(now); + osc.stop(now + 0.26); + } + + pulseSnap(): void { + if (!this.context || !this.master) { + return; + } + + const osc = this.context.createOscillator(); + const gain = this.context.createGain(); + osc.type = 'triangle'; + osc.frequency.value = 680; + gain.gain.value = 0.0001; + osc.connect(gain); + gain.connect(this.master); + + const now = this.context.currentTime; + gain.gain.exponentialRampToValueAtTime(0.08, now + 0.01); + gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.45); + osc.frequency.exponentialRampToValueAtTime(240, now + 0.45); + osc.start(now); + osc.stop(now + 0.5); + } +} |
