2026-04-14 01:40:52 +02:00

81 lines
2.3 KiB
TypeScript

/**
* Pre-generated notification sounds for the robot.
*
* All sounds are raw S16LE mono PCM at 16 kHz, ready to pass to AudioService.play().
* Generated once at import time — zero runtime cost per playback.
*/
const SAMPLE_RATE = 16_000;
/**
* Generate a sine wave tone with attack/release envelope to prevent speaker clicks.
*/
function generateTone(freqHz: number, durationMs: number, amplitude = 0.5): Buffer {
const sampleCount = Math.floor((SAMPLE_RATE * durationMs) / 1000);
const buf = Buffer.alloc(sampleCount * 2); // 16-bit = 2 bytes per sample
const amp = Math.max(0, Math.min(1, amplitude)) * 32767;
const twoPiF = (2 * Math.PI * freqHz) / SAMPLE_RATE;
// 5ms linear attack/release envelope
const rampSamples = Math.floor((SAMPLE_RATE * 5) / 1000);
for (let i = 0; i < sampleCount; i++) {
let env = 1;
if (i < rampSamples) env = i / rampSamples;
else if (i > sampleCount - rampSamples) env = (sampleCount - i) / rampSamples;
const s = Math.round(Math.sin(i * twoPiF) * amp * env);
buf.writeInt16LE(Math.max(-32768, Math.min(32767, s)), i * 2);
}
return buf;
}
/**
* Concatenate multiple PCM buffers with silence gaps between them.
*/
function concat(parts: Buffer[], gapMs = 0): Buffer {
if (gapMs <= 0) return Buffer.concat(parts);
const gapBytes = Math.floor((SAMPLE_RATE * gapMs) / 1000) * 2;
const silence = Buffer.alloc(gapBytes);
const result: Buffer[] = [];
for (let i = 0; i < parts.length; i++) {
result.push(parts[i]);
if (i < parts.length - 1) result.push(silence);
}
return Buffer.concat(result);
}
// ── Pre-generated sounds ──────────────────────────────────────────
/**
* Short rising two-tone chirp: "bi-bip!" (80ms + 80ms)
* Played when a remote trigger starts a conversation.
*/
export const SOUND_TRIGGER = concat(
[
generateTone(660, 80, 0.4), // E5
generateTone(880, 80, 0.4), // A5
],
30, // 30ms gap
);
/**
* Single soft beep for generic notifications.
*/
export const SOUND_NOTIFY = generateTone(440, 120, 0.3);
/**
* Three descending tones for errors.
*/
export const SOUND_ERROR = concat(
[
generateTone(880, 100, 0.35),
generateTone(660, 100, 0.35),
generateTone(440, 150, 0.35),
],
40,
);