81 lines
2.3 KiB
TypeScript
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,
|
|
);
|