feat(audio): populate MicActivity timeline from the loopback writer (FR-SPK)

This commit is contained in:
iamdoubz committed 2026-07-14 00:04:57 -05:00
1 parent fee18ebc59
commit 39294adcbc
1 file changed
+25 -5
+25 -5
View File
@@ -287,9 +287,6 @@ impl MicActivity {
}
/// The loopback writer publishes its rate so frame positions map to windows.
// ponytail: wired into capture_loop + stop in the next Phase 3 slice; the
// struct + span logic land first, tested, so the wiring is mechanical.
#[allow(dead_code)]
fn set_rate(&self, rate: u32) {
self.rate.store(rate, Ordering::Relaxed);
}
@@ -297,7 +294,6 @@ impl MicActivity {
/// Flag the `audio.wav` frames `[start_frame, start_frame + mic.len())` as
/// active when the pulled mic chunk is speech-level. Silence is left `false`
/// (the default), so gaps between utterances don't become "You".
#[allow(dead_code)]
fn mark(&self, start_frame: u64, mic: &[f32]) {
let rate = self.rate.load(Ordering::Relaxed) as u64;
if rate == 0 || mic.is_empty() {
@@ -379,6 +375,7 @@ impl WasapiCapture {
event_sink: EventSink,
bridge: Option<Arc<MicBridge>>,
voice_sample: Option<Arc<VoiceSample>>,
mic_activity: Option<Arc<MicActivity>>,
emit_level: bool,
) -> Result<CaptureHandle, AudioError> {
let running = Arc::new(AtomicBool::new(true));
@@ -401,6 +398,7 @@ impl WasapiCapture {
&paused_th,
bridge.as_ref(),
voice_sample.as_ref(),
mic_activity.as_ref(),
emit_level,
)
})
@@ -423,6 +421,7 @@ impl WasapiCapture {
frame_sink: FrameSink,
event_sink: EventSink,
bridge: Arc<MicBridge>,
mic_activity: Option<Arc<MicActivity>>,
) -> Result<CaptureHandle, AudioError> {
self.start_capture(
"wa-audio-capture",
@@ -433,6 +432,7 @@ impl WasapiCapture {
event_sink,
Some(bridge),
None,
mic_activity,
true,
)
}
@@ -458,6 +458,7 @@ impl WasapiCapture {
event_sink,
Some(bridge),
voice_sample,
None,
true,
)
}
@@ -481,6 +482,7 @@ impl AudioCapture for WasapiCapture {
event_sink,
None,
None,
None,
true,
)
}
@@ -500,6 +502,7 @@ impl AudioCapture for WasapiCapture {
event_sink,
None,
None,
None,
true,
)
}
@@ -671,6 +674,7 @@ fn capture_loop(
paused: &AtomicBool,
bridge: Option<&Arc<MicBridge>>,
voice_sample: Option<&Arc<VoiceSample>>,
mic_activity: Option<&Arc<MicActivity>>,
emit_level: bool,
) -> Result<CaptureSummary, AudioError> {
wasapi::initialize_mta()
@@ -682,12 +686,16 @@ fn capture_loop(
// UI can overlay them — but only the loopback stream writes a WAV.
let mut session = open_capture_session(&direction, device_id)?;
// Loopback publishes its rate so the mic knows what to resample to before
// pushing into the shared bridge (mic-into-recording, FR-CAP-7).
// pushing into the shared bridge (mic-into-recording, FR-CAP-7), and so the
// Phase 3 mic-activity timeline maps frame positions to windows.
if is_loopback {
if let Some(b) = bridge {
b.rate
.store(session.format.get_samplespersec(), Ordering::Relaxed);
}
if let Some(ma) = mic_activity {
ma.set_rate(session.format.get_samplespersec());
}
}
let spec = wav_spec_for(&session.format)?;
let mut writer = match wav_path {
@@ -746,6 +754,9 @@ fn capture_loop(
b.rate
.store(new_session.format.get_samplespersec(), Ordering::Relaxed);
}
if let Some(ma) = mic_activity {
ma.set_rate(new_session.format.get_samplespersec());
}
}
session = new_session;
let _ = event_sink.try_send(CaptureEvent::DeviceChanged {
@@ -770,7 +781,11 @@ fn capture_loop(
* (session.format.get_bitspersample() as usize / 8);
let silence = vec![0u8; gap_frames * bpf];
let mic = b.pull(gap_frames);
let before = frames_written;
frames_written += write_wav_bytes(w, &silence, &session.format, &mic)?;
if let Some(ma) = mic_activity {
ma.mark(before, &mic);
}
last_write = Instant::now();
}
}
@@ -793,7 +808,11 @@ fn capture_loop(
Some(b) if is_loopback => b.pull(frame_count(&bytes, &session.format)),
_ => Vec::new(),
};
let before = frames_written;
frames_written += write_wav_bytes(w, &bytes, &session.format, &mic)?;
if let Some(ma) = mic_activity {
ma.mark(before, &mic);
}
}
let mono = decode_mono_f32(&bytes, &session.format)?;
@@ -1603,6 +1622,7 @@ mod tests {
loop_tx,
ev_tx,
bridge.clone(),
None,
) {
Ok(h) => h,
Err(e) => {