diff --git a/src-tauri/src/audio/mod.rs b/src-tauri/src/audio/mod.rs index 738e665..be5ac28 100644 --- a/src-tauri/src/audio/mod.rs +++ b/src-tauri/src/audio/mod.rs @@ -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>, voice_sample: Option>, + mic_activity: Option>, emit_level: bool, ) -> Result { 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, + mic_activity: Option>, ) -> Result { 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>, voice_sample: Option<&Arc>, + mic_activity: Option<&Arc>, emit_level: bool, ) -> Result { 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) => {