P4
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { VoiceLoop, fastCommand } from '../daemon/voice-loop.js';
|
||||
import { WakeEngine } from '../daemon/wake-engine.js';
|
||||
import { VadSegmenter } from '../daemon/vad.js';
|
||||
import { PipeWireCapture, pcmRms } from '../daemon/audio-pipewire.js';
|
||||
import { SentenceBuffer, isMeaningfulTranscript } from '../daemon/transcript.js';
|
||||
|
||||
test('Phase 4 transcript filtering and sentence buffering are deterministic', () => {
|
||||
assert.equal(isMeaningfulTranscript('[BLANK_AUDIO]'), false);
|
||||
assert.equal(isMeaningfulTranscript('hi'), false);
|
||||
assert.equal(isMeaningfulTranscript('what time is it'), true);
|
||||
const out = []; const buffer = new SentenceBuffer({ onSentence: (s) => out.push(s) });
|
||||
buffer.push('First sentence. Second'); buffer.push(' sentence!'); buffer.flush();
|
||||
assert.deepEqual(out, ['First sentence.', 'Second sentence!']);
|
||||
});
|
||||
|
||||
test('Phase 4 wake engine emits only configured local detections', () => {
|
||||
const wake = new WakeEngine({ phrases: ['hey jarvis'], detect: () => ({ phrase: 'hey jarvis' }) });
|
||||
const heard = []; wake.on('wake', (phrase) => heard.push(phrase)); wake.push(Buffer.from([0]));
|
||||
assert.deepEqual(heard, ['hey jarvis']);
|
||||
wake.pause(); wake.push(Buffer.from([0])); assert.equal(heard.length, 1);
|
||||
});
|
||||
|
||||
test('Phase 4 VAD emits a bounded utterance after silence', () => {
|
||||
const vad = new VadSegmenter({ frameMs: 100, params: { threshold: 0.6, minSpeechDurationMs: 100, minSilenceDurationMs: 200 } });
|
||||
const utterances = []; vad.on('utterance', (audio) => utterances.push(audio));
|
||||
const loud = Buffer.alloc(3200); for (let i = 0; i < loud.length; i += 2) loud.writeInt16LE(20_000, i); const quiet = Buffer.alloc(3200);
|
||||
vad.push(loud); vad.push(quiet); vad.push(quiet);
|
||||
assert.equal(utterances.length, 1); assert.ok(utterances[0].length > 0); assert.ok(pcmRms(loud) > 0);
|
||||
});
|
||||
|
||||
test('Phase 4 fast commands are handled before the harness', () => {
|
||||
assert.equal(fastCommand('Hands Off'), 'cancel');
|
||||
assert.equal(fastCommand('take the wheel'), 'computer');
|
||||
assert.equal(fastCommand('ordinary question'), null);
|
||||
});
|
||||
|
||||
test('Phase 4 feedback gate drops capture while speaking and during cooldown', () => {
|
||||
const capture = new EventEmitter(); capture.start = () => {}; capture.stop = () => {};
|
||||
const wake = new WakeEngine({ detect: () => ({ phrase: 'hey jarvis' }) });
|
||||
const vad = new VadSegmenter({ frameMs: 20 }); let pushed = 0; const original = vad.push.bind(vad); vad.push = (x) => { pushed++; original(x); };
|
||||
const daemon = new EventEmitter(); daemon.state = 'LISTENING';
|
||||
const loop = new VoiceLoop({ daemon, capture, wake, vad, now: () => 1000 }); loop.running = true;
|
||||
loop.isSpeaking = true; loop.pushAudio(Buffer.alloc(3200)); assert.equal(pushed, 0);
|
||||
loop.isSpeaking = false; loop.cooldownUntil = 1200; loop.pushAudio(Buffer.alloc(3200)); assert.equal(pushed, 0);
|
||||
});
|
||||
|
||||
test('Phase 4 PipeWire capture uses a named 16 kHz mono node', () => {
|
||||
let args; const child = new EventEmitter(); child.stdout = new EventEmitter(); child.stderr = new EventEmitter(); child.kill = () => {};
|
||||
const capture = new PipeWireCapture({ spawnImpl: (_cmd, received) => { args = received; return child; } }); capture.start();
|
||||
assert.deepEqual(args, ['--record', '--raw', '--format', 's16', '--rate', '16000', '--channels', '1', '--name', 'Jarvis']);
|
||||
capture.stop();
|
||||
});
|
||||
Reference in New Issue
Block a user