mirror of
https://github.com/zbanks/radiance.git
synced 2026-06-16 04:26:25 +02:00
Add audio input device selector
Add a ComboBox in the top bar to select the audio input device at runtime. Users with USB audio interfaces or multiple input devices can now switch without changing their OS default prior to launching the app. - Add selected_device_name field and switch_device()/ available_input_devices()/selected_device_name() methods to Mir - Refactor audio_input() to accept an optional device name - Add "Audio input:" ComboBox in the top bar after latency compensation - Cache the device list to avoid per-frame CoreAudio enumeration, refresh on ComboBox click to pick up newly connected devices Signed-off-by: Christina Ying Wang <cywang117@outlook.com>
This commit is contained in:
@@ -131,6 +131,8 @@ struct App<'a> {
|
||||
left_panel_expanded: bool,
|
||||
library_newly_opened: bool,
|
||||
insertion_point: InsertionPoint,
|
||||
selected_device_name: String,
|
||||
available_devices: Vec<String>,
|
||||
preview_images: HashMap<NodeId, egui::TextureId>,
|
||||
winit_output: WinitOutput<'a>,
|
||||
app_ui: Option<AppUi>, // Stuff we can't make until we have a window
|
||||
@@ -333,6 +335,8 @@ impl App<'_> {
|
||||
left_panel_expanded: false,
|
||||
library_newly_opened: false,
|
||||
insertion_point: Default::default(),
|
||||
selected_device_name: "Default".to_owned(),
|
||||
available_devices: Mir::available_input_devices(),
|
||||
preview_images: Default::default(),
|
||||
winit_output,
|
||||
app_ui: None,
|
||||
@@ -798,6 +802,35 @@ impl App<'_> {
|
||||
.suffix("s")
|
||||
.range(0. ..=1.),
|
||||
);
|
||||
ui.label("Audio input:");
|
||||
let prev_device = self.selected_device_name.clone();
|
||||
let combo = egui::ComboBox::from_id_salt("audio input device")
|
||||
.selected_text(self.selected_device_name.as_str())
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(
|
||||
&mut self.selected_device_name,
|
||||
"Default".to_owned(),
|
||||
"Default",
|
||||
);
|
||||
for name in &self.available_devices {
|
||||
ui.selectable_value(
|
||||
&mut self.selected_device_name,
|
||||
name.clone(),
|
||||
name.as_str(),
|
||||
);
|
||||
}
|
||||
});
|
||||
if combo.response.clicked() {
|
||||
self.available_devices = Mir::available_input_devices();
|
||||
}
|
||||
if self.selected_device_name != prev_device {
|
||||
let device = if self.selected_device_name == "Default" {
|
||||
None
|
||||
} else {
|
||||
Some(self.selected_device_name.clone())
|
||||
};
|
||||
self.mir.switch_device(device);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+73
-7
@@ -1,6 +1,6 @@
|
||||
pub use crate::beat_tracking::AudioLevels;
|
||||
use crate::beat_tracking::{BeatTracker, N_FILTERS, SAMPLE_RATE};
|
||||
use cpal::traits::DeviceTrait;
|
||||
use cpal::traits::{DeviceTrait, HostTrait};
|
||||
use std::sync::mpsc;
|
||||
use std::time;
|
||||
|
||||
@@ -31,6 +31,7 @@ pub struct Mir {
|
||||
last_update: Update,
|
||||
pub global_timescale: f32,
|
||||
pub latency_compensation: f32, // Anticipate beats by this many seconds
|
||||
selected_device_name: Option<String>,
|
||||
}
|
||||
|
||||
/// Updates sent over a queue
|
||||
@@ -84,13 +85,29 @@ impl Default for Mir {
|
||||
}
|
||||
|
||||
impl Mir {
|
||||
fn audio_input(sender: mpsc::SyncSender<Update>) -> Result<cpal::Stream, String> {
|
||||
use cpal::traits::{HostTrait, StreamTrait};
|
||||
fn audio_input(
|
||||
sender: mpsc::SyncSender<Update>,
|
||||
device_name: Option<&str>,
|
||||
) -> Result<cpal::Stream, String> {
|
||||
use cpal::traits::StreamTrait;
|
||||
|
||||
let host = cpal::default_host();
|
||||
let device = host
|
||||
.default_input_device()
|
||||
.ok_or("No audio input devices found")?;
|
||||
let device = match device_name {
|
||||
Some(name) => host
|
||||
.input_devices()
|
||||
.map_err(|e| format!("Failed to enumerate input devices: {:?}", e))?
|
||||
.find(|d| {
|
||||
d.description()
|
||||
.ok()
|
||||
.map(|desc| desc.name().to_string())
|
||||
.as_deref()
|
||||
== Some(name)
|
||||
})
|
||||
.ok_or_else(|| format!("Audio input device '{}' not found", name))?,
|
||||
None => host
|
||||
.default_input_device()
|
||||
.ok_or("No audio input devices found")?,
|
||||
};
|
||||
|
||||
const MIN_USEFUL_BUFFER_SIZE: cpal::FrameCount = 256; // Lower actually would be useful, but CPAL lies about the min size, so this ought to be safe
|
||||
const SAMPLE_RATE_CPAL: cpal::SampleRate = SAMPLE_RATE as u32;
|
||||
@@ -294,7 +311,7 @@ impl Mir {
|
||||
let (sender, receiver) = mpsc::sync_channel(MESSAGE_BUFFER_SIZE);
|
||||
|
||||
// Set up system audio
|
||||
let (stream, last_update) = match Self::audio_input(sender) {
|
||||
let (stream, last_update) = match Self::audio_input(sender, None) {
|
||||
Ok(stream) => (
|
||||
Some(stream),
|
||||
Update {
|
||||
@@ -330,6 +347,55 @@ impl Mir {
|
||||
last_update,
|
||||
global_timescale: 1.,
|
||||
latency_compensation: 0.1,
|
||||
selected_device_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available_input_devices() -> Vec<String> {
|
||||
let host = cpal::default_host();
|
||||
match host.input_devices() {
|
||||
Ok(devices) => devices
|
||||
.filter_map(|d| d.description().ok().map(|desc| desc.name().to_string()))
|
||||
.collect(),
|
||||
Err(e) => {
|
||||
println!("MIR: Failed to enumerate input devices: {:?}", e);
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_device_name(&self) -> &str {
|
||||
match &self.selected_device_name {
|
||||
Some(name) => name.as_str(),
|
||||
None => "Default",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn switch_device(&mut self, device_name: Option<String>) {
|
||||
self.selected_device_name = device_name;
|
||||
|
||||
// Drop the current stream
|
||||
self._stream = None;
|
||||
|
||||
// Create a new channel and stream
|
||||
const MESSAGE_BUFFER_SIZE: usize = 16;
|
||||
let (sender, receiver) = mpsc::sync_channel(MESSAGE_BUFFER_SIZE);
|
||||
|
||||
match Self::audio_input(sender, self.selected_device_name.as_deref()) {
|
||||
Ok(stream) => {
|
||||
println!(
|
||||
"MIR: Switched to audio device: {}",
|
||||
self.selected_device_name()
|
||||
);
|
||||
self._stream = Some(stream);
|
||||
self.receiver = receiver;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("MIR: Failed to switch audio device: {}", e);
|
||||
println!("MIR: Proceeding with no audio input");
|
||||
self._stream = None;
|
||||
self.receiver = receiver;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user