summaryrefslogtreecommitdiff
path: root/src/game/AudioEngine.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/game/AudioEngine.ts')
-rw-r--r--src/game/AudioEngine.ts100
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);
+ }
+}