Auricle v1

Our own voice activity detector, trained from scratch for one job the standard models do not do: keep listening to the user when somebody else in the room is talking. 83,393 parameters, 334 KB of weights, one decision every 16 ms. MIT. Weights on Hugging Face as auricle-voice/auricle-v1.

What it is

40-band log-mel features every 16 ms, two causal convolutions, a two-layer GRU of 64 units, one sigmoid per frame. It runs as a hand-written Rust forward pass inside the same WebAssembly module as the rest of Auricle, selected with model: 'auricle'. Silero VAD v6 stays bundled as model: 'silero', which is the default until the field numbers for v1 are in.

Numbers

Every row is produced by a script in the training repository and can be re-run. Silero v6 is scored on identical audio.

Auricle v1Silero v6
TEN VAD test set, 30 human-labelled recordings, frame F1 at 0.592.7%93.6%
Competing talker, 500 clips: foreground F194.2%89.4%
Competing talker: foreground recall97.3%97.4%
Competing talker: background-only frames called speech35%82%
Parameters83kabout 1M
Weights334 KB974 KB
Background false-alarm rate against foreground recall with a second talker in the room, Auricle v1 vs Silero v6
Every threshold, same frames. At any share of the user's speech kept, Auricle v1 lets through three to four times less of the other person. The single-speaker curve and the per-language chart are on the benchmarks page.

Across languages (FLEURS test splits, two hours each, same-language background talker):

LanguageClean agreement with Silero's labelsCompeting F1, v1 vs SileroBackground false alarms, v1 vs Silero
English98.6%94.6% vs 89.7%35% vs 82%
Spanish98.6%95.9% vs 93.3%52% vs 87%
German98.4%94.5% vs 92.3%61% vs 83%
Arabic98.1%96.4% vs 94.5%59% vs 83%
Hindi97.4%95.3% vs 93.4%60% vs 80%
Mandarin95.0%89.5% vs 86.1%59% vs 84%
Japanese93.3%90.1% vs 87.3%63% vs 77%

How it was trained

961 hours of LibriSpeech read speech, labelled once on the clean audio by Silero VAD v6. The model never hears that clean audio. Each training clip is the labelled speech after a room impulse response, MUSAN noise or music, a second talker 3 to 25 dB quieter in 45% of clips, crowd babble, laughs and coughs at the user's level, bandlimiting, clipping and random gain. The labels stay those of the foreground speaker, so the only way to score well is to learn which voice is the user. 20,000 steps of 128 five-second clips on a six-core laptop CPU, 9.7 hours.

  1. Label clean speech once. 961 hours of LibriSpeech, a speech or silence label every 16 ms on the clean recording. The model never hears that audio.
  2. Corrupt what the model hears. A real room response (50% of clips), MUSAN noise or music at -5 to 25 dB (85%), a second held-out speaker 3 to 25 dB quieter labelled as not speech (45%, a quarter of those crowd babble), laughs, coughs, sighs and sniffs at the user's level labelled as not speech (20%), band-limiting (20%), clipping (10%), gain -30 to 0 dB, and 12% of clips with no foreground speech at all.
  3. Keep the clean labels. A frame where only the background talker speaks is labelled silent, so the only way to score well is to learn which voice is the user.
  4. Small and causal. 40-band log-mel, two causal convolutions, a two-layer GRU, one sigmoid. 83,393 parameters, no look-ahead.
  5. One CPU, one night. 20,000 steps of 128 clips, AdamW with cosine decay, weight averaging, boundary frames down-weighted. 9.7 hours on a six-core laptop CPU.
  6. Checked against humans. TEN VAD's annotated recordings for accuracy, held-out speakers in unseen rooms for the two-talker test, FLEURS for seven languages, and a Rust-to-PyTorch parity test within 1e-4.

What we tried that did not work

A second run with harder mixing and 4x loss weight on background-only frames moved the operating point and nothing else: at equal recall the false-alarm rate was identical to the decimal, and real-speech accuracy dropped 1.3 points. A third run that added ten hours each of six languages changed every number by less than 0.3 points. Both are documented in docs/RESEARCH.md. The lesson: with only the mixed audio as input, a small causal model separates talkers by level, and level is ambiguous. The next model gets an enrolment embedding.

Caveats, stated plainly

Use it

import { createVad } from 'auricle';
const vad = await createVad({ model: 'auricle', onSpeechEnd: (audio) => upload(audio) });
await vad.start();

Weights and the ONNX export: huggingface.co/auricle-voice/auricle-v1. Training code: VAD-train. Labels bootstrapped from Silero VAD (MIT).

Questions people ask

What is Auricle v1?

An open-source voice activity detection model with 83,393 parameters: 40-band log-mel features, two causal 1-D convolutions and a two-layer GRU, scoring 16 ms hops. It is trained to detect the person in front of the microphone rather than any speech in the room.

How was Auricle v1 trained?

From scratch on public speech (LibriSpeech), noise, music and room impulse responses, with Silero VAD v6 providing frame labels on the clean audio. The audio the model hears is then corrupted with rooms, noise and a competing talker in 45% of clips while the clean labels are kept, so the model must learn to separate the foreground voice.

How big is Auricle v1?

334 KB of float32 weights, 338 KB gzipped as a WebAssembly module with the weights inside. Silero VAD v6 is about 1M parameters and 974 KB.

Can I use Auricle v1 outside the browser?

Yes. The weights ship as a flat float32 blob with a JSON layout and as ONNX on Hugging Face (auricle-voice/auricle-v1), and the Rust crate auricle-vad runs on any target.