1
0
Fork 0
polaris-task-force/src/components/frontend/eod/sounds.ts

103 lines
3.4 KiB
TypeScript

let audioCtx: AudioContext | null = null;
function getCtx(): AudioContext | null {
if (typeof window === "undefined") return null;
const AudioCtor =
window.AudioContext ??
(window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
if (!AudioCtor) return null;
if (!audioCtx) audioCtx = new AudioCtor();
if (audioCtx.state === "suspended") {
void audioCtx.resume();
}
return audioCtx;
}
interface ToneOptions {
frequency: number;
endFrequency?: number;
duration: number;
type?: OscillatorType;
volume?: number;
delay?: number;
}
function playTone(ctx: AudioContext, opts: ToneOptions) {
const { frequency, endFrequency, duration, type = "sine", volume = 0.2, delay = 0 } = opts;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
const t0 = ctx.currentTime + delay;
osc.type = type;
osc.frequency.setValueAtTime(Math.max(frequency, 1), t0);
if (endFrequency && endFrequency !== frequency) {
osc.frequency.exponentialRampToValueAtTime(Math.max(endFrequency, 1), t0 + duration);
}
gain.gain.setValueAtTime(0.0001, t0);
gain.gain.exponentialRampToValueAtTime(volume, t0 + 0.012);
gain.gain.exponentialRampToValueAtTime(0.0001, t0 + duration);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(t0);
osc.stop(t0 + duration + 0.05);
}
function playNoise(
ctx: AudioContext,
opts: { duration: number; volume?: number; delay?: number; lowpass?: number },
) {
const { duration, volume = 0.2, delay = 0, lowpass = 800 } = opts;
const bufferSize = Math.max(1, 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;
}
const source = ctx.createBufferSource();
source.buffer = buffer;
const filter = ctx.createBiquadFilter();
filter.type = "lowpass";
filter.frequency.setValueAtTime(lowpass, ctx.currentTime + delay);
const gain = ctx.createGain();
const t0 = ctx.currentTime + delay;
gain.gain.setValueAtTime(0.0001, t0);
gain.gain.exponentialRampToValueAtTime(volume, t0 + 0.005);
gain.gain.exponentialRampToValueAtTime(0.0001, t0 + duration);
source.connect(filter);
filter.connect(gain);
gain.connect(ctx.destination);
source.start(t0);
}
export function playReveal() {
const ctx = getCtx();
if (!ctx) return;
playTone(ctx, { frequency: 400, duration: 0.05, type: "sine", volume: 0.08 });
}
export function playFlag() {
const ctx = getCtx();
if (!ctx) return;
playTone(ctx, { frequency: 300, duration: 0.07, type: "triangle", volume: 0.12 });
}
export function playExplosion() {
const ctx = getCtx();
if (!ctx) return;
playTone(ctx, { frequency: 120, endFrequency: 30, duration: 0.35, type: "square", volume: 0.28 });
playNoise(ctx, { duration: 0.28, volume: 0.35, lowpass: 900 });
}
export function playClear() {
const ctx = getCtx();
if (!ctx) return;
playTone(ctx, { frequency: 523, duration: 0.1, type: "sine", volume: 0.16 });
playTone(ctx, { frequency: 659, duration: 0.1, type: "sine", volume: 0.16, delay: 0.1 });
playTone(ctx, { frequency: 784, duration: 0.18, type: "sine", volume: 0.18, delay: 0.2 });
}
export function playXp() {
const ctx = getCtx();
if (!ctx) return;
playTone(ctx, { frequency: 660, duration: 0.09, type: "sine", volume: 0.18 });
playTone(ctx, { frequency: 880, duration: 0.16, type: "sine", volume: 0.18, delay: 0.09 });
}