Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions crates/audio/src/bin/test_mic.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
use audio::AudioInput;

/// Tests microphone access by listing available devices and attempting to create audio input.
///
/// Prints available microphone devices, the default device name, and then tries to create an `AudioInput`
/// using the default device, the first enumerated device (if any), and a set of known device names,
/// reporting success or failure to stdout.
///
/// # Examples
///
/// ```no_run
/// // Invoke the binary entry point which performs device enumeration and connection attempts.
/// main();
/// ```
fn main() {
println!("Testing microphone access...");

Expand Down
14 changes: 14 additions & 0 deletions crates/audio/src/bin/test_speaker.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
use audio::AudioInput;

/// Runs a simple binary test that creates an `AudioInput` from the default speaker, obtains its stream, and prints which `AudioStream` variant was returned.
///
/// This program exercises creation of a speaker `AudioInput`, requests its stream, and reports whether the stream is a `RealtimeSpeaker` variant. It does not poll or consume audio samples.
///
/// # Examples
///
/// ```
/// // Call the test binary's main to perform the creation and type check.
/// // The example demonstrates the intended usage; output is printed to stdout.
/// fn run() {
/// crate::main();
/// }
/// run();
/// ```
fn main() {
println!("Testing SpeakerInput creation...");

Expand Down
57 changes: 56 additions & 1 deletion crates/audio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ pub struct AudioInput {
}

impl AudioInput {
/// Get the name of the system's default input (microphone) device.
///
/// # Returns
///
/// A `String` with the device name, `"Unknown Microphone"` if the device exists but has no name, or `"No Microphone Available"` if there is no default input device.
///
/// # Examples
///
/// ```
/// let name = get_default_mic_device_name();
/// assert!(!name.is_empty());
/// ```
pub fn get_default_mic_device_name() -> String {
let host = cpal::default_host();
if let Some(device) = host.default_input_device() {
Expand All @@ -78,6 +90,19 @@ impl AudioInput {
}
}

/// Returns a list of available input (microphone) device names.
///
/// The returned list contains the names of enumerated input devices. It filters out the
/// "hypr-audio-tap" device and will append the virtual "echo-cancel-source" device if
/// `pactl list sources short` reports it and it is not already present.
///
/// # Examples
///
/// ```
/// let devices = crate::audio::list_mic_devices();
/// // devices is a Vec<String> of device names (may be empty)
/// assert!(devices.is_empty() || devices.iter().all(|s| !s.is_empty()));
/// ```
pub fn list_mic_devices() -> Vec<String> {
let host = cpal::default_host();

Expand Down Expand Up @@ -133,6 +158,20 @@ impl AudioInput {
result
}

/// Creates an AudioInput configured to stream from a microphone.
///
/// If `device_name` is `Some(name)`, attempts to open the input device with that name; if `None`, uses the default input device. On success returns an `AudioInput` with `source` set to `RealtimeMic` and `mic` initialized.
///
/// # Errors
///
/// Returns a `crate::Error` if microphone initialization fails.
///
/// # Examples
///
/// ```
/// let ai = AudioInput::from_mic(None).expect("failed to open default microphone");
/// assert!(matches!(ai.source, AudioSource::RealtimeMic));
/// ```
pub fn from_mic(device_name: Option<String>) -> Result<Self, crate::Error> {
tracing::info!("Creating AudioInput from microphone with device name: {:?}", device_name);
let mic = MicInput::new(device_name)?;
Expand All @@ -146,6 +185,22 @@ impl AudioInput {
})
}

/// Creates an AudioInput configured to capture audio from the system speaker.
///
/// The returned `AudioInput` uses `AudioSource::RealtimeSpeaker`. The `speaker` field will
/// contain `Some(SpeakerInput)` if speaker capture initialization succeeds, or `None` if it fails;
/// `mic` and `data` are always `None`.
///
/// # Examples
///
/// ```
/// let input = AudioInput::from_speaker();
/// // `input` is configured for realtime speaker capture; speaker initialization may have failed.
/// match input.source {
/// AudioSource::RealtimeSpeaker => {},
/// _ => panic!("expected RealtimeSpeaker"),
/// }
/// ```
pub fn from_speaker() -> Self {
tracing::debug!("Creating AudioInput from speaker");
let speaker = match SpeakerInput::new() {
Expand Down Expand Up @@ -248,4 +303,4 @@ impl kalosm_sound::AsyncSource for AudioStream {
AudioStream::Recorded { .. } => 16000,
}
}
}
}
38 changes: 36 additions & 2 deletions crates/audio/src/mic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ impl MicInput {
.unwrap_or("Unknown Microphone".to_string())
}

/// List available input audio device names.
///
/// A Vec<String> containing the names of available input devices. If a device's
/// name cannot be retrieved, the entry will be "Unknown Microphone".
///
/// # Examples
///
/// ```
/// let names = list_devices();
/// assert!(names.iter().all(|n| !n.is_empty()));
/// ```
pub fn list_devices() -> Vec<String> {
cpal::default_host()
.input_devices()
Expand All @@ -31,7 +42,30 @@ impl MicInput {
.collect()
}

pub fn new(device_name: Option<String>) -> Result<Self, crate::Error> {
/// Creates a new MicInput by selecting and configuring an available input device.
///
/// This tries to select the requested device when `device_name` is Some, otherwise it prefers
/// the system default input device and falls back to the first enumerated input device. If no
/// devices are directly usable the initializer attempts platform-specific fallbacks (for example
/// handling echo-cancel-source and ALSA probes) before returning an error.
///
/// # Parameters
///
/// - `device_name`: Optional device name to prefer; when `None` the function will use the default
/// input device if valid, otherwise the first available device.
///
/// # Returns
///
/// `Ok(Self)` with the chosen host, device, and supported stream configuration on success,
/// `Err(crate::Error::NoInputDevice)` if no usable input device or configuration can be found.
///
/// # Examples
///
/// ```
/// // Create a MicInput using the default device (or fallbacks).
/// let _ = MicInput::new(None);
/// ```
pub fn new(device_name: Option<String>) -> Result<Self, crate::Error> {
let host = cpal::default_host();

tracing::info!("Initializing microphone input...");
Expand Down Expand Up @@ -537,4 +571,4 @@ mod tests {

assert!(buffer.iter().any(|x| *x != 0.0));
}
}
}
87 changes: 86 additions & 1 deletion crates/audio/src/speaker/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,31 @@ use std::time::Duration;
pub struct SpeakerInput {}

impl SpeakerInput {
/// Construct a new Linux SpeakerInput handle.
///
/// Returns `Ok(Self)` on success, or an `anyhow::Error` if creation fails.
///
/// # Examples
///
/// ```
/// let input = SpeakerInput::new().unwrap();
/// ```
pub fn new() -> Result<Self, anyhow::Error> {
tracing::debug!("Creating Linux SpeakerInput");
Ok(Self {})
}

/// Creates a `SpeakerStream` for receiving speaker input samples.
///
/// Returns a `SpeakerStream` that yields `f32` audio samples (silence in the current mock).
///
/// # Examples
///
/// ```
/// let input = crate::speaker::linux::SpeakerInput::new().unwrap();
/// let mut stream = input.stream();
/// assert_eq!(stream.sample_rate(), 48000);
/// ```
pub fn stream(self) -> SpeakerStream {
tracing::debug!("Creating Linux SpeakerStream");
SpeakerStream::new()
Expand All @@ -25,6 +45,20 @@ pub struct SpeakerStream {
}

impl SpeakerStream {
/// Creates a new `SpeakerStream` that produces a continuous stream of silence.
///
/// The returned stream delivers `f32` audio samples (silence as `0.0`) and preserves
/// a background thread for sample production until the stream is dropped.
///
/// # Examples
///
/// ```
/// use futures::stream::StreamExt;
///
/// let mut stream = SpeakerStream::new();
/// let sample = futures::executor::block_on(async { stream.next().await }).unwrap();
/// assert_eq!(sample, 0.0);
/// ```
pub fn new() -> Self {
tracing::debug!("Creating Linux SpeakerStream");
// For now, we'll create a mock implementation that generates silence
Expand Down Expand Up @@ -53,6 +87,20 @@ impl SpeakerStream {
}
}

/// Audio sample rate for the speaker stream.
///
/// The method reports the sample rate used for audio frames.
///
/// # Returns
///
/// The sample rate in hertz (48000).
///
/// # Examples
///
/// ```
/// let stream = SpeakerStream::new();
/// assert_eq!(stream.sample_rate(), 48000);
/// ```
pub fn sample_rate(&self) -> u32 {
48000 // Standard sample rate
}
Expand All @@ -61,6 +109,32 @@ impl SpeakerStream {
impl Stream for SpeakerStream {
type Item = f32;

/// Polls the stream for the next audio sample from the internal channel.
///
/// Returns `Poll::Ready(Some(sample))` when a sample is available, `Poll::Pending` and
/// schedules the task to be woken when no sample is currently available, and
/// `Poll::Ready(None)` when the producer side of the channel has been disconnected,
/// signalling the end of the stream.
///
/// # Examples
///
/// ```
/// use futures::stream::StreamExt;
/// use std::pin::Pin;
///
/// // Create the speaker stream and pin it for polling.
/// let stream = crate::speaker::linux::SpeakerStream::new();
/// let mut pinned = Box::pin(stream);
///
/// // Poll the stream asynchronously to get the next sample.
/// let sample = futures::executor::block_on(async {
/// pinned.as_mut().next().await
/// });
///
/// // The implementation sends silence (0.0) periodically, so we should get a sample
/// // while the background producer thread is running.
/// assert!(sample.is_some());
/// ```
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
Expand All @@ -78,9 +152,20 @@ impl Stream for SpeakerStream {
}

impl Drop for SpeakerStream {
/// Logs when the SpeakerStream is dropped and allows its background producer to terminate by closing the channel.
///
/// Dropping the stream closes its receiving endpoint; the background thread will observe the channel closure and exit.
///
/// # Examples
///
/// ```
/// # use crates::audio::speaker::linux::SpeakerStream;
/// let stream = SpeakerStream::new();
/// drop(stream);
/// ```
fn drop(&mut self) {
// The thread will automatically exit when the sender is dropped
// and the receiver gets a Disconnected error
tracing::debug!("Dropping SpeakerStream");
}
}
}
Loading