#[cfg(feature = "vad")]
use std::path::PathBuf;
use thiserror::Error;
#[cfg(target_os = "macos")]
const PERMISSION_HINT: &str = "Enable in System Preferences > Security & Privacy > Microphone.";
#[cfg(target_os = "windows")]
const PERMISSION_HINT: &str = "Enable in Settings > Privacy & Security > Microphone.";
#[cfg(target_os = "linux")]
const PERMISSION_HINT: &str =
"Check your distribution's audio permissions (PulseAudio / PipeWire).";
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
const PERMISSION_HINT: &str = "Check your system audio permissions.";
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DecibriError {
#[error("sample rate must be between 1000 and 384000")]
SampleRateOutOfRange,
#[error("channels must be between 1 and 32")]
ChannelsOutOfRange,
#[error("frames per buffer must be between 64 and 65536")]
FramesPerBufferOutOfRange,
#[error("format must be 'int16' or 'float32'")]
InvalidFormat,
#[error("No microphone found matching \"{0}\"")]
MicrophoneNotFound(String),
#[error("No speaker found matching \"{0}\"")]
SpeakerNotFound(String),
#[error("Multiple devices match \"{name}\":\n{matches}")]
MultipleDevicesMatch { name: String, matches: String },
#[error("device index out of range. Call Decibri.devices() to list available devices")]
DeviceIndexOutOfRange,
#[error("No microphone found. Check system audio input settings.")]
NoMicrophoneFound,
#[error("No speaker found. Check system audio settings.")]
NoSpeakerFound,
#[error("Selected device is not a valid microphone.")]
NotAnInputDevice,
#[error("Failed to enumerate devices: {0}")]
DeviceEnumerationFailed(String),
#[error("audio stream is already running. Call stop() first.")]
AlreadyRunning,
#[error("Failed to open audio stream: {0}")]
StreamOpenFailed(String),
#[error("Failed to start audio stream: {0}")]
StreamStartFailed(String),
#[error("Microphone permission denied. {}", PERMISSION_HINT)]
PermissionDenied,
#[error("Microphone stream is closed")]
MicrophoneStreamClosed,
#[error("Speaker stream is closed")]
SpeakerStreamClosed,
#[error("Silero VAD only supports sample rates 8000 and 16000")]
VadSampleRateUnsupported(u32),
#[error("VAD threshold must be between 0.0 and 1.0")]
VadThresholdOutOfRange(f32),
#[cfg(feature = "vad")]
#[error(
"decibri: failed to initialize ONNX Runtime: {source}. \
Either pass ort_library_path when constructing the VAD, set \
ORT_DYLIB_PATH to point to a valid ONNX Runtime library, or \
enable the `ort-download-binaries` feature for zero-config builds."
)]
OrtInitFailed {
#[source]
source: ort::Error,
},
#[cfg(feature = "vad")]
#[error(
"decibri: failed to load ONNX Runtime from {}: {source}. \
If ORT_DYLIB_PATH is set, verify it points to a valid ONNX Runtime \
library for your platform. Otherwise the bundled ONNX Runtime may \
be missing from your installation. Try reinstalling decibri.",
path.display()
)]
OrtLoadFailed {
path: PathBuf,
#[source]
source: ort::Error,
},
#[cfg(feature = "vad")]
#[error(
"decibri: failed to load ONNX Runtime from {}: {reason}. \
If ORT_DYLIB_PATH is set, verify it points to a valid ONNX Runtime \
library for your platform. Otherwise the bundled ONNX Runtime may \
be missing from your installation. Try reinstalling decibri.",
path.display()
)]
OrtPathInvalid { path: PathBuf, reason: &'static str },
#[cfg(feature = "vad")]
#[error("Failed to create ort session builder: {0}")]
OrtSessionBuildFailed(#[source] ort::Error),
#[cfg(feature = "vad")]
#[error("Failed to set ort threads: {0}")]
OrtThreadsConfigFailed(#[source] ort::Error),
#[cfg(feature = "vad")]
#[error("Failed to load Silero VAD model from {}: {source}", path.display())]
VadModelLoadFailed {
path: PathBuf,
#[source]
source: ort::Error,
},
#[cfg(feature = "vad")]
#[error("Silero VAD inference failed: {0}")]
OrtInferenceFailed(#[source] ort::Error),
#[cfg(feature = "vad")]
#[error("Failed to create {kind} tensor: {source}")]
OrtTensorCreateFailed {
kind: &'static str,
#[source]
source: ort::Error,
},
#[cfg(feature = "vad")]
#[error("Failed to extract {kind} tensor: {source}")]
OrtTensorExtractFailed {
kind: &'static str,
#[source]
source: ort::Error,
},
#[error(
"ONNX Runtime was initialized in pid {init_pid} but is being used in pid {current_pid}. \
Python's fork() start method is incompatible with ORT initialization. Use \
multiprocessing.set_start_method('spawn') or construct Microphone(vad='silero') \
inside each child process."
)]
ForkAfterOrtInit { init_pid: u32, current_pid: u32 },
#[error("ONNX backend error from {backend}: {source}")]
OnnxBackendFailed {
backend: &'static str,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
}
impl DecibriError {
pub fn is_ort_path_error(&self) -> bool {
#[cfg(feature = "vad")]
{
matches!(
self,
Self::OrtLoadFailed { .. } | Self::OrtPathInvalid { .. }
)
}
#[cfg(not(feature = "vad"))]
{
false
}
}
}