cjpais/Handy · error

threshold must be between 0.0 and 1.0

Error message

threshold must be between 0.0 and 1.0

What it means

SileroVad::new validates its probability threshold argument before creating the ONNX engine: the value must lie in 0.0..=1.0 inclusive. The bail happens before any resource is loaded, so this is a caller-contract violation, not an environment failure. NaN also fails the range check and triggers the same bail.

Source

Thrown at src-tauri/src/audio_toolkit/vad/silero.rs:21

use vad_rs::Vad;

use super::{VadFrame, VoiceActivityDetector};
use crate::audio_toolkit::constants;

const SILERO_FRAME_MS: u32 = 30;
const SILERO_FRAME_SAMPLES: usize =
    (constants::WHISPER_SAMPLE_RATE * SILERO_FRAME_MS / 1000) as usize;

pub struct SileroVad {
    engine: Vad,
    threshold: f32,
}

impl SileroVad {
    pub fn new<P: AsRef<Path>>(model_path: P, threshold: f32) -> Result<Self> {
        if !(0.0..=1.0).contains(&threshold) {
            anyhow::bail!("threshold must be between 0.0 and 1.0");
        }

        Ok(Self {
            engine: Vad::new(&model_path, constants::WHISPER_SAMPLE_RATE as usize)
                .map_err(|e| anyhow::anyhow!("Failed to create VAD: {e}"))?,
            threshold,
        })
    }
}

impl VoiceActivityDetector for SileroVad {
    fn push_frame<'a>(&'a mut self, frame: &'a [f32]) -> Result<VadFrame<'a>> {
        if frame.len() != SILERO_FRAME_SAMPLES {
            anyhow::bail!(
                "expected {SILERO_FRAME_SAMPLES} samples, got {}",
                frame.len()
            );
        }

View on GitHub (pinned to 98a4d80cce)

Solutions

  1. Clamp before constructing: SileroVad::new(path, t.clamp(0.0, 1.0))
  2. Check the unit of the incoming value — percent vs fraction — and divide by 100.0 at the settings boundary
  3. Validate and reject out-of-range values in the settings UI/store so they never reach the constructor
  4. If NaN appears, trace the upstream math (division by zero, empty-window average)

Example fix

// before
let silero = SileroVad::new(vad_path, user_threshold)?;

// after
let threshold = if user_threshold.is_finite() {
    user_threshold.clamp(0.0, 1.0)
} else {
    VAD_THRESHOLD // fall back to the built-in default
};
let silero = SileroVad::new(vad_path, threshold)?;
Defensive patterns

Strategy: validation

Validate before calling

fn normalized_threshold(raw: f32) -> Option<f32> {
    if raw.is_finite() && (0.0..=1.0).contains(&raw) {
        Some(raw)
    } else {
        None
    }
}

// before constructing:
let t = normalized_threshold(user_value).unwrap_or(VAD_THRESHOLD);
let silero = SileroVad::new(vad_path, t)?;

Try / catch

match SileroVad::new(path, t) {
    Ok(v) => v,
    Err(e) if e.to_string().contains("threshold must be between") => {
        // fall back to the default threshold rather than failing the session
        SileroVad::new(path, VAD_THRESHOLD)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling SileroVad::new(model_path, threshold) with threshold < 0.0 or > 1.0 (e.g. 1.5), or with NaN/Infinity (a divide-by-zero or bad parse feeding the value). In this repo the caller passes the compile-time VAD_THRESHOLD constant, so hitting it means custom code or a modified constant.

Common situations: Exposing VAD sensitivity as a user setting without clamping; parsing a percentage (0-100) from config and passing it where a 0.0-1.0 fraction is expected; computing a threshold dynamically (average, ratio) that can go out of range or NaN.

Related errors


AI-assisted analysis of cjpais/Handy@98a4d80cce (2026-08-16). Data as JSON: /api/errors/84408a9e8ea9903c. Report an issue: GitHub.