use crate::error::{DrivenError, Result};
#[derive(Debug, Clone)]
pub struct WakeWord {
pub phrase: String,
pub phonemes: Vec<String>,
pub sensitivity: Option<f32>,
}
impl WakeWord {
pub fn new(phrase: impl Into<String>) -> Self {
let phrase = phrase.into();
Self {
phonemes: Self::to_phonemes(&phrase),
phrase,
sensitivity: None,
}
}
pub fn with_sensitivity(mut self, sensitivity: f32) -> Self {
self.sensitivity = Some(sensitivity);
self
}
fn to_phonemes(phrase: &str) -> Vec<String> {
phrase
.to_lowercase()
.split_whitespace()
.map(|w| w.to_string())
.collect()
}
}
pub struct WakeWordDetector {
wake_words: Vec<WakeWord>,
sensitivity: f32,
buffer: Vec<f32>,
buffer_size: usize,
}
impl WakeWordDetector {
const DEFAULT_BUFFER_SIZE: usize = 16000;
pub fn new(phrases: &[String], sensitivity: f32) -> Result<Self> {
let wake_words = phrases
.iter()
.map(|p| WakeWord::new(p))
.collect();
Ok(Self {
wake_words,
sensitivity,
buffer: Vec::with_capacity(Self::DEFAULT_BUFFER_SIZE),
buffer_size: Self::DEFAULT_BUFFER_SIZE,
})
}
pub fn add_wake_word(&mut self, word: WakeWord) {
self.wake_words.push(word);
}
pub fn detect(&self, samples: &[f32]) -> Result<Option<(String, f32)>> {
Ok(None)
}
pub fn process(&mut self, samples: &[f32]) {
self.buffer.extend_from_slice(samples);
if self.buffer.len() > self.buffer_size {
let excess = self.buffer.len() - self.buffer_size;
self.buffer.drain(0..excess);
}
}
pub fn clear(&mut self) {
self.buffer.clear();
}
pub fn buffer(&self) -> &[f32] {
&self.buffer
}
fn calculate_energy(samples: &[f32]) -> f32 {
if samples.is_empty() {
return 0.0;
}
let sum: f32 = samples.iter().map(|s| s * s).sum();
(sum / samples.len() as f32).sqrt()
}
pub fn is_speech(&self, samples: &[f32], threshold: f32) -> bool {
let energy = Self::calculate_energy(samples);
let db = 20.0 * energy.log10();
db > threshold
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wake_word_creation() {
let word = WakeWord::new("hey dx");
assert_eq!(word.phrase, "hey dx");
assert_eq!(word.phonemes, vec!["hey", "dx"]);
}
#[test]
fn test_detector_creation() {
let phrases = vec!["hey dx".to_string()];
let detector = WakeWordDetector::new(&phrases, 0.5);
assert!(detector.is_ok());
}
#[test]
fn test_energy_calculation() {
let samples = vec![0.5, -0.5, 0.5, -0.5];
let energy = WakeWordDetector::calculate_energy(&samples);
assert!((energy - 0.5).abs() < 0.001);
}
}