-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsound.js
More file actions
63 lines (54 loc) · 2.42 KB
/
Copy pathsound.js
File metadata and controls
63 lines (54 loc) · 2.42 KB
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
// TileLoom sound effects — synthesized with WebAudio, no audio files.
'use strict';
const Sound = (() => {
let ctx = null;
let muted = false;
try { muted = localStorage.getItem('tileloom-muted') === '1'; } catch (e) { /* ignore */ }
function ac() {
if (!ctx) {
const AC = window.AudioContext || window.webkitAudioContext;
if (!AC) return null;
ctx = new AC();
}
if (ctx.state === 'suspended') ctx.resume().catch(() => {});
return ctx;
}
// Browsers only allow audio after a user gesture; warm the context up early
document.addEventListener('pointerdown', () => { if (!muted) ac(); }, { once: true, capture: true });
function tone(freq, dur, opts = {}) {
const c = ac();
if (!c) return;
const t0 = c.currentTime + (opts.delay || 0);
const o = c.createOscillator();
const g = c.createGain();
o.type = opts.type || 'sine';
o.frequency.setValueAtTime(freq, t0);
if (opts.slide) o.frequency.exponentialRampToValueAtTime(opts.slide, t0 + dur);
g.gain.setValueAtTime(0.0001, t0);
g.gain.exponentialRampToValueAtTime(opts.gain || 0.15, t0 + 0.01);
g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
o.connect(g).connect(c.destination);
o.start(t0);
o.stop(t0 + dur + 0.02);
}
const FX = {
place() { tone(520, 0.07, { type: 'triangle', slide: 300, gain: 0.2 }); tone(1600, 0.03, { type: 'square', gain: 0.05 }); },
pickup() { tone(700, 0.05, { type: 'triangle', slide: 950, gain: 0.12 }); },
invalid() { tone(220, 0.18, { type: 'sawtooth', slide: 140, gain: 0.1 }); },
word() { [523, 659, 784].forEach((f, i) => tone(f, 0.12, { delay: i * 0.06, gain: 0.12 })); },
bingo() { [523, 659, 784, 1047, 1319].forEach((f, i) => tone(f, 0.16, { delay: i * 0.07, gain: 0.14 })); },
swap() { tone(300, 0.12, { type: 'triangle', slide: 900, gain: 0.1 }); },
win() { [392, 523, 659, 784, 1047].forEach((f, i) => tone(f, 0.22, { delay: i * 0.12, gain: 0.15 })); },
lose() { [440, 330, 247].forEach((f, i) => tone(f, 0.25, { delay: i * 0.14, type: 'triangle', gain: 0.12 })); },
};
function play(name) {
if (muted) return;
try { if (FX[name]) FX[name](); } catch (e) { /* audio is best-effort */ }
}
function toggleMute() {
muted = !muted;
try { localStorage.setItem('tileloom-muted', muted ? '1' : '0'); } catch (e) { /* ignore */ }
return muted;
}
return { play, toggleMute, get muted() { return muted; } };
})();