-
Notifications
You must be signed in to change notification settings - Fork 537
Mixed audio #1471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mixed audio #1471
Changes from all commits
ab83ce2
e7dd87b
f236e3d
a24cec6
f16ae61
b317639
d4ef0ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,20 @@ | ||
| mod device_monitor; | ||
| mod errors; | ||
| mod mic; | ||
| mod mixed; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainCross‑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 -C2Length of output: 783 Unconditional MixedInput reference breaks non‑mac builds — fix required mixed/macos.rs defines Actionable fixes (choose one):
🤖 Prompt for AI Agents |
||
| mod norm; | ||
| mod resampler; | ||
| mod speaker; | ||
| mod utils; | ||
|
|
||
| pub use device_monitor::*; | ||
| pub use errors::*; | ||
| pub use mic::*; | ||
| pub use mixed::*; | ||
| pub use norm::*; | ||
| pub use resampler::*; | ||
| pub use speaker::*; | ||
| pub use utils::*; | ||
|
|
||
| pub use cpal; | ||
| use cpal::traits::{DeviceTrait, HostTrait}; | ||
|
|
@@ -62,21 +66,42 @@ impl AudioOutput { | |
| pub enum AudioSource { | ||
| RealtimeMic, | ||
| RealtimeSpeaker, | ||
| RealtimeMixed, | ||
| Recorded, | ||
| } | ||
|
|
||
| pub struct AudioInput { | ||
| source: AudioSource, | ||
| mic: Option<MicInput>, | ||
| speaker: Option<SpeakerInput>, | ||
| mixed: Option<MixedInput>, | ||
| data: Option<Vec<u8>>, | ||
| } | ||
|
|
||
| impl AudioInput { | ||
| pub fn get_default_mic_device_name() -> String { | ||
| let host = cpal::default_host(); | ||
| let device = host.default_input_device().unwrap(); | ||
| device.name().unwrap_or("Unknown Microphone".to_string()) | ||
| 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 | ||
| } | ||
|
Comment on lines
+82
to
+90
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 🧩 Analysis chainPotential panic when no default input device.
🏁 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=rsLength 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"
fiLength 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 |
||
|
|
||
| pub fn is_using_headphone() -> bool { | ||
| let headphone = { | ||
| #[cfg(target_os = "macos")] | ||
| { | ||
| utils::macos::is_headphone_from_default_output_device() | ||
| } | ||
| #[cfg(not(target_os = "macos"))] | ||
| { | ||
| false | ||
| } | ||
| }; | ||
|
|
||
| headphone | ||
| } | ||
|
|
||
| pub fn list_mic_devices() -> Vec<String> { | ||
|
|
@@ -101,6 +126,7 @@ impl AudioInput { | |
| source: AudioSource::RealtimeMic, | ||
| mic: Some(mic), | ||
| speaker: None, | ||
| mixed: None, | ||
| data: None, | ||
| }) | ||
| } | ||
|
|
@@ -110,24 +136,40 @@ impl AudioInput { | |
| source: AudioSource::RealtimeSpeaker, | ||
| mic: None, | ||
| speaker: Some(SpeakerInput::new().unwrap()), | ||
| mixed: None, | ||
| data: None, | ||
| } | ||
| } | ||
|
|
||
| #[cfg(target_os = "macos")] | ||
| pub fn from_mixed() -> Result<Self, crate::Error> { | ||
| let mixed = MixedInput::new().unwrap(); | ||
|
|
||
| Ok(Self { | ||
| source: AudioSource::RealtimeMixed, | ||
| mic: None, | ||
| speaker: None, | ||
| mixed: Some(mixed), | ||
| data: None, | ||
| }) | ||
| } | ||
yujonglee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| pub fn from_recording(data: Vec<u8>) -> Self { | ||
| Self { | ||
| source: AudioSource::Recorded, | ||
| mic: None, | ||
| speaker: None, | ||
| mixed: None, | ||
| data: Some(data), | ||
| } | ||
| } | ||
|
|
||
| pub fn device_name(&self) -> String { | ||
| match &self.source { | ||
| AudioSource::RealtimeMic => self.mic.as_ref().unwrap().device_name(), | ||
| AudioSource::RealtimeSpeaker => "TODO".to_string(), | ||
| AudioSource::Recorded => "TODO".to_string(), | ||
| AudioSource::RealtimeSpeaker => "RealtimeSpeaker".to_string(), | ||
| AudioSource::RealtimeMixed => "Mixed Audio".to_string(), | ||
| AudioSource::Recorded => "Recorded".to_string(), | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -139,6 +181,9 @@ impl AudioInput { | |
| AudioSource::RealtimeSpeaker => AudioStream::RealtimeSpeaker { | ||
| speaker: self.speaker.take().unwrap().stream().unwrap(), | ||
| }, | ||
| AudioSource::RealtimeMixed => AudioStream::RealtimeMixed { | ||
| mixed: self.mixed.take().unwrap().stream().unwrap(), | ||
| }, | ||
| AudioSource::Recorded => AudioStream::Recorded { | ||
| data: self.data.as_ref().unwrap().clone(), | ||
| position: 0, | ||
|
|
@@ -150,6 +195,7 @@ impl AudioInput { | |
| pub enum AudioStream { | ||
| RealtimeMic { mic: MicStream }, | ||
| RealtimeSpeaker { speaker: SpeakerStream }, | ||
| RealtimeMixed { mixed: MixedStream }, | ||
| Recorded { data: Vec<u8>, position: usize }, | ||
| } | ||
|
|
||
|
|
@@ -166,7 +212,7 @@ impl Stream for AudioStream { | |
| match &mut *self { | ||
| AudioStream::RealtimeMic { mic } => mic.poll_next_unpin(cx), | ||
| AudioStream::RealtimeSpeaker { speaker } => speaker.poll_next_unpin(cx), | ||
| // assume pcm_s16le, without WAV header | ||
| AudioStream::RealtimeMixed { mixed } => mixed.poll_next_unpin(cx), | ||
| AudioStream::Recorded { data, position } => { | ||
| if *position + 2 <= data.len() { | ||
| let bytes = [data[*position], data[*position + 1]]; | ||
|
|
@@ -192,7 +238,34 @@ impl kalosm_sound::AsyncSource for AudioStream { | |
| match self { | ||
| AudioStream::RealtimeMic { mic } => mic.sample_rate(), | ||
| AudioStream::RealtimeSpeaker { speaker } => speaker.sample_rate(), | ||
| AudioStream::RealtimeMixed { mixed } => mixed.sample_rate(), | ||
| AudioStream::Recorded { .. } => 16000, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| pub(crate) fn play_sine_for_sec(seconds: u64) -> std::thread::JoinHandle<()> { | ||
| use rodio::{ | ||
| cpal::SampleRate, | ||
| source::{Function::Sine, SignalGenerator, Source}, | ||
| OutputStream, | ||
| }; | ||
| use std::{ | ||
| thread::{sleep, spawn}, | ||
| time::Duration, | ||
| }; | ||
|
|
||
| spawn(move || { | ||
| let (_stream, stream_handle) = OutputStream::try_default().unwrap(); | ||
| let source = SignalGenerator::new(SampleRate(44100), 440.0, Sine); | ||
|
|
||
| let source = source | ||
| .convert_samples() | ||
| .take_duration(Duration::from_secs(seconds)) | ||
| .amplify(0.01); | ||
|
|
||
| stream_handle.play_raw(source).unwrap(); | ||
| sleep(Duration::from_secs(seconds)); | ||
| }) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.