Add simple device selector window

This commit is contained in:
Lucas Schumacher 2024-05-31 22:36:45 -04:00
parent 243fcdb31e
commit f3216e2d8d
6 changed files with 212 additions and 49 deletions

View File

@ -1,13 +1,15 @@
use eframe::{egui_glow, glow};
use egui::mutex::Mutex;
use egui::{mutex::Mutex, ScrollArea};
use std::sync::Arc;
use crate::backend::{self, Backends};
pub mod debug_plot;
use debug_plot::DebugPlots;
mod waterfall;
use waterfall::Waterfall;
mod audio;
use audio::Audio;
//mod aud1o;
//use aud1o::Audio;
mod fft;
use fft::Fft;
pub mod turbo_colormap;
@ -21,8 +23,11 @@ pub struct TemplateApp {
value: f32,
/// Behind an `Arc<Mutex<…>>` so we can pass it to [`egui::PaintCallback`] and paint later.
waterfall: Arc<Mutex<Waterfall>>,
_stream: Audio,
//_stream: Audio,
_fft: Fft,
_backends: backend::Backends,
_selected_backend: usize,
_open_device: Option<Box<dyn backend::Device>>,
}
impl TemplateApp {
@ -38,7 +43,7 @@ impl TemplateApp {
//let (stream, rx) = AudioFFT::new(FFT_SIZE, plots.get_sender()).unwrap();
let (fft, rx) = Fft::new(FFT_SIZE, plots.get_sender()).unwrap();
let stream = Audio::new(fft.tx.clone(), plots.get_sender()).unwrap();
//let stream = Audio::new(fft.tx.clone(), plots.get_sender()).unwrap();
let wf_size = fft.output_len;
let gl = cc
@ -52,8 +57,11 @@ impl TemplateApp {
label: "Hello World!".to_owned(),
value: 2.7,
waterfall: Arc::new(Mutex::new(Waterfall::new(gl, wf_size, wf_size, rx))),
_stream: stream,
//_stream: stream,
_fft: fft,
_backends: Backends::default(),
_selected_backend: 0,
_open_device: None,
}
}
}
@ -100,6 +108,54 @@ impl eframe::App for TemplateApp {
self.plots.render_plot_windows(ctx);
egui::Window::new("Select Device")
.default_width(600.0)
.default_height(400.0)
.vscroll(false)
.resizable(true)
.show(ctx, |ui| {
egui::SidePanel::left("Select Driver")
.resizable(true)
.default_width(150.0)
.width_range(80.0..=200.0)
.show_inside(ui, |ui| {
ScrollArea::vertical().show(ui, |ui| {
ui.with_layout(
egui::Layout::top_down_justified(egui::Align::LEFT),
|ui| {
for (i, b) in self._backends.0.iter().enumerate() {
ui.selectable_value(
&mut self._selected_backend,
i,
b.display_text(),
);
}
},
);
});
});
//egui::CentralPanel::default().show_inside(ui, |ui| {
ui.vertical_centered(|ui| {
egui::ScrollArea::vertical().show(ui, |ui| {
//if self._selected_backend < self._backends.0.len() {
if let Some(b) = self._backends.0.get_mut(self._selected_backend) {
//let mut b = &self._backends.0[self._selected_backend];
b.show_device_selection(ui);
if ui.add(egui::Button::new("Apply")).clicked() {
drop(self._open_device.take());
if let Ok(device) =
b.build_device(self._fft.tx.clone(), self.plots.get_sender())
{
self._open_device = Some(device);
}
}
} else {
ui.add(egui::Label::new("Select a Device Driver"));
}
});
});
});
egui::CentralPanel::default().show(ctx, |ui| {
// The central panel the region left after adding TopPanel's and SidePanel's
ui.heading("eframe template");

View File

@ -1,43 +0,0 @@
use anyhow::{anyhow, Result};
use cpal::{
self,
traits::{DeviceTrait, HostTrait},
BufferSize, StreamConfig,
};
use std::sync::mpsc::Sender;
use super::debug_plot::PlotData;
pub struct Audio {
pub stream: cpal::Stream,
}
impl Audio {
pub fn new(
fft_input: Sender<Vec<f32>>,
_plot_tx: Sender<(&'static str, PlotData)>,
) -> Result<Self> {
// Setup audio input
let host = cpal::default_host();
let device = host
.default_input_device()
.ok_or(anyhow!("No input audio device found"))?;
// Basic config that 'should' be suppoted by most devices
let config = StreamConfig {
channels: 1,
sample_rate: cpal::SampleRate(44100),
buffer_size: BufferSize::Default,
};
let stream = device.build_input_stream(
&config,
move |data: &[f32], _: &cpal::InputCallbackInfo| {
fft_input.send(data.to_vec()).unwrap();
},
move |err| log::error!("Audio Thread Error: {err}"),
None,
)?;
Ok(Self { stream })
}
}

106
src/backend/audio.rs Normal file
View File

@ -0,0 +1,106 @@
use anyhow::Result;
use cpal::{
self,
traits::{DeviceTrait, HostTrait},
BufferSize,
};
use std::sync::mpsc::Sender;
use crate::app::debug_plot::PlotData;
pub struct Audio {
pub stream: cpal::Stream,
}
impl Audio {
pub fn new(
device: &cpal::Device,
config: cpal::StreamConfig,
fft_input: Sender<Vec<f32>>,
_plot_tx: Sender<(&'static str, PlotData)>,
) -> Result<Self> {
let stream = device.build_input_stream(
&config,
move |data: &[f32], _: &cpal::InputCallbackInfo| {
fft_input.send(data.to_vec()).unwrap();
},
move |err| log::error!("Audio Thread Error: {err}"),
None,
)?;
Ok(Self { stream })
}
}
impl crate::backend::Device for Audio {
fn show_settings(&mut self, _ui: &mut egui::Ui) {
todo!()
}
fn can_tune(&self) -> bool {
false
}
fn tune(&mut self, _freq: usize) -> anyhow::Result<()> {
anyhow::bail!("Can't tune this device")
}
}
pub struct AudioBackend {
host: cpal::Host,
devices: Vec<cpal::Device>,
current_device: usize,
}
impl AudioBackend {
pub fn new() -> Self {
let host = cpal::default_host();
let devices = host.devices().unwrap().collect();
let current_device = 0;
Self {
host,
devices,
current_device,
}
}
fn update_devices(&mut self) {
self.devices.clear();
self.devices = self.host.devices().unwrap().collect();
self.current_device = 0;
}
}
impl super::Backend for AudioBackend {
fn display_text(&self) -> &'static str {
"Audio"
}
fn show_device_selection(&mut self, ui: &mut egui::Ui) {
egui::ComboBox::from_label("Device")
.selected_text(
self.devices[self.current_device]
.name()
.unwrap_or("UNKNOWN DEVICE".into()),
)
.show_index(ui, &mut self.current_device, self.devices.len(), |i| {
self.devices[i].name().unwrap_or("UNKNOWN DEVICE".into())
});
if ui.add(egui::Button::new("Refresh")).clicked() {
self.update_devices();
}
}
fn build_device(
&mut self,
fft_input: Sender<Vec<f32>>,
_plot_tx: Sender<(&'static str, PlotData)>,
) -> anyhow::Result<Box<dyn super::Device>> {
let config = cpal::StreamConfig {
channels: 1,
sample_rate: cpal::SampleRate(44100),
buffer_size: BufferSize::Default,
};
Ok(Box::new(Audio::new(
&self.devices[self.current_device],
config,
fft_input,
_plot_tx,
)?))
}
}

42
src/backend/mod.rs Normal file
View File

@ -0,0 +1,42 @@
use std::sync::mpsc::Sender;
use egui::Ui;
use crate::app::debug_plot::PlotData;
mod audio;
pub trait Device {
fn show_settings(&mut self, ui: &mut Ui);
fn can_tune(&self) -> bool;
fn tune(&mut self, freq: usize) -> anyhow::Result<()>;
}
pub trait Backend {
fn display_text(&self) -> &'static str;
fn show_device_selection(&mut self, ui: &mut Ui);
fn build_device(
&mut self,
fft_input: Sender<Vec<f32>>,
_plot_tx: Sender<(&'static str, PlotData)>,
) -> anyhow::Result<Box<dyn Device>>;
}
pub struct Backends(pub Vec<Box<dyn Backend>>);
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
impl Default for Backends {
fn default() -> Self {
Backends(vec![Box::new(audio::AudioBackend::new())])
}
}
#[cfg(target_arch = "wasm32")]
impl Default for Backends {
fn default() -> Self {
Backends(vec![])
}
}
#[cfg(target_os = "android")]
impl Default for Backends {
fn default() -> Self {
Backends(vec![])
}
}

View File

@ -1,6 +1,7 @@
#![warn(clippy::all, rust_2018_idioms)]
pub mod app;
mod backend;
#[cfg(target_os = "android")]
#[no_mangle]

View File

@ -1,5 +1,6 @@
mod app;
use app::TemplateApp;
mod backend;
//#[cfg(target_os = "android")]
//fn main() {}