generated from lks/eframe_template_android
Add simple device selector window
This commit is contained in:
parent
243fcdb31e
commit
f3216e2d8d
68
src/app.rs
68
src/app.rs
@ -1,13 +1,15 @@
|
|||||||
use eframe::{egui_glow, glow};
|
use eframe::{egui_glow, glow};
|
||||||
use egui::mutex::Mutex;
|
use egui::{mutex::Mutex, ScrollArea};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::backend::{self, Backends};
|
||||||
|
|
||||||
pub mod debug_plot;
|
pub mod debug_plot;
|
||||||
use debug_plot::DebugPlots;
|
use debug_plot::DebugPlots;
|
||||||
mod waterfall;
|
mod waterfall;
|
||||||
use waterfall::Waterfall;
|
use waterfall::Waterfall;
|
||||||
mod audio;
|
//mod aud1o;
|
||||||
use audio::Audio;
|
//use aud1o::Audio;
|
||||||
mod fft;
|
mod fft;
|
||||||
use fft::Fft;
|
use fft::Fft;
|
||||||
pub mod turbo_colormap;
|
pub mod turbo_colormap;
|
||||||
@ -21,8 +23,11 @@ pub struct TemplateApp {
|
|||||||
value: f32,
|
value: f32,
|
||||||
/// Behind an `Arc<Mutex<…>>` so we can pass it to [`egui::PaintCallback`] and paint later.
|
/// Behind an `Arc<Mutex<…>>` so we can pass it to [`egui::PaintCallback`] and paint later.
|
||||||
waterfall: Arc<Mutex<Waterfall>>,
|
waterfall: Arc<Mutex<Waterfall>>,
|
||||||
_stream: Audio,
|
//_stream: Audio,
|
||||||
_fft: Fft,
|
_fft: Fft,
|
||||||
|
_backends: backend::Backends,
|
||||||
|
_selected_backend: usize,
|
||||||
|
_open_device: Option<Box<dyn backend::Device>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TemplateApp {
|
impl TemplateApp {
|
||||||
@ -38,7 +43,7 @@ impl TemplateApp {
|
|||||||
|
|
||||||
//let (stream, rx) = AudioFFT::new(FFT_SIZE, plots.get_sender()).unwrap();
|
//let (stream, rx) = AudioFFT::new(FFT_SIZE, plots.get_sender()).unwrap();
|
||||||
let (fft, rx) = Fft::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 wf_size = fft.output_len;
|
||||||
let gl = cc
|
let gl = cc
|
||||||
@ -52,8 +57,11 @@ impl TemplateApp {
|
|||||||
label: "Hello World!".to_owned(),
|
label: "Hello World!".to_owned(),
|
||||||
value: 2.7,
|
value: 2.7,
|
||||||
waterfall: Arc::new(Mutex::new(Waterfall::new(gl, wf_size, wf_size, rx))),
|
waterfall: Arc::new(Mutex::new(Waterfall::new(gl, wf_size, wf_size, rx))),
|
||||||
_stream: stream,
|
//_stream: stream,
|
||||||
_fft: fft,
|
_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);
|
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| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
// The central panel the region left after adding TopPanel's and SidePanel's
|
// The central panel the region left after adding TopPanel's and SidePanel's
|
||||||
ui.heading("eframe template");
|
ui.heading("eframe template");
|
||||||
|
|||||||
@ -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
106
src/backend/audio.rs
Normal 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
42
src/backend/mod.rs
Normal 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![])
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
#![warn(clippy::all, rust_2018_idioms)]
|
#![warn(clippy::all, rust_2018_idioms)]
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
|
mod backend;
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
mod app;
|
mod app;
|
||||||
use app::TemplateApp;
|
use app::TemplateApp;
|
||||||
|
mod backend;
|
||||||
|
|
||||||
//#[cfg(target_os = "android")]
|
//#[cfg(target_os = "android")]
|
||||||
//fn main() {}
|
//fn main() {}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user