Skip to content

Conversation

@richiemcilroy
Copy link
Member

@richiemcilroy richiemcilroy commented Oct 28, 2025

Aligns the audio mixer’s clock with microphone frame timestamps to prevent false gap detection and silence insertion that caused pulsing/stutter on playback.

Root cause: Mic frames are timestamped using OS-native clocks (e.g., Mach absolute time on macOS), but the mixer compared them against Instant::now(). The mismatched timebases made the mixer think frames were late, so it periodically inserted silence, heard as a “helicopter” effect.

Summary by CodeRabbit

  • Refactor
    • Audio tick now uses platform-native time sources (macOS, Windows, others) instead of a single universal clock.
    • The tick invocation was updated to pass the OS-derived timestamp.
    • Existing error handling and control flow around the tick remain unchanged.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 28, 2025

Walkthrough

The pull request refactors timestamp computation in the audio mixer by using platform-specific clock sources instead of a universal approach. macOS uses MachAbsoluteTime, Windows uses PerformanceCounter, and other platforms fall back to Instant. This timestamp is then passed to mixer.tick().

Changes

Cohort / File(s) Summary
OS-specific timestamp computation for audio mixer
crates/recording/src/sources/audio_mixer.rs
Refactors tick timestamp handling to use platform-specific clock sources (MachAbsoluteTime for macOS, PerformanceCounter for Windows, Instant for others) before passing to mixer.tick() instead of using universal Instant::now()

Sequence Diagram

sequenceDiagram
    participant Code as Audio Mixer Loop
    participant OS as Platform Layer
    participant Mixer as mixer.tick()
    
    Code->>OS: Check target platform
    
    alt macOS
        OS->>OS: Use MachAbsoluteTime
    else Windows
        OS->>OS: Use PerformanceCounter
    else Other
        OS->>OS: Use Instant::now()
    end
    
    OS->>Code: Return timestamp
    Code->>Mixer: mixer.tick(start, now)
    
    alt Success
        Mixer->>Code: Ok(...)
        Code->>Code: Continue loop
    else Error
        Mixer->>Code: Err(())
        Code->>Code: Break
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

  • Review focus: Verify platform-specific timestamp implementations and conditional compilation flags, and confirm tick semantics and error handling remain unchanged.

Possibly related PRs

Poem

🐰 I hop on ticks from each machine's heart,
Mach, Perf, or Instant — each plays its part.
I mix the streams with timely cheer,
One small tick, the output's clear! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "fix: eliminate "helicopter" mic stutter by aligning mixer timebase" is clearly and directly related to the main change in the changeset. The title accurately describes both the problem being addressed (the "helicopter" stutter effect) and the solution (aligning the mixer's timebase with OS-native timestamps). The changes implement this by replacing the universal Instant::now() with OS-specific timestamp variants (MachAbsoluteTime on macOS, PerformanceCounter on Windows), which directly aligns with the title's stated objective. The title is concise, specific, and communicates the primary change in a way that teammates can readily understand when scanning pull request history.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch helicopter-voice

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
crates/recording/src/sources/audio_mixer.rs (2)

171-179: Align anchors: use one Timestamps base to avoid subtle drift between buffering and emit paths.

Buffering compares against self.timestamps (captured in build()), while tick() builds output timestamps from start (captured separately in run()). Unify by anchoring start to mixer.timestamps so both paths share the same base.

Apply this change:

-        let start = Timestamps::now();
-
         let mut mixer = match self.build(output) {
             Ok(mixer) => mixer,
             Err(e) => {
                 tracing::error!("Failed to build audio mixer: {}", e);
                 let _ = ready_tx.send(Err(e.into()));
                 return;
             }
         };
 
         let _ = ready_tx.send(Ok(()));
 
+        // Use the same anchor as the mixer to keep buffer math and emit timestamps aligned.
+        let start = mixer.timestamps;

Also, make the cfg predicates consistent and symmetric for readability:

-            #[cfg(target_os = "macos")]
+            #[cfg(target_os = "macos")]
             let now = Timestamp::MachAbsoluteTime(cap_timestamp::MachAbsoluteTimestamp::now());
