voice - implement super simple vad

This commit is contained in:
Benjamin Pasero
2023-09-15 10:05:34 +02:00
parent bccfade64a
commit ea27cc09a8
3 changed files with 43 additions and 13 deletions
@@ -69,18 +69,11 @@ class VoiceTranscriber extends Disposable {
}
private async handleRequest(e: MessageEvent, cancellation: CancellationToken): Promise<void> {
if (!(Array.isArray(e.data))) {
if (!(e.data instanceof Float32Array)) {
return;
}
const newData: Float32Array[] = [];
for (const channelData of e.data) {
if (channelData instanceof Float32Array) {
newData.push(channelData);
}
}
const dataCandidate = this.joinFloat32Arrays(this.data ? [this.data, ...newData] : newData);
const dataCandidate = this.data ? this.joinFloat32Arrays([this.data, e.data]) : e.data;
if (dataCandidate.length > VoiceTranscriber.MAX_DATA_LENGTH) {
this.logService.warn(`[voice] transcriber: refusing to accept more than 30s of audio data`);
@@ -13,6 +13,7 @@ declare class AudioWorkletProcessor {
interface IVoiceTranscriptionWorkletOptions extends AudioWorkletNodeOptions {
processorOptions: {
readonly bufferTimespan: number;
readonly vadThreshold: number;
};
}
@@ -22,6 +23,7 @@ class VoiceTranscriptionWorklet extends AudioWorkletProcessor {
private stopped: boolean = false;
private buffer: Float32Array[] = [];
private text = '';
private sharedProcessConnection: MessagePort | undefined = undefined;
@@ -43,7 +45,7 @@ class VoiceTranscriptionWorklet extends AudioWorkletProcessor {
}
if (typeof event.data === 'string') {
this.port.postMessage(event.data);
this.processText(event.data);
}
};
@@ -63,6 +65,11 @@ class VoiceTranscriptionWorklet extends AudioWorkletProcessor {
};
}
private processText(text: string = this.text): void {
this.text = text;
this.port.postMessage(this.text);
}
override process(inputs: [Float32Array[]]): boolean {
if (this.startTime === undefined) {
this.startTime = Date.now();
@@ -76,16 +83,43 @@ class VoiceTranscriptionWorklet extends AudioWorkletProcessor {
this.buffer.push(inputChannelData.slice(0));
if (Date.now() - this.startTime > this.options.processorOptions.bufferTimespan && this.sharedProcessConnection) {
const buffer = this.buffer;
const buffer = this.joinFloat32Arrays(this.buffer);
this.buffer = [];
this.sharedProcessConnection.postMessage(buffer);
if (!this.appearsToBeSilence(buffer)) {
this.sharedProcessConnection.postMessage(buffer);
} else {
this.processText();
}
this.startTime = Date.now();
}
return !this.stopped;
}
private appearsToBeSilence(data: Float32Array): boolean {
let sum = 0;
for (let i = 0; i < data.length; i++) {
sum += data[i] * data[i];
}
const rms = Math.sqrt(sum / data.length);
return rms < this.options.processorOptions.vadThreshold;
}
private joinFloat32Arrays(float32Arrays: Float32Array[]): Float32Array {
const result = new Float32Array(float32Arrays.reduce((prev, curr) => prev + curr.length, 0));
let offset = 0;
for (const float32Array of float32Arrays) {
result.set(float32Array, offset);
offset += float32Array.length;
}
return result;
}
}
// @ts-ignore
@@ -41,6 +41,7 @@ export interface IWorkbenchVoiceRecognitionService {
interface IVoiceTranscriptionWorkletOptions extends AudioWorkletNodeOptions {
processorOptions: {
readonly bufferTimespan: number;
readonly vadThreshold: number;
};
}
@@ -93,6 +94,7 @@ export class WorkbenchVoiceRecognitionService implements IWorkbenchVoiceRecognit
private static readonly AUDIO_CHANNELS = 1;
private static readonly BUFFER_TIMESPAN = 1000;
private static readonly VAD_THRESHOLD = 0.02;
constructor(
@IProgressService private readonly progressService: IProgressService,
@@ -172,7 +174,8 @@ export class WorkbenchVoiceRecognitionService implements IWorkbenchVoiceRecognit
channelCount: WorkbenchVoiceRecognitionService.AUDIO_CHANNELS,
channelCountMode: 'explicit',
processorOptions: {
bufferTimespan: WorkbenchVoiceRecognitionService.BUFFER_TIMESPAN
bufferTimespan: WorkbenchVoiceRecognitionService.BUFFER_TIMESPAN,
vadThreshold: WorkbenchVoiceRecognitionService.VAD_THRESHOLD
}
}, onDidTranscribe, this.sharedProcessService);
await voiceTranscriptionTarget.start(cts.token);