sound.ts
examples/fxedit/sound.ts · raw · open the example · all examples · site home
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);
}
}