Skip to main content

RNNoise or Silero: choosing a voice activity detector for dictation

RNNoise suppresses noise and reports speech probability as a by-product. Silero detects speech and leaves the audio alone. Why Voice Type gates with Silero, with the thresholds it actually ships.

Key takeaways

answer-first
  • Segmentation decides what the recogni

The recognizer does not need to hear your keyboard, the air conditioner, or the three seconds of silence while you think.

Voice Type uses a streaming Silero voice activity detector to decide which parts of the microphone stream are speech. Everything downstream, including how long each batch is and when the model runs, follows from what that gate marks.

A correction. An earlier version of this page said Voice Type used RNNoise. It does not, and it did not. The app has never shipped RNNoise, and the claim was wrong when we published it. The gate is Silero, and the settings below are the ones that actually ship. If you arrived here searching for RNNoise, the next section is for you.

RNNoise, and why it is not the gate

RNNoise is a recurrent neural network for noise suppression, published by Xiph.Org and released under a BSD licence. It is small, fast, and genuinely good at what it does: it takes noisy speech and returns cleaner speech. It ships inside a lot of voice software, and the version most people meet is the one bundled with OBS.

The reason it is not in Voice Type is that noise suppression and voice activity detection are different jobs.

  • Noise suppression changes the audio. It attenuates what it judges to be noise and passes the rest through, so the signal handed downstream is not the signal that was captured.
  • Voice activity detection changes nothing. It reads the audio and marks which spans contain speech. The recognizer still receives the original samples.

RNNoise does expose a voice activity probability as a by-product of the suppression it performs, which is why it turns up in searches for a VAD. Using that output as a gate means running a suppressor you do not want in order to get a signal that is not its main purpose. Silero is a detector, so it does the detection job directly and leaves the audio alone.

That distinction matters more for dictation than for a call. On a call, cleaner-sounding audio is the goal, and a suppressor removing some real speech along with the fan is an acceptable trade because a listener fills the gap. A recognizer does not fill the gap. It transcribes what it is given, so audio that has been altered before recognition can cost accuracy in exactly the consonants a suppressor finds hardest to distinguish from noise.

If you are looking for an RNNoise VAD threshold, the value depends on which wrapper you are using, since RNNoise itself returns a probability between 0 and 1 and each integration picks its own cut-off. For reference, the equivalent numbers in this app's Silero gate are 0.30 to stay in speech and 0.22 to enter it, and the reasoning behind the two values is in the section below. Those numbers are Silero's, not RNNoise's, and they will not transfer directly.

RNNoise is a good piece of software. It is solving a problem we do not have.

TL;DR

  • Segmentation decides what the recognizer is given. A model handed silence still returns text, because returning text is what it does.
  • The gate runs on 512-sample frames at 16 kHz, with a speech threshold of 0.30.
  • Speech regions get 1,000 ms of pre-roll on the first phrase, then 150 ms either side, so words are not clipped at the boundary.
  • A batch shorter than 5 seconds is never sent on a pause. It waits for the next phrase.

Why segmentation, not the model, sets the accuracy ceiling

Feed the recognizer a clean speech segment and it transcribes speech. Feed it three seconds of HVAC hum with one word buried in it and the decoder still produces text. The failure looks like a model error and is an input error.

That is why the gate is the interesting component. It is not there to make audio sound nicer to a human. It is there to make sure the model is only ever asked about audio that contains a question.

The settings that ship

These are the current defaults in the balanced dictation mode, which is what runs unless you switch to raw capture.

| Setting | Value | | --- | --- | | Frame size | 512 samples at 16 kHz | | Speech threshold | 0.30 | | Quiet-start threshold | 0.22 | | Pre-roll before the first phrase | 1,000 ms | | Pre-roll on later phrases | 150 ms | | Post-roll | 150 ms | | Silence that closes a phrase, live | 400 ms | | Silence that closes a phrase, at stop | 350 ms |

The two thresholds are the part worth understanding. A single threshold has to be both sensitive enough to catch a quiet first syllable and strict enough to ignore a fan. Those are different jobs, so the gate uses a lower bar (0.22) when it is waiting for speech to begin and the normal bar (0.30) once speech is running.

The asymmetric pre-roll does the same trick in the time domain. The first word of an utterance is the one most often clipped, because the gate has nothing before it to work from, so it gets a full second of audio ahead of the detection point. Later phrases only need 150 ms, because the gate is already tracking you.

Why a short batch waits

The batcher appends whole speech regions and refuses to cut one in half. It also refuses to send a batch that is too short to be worth a decode.

If the current batch holds less than 5 seconds of selected audio and you pause, nothing is sent. The batch survives the pause and the next phrase joins it. That is deliberate: a 1.5-second fragment decoded on its own has no surrounding context, and Whisper-family models use context. Two fragments decoded together beat two fragments decoded apart.

At the 5-second mark the normal rules resume: a 400 ms pause after a closed phrase flushes the batch, and so does a phrase that would push the batch past the 29-second target. When you release the hotkey, everything remaining is flushed regardless of length.

Flow diagram of the Voice Type speech gate. A 16 kHz microphone stream feeds a Silero voice activity detector running on ggml on a single CPU thread with the GPU disabled. The detector marks speech regions, the batcher holds a region until it reaches the target length, clamped between 15 and 29 seconds and never split mid-phrase, and only then hands the batch to the whisper.cpp recognizer running on the Metal GPU.

The gate runs on one CPU thread so it never competes with the recognizer for the GPU. Source: StreamingSileroSpeechGate.swift.

The path a batch takes

Once a batch is flushed, it is conditioned and then decoded:

  1. Remove the DC offset.
  2. Apply the active filters and loudness control, including a 50 Hz high-pass by default.
  3. Resample to the 16 kHz Whisper expects, if the capture device was not already there.
  4. Run the model off the main actor.
  5. Join the resulting text to the transcript, deduplicating repeated words at the batch boundary.

Step 5 matters more than it looks. Because batches are decoded independently, the same word can land at the end of one and the start of the next. The last committed words are also passed forward as prompt context, so the model knows what it just said without re-decoding old audio.

When the gate fails

Voice activity detection is a model too, and it can be wrong. If Silero fails outright, the pipeline can fall back to using captured audio as marked fallback segments.

There is one deliberate exception. If the gate runs normally and reports no speech at all, the app does not fall back to an energy threshold. That is the failure mode where fans, typing, and the click of the stop key become a transcript, and a false transcript is worse than no transcript.

Related: Why we turned off the Core ML encoder · Resampling to 16 kHz