Tune web voice VAD for short utterances
This commit is contained in:
@@ -8,6 +8,10 @@ import { activateKeepAwakeAsync, deactivateKeepAwake } from "expo-keep-awake";
|
|||||||
|
|
||||||
const VOICE_CONVERSATION_ID_STORAGE_KEY = "@paseo:voice-conversation-id";
|
const VOICE_CONVERSATION_ID_STORAGE_KEY = "@paseo:voice-conversation-id";
|
||||||
const KEEP_AWAKE_TAG = "paseo:voice";
|
const KEEP_AWAKE_TAG = "paseo:voice";
|
||||||
|
const VOICE_VAD_VOLUME_THRESHOLD = 0.18;
|
||||||
|
const VOICE_VAD_SILENCE_DURATION_MS = 1400;
|
||||||
|
const VOICE_VAD_SPEECH_CONFIRMATION_MS = 120;
|
||||||
|
const VOICE_VAD_DETECTION_GRACE_PERIOD_MS = 700;
|
||||||
|
|
||||||
interface VoiceContextValue {
|
interface VoiceContextValue {
|
||||||
isVoiceMode: boolean;
|
isVoiceMode: boolean;
|
||||||
@@ -123,10 +127,10 @@ export function VoiceProvider({ children }: VoiceProviderProps) {
|
|||||||
console.error("[Voice] Cannot handle error - setMessages not available from SessionState");
|
console.error("[Voice] Cannot handle error - setMessages not available from SessionState");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
volumeThreshold: 0.3,
|
volumeThreshold: VOICE_VAD_VOLUME_THRESHOLD,
|
||||||
silenceDuration: 2000,
|
silenceDuration: VOICE_VAD_SILENCE_DURATION_MS,
|
||||||
speechConfirmationDuration: 300,
|
speechConfirmationDuration: VOICE_VAD_SPEECH_CONFIRMATION_MS,
|
||||||
detectionGracePeriod: 200,
|
detectionGracePeriod: VOICE_VAD_DETECTION_GRACE_PERIOD_MS,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -88,4 +88,37 @@ describe("SpeechSegmenter", () => {
|
|||||||
expect(lastCall.isLast).toBe(true);
|
expect(lastCall.isLast).toBe(true);
|
||||||
expect(lastCall.audioData.length).toBeGreaterThan(0);
|
expect(lastCall.audioData.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps detection alive across a brief pause and confirms short utterances", () => {
|
||||||
|
const onSpeechStart = vi.fn();
|
||||||
|
const detectingChanges: boolean[] = [];
|
||||||
|
|
||||||
|
const segmenter = new SpeechSegmenter(
|
||||||
|
{
|
||||||
|
enableContinuousStreaming: false,
|
||||||
|
volumeThreshold: 0.18,
|
||||||
|
silenceDurationMs: 1400,
|
||||||
|
speechConfirmationMs: 120,
|
||||||
|
detectionGracePeriodMs: 700,
|
||||||
|
minChunkDurationMs: 100,
|
||||||
|
pcmSampleRate: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSpeechStart,
|
||||||
|
onDetectingChange: (v) => detectingChanges.push(v),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const t0 = 20_000;
|
||||||
|
|
||||||
|
segmenter.pushVolumeLevel(0.4, t0);
|
||||||
|
segmenter.pushPcmChunk(mkPcmBytes(20));
|
||||||
|
segmenter.pushVolumeLevel(0.0, t0 + 80);
|
||||||
|
segmenter.pushPcmChunk(mkPcmBytes(20));
|
||||||
|
segmenter.pushVolumeLevel(0.4, t0 + 140);
|
||||||
|
segmenter.pushPcmChunk(mkPcmBytes(20));
|
||||||
|
|
||||||
|
expect(detectingChanges).toContain(true);
|
||||||
|
expect(onSpeechStart).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user