pub mod wake;
pub mod whisper;
use crate::error::{DrivenError, Result};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::{mpsc, RwLock};
pub use wake::{WakeWordDetector, WakeWord};
pub use whisper::WhisperClient;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoiceWakeConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_wake_words")]
pub wake_words: Vec<String>,
#[serde(default = "default_sensitivity")]
pub sensitivity: f32,
#[serde(default = "default_sample_rate")]
pub sample_rate: u32,
pub whisper_model: Option<PathBuf>,
#[serde(default = "default_true")]
pub use_vad: bool,
#[serde(default = "default_silence_threshold")]
pub silence_threshold: f32,
#[serde(default = "default_min_speech_duration")]
pub min_speech_duration: u32,
}
fn default_true() -> bool {
true
}
fn default_wake_words() -> Vec<String> {
vec!["hey dx".to_string(), "ok dx".to_string()]
}
fn default_sensitivity() -> f32 {
0.5
}
fn default_sample_rate() -> u32 {
16000
}
fn default_silence_threshold() -> f32 {
-40.0
}
fn default_min_speech_duration() -> u32 {
300
}
impl Default for VoiceWakeConfig {
fn default() -> Self {
Self {
enabled: true,
wake_words: default_wake_words(),
sensitivity: default_sensitivity(),
sample_rate: default_sample_rate(),
whisper_model: None,
use_vad: true,
silence_threshold: default_silence_threshold(),
min_speech_duration: default_min_speech_duration(),
}
}
}
impl VoiceWakeConfig {
pub fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self> {
let content = std::fs::read_to_string(path.as_ref())
.map_err(|e| DrivenError::Io(e))?;
Self::parse_sr(&content)
}
fn parse_sr(_content: &str) -> Result<Self> {
Ok(Self::default())
}
}
#[derive(Debug, Clone)]
pub enum VoiceWakeEvent {
WakeDetected {
wake_word: String,
confidence: f32,
},
SpeechTranscribed {
text: String,
confidence: f32,
},
SpeechStarted,
SpeechEnded,
Error(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VoiceWakeState {
Idle,
Listening,
Activated,
Processing,
}
pub struct VoiceWake {
config: VoiceWakeConfig,
state: Arc<RwLock<VoiceWakeState>>,
detector: Option<WakeWordDetector>,
whisper: Option<WhisperClient>,
event_tx: Option<mpsc::Sender<VoiceWakeEvent>>,
}
impl VoiceWake {
pub fn new(config: &VoiceWakeConfig) -> Result<Self> {
let detector = if config.enabled {
Some(WakeWordDetector::new(&config.wake_words, config.sensitivity)?)
} else {
None
};
let whisper = if let Some(ref model_path) = config.whisper_model {
Some(WhisperClient::new(model_path)?)
} else {
None
};
Ok(Self {
config: config.clone(),
state: Arc::new(RwLock::new(VoiceWakeState::Idle)),
detector,
whisper,
event_tx: None,
})
}
pub fn is_enabled(&self) -> bool {
self.config.enabled && self.detector.is_some()
}
pub async fn state(&self) -> VoiceWakeState {
*self.state.read().await
}
pub async fn start_listening(&mut self) -> Result<mpsc::Receiver<VoiceWakeEvent>> {
if !self.is_enabled() {
return Err(DrivenError::Config("Voice wake not enabled".into()));
}
let (tx, rx) = mpsc::channel(100);
self.event_tx = Some(tx);
*self.state.write().await = VoiceWakeState::Listening;
self.start_audio_capture().await?;
Ok(rx)
}
pub async fn stop_listening(&mut self) -> Result<()> {
*self.state.write().await = VoiceWakeState::Idle;
self.event_tx = None;
Ok(())
}
pub async fn process_audio(&mut self, samples: &[f32]) -> Result<()> {
let state = *self.state.read().await;
match state {
VoiceWakeState::Idle => return Ok(()),
VoiceWakeState::Listening => {
if let Some(ref detector) = self.detector {
if let Some((wake_word, confidence)) = detector.detect(samples)? {
self.emit_event(VoiceWakeEvent::WakeDetected {
wake_word,
confidence,
}).await;
*self.state.write().await = VoiceWakeState::Activated;
}
}
}
VoiceWakeState::Activated => {
self.emit_event(VoiceWakeEvent::SpeechStarted).await;
*self.state.write().await = VoiceWakeState::Processing;
}
VoiceWakeState::Processing => {
if let Some(ref whisper) = self.whisper {
match whisper.transcribe(samples).await {
Ok((text, confidence)) => {
self.emit_event(VoiceWakeEvent::SpeechTranscribed {
text,
confidence,
}).await;
}
Err(e) => {
self.emit_event(VoiceWakeEvent::Error(e.to_string())).await;
}
}
}
self.emit_event(VoiceWakeEvent::SpeechEnded).await;
*self.state.write().await = VoiceWakeState::Listening;
}
}
Ok(())
}
async fn start_audio_capture(&self) -> Result<()> {
tracing::info!("Starting audio capture at {} Hz", self.config.sample_rate);
Ok(())
}
async fn emit_event(&self, event: VoiceWakeEvent) {
if let Some(ref tx) = self.event_tx {
let _ = tx.send(event).await;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = VoiceWakeConfig::default();
assert!(config.enabled);
assert_eq!(config.wake_words, vec!["hey dx", "ok dx"]);
assert_eq!(config.sample_rate, 16000);
}
#[tokio::test]
async fn test_voice_wake_creation() {
let config = VoiceWakeConfig::default();
let wake = VoiceWake::new(&config);
assert!(wake.is_ok());
}
}