File size: 3,097 Bytes
94193b5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
101
102
103
104
105
106
const VOLUME = 0.176; // base peak gain (80% of 0.22)

let sharedContext: AudioContext | null = null;

function getContext(): AudioContext | null {
  if (typeof window === 'undefined') return null;
  if (sharedContext && sharedContext.state !== 'closed') return sharedContext;
  const AC = window.AudioContext || (window as any).webkitAudioContext;
  sharedContext = AC ? new AC() : null;
  return sharedContext;
}

export function playTaskCompleteSound() {
  const ac = getContext();
  if (!ac) return;

  const t0 = ac.currentTime;
  const freqs = [523.3, 784];
  const spd = 0.11;
  const sw = 0.080;

  const dly = ac.createDelay(1.0);
  dly.delayTime.value = spd * 1.5;
  const dlyG = ac.createGain();
  dlyG.gain.value = 0.12;
  const fb = ac.createGain();
  fb.gain.value = 0.3;
  dly.connect(dlyG).connect(ac.destination);
  dly.connect(fb).connect(dly);

  freqs.forEach((freq, i) => {
    const t = t0 + i * spd + (i % 2 === 1 ? spd * sw : 0);
    const prev = i > 0 ? freqs[i - 1] : freq;

    const o1 = ac.createOscillator();
    const o2 = ac.createOscillator();
    o1.type = o2.type = 'sine';
    o1.frequency.setValueAtTime(prev, t);
    o1.frequency.exponentialRampToValueAtTime(freq, t + 0.02);
    o2.frequency.setValueAtTime(prev * 1.004, t);
    o2.frequency.exponentialRampToValueAtTime(freq * 1.004, t + 0.02);

    const flt = ac.createBiquadFilter();
    flt.type = 'lowpass';
    flt.Q.value = 1;
    flt.frequency.setValueAtTime(3500, t);
    flt.frequency.exponentialRampToValueAtTime(3000, t + spd + 0.2);

    const g = ac.createGain();
    g.gain.setValueAtTime(0, t);
    g.gain.linearRampToValueAtTime(VOLUME, t + 0.015);
    g.gain.setValueAtTime(VOLUME, t + spd * 0.4);
    g.gain.exponentialRampToValueAtTime(0.001, t + spd + 0.2);

    const mx = ac.createGain();
    mx.gain.value = 0.5;
    o1.connect(mx);
    o2.connect(mx);
    mx.connect(flt);
    flt.connect(g).connect(ac.destination);
    g.connect(dly);
    o1.start(t);
    o1.stop(t + spd + 0.2 + 0.1);
    o2.start(t);
    o2.stop(t + spd + 0.2 + 0.1);
  });
}

export function playTaskCompleteSoundSubtle() {
  const ac = getContext();
  if (!ac) return;

  const t0 = ac.currentTime;
  const freq = 784; // G5
  const spd = 0.11;

  const o1 = ac.createOscillator();
  const o2 = ac.createOscillator();
  o1.type = o2.type = 'sine';
  o1.frequency.setValueAtTime(freq, t0);
  o2.frequency.setValueAtTime(freq * 1.004, t0);

  const flt = ac.createBiquadFilter();
  flt.type = 'lowpass';
  flt.Q.value = 1;
  flt.frequency.setValueAtTime(3500, t0);
  flt.frequency.exponentialRampToValueAtTime(3000, t0 + spd + 0.2);

  const g = ac.createGain();
  g.gain.setValueAtTime(0, t0);
  g.gain.linearRampToValueAtTime(VOLUME, t0 + 0.015);
  g.gain.setValueAtTime(VOLUME, t0 + spd * 0.4);
  g.gain.exponentialRampToValueAtTime(0.001, t0 + spd + 0.2);

  const mx = ac.createGain();
  mx.gain.value = 0.5;
  o1.connect(mx);
  o2.connect(mx);
  mx.connect(flt);
  flt.connect(g).connect(ac.destination);
  o1.start(t0);
  o1.stop(t0 + spd + 0.2 + 0.1);
  o2.start(t0);
  o2.stop(t0 + spd + 0.2 + 0.1);
}