All checks were successful
Deploy / build-and-deploy (push) Successful in 1m41s
136 lines
4.2 KiB
TypeScript
136 lines
4.2 KiB
TypeScript
let audioCtx: AudioContext | null = null;
|
|
|
|
function getCtx(): AudioContext {
|
|
if (!audioCtx) {
|
|
audioCtx = new AudioContext();
|
|
}
|
|
return audioCtx;
|
|
}
|
|
|
|
type OscillatorConfig = {
|
|
freq: number;
|
|
type: OscillatorType;
|
|
duration: number;
|
|
gain?: number;
|
|
};
|
|
|
|
function playTone(config: OscillatorConfig) {
|
|
try {
|
|
const ctx = getCtx();
|
|
const osc = ctx.createOscillator();
|
|
const gain = ctx.createGain();
|
|
osc.type = config.type;
|
|
osc.frequency.setValueAtTime(config.freq, ctx.currentTime);
|
|
gain.gain.setValueAtTime(config.gain ?? 0.3, ctx.currentTime);
|
|
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + config.duration);
|
|
osc.connect(gain);
|
|
gain.connect(ctx.destination);
|
|
osc.start(ctx.currentTime);
|
|
osc.stop(ctx.currentTime + config.duration);
|
|
} catch {
|
|
// Audio not supported
|
|
}
|
|
}
|
|
|
|
function playNoise(duration: number, gain = 0.15) {
|
|
try {
|
|
const ctx = getCtx();
|
|
const bufferSize = Math.floor(ctx.sampleRate * duration);
|
|
const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
|
|
const data = buffer.getChannelData(0);
|
|
for (let i = 0; i < bufferSize; i++) {
|
|
data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / bufferSize, 2);
|
|
}
|
|
const source = ctx.createBufferSource();
|
|
source.buffer = buffer;
|
|
const gainNode = ctx.createGain();
|
|
gainNode.gain.setValueAtTime(gain, ctx.currentTime);
|
|
gainNode.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);
|
|
source.connect(gainNode);
|
|
gainNode.connect(ctx.destination);
|
|
source.start();
|
|
} catch {
|
|
// Audio not supported
|
|
}
|
|
}
|
|
|
|
export function playHonk() {
|
|
playTone({ freq: 800, type: "square", duration: 0.08, gain: 0.2 });
|
|
setTimeout(() => playTone({ freq: 600, type: "square", duration: 0.12, gain: 0.2 }), 80);
|
|
}
|
|
|
|
export function playReactorAlarm() {
|
|
for (let i = 0; i < 4; i++) {
|
|
setTimeout(() => {
|
|
playTone({ freq: 440 + i * 110, type: "sawtooth", duration: 0.2, gain: 0.15 });
|
|
setTimeout(() => playTone({ freq: 330 + i * 80, type: "sawtooth", duration: 0.2, gain: 0.15 }), 100);
|
|
}, i * 300);
|
|
}
|
|
}
|
|
|
|
export function playHullBreach() {
|
|
playTone({ freq: 100, type: "sine", duration: 0.5, gain: 0.4 });
|
|
setTimeout(() => playTone({ freq: 80, type: "sine", duration: 0.5, gain: 0.3 }), 200);
|
|
playNoise(0.5, 0.1);
|
|
}
|
|
|
|
export function playExplosion() {
|
|
playNoise(0.6, 0.4);
|
|
playTone({ freq: 60, type: "sine", duration: 0.5, gain: 0.5 });
|
|
setTimeout(() => playTone({ freq: 40, type: "sine", duration: 0.3, gain: 0.3 }), 200);
|
|
}
|
|
|
|
export function playMonster() {
|
|
playTone({ freq: 80, type: "sawtooth", duration: 0.4, gain: 0.2 });
|
|
setTimeout(() => playTone({ freq: 60, type: "sawtooth", duration: 0.4, gain: 0.15 }), 200);
|
|
setTimeout(() => playTone({ freq: 50, type: "square", duration: 0.5, gain: 0.2 }), 400);
|
|
}
|
|
|
|
export function playDeath() {
|
|
playTone({ freq: 400, type: "sine", duration: 0.15, gain: 0.2 });
|
|
setTimeout(() => playTone({ freq: 300, type: "sine", duration: 0.15, gain: 0.2 }), 150);
|
|
setTimeout(() => playTone({ freq: 200, type: "sine", duration: 0.3, gain: 0.2 }), 300);
|
|
}
|
|
|
|
export function playBingo() {
|
|
const notes = [523, 659, 784, 1047];
|
|
notes.forEach((freq, i) => {
|
|
setTimeout(() => playTone({ freq, type: "square", duration: 0.2, gain: 0.2 }), i * 120);
|
|
});
|
|
setTimeout(() => playNoise(0.3, 0.1), 480);
|
|
}
|
|
|
|
export function playChaosRiser() {
|
|
const pitches = [200, 250, 300, 400, 500, 600, 800, 1000];
|
|
pitches.forEach((freq, i) => {
|
|
setTimeout(() => playTone({ freq, type: "sawtooth", duration: 0.1, gain: 0.08 }), i * 50);
|
|
});
|
|
}
|
|
|
|
export function playSoundUrl(url: string) {
|
|
try {
|
|
const audio = new Audio(url);
|
|
audio.preload = "auto";
|
|
audio.volume = 0.4;
|
|
audio.play().catch(() => {});
|
|
} catch {}
|
|
}
|
|
|
|
export function playSound(category: string, soundUrl?: string | null) {
|
|
if (soundUrl) {
|
|
playSoundUrl(soundUrl);
|
|
return;
|
|
}
|
|
switch (category) {
|
|
case "horn": playHonk(); break;
|
|
case "alarm": playReactorAlarm(); break;
|
|
case "flood": playHullBreach(); break;
|
|
case "explosion": playExplosion(); break;
|
|
case "monster": playMonster(); break;
|
|
case "death": playDeath(); break;
|
|
case "bingo": playBingo(); break;
|
|
case "chaos": playChaosRiser(); break;
|
|
default: playHonk(); break;
|
|
}
|
|
}
|