The Sims Online 2D — full game: CAS, build mode, needs, AI Mode (whim-driven autonomy), careers+chance cards, neighborhood AI sims, death/ghosts, meal tiers+sickness, paintings/novels, house parties, memories, weather; 38 headless tests green
This commit is contained in:
+63
@@ -0,0 +1,63 @@
|
||||
/* ============================================================
|
||||
* 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 });
|
||||
Reference in New Issue
Block a user