summaryrefslogtreecommitdiff
path: root/src/game/AudioEngine.ts
blob: 1a6f907e284284a135cf985d87764eacdf298f0b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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);
  }
}