initial
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
// Two-tone notification chime generated via WebAudio. No asset file needed.
|
||||
// Throttled so a burst of messages doesn't turn into a machine gun.
|
||||
|
||||
let lastPlay = 0;
|
||||
|
||||
export function playNotificationTone(): void {
|
||||
const now = Date.now();
|
||||
if (now - lastPlay < 800) return;
|
||||
lastPlay = now;
|
||||
|
||||
const AudioCtx =
|
||||
window.AudioContext ??
|
||||
(window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
|
||||
if (!AudioCtx) return;
|
||||
|
||||
const ctx = new AudioCtx();
|
||||
const master = ctx.createGain();
|
||||
master.connect(ctx.destination);
|
||||
master.gain.value = 0.12;
|
||||
|
||||
const tones: { freq: number; delay: number }[] = [
|
||||
{ freq: 880, delay: 0 },
|
||||
{ freq: 1320, delay: 0.08 },
|
||||
];
|
||||
for (const { freq, delay } of tones) {
|
||||
const osc = ctx.createOscillator();
|
||||
const gain = ctx.createGain();
|
||||
osc.type = 'sine';
|
||||
osc.frequency.value = freq;
|
||||
osc.connect(gain);
|
||||
gain.connect(master);
|
||||
const t0 = ctx.currentTime + delay;
|
||||
gain.gain.setValueAtTime(0, t0);
|
||||
gain.gain.linearRampToValueAtTime(1, t0 + 0.015);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, t0 + 0.28);
|
||||
osc.start(t0);
|
||||
osc.stop(t0 + 0.3);
|
||||
}
|
||||
|
||||
window.setTimeout(() => {
|
||||
void ctx.close().catch(() => {
|
||||
/* ignore */
|
||||
});
|
||||
}, 600);
|
||||
}
|
||||
Reference in New Issue
Block a user