2026-04-09 02:47:53 +02:00

100 lines
3.4 KiB
TypeScript

/**
* Ti-Pote — Pure tone speaker test.
*
* Generates a 440 Hz sine wave at ~70% of full scale and streams it
* to the ESP32 speaker via AUDIO_DOWN frames, then a second beep at
* 880 Hz. Completely independent of the microphone — if this does
* not produce audible sound, the problem is downstream of the ESP32
* on the speaker path (MAX98357A wiring, SD pin, VIN, speaker leads).
*
* Run with:
* HARDWARE_SERIAL_PORT=/dev/serial0 pnpm --filter @ti-pote/robot-client audio:beep
*
* Optional env:
* BEEP_MS — length of each beep in ms (default 1500)
* BEEP_FREQ — primary frequency in Hz (default 440)
* BEEP_AMP — amplitude 0.0..1.0 (default 0.7)
*/
import { HardwareService, Emotion } from '../src/hardware/index.js';
import { Esp32AudioService } from '../src/services/audio.service.js';
const path = process.env.HARDWARE_SERIAL_PORT ?? '/dev/serial0';
const baudRate = parseInt(process.env.HARDWARE_SERIAL_BAUD ?? '921600', 10);
const beepMs = parseInt(process.env.BEEP_MS ?? '1500', 10);
const beepFreq = parseInt(process.env.BEEP_FREQ ?? '440', 10);
const beepAmp = parseFloat(process.env.BEEP_AMP ?? '0.7');
const SAMPLE_RATE = 16000;
function generateSine(freqHz: number, durationMs: number, amplitude: number): Buffer {
const sampleCount = Math.floor((SAMPLE_RATE * durationMs) / 1000);
const buf = Buffer.alloc(sampleCount * 2);
const amp = Math.max(0, Math.min(1, amplitude)) * 32767;
const twoPiF = (2 * Math.PI * freqHz) / SAMPLE_RATE;
// 5 ms linear attack/release so the speaker doesn't click.
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;
}
async function sleep(ms: number): Promise<void> {
return new Promise((r) => setTimeout(r, ms));
}
async function main(): Promise<void> {
const hw = new HardwareService({ path, baudRate, heartbeatIntervalMs: 1000 });
hw.on('log', (line) => console.log(`[firmware] ${line}`));
hw.on('error', (err) => console.error(`[firmware error] ${err.message}`));
console.log(`→ opening ${path} @ ${baudRate} baud`);
await hw.connect();
try {
const rtt = await hw.ping(Buffer.from('beep'));
console.log(`→ ping round-trip: ${rtt.toFixed(1)} ms`);
const audio = new Esp32AudioService(
{
backend: 'esp32',
captureDevice: 'default',
playbackDevice: 'default',
sampleRate: SAMPLE_RATE,
bitDepth: 16,
channels: 1,
chunkDurationMs: 20,
},
hw,
);
hw.sendEmotion(Emotion.HAPPY);
console.log(`🔊 Beep 1: ${beepFreq} Hz · ${beepMs} ms · amp=${beepAmp}`);
const tone1 = generateSine(beepFreq, beepMs, beepAmp);
await audio.play(tone1);
await sleep(400);
console.log(`🔊 Beep 2: ${beepFreq * 2} Hz · ${beepMs} ms · amp=${beepAmp}`);
const tone2 = generateSine(beepFreq * 2, beepMs, beepAmp);
await audio.play(tone2);
console.log('✅ done — did you hear two beeps?');
} finally {
hw.sendEmotion(Emotion.NEUTRAL);
await sleep(200);
await hw.disconnect();
}
}
main().catch((err) => {
console.error('beep failed:', err);
process.exit(1);
});