fix(recording): capture mic alongside system audio

Mic callbacks returned early when the system-audio tap was live, so your
own voice was never transcribed or recorded. Always run the mic writer;
it feeds the STT buffer in both modes and writes the WAV only when the
system tap is unavailable (mic-only).
This commit is contained in:
lakshit verma 2026-08-13 20:36:20 +05:30
parent 51b8c4a272
commit c2a3e60eee
No known key found for this signature in database
GPG key ID: EB498AFC60A7A01A

View file

@ -177,9 +177,9 @@ pub async fn start_recording(
#[cfg(not(target_os = "macos"))]
let system_audio = ();
// Mic: only feed the STT buffer when system audio is unavailable.
// When system tap is live, it already captures meeting playback; mixing both
// into one stream desyncs the timeline. Mic is still opened for diagnostics.
// Mic is always captured: it feeds the STT buffer even when the system tap
// is live (your voice is transcribed alongside meeting playback). When the
// system tap is unavailable, mic becomes the sole source for the WAV too.
#[cfg(target_os = "macos")]
let system_up = system_audio.is_some();
#[cfg(not(target_os = "macos"))]
@ -212,7 +212,7 @@ pub async fn start_recording(
let _ = (cap.sample_rate, cap.channels);
if system_up {
let mut d = diagnostics.lock().unwrap();
d.push_log("mic open for presence; STT uses SYSTEM AUDIO only (meeting playback)");
d.push_log("mic open + feeding STT (your voice captured alongside system audio)");
}
(cap.stream, cap.writer)
}
@ -311,7 +311,6 @@ fn start_cpal_capture(
channels,
from_rate: sample_rate,
to_rate: target_rate,
system_up,
};
let stream = match config.sample_format() {
cpal::SampleFormat::F32 => build_stream_f32(&device, &stream_config, capture)?,
@ -320,20 +319,15 @@ fn start_cpal_capture(
other => anyhow::bail!("unsupported sample format: {other:?}"),
};
// Writer thread drains WAV + STT feed off the audio thread. Start before
// play() so no mic chunk is ever dropped. When system audio is up the mic
// callback returns immediately and no writer thread is needed.
let writer = if system_up {
None
} else {
std::thread::Builder::new()
.name("meeticulous-mic-writer".into())
.spawn(move || mic_writer_loop(rx, wav_writer, stt_samples, target_rate))
.ok()
};
// play() so no mic chunk is ever dropped.
let writer = std::thread::Builder::new()
.name("meeticulous-mic-writer".into())
.spawn(move || mic_writer_loop(rx, wav_writer, stt_samples, target_rate, system_up))
.ok();
stream.play()?;
{
let mut d = diag.lock().unwrap();
d.push_log("mic stream playing (mixed with system when available)");
d.push_log("mic stream playing (fed to STT; WAV source when system tap is down)");
}
Ok(MicCapture {
stream: Some(stream),
@ -360,7 +354,6 @@ struct CaptureContext {
channels: u16,
from_rate: u32,
to_rate: u32,
system_up: bool,
}
impl CaptureContext {
@ -381,9 +374,12 @@ fn mic_writer_loop(
wav_writer: LiveWavWriter,
stt_samples: Arc<Mutex<Vec<f32>>>,
sample_rate: u32,
system_up: bool,
) {
while let Ok(chunk) = rx.recv() {
append_wav_samples(&wav_writer, &chunk);
if !system_up {
append_wav_samples(&wav_writer, &chunk);
}
if let Ok(mut feed) = stt_samples.lock() {
extend_stt_feed(&mut feed, &chunk, sample_rate);
}
@ -399,7 +395,7 @@ fn build_stream_f32(
let stream = device.build_input_stream(
config,
move |data: &[f32], _| {
if capture.stop_flag.load(Ordering::Relaxed) || capture.system_up {
if capture.stop_flag.load(Ordering::Relaxed) {
return;
}
let mono = capture.mono_chunk(data);
@ -420,7 +416,7 @@ fn build_stream_i16(
let stream = device.build_input_stream(
config,
move |data: &[i16], _| {
if capture.stop_flag.load(Ordering::Relaxed) || capture.system_up {
if capture.stop_flag.load(Ordering::Relaxed) {
return;
}
let f: Vec<f32> = data.iter().map(|&s| s as f32 / i16::MAX as f32).collect();
@ -442,7 +438,7 @@ fn build_stream_u16(
let stream = device.build_input_stream(
config,
move |data: &[u16], _| {
if capture.stop_flag.load(Ordering::Relaxed) || capture.system_up {
if capture.stop_flag.load(Ordering::Relaxed) {
return;
}
let f: Vec<f32> = data