64 lines
2.8 KiB
JavaScript
64 lines
2.8 KiB
JavaScript
/* ============================================================
|
|
* audio.js — tiny WebAudio synth SFX (no asset files needed)
|
|
* ============================================================ */
|
|
'use strict';
|
|
|
|
const AudioSys = {
|
|
ctx: null,
|
|
muted: false,
|
|
|
|
/** Lazily create/resume the context (must follow a user gesture). */
|
|
ensure() {
|
|
if (this.muted) return null;
|
|
try {
|
|
if (!this.ctx) {
|
|
const AC = window.AudioContext || window.webkitAudioContext;
|
|
if (!AC) return null;
|
|
this.ctx = new AC();
|
|
}
|
|
if (this.ctx.state === 'suspended') this.ctx.resume();
|
|
return this.ctx;
|
|
} catch (e) { return null; }
|
|
},
|
|
|
|
tone(freq, dur, type = 'sine', vol = 0.12, when = 0, glideTo = 0) {
|
|
const c = this.ensure();
|
|
if (!c) return;
|
|
const t = c.currentTime + when;
|
|
const o = c.createOscillator();
|
|
const g = c.createGain();
|
|
o.type = type;
|
|
o.frequency.setValueAtTime(freq, t);
|
|
if (glideTo) o.frequency.exponentialRampToValueAtTime(Math.max(30, glideTo), t + dur);
|
|
g.gain.setValueAtTime(vol, t);
|
|
g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
|
|
o.connect(g); g.connect(c.destination);
|
|
o.start(t); o.stop(t + dur + 0.03);
|
|
},
|
|
|
|
sfx(name) {
|
|
switch (name) {
|
|
case 'click': this.tone(650, .05, 'square', .04); break;
|
|
case 'toast': this.tone(520, .07, 'sine', .06); this.tone(780, .06, 'sine', .04, .05); break;
|
|
case 'error': this.tone(170, .16, 'sawtooth', .09); this.tone(120, .18, 'sawtooth', .08, .08); break;
|
|
case 'place': this.tone(340, .07, 'triangle', .12); this.tone(240, .09, 'triangle', .10, .06); break;
|
|
case 'sell': [880, 1175, 1568].forEach((f, i) => this.tone(f, .09, 'square', .06, i * .06)); break;
|
|
case 'chime': this.tone(660, .12, 'sine', .09); this.tone(990, .14, 'sine', .07, .09); break;
|
|
case 'level': this.tone(700, .08, 'square', .06); this.tone(1050, .12, 'square', .06, .07); break;
|
|
case 'fanfare':
|
|
[[523, 0], [659, .11], [784, .22], [1047, .33]].forEach(([f, d]) => this.tone(f, .16, 'triangle', .1, d));
|
|
this.tone(1047, .4, 'sine', .07, .5); break;
|
|
case 'bill': this.tone(330, .1, 'square', .07); this.tone(330, .12, 'square', .07, .14); break;
|
|
case 'horn': this.tone(290, .28, 'sawtooth', .08); this.tone(290, .34, 'sawtooth', .08, .32); break;
|
|
case 'thud': this.tone(120, .22, 'sine', .16, 0, 55); break;
|
|
case 'splash': this.tone(500, .1, 'sine', .08, 0, 180); this.tone(260, .16, 'sine', .08, .05, 90); break;
|
|
case 'toll': this.tone(98, 1.4, 'sine', .2); this.tone(196, 1.2, 'sine', .08, .02); break;
|
|
case 'kiss': this.tone(900, .06, 'sine', .07); this.tone(1300, .08, 'sine', .05, .06); break;
|
|
default: break;
|
|
}
|
|
},
|
|
};
|
|
|
|
/* unlock audio on first gesture */
|
|
window.addEventListener('pointerdown', () => AudioSys.ensure(), { once: true });
|