feat(kon): add audio crate — cpal capture, VAD, rubato resample, symphonia decode
- File decode via symphonia (mp3, aac, flac, wav, ogg) with mono mixdown - Sinc interpolation resampling via rubato 0.15 (48kHz/44.1kHz → 16kHz) - WAV I/O via hound (read/write with f32→i16 conversion) - Microphone capture via cpal 0.17 (WASAPI on Windows) with mpsc channel output - Voice activity detection via Silero VAD V5 (voice_activity_detector 0.2) - Async decode_and_resample() for file transcription pipeline - 3 tests passing (resample passthrough, duration preservation, WAV roundtrip) - clippy clean, no ort version conflicts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
50
crates/audio/src/vad.rs
Normal file
50
crates/audio/src/vad.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use voice_activity_detector::VoiceActivityDetector;
|
||||
|
||||
use kon_core::constants::{VAD_SPEECH_THRESHOLD, WHISPER_SAMPLE_RATE};
|
||||
use kon_core::error::{KonError, Result};
|
||||
|
||||
/// VAD chunk size for 16kHz audio (required by Silero VAD V5).
|
||||
const VAD_CHUNK_SIZE: usize = 512;
|
||||
|
||||
/// Wraps Silero VAD V5 for speech detection.
|
||||
pub struct SpeechDetector {
|
||||
vad: VoiceActivityDetector,
|
||||
threshold: f64,
|
||||
}
|
||||
|
||||
impl SpeechDetector {
|
||||
/// Create a new speech detector with the default threshold.
|
||||
pub fn new() -> Result<Self> {
|
||||
let vad = VoiceActivityDetector::builder()
|
||||
.sample_rate(WHISPER_SAMPLE_RATE as i64)
|
||||
.chunk_size(VAD_CHUNK_SIZE)
|
||||
.build()
|
||||
.map_err(|e| KonError::AudioCaptureFailed(format!("VAD init failed: {e}")))?;
|
||||
|
||||
Ok(Self {
|
||||
vad,
|
||||
threshold: VAD_SPEECH_THRESHOLD,
|
||||
})
|
||||
}
|
||||
|
||||
/// Predict whether a chunk of 16kHz f32 audio contains speech.
|
||||
/// Returns the speech probability (0.0 to 1.0).
|
||||
pub fn predict(&mut self, samples: &[f32]) -> f32 {
|
||||
self.vad.predict(samples.iter().copied())
|
||||
}
|
||||
|
||||
/// Returns true if the chunk contains speech above the threshold.
|
||||
pub fn is_speech(&mut self, samples: &[f32]) -> bool {
|
||||
self.predict(samples) > self.threshold as f32
|
||||
}
|
||||
|
||||
/// Reset the internal VAD state (call between recording sessions).
|
||||
pub fn reset(&mut self) {
|
||||
self.vad.reset();
|
||||
}
|
||||
|
||||
/// The required chunk size for this VAD instance.
|
||||
pub fn chunk_size(&self) -> usize {
|
||||
VAD_CHUNK_SIZE
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user