/** * 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 { return new Promise((r) => setTimeout(r, ms)); } async function main(): Promise { 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); });