Initialisieren

This commit is contained in:
2026-07-23 21:59:15 +02:00
commit 2d961adad4
76 changed files with 14318 additions and 0 deletions

122
lib/sounds.ts Normal file
View File

@@ -0,0 +1,122 @@
/**
* Prozedurale Sound-Engine auf Basis der WebAudio API.
* Keine Audio-Dateien nötig alles wird live synthetisiert.
*/
export type SoundName = "tick" | "coin" | "dice" | "pop" | "win" | "whoosh";
let ctx: AudioContext | null = null;
function ac(): AudioContext | null {
if (typeof window === "undefined") return null;
try {
if (!ctx) {
const AC =
window.AudioContext ||
(window as unknown as { webkitAudioContext?: typeof AudioContext })
.webkitAudioContext;
if (!AC) return null;
ctx = new AC();
}
if (ctx.state === "suspended") {
ctx.resume().catch(() => {});
}
return ctx;
} catch {
return null;
}
}
function tone(
c: AudioContext,
freq: number,
type: OscillatorType,
dur: number,
peak = 0.2,
when = 0,
slideTo?: number,
) {
const t0 = c.currentTime + when;
const osc = c.createOscillator();
const gain = c.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, t0);
if (slideTo) {
osc.frequency.exponentialRampToValueAtTime(slideTo, t0 + dur);
}
gain.gain.setValueAtTime(0, t0);
gain.gain.linearRampToValueAtTime(peak, t0 + 0.008);
gain.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
osc.connect(gain).connect(c.destination);
osc.start(t0);
osc.stop(t0 + dur + 0.05);
}
function noiseBurst(
c: AudioContext,
dur: number,
peak: number,
filterFreq: number,
when = 0,
sweepTo?: number,
) {
const t0 = c.currentTime + when;
const len = Math.max(1, Math.floor(c.sampleRate * dur));
const buffer = c.createBuffer(1, len, c.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < len; i++) data[i] = Math.random() * 2 - 1;
const src = c.createBufferSource();
src.buffer = buffer;
const filter = c.createBiquadFilter();
filter.type = "bandpass";
filter.frequency.setValueAtTime(filterFreq, t0);
filter.Q.value = 1.1;
if (sweepTo) {
filter.frequency.exponentialRampToValueAtTime(sweepTo, t0 + dur);
}
const gain = c.createGain();
gain.gain.setValueAtTime(0, t0);
gain.gain.linearRampToValueAtTime(peak, t0 + 0.01);
gain.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
src.connect(filter).connect(gain).connect(c.destination);
src.start(t0);
}
export function playSound(name: SoundName) {
const c = ac();
if (!c) return;
try {
switch (name) {
case "tick":
tone(c, 1900, "square", 0.035, 0.07);
break;
case "coin":
// Metallischer Doppelton + Glitzer
tone(c, 2093, "triangle", 0.55, 0.16);
tone(c, 2637, "triangle", 0.45, 0.1, 0.012);
tone(c, 3520, "sine", 0.3, 0.05, 0.02);
break;
case "dice":
// Prasselndes Klackern
[0, 0.09, 0.21, 0.31].forEach((w, i) =>
noiseBurst(c, 0.05, 0.2 - i * 0.03, 1600 + Math.random() * 1000, w),
);
break;
case "pop":
tone(c, 420, "sine", 0.14, 0.22, 0, 880);
break;
case "whoosh":
noiseBurst(c, 0.55, 0.1, 500, 0, 2600);
break;
case "win":
// Kleine Fanfare C5E5G5C6
[523.25, 659.25, 783.99, 1046.5].forEach((f, i) =>
tone(c, f, "triangle", 0.4, 0.13, i * 0.09),
);
break;
}
} catch {
// Sound ist Dekoration niemals crashen
}
}