gigaslop/src/ui/sfx.ts
type-two d2bdffaa16 Tier 1 playable slice: bedroom slop empire
Deterministic 10Hz sim (power/breaker, spatial heat, uplink bandwidth,
train-vs-infer economy), Pixi isometric renderer over MODELBEAST-generated
room + sprites, DOM HUD with SlopTube feed, save/load, company naming,
WebAudio SFX. Slop thumbnails are genuine diffusion slop; low model tiers
publish deranged qwen-7B titles, training buys coherence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 09:55:55 +10:00

64 lines
2.3 KiB
TypeScript

// Tiny WebAudio synth — no asset files. All sounds built from oscillators/noise.
let ctx: AudioContext | null = null;
let muted = localStorage.getItem("gigaslop-muted") === "1";
function ac(): AudioContext | null {
if (muted) return null;
if (!ctx) ctx = new AudioContext();
if (ctx.state === "suspended") ctx.resume();
return ctx;
}
export function setMuted(m: boolean) {
muted = m;
localStorage.setItem("gigaslop-muted", m ? "1" : "0");
}
export function isMuted() {
return muted;
}
function tone(freq: number, dur: number, type: OscillatorType, vol: number, when = 0, glideTo?: number) {
const c = ac();
if (!c) return;
const t0 = c.currentTime + when;
const o = c.createOscillator();
const g = c.createGain();
o.type = type;
o.frequency.setValueAtTime(freq, t0);
if (glideTo) o.frequency.exponentialRampToValueAtTime(glideTo, t0 + dur);
g.gain.setValueAtTime(vol, t0);
g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
o.connect(g).connect(c.destination);
o.start(t0);
o.stop(t0 + dur + 0.02);
}
function noise(dur: number, vol: number, lowpass = 800) {
const c = ac();
if (!c) return;
const buf = c.createBuffer(1, c.sampleRate * dur, c.sampleRate);
const d = buf.getChannelData(0);
for (let i = 0; i < d.length; i++) d[i] = (Math.random() * 2 - 1) * (1 - i / d.length);
const src = c.createBufferSource();
src.buffer = buf;
const f = c.createBiquadFilter();
f.type = "lowpass";
f.frequency.value = lowpass;
const g = c.createGain();
g.gain.value = vol;
src.connect(f).connect(g).connect(c.destination);
src.start();
}
export const sfx = {
click: () => tone(880, 0.05, "square", 0.04),
place: () => { noise(0.12, 0.25, 500); tone(140, 0.1, "sine", 0.15); },
sell: () => tone(520, 0.12, "triangle", 0.08, 0, 260),
wire: () => { tone(660, 0.06, "square", 0.05); tone(990, 0.06, "square", 0.05, 0.07); },
publish: () => { tone(523, 0.08, "sine", 0.07); tone(784, 0.12, "sine", 0.07, 0.08); },
viral: () => [523, 659, 784, 1047, 1319].forEach((f, i) => tone(f, 0.18, "triangle", 0.09, i * 0.07)),
tierUp: () => [392, 523, 659, 784].forEach((f, i) => tone(f, 0.3, "sawtooth", 0.05, i * 0.12)),
breaker: () => { noise(0.4, 0.5, 250); tone(80, 0.5, "sawtooth", 0.2, 0, 35); },
error: () => tone(180, 0.15, "square", 0.06),
};