233 lines
7.8 KiB
Rust
233 lines
7.8 KiB
Rust
use crate::settings::get_settings;
|
|
use audio_toolkit::{vad::SmoothedVad, AudioRecorder, SileroVad};
|
|
use log::{debug, info};
|
|
use std::sync::{Arc, Mutex};
|
|
use std::time::Instant;
|
|
use tauri::{App, Manager};
|
|
|
|
const WHISPER_SAMPLE_RATE: usize = 16000;
|
|
|
|
/* ──────────────────────────────────────────────────────────────── */
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum RecordingState {
|
|
Idle,
|
|
Recording { binding_id: String },
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum MicrophoneMode {
|
|
AlwaysOn,
|
|
OnDemand,
|
|
}
|
|
|
|
/* ──────────────────────────────────────────────────────────────── */
|
|
|
|
fn create_audio_recorder(vad_path: &str) -> Result<AudioRecorder, anyhow::Error> {
|
|
let silero = SileroVad::new(vad_path, 0.5)
|
|
.map_err(|e| anyhow::anyhow!("Failed to create SileroVad: {}", e))?;
|
|
let smoothed_vad = SmoothedVad::new(Box::new(silero), 15, 15);
|
|
let recorder = AudioRecorder::new()
|
|
.map_err(|e| anyhow::anyhow!("Failed to create AudioRecorder: {}", e))?
|
|
.with_vad(Box::new(smoothed_vad));
|
|
Ok(recorder)
|
|
}
|
|
|
|
/* ──────────────────────────────────────────────────────────────── */
|
|
|
|
#[derive(Clone)]
|
|
pub struct AudioRecordingManager {
|
|
state: Arc<Mutex<RecordingState>>,
|
|
mode: Arc<Mutex<MicrophoneMode>>,
|
|
app_handle: tauri::AppHandle,
|
|
|
|
recorder: Arc<Mutex<Option<AudioRecorder>>>,
|
|
is_open: Arc<Mutex<bool>>,
|
|
is_recording: Arc<Mutex<bool>>,
|
|
}
|
|
|
|
impl AudioRecordingManager {
|
|
/* ---------- construction ------------------------------------------------ */
|
|
|
|
pub fn new(app: &App) -> Result<Self, anyhow::Error> {
|
|
let settings = get_settings(&app.handle());
|
|
let mode = if settings.always_on_microphone {
|
|
MicrophoneMode::AlwaysOn
|
|
} else {
|
|
MicrophoneMode::OnDemand
|
|
};
|
|
|
|
let manager = Self {
|
|
state: Arc::new(Mutex::new(RecordingState::Idle)),
|
|
mode: Arc::new(Mutex::new(mode.clone())),
|
|
app_handle: app.handle().clone(),
|
|
|
|
recorder: Arc::new(Mutex::new(None)),
|
|
is_open: Arc::new(Mutex::new(false)),
|
|
is_recording: Arc::new(Mutex::new(false)),
|
|
};
|
|
|
|
// Always-on? Open immediately.
|
|
if matches!(mode, MicrophoneMode::AlwaysOn) {
|
|
manager.start_microphone_stream()?;
|
|
}
|
|
|
|
Ok(manager)
|
|
}
|
|
|
|
/* ---------- microphone life-cycle -------------------------------------- */
|
|
|
|
pub fn start_microphone_stream(&self) -> Result<(), anyhow::Error> {
|
|
let mut open_flag = self.is_open.lock().unwrap();
|
|
if *open_flag {
|
|
debug!("Microphone stream already active");
|
|
return Ok(());
|
|
}
|
|
|
|
let start_time = Instant::now();
|
|
|
|
let vad_path = self
|
|
.app_handle
|
|
.path()
|
|
.resolve(
|
|
"resources/models/silero_vad_v4.onnx",
|
|
tauri::path::BaseDirectory::Resource,
|
|
)
|
|
.map_err(|e| anyhow::anyhow!("Failed to resolve VAD path: {}", e))?;
|
|
let mut recorder_opt = self.recorder.lock().unwrap();
|
|
|
|
if recorder_opt.is_none() {
|
|
*recorder_opt = Some(create_audio_recorder(vad_path.to_str().unwrap())?);
|
|
}
|
|
|
|
if let Some(rec) = recorder_opt.as_mut() {
|
|
rec.open(None)
|
|
.map_err(|e| anyhow::anyhow!("Failed to open recorder: {}", e))?;
|
|
}
|
|
|
|
*open_flag = true;
|
|
info!(
|
|
"Microphone stream initialized in {:?}",
|
|
start_time.elapsed()
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn stop_microphone_stream(&self) {
|
|
let mut open_flag = self.is_open.lock().unwrap();
|
|
if !*open_flag {
|
|
return;
|
|
}
|
|
|
|
if let Some(rec) = self.recorder.lock().unwrap().as_mut() {
|
|
// If still recording, stop first.
|
|
if *self.is_recording.lock().unwrap() {
|
|
let _ = rec.stop();
|
|
*self.is_recording.lock().unwrap() = false;
|
|
}
|
|
let _ = rec.close();
|
|
}
|
|
|
|
*open_flag = false;
|
|
debug!("Microphone stream stopped");
|
|
}
|
|
|
|
/* ---------- mode switching --------------------------------------------- */
|
|
|
|
pub fn update_mode(&self, new_mode: MicrophoneMode) -> Result<(), anyhow::Error> {
|
|
let mode_guard = self.mode.lock().unwrap();
|
|
let cur_mode = mode_guard.clone();
|
|
|
|
match (cur_mode, &new_mode) {
|
|
(MicrophoneMode::AlwaysOn, MicrophoneMode::OnDemand) => {
|
|
if matches!(*self.state.lock().unwrap(), RecordingState::Idle) {
|
|
drop(mode_guard);
|
|
self.stop_microphone_stream();
|
|
}
|
|
}
|
|
(MicrophoneMode::OnDemand, MicrophoneMode::AlwaysOn) => {
|
|
drop(mode_guard);
|
|
self.start_microphone_stream()?;
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
*self.mode.lock().unwrap() = new_mode;
|
|
Ok(())
|
|
}
|
|
|
|
/* ---------- recording --------------------------------------------------- */
|
|
|
|
pub fn try_start_recording(&self, binding_id: &str) -> bool {
|
|
let mut state = self.state.lock().unwrap();
|
|
|
|
if let RecordingState::Idle = *state {
|
|
// Ensure microphone is open in on-demand mode
|
|
if matches!(*self.mode.lock().unwrap(), MicrophoneMode::OnDemand) {
|
|
if let Err(e) = self.start_microphone_stream() {
|
|
eprintln!("Failed to open microphone stream: {e}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if let Some(rec) = self.recorder.lock().unwrap().as_ref() {
|
|
if rec.start().is_ok() {
|
|
*self.is_recording.lock().unwrap() = true;
|
|
*state = RecordingState::Recording {
|
|
binding_id: binding_id.to_string(),
|
|
};
|
|
debug!("Recording started for binding {binding_id}");
|
|
return true;
|
|
}
|
|
}
|
|
eprintln!("Recorder not available");
|
|
false
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn stop_recording(&self, binding_id: &str) -> Option<Vec<f32>> {
|
|
let mut state = self.state.lock().unwrap();
|
|
|
|
match *state {
|
|
RecordingState::Recording {
|
|
binding_id: ref active,
|
|
} if active == binding_id => {
|
|
*state = RecordingState::Idle;
|
|
drop(state);
|
|
|
|
let samples = if let Some(rec) = self.recorder.lock().unwrap().as_ref() {
|
|
match rec.stop() {
|
|
Ok(buf) => buf,
|
|
Err(e) => {
|
|
eprintln!("stop() failed: {e}");
|
|
Vec::new()
|
|
}
|
|
}
|
|
} else {
|
|
eprintln!("Recorder not available");
|
|
Vec::new()
|
|
};
|
|
|
|
*self.is_recording.lock().unwrap() = false;
|
|
|
|
// In on-demand mode turn the mic off again
|
|
if matches!(*self.mode.lock().unwrap(), MicrophoneMode::OnDemand) {
|
|
self.stop_microphone_stream();
|
|
}
|
|
|
|
// Pad if very short
|
|
let s_len = samples.len();
|
|
if s_len < WHISPER_SAMPLE_RATE && s_len > 1000 {
|
|
let mut padded = samples;
|
|
padded.resize(WHISPER_SAMPLE_RATE * 5 / 4, 0.0);
|
|
Some(padded)
|
|
} else {
|
|
Some(samples)
|
|
}
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|