-            #[cfg(windows)]
-            let now = Timestamp::PerformanceCounter(cap_timestamp::PerformanceCounterTimestamp::now());
-            #[cfg(not(any(target_os = "macos", windows)))]
+            #[cfg(target_os = "windows")]
+            let now = Timestamp::PerformanceCounter(
+                cap_timestamp::PerformanceCounterTimestamp::now()
+            );
+            #[cfg(not(any(target_os = "macos", target_os = "windows")))]
             let now = Timestamp::Instant(Instant::now());
  • Confirm that iOS is not a supported target; if it is, consider adding an #[cfg(target_os = "ios")] branch using the Mach timebase as on macOS.

Also applies to: 188-196, 196-203


196-203: Add a regression test that exercises non-Instant timebases.

Current tests use only Timestamp::Instant. Add macOS/Windows-guarded tests that enqueue frames timestamped with MachAbsoluteTime/PerformanceCounter and assert no gap-induced silence is inserted when now uses the same timebase.

I can draft a minimal #[cfg(target_os = "macos")]/#[cfg(target_os = "windows")] test mirroring source_buffer::frame_gap.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4aa9628 and 5a72e60.

📒 Files selected for processing (1)
  • crates/recording/src/sources/audio_mixer.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format Rust code using rustfmt and ensure all Rust code passes workspace-level clippy lints.
Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Files:

  • crates/recording/src/sources/audio_mixer.rs
crates/*/src/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Rust crates should place tests within the src/ and/or a sibling tests/ directory for each crate inside crates/*.

Files:

  • crates/recording/src/sources/audio_mixer.rs
🧬 Code graph analysis (1)
crates/recording/src/sources/audio_mixer.rs (3)
crates/timestamp/src/macos.rs (1)
  • now (18-20)
crates/timestamp/src/win.rs (1)
  • now (49-53)
crates/timestamp/src/lib.rs (1)
  • now (103-112)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
crates/recording/src/sources/audio_mixer.rs (1)

196-202: LGTM! Platform-specific timestamp selection correctly aligns mixer timebase.

The platform-specific timestamp selection properly addresses the root cause described in the PR: aligning the mixer's clock with the OS-native clocks used by microphone frame timestamps. This prevents false gap detection that caused the "helicopter" stutter effect.

Consider adding a brief comment explaining the rationale:

+        // Use the same timestamp source as microphone frames to prevent false gap detection.
+        // Mic frames use OS-native clocks (Mach absolute time on macOS, QueryPerformanceCounter
+        // on Windows), so the mixer must use the same timebase for accurate comparison.
         #[cfg(target_os = "macos")]
         let now = Timestamp::MachAbsoluteTime(cap_timestamp::MachAbsoluteTimestamp::now());
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a72e60 and b84b43d.

📒 Files selected for processing (1)
  • crates/recording/src/sources/audio_mixer.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format Rust code using rustfmt and ensure all Rust code passes workspace-level clippy lints.
Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Files:

  • crates/recording/src/sources/audio_mixer.rs
crates/*/src/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Rust crates should place tests within the src/ and/or a sibling tests/ directory for each crate inside crates/*.

Files:

  • crates/recording/src/sources/audio_mixer.rs
🧬 Code graph analysis (1)
crates/recording/src/sources/audio_mixer.rs (3)
crates/timestamp/src/macos.rs (1)
  • now (18-20)
crates/timestamp/src/win.rs (1)
  • now (49-53)
crates/timestamp/src/lib.rs (1)
  • now (103-112)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (1)
crates/recording/src/sources/audio_mixer.rs (1)

204-204: Correct usage of platform-specific timestamp in tick call.

The updated tick call correctly passes the platform-specific now timestamp, which is used for accurate gap detection in buffer_sources. The output timestamps (line 426) continue to use Instant for downstream consumers, maintaining compatibility while fixing the gap detection issue.

@richiemcilroy richiemcilroy merged commit 69699f7 into main Oct 28, 2025
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants