Skip to content

Conversation

@yujonglee
Copy link
Contributor

No description provided.

@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

📝 Walkthrough

Walkthrough

Simplifies DeviceEvent::DefaultInputChanged to a unit variant; centralizes headphone detection for default output via a new macOS util; adds a mixed-audio capture/streaming path (macOS + fallback) with MixedInput/MixedStream and wires it through AudioInput/AudioStream and the listener pipeline; renames ProcMsg variants and updates the source actor to choose mixed vs mic+speaker streams based on headphone state.

Changes

Cohort / File(s) Summary
Device events & utils (macOS)
crates/audio/src/device_monitor.rs, crates/audio/src/utils.rs
DeviceEvent::DefaultInputChanged changed to a unit variant. Default output headphone flag now computed via new macOS util is_headphone_from_default_output_device(). Removes old detect_headphones logic and adds macOS-only helper and test.
Audio core API & routing
crates/audio/src/lib.rs
Exposes mixed and utils modules; adds AudioSource::RealtimeMixed and AudioStream::RealtimeMixed; AudioInput gains mixed: Option<MixedInput>, from_mixed() (macOS), is_using_headphone(), renames get_default_mic_device_name()get_default_mic_name(); routes mixed stream through device_name/stream/poll_next/sample_rate; adds test helper play_sine_for_sec.
Mixed audio implementation (macOS + fallback)
crates/audio/src/mixed/mod.rs, crates/audio/src/mixed/macos.rs, crates/audio/src/mixed/other.rs
New MixedInput and MixedStream wrappers. macOS implements full Core Audio tap/aggregate, proc callback mixing to mono, ring buffer (HeapRb), waker coordination, sample-rate tracking, and lifecycle; other.rs supplies a skeleton fallback stream.
Speaker path & tests
crates/audio/src/speaker/macos.rs, crates/audio/src/speaker/mod.rs
Removed runtime device-info logging; proc callback now uses device.nominal_sample_rate() for current sample rate; tests now import crate-level play_sine_for_sec instead of a local helper.
Listener RX init refactor
plugins/listener/src/actors/listen.rs
Extracted RX initialization into spawn_rx_task(...) returning (tx, JoinHandle); RX task logic preserved (connect, realtime stream, transcript handling, partial/final events); connection retrieval simplified with ?.
Processor & session messages
plugins/listener/src/actors/processor.rs, plugins/listener/src/actors/session.rs
ProcMsg variants renamed: SpkSpeaker, AttachListenAttachListener; added Mixed(AudioChunk) variant; session and processor updated to new variant names.
Source actor: mic_device & mixed vs dual-path streaming
plugins/listener/src/actors/source.rs
SourceState.devicemic_device. Watches DefaultInputChanged/DefaultOutputChanged events and refreshes default mic via AudioInput::get_default_mic_name(). start_source_loop signature gains myself. On macOS chooses use_mixed = !AudioInput::is_using_headphone() to use AudioInput::from_mixed() and emit ProcMsg::Mixed, otherwise runs separate mic+speaker streams emitting Mic/Speaker.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Source as Source Actor
  participant Audio as AudioInput / MixedStream
  participant Proc as AudioProcessor
  Note right of Audio: utils::is_headphone_from_default_output_device() used for DefaultOutputChanged (macOS)

  User->>Source: Start
  Source->>Audio: get_default_mic_name() / is_using_headphone()
  alt use_mixed (no headphones on macOS)
    Source->>Audio: from_mixed().stream()
    loop per sample
      Audio-->>Source: f32 sample
      Source->>Proc: ProcMsg::Mixed(AudioChunk)
      Proc-->>Proc: join/mix -> transcript flow
    end
  else mic+speaker path
    par mic stream
      Audio-->>Source: mic chunk
      Source->>Proc: ProcMsg::Mic(AudioChunk)
    and speaker stream
      Audio-->>Source: spk chunk
      Source->>Proc: ProcMsg::Speaker(AudioChunk)
    end
    Proc-->>Proc: join -> transcript flow
  end
  Note over Source,Audio: Cancellation tokens stop streams and actor
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ❓ Inconclusive No pull request description was provided by the author, so there is no author-written summary to corroborate intent or testing guidance; the raw_summary indicates substantial mixed-audio additions and cross-cutting API changes but the absence of a description makes the check inconclusive. Without an author description we cannot determine the intended scope, migration notes, or testing expectations from the PR metadata alone. Please add a brief description summarizing goals, platform scope, and any required upgrade/testing steps. Add a short PR description that states the objective (e.g., "Introduce mixed audio path with RealtimeMixed, MixedInput/MixedStream; macOS implementation plus a non-macOS skeleton"), lists important public API changes and platform gating, and includes testing steps or known limitations so reviewers can validate behavior and risks.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "Mixed audio" is concise and accurately reflects the primary change set which introduces a mixed audio path (RealtimeMixed), MixedInput/MixedStream types, and related modules across the audio crate; it clearly ties to the main feature rather than unrelated files or noise. It is short and readable, though somewhat generic and could be slightly more descriptive about scope or platform. Overall it conveys the main intent for a quick scan of history.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mixed-audio

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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

@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: 8

🧹 Nitpick comments (8)
crates/audio/src/speaker/macos.rs (1)

109-114: Nominal sample rate change: avoid truncation; confirm intent.

Switching to nominal rate is fine, but casting f64 → u32 via as truncates. Prefer rounding to prevent 47999 on 48k, etc. Also confirm that reporting nominal (not actual) is desired for downstream consumers.

-            ctx.current_sample_rate.store(
-                device
-                    .nominal_sample_rate()
-                    .unwrap_or(ctx.format.absd().sample_rate) as u32,
-                Ordering::Release,
-            );
+            ctx.current_sample_rate.store(
+                device
+                    .nominal_sample_rate()
+                    .unwrap_or(ctx.format.absd().sample_rate)
+                    .round() as u32,
+                Ordering::Release,
+            );
plugins/listener/src/actors/listen.rs (1)

163-175: Drop unnecessary deep clone when updating the session.

You can build the flattened words vector from the existing map without cloning the entire structure.

-                    update_session(
-                        &app,
-                        &session_id,
-                        final_words_by_channel
-                            .clone()
-                            .values()
-                            .flatten()
-                            .cloned()
-                            .collect(),
-                    )
+                    update_session(
+                        &app,
+                        &session_id,
+                        final_words_by_channel
+                            .values()
+                            .flatten()
+                            .cloned()
+                            .collect(),
+                    )
                     .await
                     .unwrap();
crates/audio/src/utils.rs (1)

28-40: Test module visibility/naming nit.

Consider making the test module private and naming it tests for convention.

-#[cfg(test)]
-pub mod test {
+#[cfg(test)]
+mod tests {
crates/audio/src/device_monitor.rs (1)

120-126: Make stop() responsive by bounding the run loop wait.

run_until_date(distant_future) can block indefinitely; stop may not break until another event occurs. Poll the run loop with a short horizon.

-        loop {
-            run_loop.run_until_date(&ns::Date::distant_future());
+        loop {
+            // Short horizon to periodically check the stop notifier.
+            run_loop.run_until_date(&ns::Date::with_time_interval_since_now(0.1));
             if stop_notifier_rx.try_recv().is_ok() {
                 break;
             }
             std::thread::sleep(std::time::Duration::from_millis(100));
         }

If with_time_interval_since_now isn’t available, use the equivalent API in cidre::ns.

crates/audio/src/mixed/macos.rs (1)

137-156: Avoid per-frame per-channel lookups in the mixing loop.

Cache channel slices and reuse them to reduce overhead in the callback.

-                let format = view.format();
-
-                if format.channel_count() > 1 {
-                    let frame_count = view.frame_len() as usize;
-                    let mut mixed_buffer = Vec::with_capacity(frame_count);
-
-                    for frame_idx in 0..frame_count {
-                        let mut mixed_sample = 0.0f32;
-                        let channel_count = format.channel_count() as usize;
-
-                        for channel in 0..channel_count {
-                            if let Some(channel_data) = view.data_f32_at(channel) {
-                                if frame_idx < channel_data.len() {
-                                    mixed_sample += channel_data[frame_idx];
-                                }
-                            }
-                        }
-
-                        // Average the mixed sample
-                        mixed_sample /= channel_count as f32;
-                        mixed_buffer.push(mixed_sample);
-                    }
+                let format = view.format();
+                let channel_count = format.channel_count() as usize;
+                if channel_count > 1 {
+                    let slices: Vec<&[f32]> = (0..channel_count)
+                        .filter_map(|ch| view.data_f32_at(ch))
+                        .collect();
+                    let frame_count = view.frame_len() as usize;
+                    let mut mixed_buffer = Vec::with_capacity(frame_count);
+                    for frame_idx in 0..frame_count {
+                        let mut acc = 0.0f32;
+                        let mut n = 0usize;
+                        for data in &slices {
+                            if frame_idx < data.len() {
+                                acc += data[frame_idx];
+                                n += 1;
+                            }
+                        }
+                        mixed_buffer.push(if n > 0 { acc / n as f32 } else { 0.0 });
+                    }
plugins/listener/src/actors/source.rs (3)

246-254: Avoid hot-path allocations when applying mutes

Zero buffers in place instead of allocating a new Vec every chunk.

Apply these diffs:

-                            let output_data = if mic_muted.load(Ordering::Relaxed) {
-                                vec![0.0; data.len()]
-                            } else {
-                                data
-                            };
-
-                            let msg = ProcMsg::Mic(AudioChunk{ data: output_data });
+                            let mut data = data;
+                            if mic_muted.load(Ordering::Relaxed) {
+                                data.fill(0.0);
+                            }
+                            let msg = ProcMsg::Mic(AudioChunk{ data });
-                            let output_data = if spk_muted.load(Ordering::Relaxed) {
-                                vec![0.0; data.len()]
-                            } else {
-                                data
-                            };
-
-                            let msg = ProcMsg::Speaker(AudioChunk{ data: output_data });
+                            let mut data = data;
+                            if spk_muted.load(Ordering::Relaxed) {
+                                data.fill(0.0);
+                            }
+                            let msg = ProcMsg::Speaker(AudioChunk{ data });

Also applies to: 260-268


173-175: Log level nit: prefer debug for path selection

use_mixed toggles often; debug is less noisy for normal runs.

Apply this diff:

-    tracing::info!(use_mixed = use_mixed);
+    tracing::debug!(use_mixed = use_mixed);

24-31: Align naming: SourceArgs.device vs SourceState.mic_device

Consider renaming SourceArgs.device to mic_device for clarity and symmetry (or add a doc comment explaining the distinction).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b0334e and a24cec6.

📒 Files selected for processing (11)
  • crates/audio/src/device_monitor.rs (3 hunks)
  • crates/audio/src/lib.rs (8 hunks)
  • crates/audio/src/mixed/macos.rs (1 hunks)
  • crates/audio/src/mixed/mod.rs (1 hunks)
  • crates/audio/src/speaker/macos.rs (1 hunks)
  • crates/audio/src/speaker/mod.rs (1 hunks)
  • crates/audio/src/utils.rs (1 hunks)
  • plugins/listener/src/actors/listen.rs (2 hunks)
  • plugins/listener/src/actors/processor.rs (3 hunks)
  • plugins/listener/src/actors/session.rs (1 hunks)
  • plugins/listener/src/actors/source.rs (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}

⚙️ CodeRabbit configuration file

**/*.{js,ts,tsx,rs}: 1. Do not add any error handling. Keep the existing one.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

Files:

  • crates/audio/src/speaker/macos.rs
  • plugins/listener/src/actors/session.rs
  • plugins/listener/src/actors/processor.rs
  • crates/audio/src/mixed/mod.rs
  • crates/audio/src/speaker/mod.rs
  • plugins/listener/src/actors/listen.rs
  • crates/audio/src/utils.rs
  • crates/audio/src/device_monitor.rs
  • crates/audio/src/mixed/macos.rs
  • plugins/listener/src/actors/source.rs
  • crates/audio/src/lib.rs
🧬 Code graph analysis (6)
crates/audio/src/speaker/mod.rs (1)
crates/audio/src/lib.rs (1)
  • play_sine_for_sec (247-270)
plugins/listener/src/actors/listen.rs (1)
plugins/listener/src/manager.rs (1)
  • with_unix_timestamp (21-27)
crates/audio/src/device_monitor.rs (1)
crates/audio/src/utils.rs (1)
  • is_headphone_from_default_output_device (22-25)
crates/audio/src/mixed/macos.rs (2)
crates/audio/src/speaker/macos.rs (8)
  • std (125-125)
  • sample_rate (36-38)
  • new (51-92)
  • start_device (94-143)
  • proc (98-136)
  • stream (145-180)
  • poll_next (218-240)
  • drop (244-246)
crates/audio/src/lib.rs (4)
  • sample_rate (236-243)
  • stream (175-191)
  • poll_next (204-228)
  • play_sine_for_sec (247-270)
plugins/listener/src/actors/source.rs (2)
crates/audio/src/lib.rs (5)
  • get_default_mic_name (82-90)
  • is_using_headphone (92-105)
  • from_mixed (144-154)
  • from_mic (122-132)
  • from_speaker (134-142)
plugins/listener/src/actors/processor.rs (2)
  • new (168-173)
  • handle (78-113)
crates/audio/src/lib.rs (3)
crates/audio/src/mic.rs (3)
  • device (84-84)
  • new (34-66)
  • device_name (20-24)
crates/audio/src/utils.rs (1)
  • is_headphone_from_default_output_device (22-25)
crates/audio/src/mixed/macos.rs (1)
  • new (52-65)
⏰ 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). (2)
  • GitHub Check: ci (windows, windows-latest)
  • GitHub Check: ci (macos, macos-14)
🔇 Additional comments (9)
plugins/listener/src/actors/listen.rs (1)

50-52: Avoid panicking in actor startup (verify acceptable).

unwrap() on spawn_rx_task(...) will crash the actor if connection setup fails. If fail‑fast here is intentional, all good—otherwise consider propagating the error in a follow‑up.

crates/audio/src/speaker/mod.rs (1)

102-105: LGTM: central test helper import reduces duplication.

Switching tests to crate::play_sine_for_sec is cleaner and keeps one source of truth.

plugins/listener/src/actors/session.rs (1)

315-315: Enum rename verified — no remaining AttachListen references.
ProcMsg::AttachListener is defined/handled in plugins/listener/src/actors/processor.rs and invoked from plugins/listener/src/actors/session.rs; search found no occurrences of the old AttachListen variant.

plugins/listener/src/actors/processor.rs (2)

19-23: Renamed variants + new Mixed path look consistent.

Enum surface aligns with the broader rename and addition. No issues spotted here.


101-110: AGC choice for Mixed: confirm intent.

Mixed uses mic AGC (agc_m). If Mixed already contains speaker+mic, AGC tuned for mic may pump the program audio. Confirm this is desired.

crates/audio/src/utils.rs (1)

1-26: macOS headphone detection util looks solid.

Approach is reasonable and safely handles None/Err.

crates/audio/src/device_monitor.rs (1)

66-86: Centralizing headphone detection LGTM.

Import/use of is_headphone_from_default_output_device simplifies the macOS path.

crates/audio/src/lib.rs (1)

82-90: Panic risk if no default input device.

get_default_mic_name() unwraps the default device; confirm this is only called when a device is guaranteed to exist.

plugins/listener/src/actors/source.rs (1)

173-174: Confirm intended policy: mixed when not using headphones (macOS-only detection found)

AudioInput::is_using_headphone() is used to decide mixed (plugins/listener/src/actors/source.rs:173). is_using_headphone() calls the macOS helper (crates/audio/src/lib.rs -> utils::macos::is_headphone_from_default_output_device()) and device monitoring sends DefaultOutputChanged only on macOS (crates/audio/src/device_monitor.rs). Consequence: non-macOS platforms lack detection and will default to “not using headphones” → use_mixed = true. Confirm this default is intended; otherwise implement cross‑platform detection or document the behavior.

Copy link

@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

♻️ Duplicate comments (1)
plugins/listener/src/actors/processor.rs (1)

118-141: Bypass AEC and avoid double-summing when mic/spk share the same Arc (Mixed case)

Detect identical buffers with Arc::ptr_eq. If identical: (1) skip AEC, (2) recorder forwards the single stream as‑is, (3) listener gets identical bytes on both channels.

Apply this diff:

 async fn process_ready(st: &mut ProcState) {
     while let Some((mic, spk)) = st.joiner.pop_pair() {
-        let mic = st
-            .aec
-            .process_streaming(&mic, &spk)
-            .unwrap_or_else(|_| mic.to_vec());
+        let identical = Arc::ptr_eq(&mic, &spk);
+        let mic = if identical {
+            // Already mixed; preserve as-is.
+            mic.to_vec()
+        } else {
+            st.aec
+                .process_streaming(&mic, &spk)
+                .unwrap_or_else(|_| mic.to_vec())
+        };
@@
-            if let Some(rec) = &st.recorder {
-                let mixed: Vec<f32> = mic
-                    .iter()
-                    .zip(spk.iter())
-                    .map(|(m, s)| (m + s).clamp(-1.0, 1.0))
-                    .collect();
-                rec.cast(RecMsg::Audio(mixed)).ok();
-            }
+            if let Some(rec) = &st.recorder {
+                let mixed: Vec<f32> = if identical {
+                    mic.clone()
+                } else {
+                    mic.iter()
+                        .zip(spk.iter())
+                        .map(|(m, s)| (m + s).clamp(-1.0, 1.0))
+                        .collect()
+                };
+                rec.cast(RecMsg::Audio(mixed)).ok();
+            }
@@
-        if let Some(actor) = &st.listen {
-            let mic_bytes = hypr_audio_utils::f32_to_i16_bytes(mic.into_iter());
-            let spk_bytes = hypr_audio_utils::f32_to_i16_bytes(spk.iter().copied());
-
-            actor
-                .cast(ListenMsg::Audio(mic_bytes.into(), spk_bytes.into()))
-                .ok();
-        }
+        if let Some(actor) = &st.listen {
+            if identical {
+                let spk_bytes = hypr_audio_utils::f32_to_i16_bytes(spk.iter().copied());
+                actor
+                    .cast(ListenMsg::Audio(spk_bytes.clone().into(), spk_bytes.into()))
+                    .ok();
+            } else {
+                let mic_bytes = hypr_audio_utils::f32_to_i16_bytes(mic.into_iter());
+                let spk_bytes = hypr_audio_utils::f32_to_i16_bytes(spk.iter().copied());
+                actor
+                    .cast(ListenMsg::Audio(mic_bytes.into(), spk_bytes.into()))
+                    .ok();
+            }
+        }

Side effect to confirm: VU metering (Lines 153–161) will show identical mic/spk amplitudes in Mixed. If UI expects different semantics, we can gate meter updates behind identical.

Also applies to: 143-150

🧹 Nitpick comments (1)
plugins/listener/src/actors/processor.rs (1)

101-112: Mixed path: avoid zero-mic allocation; treat as identical buffers and bypass AEC downstream

Feeding a zeroed mic causes needless AEC work and mutes the mic channel for listeners. Prefer pushing the same Arc to mic+spk, run speaker AGC (or none), and let process_ready detect identical buffers to skip AEC and avoid double-summing. This also fixes VU metering so mic isn’t always silent in Mixed.

Apply this diff here:

-            ProcMsg::Mixed(mut c) => {
-                st.agc_m.process(&mut c.data);
-
-                let empty_arc = Arc::<[f32]>::from(vec![0.0; c.data.len()]);
-                let arc = Arc::<[f32]>::from(c.data);
-
-                st.last_mic = Some(empty_arc.clone());
-                st.last_spk = Some(arc.clone());
-                st.joiner.push_mic(empty_arc.clone());
-                st.joiner.push_spk(arc);
-                process_ready(st).await;
-            }
+            ProcMsg::Mixed(mut c) => {
+                // Mixed stream gain-shaping should follow speaker characteristics.
+                st.agc_s.process(&mut c.data);
+                let arc = Arc::<[f32]>::from(c.data);
+                // Identical buffers => let process_ready() special‑case this.
+                st.last_mic = Some(arc.clone());
+                st.last_spk = Some(arc.clone());
+                st.joiner.push_mic(arc.clone());
+                st.joiner.push_spk(arc);
+                process_ready(st).await;
+            }

Note: This expects process_ready to handle identical mic/spk (see next comment).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a24cec6 and f16ae61.

📒 Files selected for processing (1)
  • plugins/listener/src/actors/processor.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}

⚙️ CodeRabbit configuration file

**/*.{js,ts,tsx,rs}: 1. Do not add any error handling. Keep the existing one.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

Files:

  • plugins/listener/src/actors/processor.rs
⏰ 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). (2)
  • GitHub Check: ci (windows, windows-latest)
  • GitHub Check: ci (macos, macos-14)
🔇 Additional comments (3)
plugins/listener/src/actors/processor.rs (3)

85-86: LGTM: rename wiring is consistent

State assignment for AttachListener mirrors the previous AttachListen behavior.


94-100: LGTM: speaker path

AGC on speaker, state update, and joiner enqueue remain consistent with prior semantics.


19-22: Resolved: public message renames and Mixed wiring verified

All call sites migrated — no Spk or AttachListen remains. Processor handles ProcMsg::AttachListener (plugins/listener/src/actors/processor.rs:85), ProcMsg::Speaker (plugins/listener/src/actors/processor.rs:94), ProcMsg::Mixed (plugins/listener/src/actors/processor.rs:101); producers send ProcMsg::Mixed/ProcMsg::Speaker (plugins/listener/src/actors/source.rs:205,266); session casts AttachListener (plugins/listener/src/actors/session.rs:314).

Copy link

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugins/listener/src/actors/source.rs (1)

8-12: Fix DeviceEvent match: DefaultInputChanged is a unit variant

plugins/listener/src/actors/source.rs (around line 67) matches DefaultInputChanged with { .. } but DeviceEvent::DefaultInputChanged is a unit variant (crates/audio/src/device_monitor.rs). Replace the match arm with the unit pattern, e.g.:

DeviceEvent::DefaultInputChanged
| DeviceEvent::DefaultOutputChanged { .. } => { /* ... */ }

♻️ Duplicate comments (4)
crates/audio/src/lib.rs (1)

144-155: Make from_mixed consistent: return Self and drop the Result/unwrap mismatch

Function returns Result but constructs MixedInput with unwrap, so Err can never occur; callers then unwrap again. Prefer a Self return for clarity.

Apply:

-    #[cfg(target_os = "macos")]
-    pub fn from_mixed() -> Result<Self, crate::Error> {
-        let mixed = MixedInput::new().unwrap();
-
-        Ok(Self {
+    #[cfg(target_os = "macos")]
+    pub fn from_mixed() -> Self {
+        let mixed = MixedInput::new().unwrap();
+
+        Self {
             source: AudioSource::RealtimeMixed,
             mic: None,
             speaker: None,
             mixed: Some(mixed),
             data: None,
-        })
+        }
     }
plugins/listener/src/actors/source.rs (3)

66-71: Don’t overwrite user‑selected mic on output changes; treat output changes as restart‑only

Update to only change mic on DefaultInputChanged; for DefaultOutputChanged, just trigger restart logic.

Apply:

-                match event {
-                    DeviceEvent::DefaultInputChanged { .. }
-                    | DeviceEvent::DefaultOutputChanged { .. } => {
-                        let new_device = AudioInput::get_default_mic_name();
-                        let _ = myself_clone.cast(SourceCtrl::SetMicDevice(Some(new_device)));
-                    }
-                }
+                match event {
+                    DeviceEvent::DefaultInputChanged { .. } => {
+                        let new_device = AudioInput::get_default_mic_name();
+                        let _ = myself_clone.cast(SourceCtrl::SetMicDevice(Some(new_device)));
+                    }
+                    DeviceEvent::DefaultOutputChanged { .. } => {
+                        // restart stream, keep current mic selection
+                        let _ = myself_clone.cast(SourceCtrl::SetMicDevice(None));
+                    }
+                }

185-191: If from_mixed returns Self, drop the extra unwrap here

Align call site with the proposed signature change.

Apply:

-                let mut mixed_input = AudioInput::from_mixed().unwrap();
+                let mut mixed_input = AudioInput::from_mixed();

204-213: Avoid per‑chunk allocations when muting; zero in place

Allocate‑free path reduces GC/allocator pressure in hot loop.

Apply:

-                        if let Some(data) = mixed_next {
-                            // TODO: should be able to mute each stream
-                            let output_data = if mic_muted.load(Ordering::Relaxed) && spk_muted.load(Ordering::Relaxed) {
-                                vec![0.0; data.len()]
-                            } else {
-                                data
-                            };
-
-                            let msg = ProcMsg::Mixed(AudioChunk{ data: output_data });
+                        if let Some(mut data) = mixed_next {
+                            // Why: avoid allocs; only silence when both are muted
+                            if mic_muted.load(Ordering::Relaxed) && spk_muted.load(Ordering::Relaxed) {
+                                data.fill(0.0);
+                            }
+                            let msg = ProcMsg::Mixed(AudioChunk{ data });
🧹 Nitpick comments (3)
crates/audio/src/lib.rs (1)

82-90: get_default_mic_name() can panic on systems without an input device

default_input_device().unwrap() will crash if no mic is present. If that invariant can’t be guaranteed at startup, consider a non‑panicking fallback; otherwise document the assumption.

plugins/listener/src/actors/source.rs (2)

125-136: Preserve mic selection when restart‑signaling with None

Only update st.mic_device when Some(...) is provided; None should mean “restart with existing device.”

Apply:

-            SourceCtrl::SetMicDevice(dev) => {
-                st.mic_device = dev;
+            SourceCtrl::SetMicDevice(dev) => {
+                if let Some(d) = dev {
+                    st.mic_device = Some(d);
+                }

252-260: Same zero‑in‑place optimization for mic/spk branches

Remove vec allocations on every muted chunk.

Apply:

-                        if let Some(data) = mic_next {
-                            let output_data = if mic_muted.load(Ordering::Relaxed) {
-                                vec![0.0; data.len()]
-                            } else {
-                                data
-                            };
-                            let msg = ProcMsg::Mic(AudioChunk{ data: output_data });
+                        if let Some(mut data) = mic_next {
+                            if mic_muted.load(Ordering::Relaxed) {
+                                data.fill(0.0);
+                            }
+                            let msg = ProcMsg::Mic(AudioChunk{ data });
-                        if let Some(data) = spk_next {
-                            let output_data = if spk_muted.load(Ordering::Relaxed) {
-                                vec![0.0; data.len()]
-                            } else {
-                                data
-                            };
-                            let msg = ProcMsg::Speaker(AudioChunk{ data: output_data });
+                        if let Some(mut data) = spk_next {
+                            if spk_muted.load(Ordering::Relaxed) {
+                                data.fill(0.0);
+                            }
+                            let msg = ProcMsg::Speaker(AudioChunk{ data });

Also applies to: 266-274

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f16ae61 and b317639.

📒 Files selected for processing (4)
  • crates/audio/src/lib.rs (8 hunks)
  • crates/audio/src/mixed/macos.rs (1 hunks)
  • crates/audio/src/mixed/mod.rs (1 hunks)
  • plugins/listener/src/actors/source.rs (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/audio/src/mixed/mod.rs
  • crates/audio/src/mixed/macos.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}

⚙️ CodeRabbit configuration file

**/*.{js,ts,tsx,rs}: 1. Do not add any error handling. Keep the existing one.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

Files:

  • crates/audio/src/lib.rs
  • plugins/listener/src/actors/source.rs
🧬 Code graph analysis (2)
crates/audio/src/lib.rs (3)
crates/audio/src/mic.rs (3)
  • device (84-84)
  • new (34-66)
  • device_name (20-24)
crates/audio/src/utils.rs (1)
  • is_headphone_from_default_output_device (22-25)
crates/audio/src/mixed/macos.rs (1)
  • new (52-65)
plugins/listener/src/actors/source.rs (2)
crates/audio/src/lib.rs (6)
  • get_default_mic_name (82-90)
  • silence (48-63)
  • is_using_headphone (92-105)
  • from_mixed (145-155)
  • from_mic (122-132)
  • from_speaker (134-142)
plugins/listener/src/actors/processor.rs (2)
  • new (170-175)
  • handle (78-115)
⏰ 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). (2)
  • GitHub Check: ci (windows, windows-latest)
  • GitHub Check: ci (macos, macos-14)
🔇 Additional comments (5)
crates/audio/src/lib.rs (2)

247-271: LGTM: handy test helper

The gated sine helper is tidy and localized to tests.


184-186: Unwrap safe — RealtimeMixed only constructed with mixed = Some(...).
The only construction found is crates/audio/src/lib.rs (lines ~149–152) which sets mixed: Some(mixed); no other constructors referencing AudioSource::RealtimeMixed were found.

plugins/listener/src/actors/source.rs (3)

76-79: Honoring SourceArgs.device: nice fix

Preferring args.device over the system default removes the unused‑param smell and respects user intent.


175-180: Mixed path gating looks right

macOS uses headphone detection; other OSes keep use_mixed=false.


181-221: Cancellation paths: good cleanup and actor stop semantics

Dropping pinned streams and stopping the actor on token cancellation is clear and consistent across branches.

Also applies to: 222-281

mod device_monitor;
mod errors;
mod mic;
mod mixed;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Cross‑platform MixedInput availability: verify non‑mac builds compile

MixedInput is referenced in the public struct and re‑exported unconditionally. Please confirm mixed defines a cross‑platform symbol (stub or cfg‑guarded module exposing MixedInput/MixedStream) so Linux/Windows builds don’t break.

Run to check symbol presence and cfg guards:

Also applies to: 13-13, 77-77


🏁 Script executed:

#!/bin/bash
# Expect: at least one unconditional `pub struct MixedInput` (or a re-export)
# or lib.rs fields gated accordingly; otherwise non-mac will fail.
rg -nP '^\s*(pub\s+)?struct\s+MixedInput\b|type\s+MixedInput\b|pub\s+use\s+.*MixedInput' -C2
rg -nP 'cfg\s*\(\s*target_os\s*=\s*"macos"\s*\)' crates/audio/src/mixed -n
# Verify lib.rs doesn't reference MixedInput behind cfg on non-mac
rg -nP 'MixedInput' crates/audio/src/lib.rs -n -C2

Length of output: 783


Unconditional MixedInput reference breaks non‑mac builds — fix required

mixed/macos.rs defines pub struct MixedInput (crates/audio/src/mixed/macos.rs:17) and crates/audio/src/mixed/mod.rs is #[cfg(target_os = "macos")] (crates/audio/src/mixed/mod.rs:1), but lib.rs references MixedInput unguarded (crates/audio/src/lib.rs:75–77: mixed: Option<MixedInput>). This will fail on Linux/Windows.

Actionable fixes (choose one):

  • Gate the module/uses: #[cfg(target_os = "macos")] mod mixed; and #[cfg(target_os = "macos")] on the field/funcs that use MixedInput; or
  • Provide a non‑mac stub/re‑export in mixed/mod.rs (e.g. #[cfg(not(target_os = "macos"))] pub struct MixedInput; or a no‑op reexport) so MixedInput exists cross‑platform.
🤖 Prompt for AI Agents
In crates/audio/src/lib.rs (mod declaration at line 4 and usages around lines
75–77) the code references MixedInput unconditionally even though mixed/mod.rs
is only compiled on macOS, which breaks non‑mac builds; fix by either gating the
module and all declarations/uses with #[cfg(target_os = "macos")] (e.g.
#[cfg(target_os = "macos")] mod mixed; and #[cfg(target_os = "macos")] on the
MixedInput field and any functions that touch it) or by adding a non‑mac
stub/reexport in mixed/mod.rs (e.g. behind #[cfg(not(target_os = "macos"))]
declare a pub struct MixedInput; or a no‑op reexport) so MixedInput is defined
on all platforms; pick one approach and make the corresponding cfg changes
consistently for the module, field, and any functions that reference MixedInput.

Copy link

@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: 4

♻️ Duplicate comments (1)
crates/audio/src/lib.rs (1)

144-155: Don’t unwrap inside a function that returns Result; propagate instead.

Keeps error semantics consistent and prevents panic.

-    pub fn from_mixed() -> Result<Self, crate::Error> {
-        let mixed = MixedInput::new().unwrap();
+    pub fn from_mixed() -> Result<Self, crate::Error> {
+        let mixed = MixedInput::new()?;
 
         Ok(Self {
             source: AudioSource::RealtimeMixed,
             mic: None,
             speaker: None,
             mixed: Some(mixed),
             data: None,
         })
     }
#!/bin/bash
# Verify error conversion exists (anyhow -> crate::Error) so `?` compiles.
rg -nP 'type\s+Error\s*=\s*anyhow::Error|impl\s+From<\s*anyhow::Error\s*>\s+for\s+crate::Error' -g 'crates/**/errors.rs' -S
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b317639 and d4ef0ac.

📒 Files selected for processing (4)
  • crates/audio/src/lib.rs (8 hunks)
  • crates/audio/src/mixed/mod.rs (1 hunks)
  • crates/audio/src/mixed/other.rs (1 hunks)
  • plugins/listener/src/actors/session.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugins/listener/src/actors/session.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}

⚙️ CodeRabbit configuration file

**/*.{js,ts,tsx,rs}: 1. Do not add any error handling. Keep the existing one.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

Files:

  • crates/audio/src/mixed/other.rs
  • crates/audio/src/mixed/mod.rs
  • crates/audio/src/lib.rs
🧬 Code graph analysis (3)
crates/audio/src/mixed/other.rs (2)
crates/audio/src/mixed/mod.rs (3)
  • new (23-26)
  • stream (28-31)
  • poll_next (41-46)
crates/audio/src/lib.rs (2)
  • stream (176-192)
  • poll_next (205-229)
crates/audio/src/mixed/mod.rs (4)
crates/audio/src/mixed/other.rs (3)
  • new (7-9)
  • stream (11-13)
  • poll_next (21-26)
crates/audio/src/mixed/macos.rs (5)
  • new (52-65)
  • stream (190-224)
  • poll_next (262-284)
  • std (170-170)
  • sample_rate (32-34)
crates/audio/src/speaker/mod.rs (7)
  • new (33-36)
  • new (39-43)
  • stream (46-49)
  • stream (52-56)
  • poll_next (67-80)
  • sample_rate (89-91)
  • sample_rate (94-96)
crates/audio/src/lib.rs (3)
  • stream (176-192)
  • poll_next (205-229)
  • sample_rate (237-244)
crates/audio/src/lib.rs (7)
crates/audio/src/mic.rs (3)
  • device (84-84)
  • new (34-66)
  • device_name (20-24)
crates/audio/src/utils.rs (1)
  • is_headphone_from_default_output_device (22-25)
crates/audio/src/mixed/mod.rs (1)
  • new (23-26)
crates/audio/src/mixed/other.rs (1)
  • new (7-9)
crates/audio/src/mixed/macos.rs (1)
  • new (52-65)
crates/audio/src/speaker/mod.rs (2)
  • new (33-36)
  • new (39-43)
crates/audio/src/speaker/macos.rs (1)
  • new (51-92)
⏰ 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). (2)
  • GitHub Check: ci (macos, macos-14)
  • GitHub Check: ci (windows, windows-latest)
🔇 Additional comments (2)
crates/audio/src/mixed/mod.rs (1)

4-16: Good cfg‑gating of platform implementations.

macOS vs. non‑mac split is clean and keeps non‑mac builds green.

crates/audio/src/lib.rs (1)

66-71: API surface extension looks consistent.

New RealtimeMixed variants across AudioSource/AudioStream are wired through stream dispatch and sample_rate.

Also applies to: 195-200

Comment on lines +82 to +90
pub fn get_default_mic_name() -> String {
let name = {
let host = cpal::default_host();
let device = host.default_input_device().unwrap();
device.name().unwrap_or("Unknown Microphone".to_string())
};

name
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🧩 Analysis chain

Potential panic when no default input device.

default_input_device().unwrap() will panic on systems without an input device; verify this is acceptable for the environments you target.


🏁 Script executed:

#!/bin/bash
# Locate all call sites of get_default_mic_name to ensure it's not used in headless/CI paths.
rg -nP '\bget_default_mic_name\s*\(' --type=rs

Length of output: 26


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Rust file count:"
find . -type f -name '*.rs' | wc -l
echo
echo "Searching for occurrences of get_default_mic_name..."
rg -nP '\bget_default_mic_name\s*\(' -S -g '**/*.rs' || true
rg -nP '\bget_default_mic_name\b' -S -g '**/*.rs' || true
echo
echo "Show crates/audio/src/lib.rs (lines 1-200) if exists:"
if [ -f crates/audio/src/lib.rs ]; then
  sed -n '1,200p' crates/audio/src/lib.rs
else
  echo "crates/audio/src/lib.rs not found"
fi

Length of output: 6061


Avoid panic: handle missing default input device in get_default_mic_name

default_input_device().unwrap() will panic if no input device; change get_default_mic_name (crates/audio/src/lib.rs, ~lines 82–90) to avoid unwrap — return Option or use .default_input_device().and_then(|d| d.name().ok()).unwrap_or_else(|| "Unknown Microphone".to_string()). Call sites to review: plugins/listener/src/actors/source.rs:69, plugins/listener/src/actors/source.rs:78.

🤖 Prompt for AI Agents
In crates/audio/src/lib.rs around lines 82–90, get_default_mic_name currently
calls default_input_device().unwrap() which will panic when no input device;
change it to avoid unwrap by either (A) changing the signature to pub fn
get_default_mic_name() -> Option<String> and return
host.default_input_device().and_then(|d| d.name().ok()), or (B) keep returning
String but use host.default_input_device().and_then(|d|
d.name().ok()).unwrap_or_else(|| "Unknown Microphone".to_string()) so it never
panics. Update call sites in plugins/listener/src/actors/source.rs (around lines
69 and 78) to handle the new Option<String> if you choose (A), or leave as-is if
you choose (B); ensure all .unwrap() usages are removed and replace with safe
handling.

Comment on lines +54 to +63
fn sample_rate(&self) -> u32 {
#[cfg(target_os = "macos")]
{
self.inner.sample_rate()
}
#[cfg(not(target_os = "macos"))]
{
0
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Non‑mac sample_rate() returns 0 — confirm no callers divide/use it as a real rate.

If any consumer assumes >0, this can cause divide‑by‑zero or timing bugs.


🏁 Script executed:

#!/bin/bash
# Grep for uses of sample_rate() and nearby arithmetic.
rg -nP -C3 '\bsample_rate\s*\(' --type=rs
rg -nP -C2 '/\s*\(?\s*sample_rate\s*\(' --type=rs
rg -nP -C2 'Duration::from_secs_f(32|64)\s*\(\s*1\.0\s*/' --type=rs

Length of output: 80


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "checking for ripgrep (rg)..."
if ! command -v rg >/dev/null 2>&1; then
  echo "rg not found; falling back to find+grep for .rs files"
  find . -type f -name '*.rs' -print0 | xargs -0 grep -n -E '\bsample_rate' || true
  exit 0
fi

rg --version || true

echo
echo "=== occurrences of 'sample_rate' (context -C3) ==="
rg -n -S '\bsample_rate' -C3 || true

echo
echo "=== occurrences of '.sample_rate(' ==="
rg -n -S '\.sample_rate\s*\(' -C3 || true

echo
echo "=== explicit 'sample_rate()' calls ==="
rg -n -S '\bsample_rate\s*\(\)' -C3 || true

echo
echo "=== possible divisions using sample_rate as denominator ==="
rg -n -S '/\s*\(?\s*sample_rate\s*\(' -C3 || true
rg -n -S '1\.\d+\s*/\s*\(?\s*sample_rate\s*\(' -C3 || true
rg -n -S '\/\s*sample_rate\s*\(\)' -C3 || true

echo
echo "=== casts to float near sample_rate ==="
rg -n -S 'sample_rate\s*\(\)\s*as\s*f(32|64)' -C3 || true
rg -n -S 'as\s*f(32|64)\s*.*sample_rate' -C3 || true

echo
echo "=== search results (paths only) for 'sample_rate()' ==="
rg -n --hidden --no-ignore-vcs '\bsample_rate\s*\(' -g '!*target/**' || true

echo
echo "=== print crates/audio/src/mixed/mod.rs (if present) ==="
if [ -f crates/audio/src/mixed/mod.rs ]; then
  sed -n '1,240p' crates/audio/src/mixed/mod.rs || true
else
  echo "file not found: crates/audio/src/mixed/mod.rs"
fi

Length of output: 48672


MixedStream::sample_rate() returns 0 on non‑mac — callers assume >0 and will misbehave or panic.

  • crayons/audio/src/mixed/mod.rs:54-60 — non‑mac branch returns 0.
  • crates/vad/src/continuous.rs:42 (used to compute chunk_samples at 46) — will set config.sample_rate = 0.
  • crates/pyannote-local/src/segmentation.rs:134-139 — divides by sample_rate and casts indices; 0 -> inf/NaN -> invalid integer casts.
  • crates/audio-utils/src/lib.rs:90-93 — resampling math uses source.sample_rate() as f64.
  • crates/audio/src/resampler.rs:15,35,41 — resampler logic relies on source.sample_rate().
  • crates/audio/src/norm.rs:66-77 — EbuR128/TruePeakLimiter created with sample_rate (must be >0).
  • crates/audio/src/speaker/mod.rs:154 — assert!(sample_rate > 0) (will fail if sample_rate() == 0).

Action: fix MixedStream::sample_rate() on non‑mac to return a valid sample rate (or change the API to return Option/Result and update callers to handle missing/zero rates).

🤖 Prompt for AI Agents
In crates/audio/src/mixed/mod.rs around lines 54-63, MixedStream::sample_rate()
currently returns 0 on non-mac which breaks callers; change the non-mac branch
to return the real sample rate instead of 0 by delegating to
self.inner.sample_rate() (remove the platform-gated 0 return), ensuring inner
exposes a valid sample_rate(); if inner cannot provide a valid rate, change the
method signature to return Option<u32> or Result<u32, Error> and update all
callers to handle None/Error (replace blind divisions/casts and asserts with
guarded logic that fails gracefully or propagates the error).

Comment on lines +11 to +13
pub fn stream(self) -> Result<MixedStream> {
Ok(MixedStream {})
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Align non‑mac API with wrapper to avoid type mismatch (compile‑blocking).

MixedInput::stream should return MixedStream (not Result) to match macOS and the wrapper in mixed/mod.rs, otherwise let inner = self.inner.stream(); in the wrapper becomes Result<_> on non‑mac and fails to compile.

-    pub fn stream(self) -> Result<MixedStream> {
-        Ok(MixedStream {})
-    }
+    pub fn stream(self) -> MixedStream {
+        MixedStream {}
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn stream(self) -> Result<MixedStream> {
Ok(MixedStream {})
}
pub fn stream(self) -> MixedStream {
MixedStream {}
}
🤖 Prompt for AI Agents
In crates/audio/src/mixed/other.rs around lines 11 to 13, the non-mac
implementation of MixedInput::stream currently returns Result<MixedStream>
causing a type mismatch with the macOS API and the wrapper in mixed/mod.rs;
change the function signature to return MixedStream (not Result), and update the
body to directly return a MixedStream value (e.g., MixedStream {}) instead of
Ok(...); ensure any Result-related imports or error handling are removed or
adjusted so the function and its callers expect a plain MixedStream.

Comment on lines +21 to +26
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
Poll::Pending
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix missing Poll path and silence the unused cx warning.

Poll isn’t in scope and cx is unused; both trigger errors/warnings on strict builds.

-    fn poll_next(
-        mut self: std::pin::Pin<&mut Self>,
-        cx: &mut std::task::Context<'_>,
-    ) -> Poll<Option<Self::Item>> {
-        Poll::Pending
-    }
+    fn poll_next(
+        mut self: std::pin::Pin<&mut Self>,
+        _cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<Option<Self::Item>> {
+        std::task::Poll::Pending
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
Poll::Pending
}
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
std::task::Poll::Pending
}
🤖 Prompt for AI Agents
In crates/audio/src/mixed/other.rs around lines 21-26, the code uses
Poll::Pending but Poll is not in scope and the cx parameter is unused; either
bring Poll into scope with use std::task::Poll; or fully-qualify the return as
std::task::Poll::Pending, and mark the context as used to silence the warning by
renaming the parameter to _cx (or add let _ = cx; inside the function). Apply
one of these fixes so the Poll path is resolved and the unused cx warning is
suppressed.

@yujonglee yujonglee merged commit 0bcadd0 into main Sep 17, 2025
15 of 16 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Sep 20, 2025
jbbjbb pushed a commit to jbbjbb/hyprnote that referenced this pull request Nov 12, 2025
Fixes fastrepl#1371

On macOS 15.x, creating a private aggregate device for speaker audio
capture triggers system-wide device change notifications. The DeviceMonitor
was reacting to these events during initialization, causing the audio
pipeline to restart in a loop and never stabilize.

This change adds an initialization_complete flag that prevents device
change events from being processed until both mic and speaker streams
have successfully initialized. The flag is reset when the user explicitly
changes devices to allow proper reinitialization.

Root Cause:
- PR fastrepl#1471 introduced aggregate device for speaker capture
- Creating "private" aggregate device unexpectedly triggers device change events
- DeviceMonitor caught in reinitialization loop during startup
- Audio pipeline never stabilized, no audio captured

Solution:
- Added Arc<AtomicBool> to track initialization completion
- Device events ignored during initialization phase
- Flag set to true after both mic+speaker streams start
- Flag reset on explicit device changes for proper reinitialization

Testing:
- Verified on macOS 15.7.2 (M4 MacBook Pro)
- Audio capture now works correctly
- No device switching loop observed
- Device changes handled gracefully during recording
- Success message "audio_streams_initialized" confirms stable pipeline

Changes:
- Modified: plugins/listener/src/actors/source.rs (+17 lines)
- No breaking changes
- Backward compatible
@coderabbitai coderabbitai bot mentioned this pull request Nov 13, 2025
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