sound.ts

examples/fxedit/sound.ts · raw · open the example · all examples · site home

Exports: SoundNameSound

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
// Tiny synthesized UI sounds (WebAudio, nothing to download); muted by default.
export type SoundName = "connect" | "disconnect" | "pick" | "drop" | "ok" | "error" | "delete" | "open" | "tick" | "add";

export class Sound {
  muted = true;
  volume = 0.5;
  private context?: AudioContext;
  private lastTick = 0;

  play(name: SoundName) {
    if (this.muted || this.volume <= 0) {
      return;
    }
    this.context ??= new AudioContext();
    const t = this.context.currentTime;
    switch (name) {
      case "connect":
        this.tone(660, t, 0.07, "triangle", 0.25);
        this.tone(990, t + 0.05, 0.1, "triangle", 0.2);
        break;
      case "disconnect":
        this.tone(620, t, 0.12, "triangle", 0.2, 310);
        break;
      case "pick":
        this.tone(520, t, 0.05, "sine", 0.15);
        break;
      case "drop":
        this.tone(190, t, 0.1, "sine", 0.35, 120);
        break;
      case "add":
        this.tone(440, t, 0.06, "sine", 0.2, 880);
        break;
      case "ok":
        [523, 659, 784].forEach((f, i) => this.tone(f, t + i * 0.06, 0.14, "sine", 0.18));
        break;
      case "error":
        this.tone(150, t, 0.16, "square", 0.08);
        this.tone(112, t + 0.08, 0.2, "square", 0.08);
        break;
      case "delete":
        this.tone(320, t, 0.14, "sawtooth", 0.08, 70);
        break;
      case "open":
        this.tone(880, t, 0.04, "sine", 0.1);
        break;
      case "tick": {
        const now = performance.now();
        if (now - this.lastTick > 45) {
          this.lastTick = now;
          this.tone(1500, t, 0.015, "sine", 0.04);
        }
        break;
      }
    }
  }

  private tone(frequency: number, start: number, duration: number, type: OscillatorType, gain: number, slideTo?: number) {
    const context = this.context!;
    const oscillator = context.createOscillator();
    const envelope = context.createGain();
    oscillator.type = type;
    oscillator.frequency.setValueAtTime(frequency, start);
    if (slideTo) {
      oscillator.frequency.exponentialRampToValueAtTime(slideTo, start + duration);
    }
    envelope.gain.setValueAtTime(0.0001, start);
    envelope.gain.exponentialRampToValueAtTime(gain * this.volume, start + 0.005);
    envelope.gain.exponentialRampToValueAtTime(0.0001, start + duration);
    oscillator.connect(envelope).connect(context.destination);
    oscillator.start(start);
    oscillator.stop(start + duration + 0.02);
  }
}