From f3216e2d8d21657153d4418db7943c02b93e2956 Mon Sep 17 00:00:00 2001 From: Lucas Schumacher Date: Fri, 31 May 2024 22:36:45 -0400 Subject: [PATCH] Add simple device selector window --- src/app.rs | 68 ++++++++++++++++++++++++--- src/app/audio.rs | 43 ------------------ src/backend/audio.rs | 106 +++++++++++++++++++++++++++++++++++++++++++ src/backend/mod.rs | 42 +++++++++++++++++ src/lib.rs | 1 + src/main.rs | 1 + 6 files changed, 212 insertions(+), 49 deletions(-) delete mode 100644 src/app/audio.rs create mode 100644 src/backend/audio.rs create mode 100644 src/backend/mod.rs diff --git a/src/app.rs b/src/app.rs index 91298d0..d0f63e8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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>` so we can pass it to [`egui::PaintCallback`] and paint later. waterfall: Arc>, - _stream: Audio, + //_stream: Audio, _fft: Fft, + _backends: backend::Backends, + _selected_backend: usize, + _open_device: Option>, } 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"); diff --git a/src/app/audio.rs b/src/app/audio.rs deleted file mode 100644 index a6d37a8..0000000 --- a/src/app/audio.rs +++ /dev/null @@ -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>, - _plot_tx: Sender<(&'static str, PlotData)>, - ) -> Result { - // 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 }) - } -} diff --git a/src/backend/audio.rs b/src/backend/audio.rs new file mode 100644 index 0000000..58f3560 --- /dev/null +++ b/src/backend/audio.rs @@ -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>, + _plot_tx: Sender<(&'static str, PlotData)>, + ) -> Result { + 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, + 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>, + _plot_tx: Sender<(&'static str, PlotData)>, + ) -> anyhow::Result> { + 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, + )?)) + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs new file mode 100644 index 0000000..d4d1f66 --- /dev/null +++ b/src/backend/mod.rs @@ -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>, + _plot_tx: Sender<(&'static str, PlotData)>, + ) -> anyhow::Result>; +} +pub struct Backends(pub Vec>); + +#[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![]) + } +} diff --git a/src/lib.rs b/src/lib.rs index fdf967b..90e8188 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![warn(clippy::all, rust_2018_idioms)] pub mod app; +mod backend; #[cfg(target_os = "android")] #[no_mangle] diff --git a/src/main.rs b/src/main.rs index 86cfc72..dcdb0fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod app; use app::TemplateApp; +mod backend; //#[cfg(target_os = "android")] //fn main() {}