Files
BaraBingo/lib/sounds.ts
SlavaVlad 0c35693598
All checks were successful
Deploy / build-and-deploy (push) Successful in 1m43s
feat: shared player with 6s cap, progress bar, stop button
2026-06-15 02:17:46 +03:00

151 lines
4.7 KiB
TypeScript

import { playUrl, stopCurrent } from "@/lib/player";
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);
});
}
// ─── URL playback via shared player ──────────────────────────────
/**
* Play a sound file URL (MP3/OGG). Goes through the shared player,
* which caps at 6s with fadeout and shows playback UI.
*/
export function playSoundUrl(url: string, name?: string) {
playUrl(url, name);
}
/**
* One-shot play with a display name. Same underlying player.
*/
export function playSoundOnce(url: string, name?: string) {
playUrl(url, name);
}
/**
* Stop current playback immediately.
*/
export { stopCurrent, getPlaybackState, subscribe } from "@/lib/player";
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;
}
}