generated from lks/eframe_template_android
Compare commits
2 Commits
fc9e04ffd2
...
24d9fc7972
| Author | SHA1 | Date | |
|---|---|---|---|
| 24d9fc7972 | |||
| 162220aa72 |
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -1013,6 +1013,15 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "egui_plot"
|
||||||
|
version = "0.27.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7854b86dc1c2d352c5270db3d600011daa913d6b554141a03939761323288a1"
|
||||||
|
dependencies = [
|
||||||
|
"egui",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
@ -2828,6 +2837,7 @@ dependencies = [
|
|||||||
"cpal",
|
"cpal",
|
||||||
"eframe",
|
"eframe",
|
||||||
"egui",
|
"egui",
|
||||||
|
"egui_plot",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
"realfft",
|
"realfft",
|
||||||
|
|||||||
@ -10,6 +10,7 @@ edition = "2021"
|
|||||||
anyhow = "1.0.83"
|
anyhow = "1.0.83"
|
||||||
cpal = "0.15.3"
|
cpal = "0.15.3"
|
||||||
egui = "0.27.0"
|
egui = "0.27.0"
|
||||||
|
egui_plot = "0.27.2"
|
||||||
log = "0.4.21"
|
log = "0.4.21"
|
||||||
realfft = "3.3.0"
|
realfft = "3.3.0"
|
||||||
|
|
||||||
|
|||||||
14
src/app.rs
14
src/app.rs
@ -2,6 +2,8 @@ use eframe::{egui_glow, glow};
|
|||||||
use egui::mutex::Mutex;
|
use egui::mutex::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub mod debug_plot;
|
||||||
|
use debug_plot::DebugPlots;
|
||||||
mod waterfall;
|
mod waterfall;
|
||||||
use waterfall::Waterfall;
|
use waterfall::Waterfall;
|
||||||
mod audio_fft;
|
mod audio_fft;
|
||||||
@ -10,8 +12,8 @@ pub mod turbo_colormap;
|
|||||||
|
|
||||||
const FFT_SIZE: usize = 1024;
|
const FFT_SIZE: usize = 1024;
|
||||||
|
|
||||||
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
|
|
||||||
pub struct TemplateApp {
|
pub struct TemplateApp {
|
||||||
|
plots: DebugPlots,
|
||||||
// Example stuff:
|
// Example stuff:
|
||||||
label: String,
|
label: String,
|
||||||
value: f32,
|
value: f32,
|
||||||
@ -29,7 +31,8 @@ impl TemplateApp {
|
|||||||
// Load previous app state (if any).
|
// Load previous app state (if any).
|
||||||
// Note that you must enable the `persistence` feature for this to work.
|
// Note that you must enable the `persistence` feature for this to work.
|
||||||
|
|
||||||
let (stream, rx) = AudioFFT::new(FFT_SIZE).unwrap();
|
let plots = DebugPlots::new();
|
||||||
|
let (stream, rx) = AudioFFT::new(FFT_SIZE, plots.get_sender()).unwrap();
|
||||||
let wf_size = stream.output_len;
|
let wf_size = stream.output_len;
|
||||||
let gl = cc
|
let gl = cc
|
||||||
.gl
|
.gl
|
||||||
@ -37,6 +40,7 @@ impl TemplateApp {
|
|||||||
.expect("Could not get gl context from glow backend");
|
.expect("Could not get gl context from glow backend");
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
plots,
|
||||||
// Example stuff:
|
// Example stuff:
|
||||||
label: "Hello World!".to_owned(),
|
label: "Hello World!".to_owned(),
|
||||||
value: 2.7,
|
value: 2.7,
|
||||||
@ -64,6 +68,7 @@ impl eframe::App for TemplateApp {
|
|||||||
// For inspiration and more examples, go to https://emilk.github.io/egui
|
// For inspiration and more examples, go to https://emilk.github.io/egui
|
||||||
|
|
||||||
ctx.request_repaint();
|
ctx.request_repaint();
|
||||||
|
self.plots.update_plots();
|
||||||
|
|
||||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||||
// The top panel is often a good place for a menu bar:
|
// The top panel is often a good place for a menu bar:
|
||||||
@ -77,13 +82,16 @@ impl eframe::App for TemplateApp {
|
|||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.add_space(16.0);
|
|
||||||
}
|
}
|
||||||
|
self.plots.render_menu_buttons(ui);
|
||||||
|
ui.add_space(16.0);
|
||||||
|
|
||||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.plots.render_plot_windows(ctx);
|
||||||
|
|
||||||
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");
|
||||||
|
|||||||
@ -5,7 +5,9 @@ use cpal::{
|
|||||||
BufferSize, StreamConfig,
|
BufferSize, StreamConfig,
|
||||||
};
|
};
|
||||||
use realfft::RealFftPlanner;
|
use realfft::RealFftPlanner;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc::{self, Sender};
|
||||||
|
|
||||||
|
use super::debug_plot::PlotData;
|
||||||
|
|
||||||
pub struct AudioFFT {
|
pub struct AudioFFT {
|
||||||
pub stream: cpal::Stream,
|
pub stream: cpal::Stream,
|
||||||
@ -13,7 +15,10 @@ pub struct AudioFFT {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AudioFFT {
|
impl AudioFFT {
|
||||||
pub fn new(size: usize) -> Result<(Self, mpsc::Receiver<Vec<u8>>)> {
|
pub fn new(
|
||||||
|
size: usize,
|
||||||
|
plot_tx: Sender<(&'static str, PlotData)>,
|
||||||
|
) -> Result<(Self, mpsc::Receiver<Vec<u8>>)> {
|
||||||
let output_len = size / 2 + 1;
|
let output_len = size / 2 + 1;
|
||||||
|
|
||||||
// Create mpsc queue
|
// Create mpsc queue
|
||||||
@ -45,9 +50,20 @@ impl AudioFFT {
|
|||||||
assert_eq!(size, fft_in.len());
|
assert_eq!(size, fft_in.len());
|
||||||
fft.process_with_scratch(&mut fft_in, &mut fft_out, &mut fft_scratch)
|
fft.process_with_scratch(&mut fft_in, &mut fft_out, &mut fft_scratch)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
plot_tx
|
||||||
|
.send(("FFT Output", PlotData::Bode32(fft_out.clone())))
|
||||||
|
.unwrap();
|
||||||
fft_in.clear();
|
fft_in.clear();
|
||||||
let output: Vec<u8> = fft_out.iter().map(|c| (c.arg() * 255.0) as u8).collect();
|
let output: Vec<u8> = fft_out
|
||||||
|
.iter()
|
||||||
|
.map(|c| {
|
||||||
|
(((c.re * c.re) + (c.im * c.im)).sqrt() / size as f32 * 255.0) as u8
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
assert_eq!(output_len, output.len());
|
assert_eq!(output_len, output.len());
|
||||||
|
plot_tx
|
||||||
|
.send(("FFT Processed Output", PlotData::U8(output.clone())))
|
||||||
|
.unwrap();
|
||||||
tx.send(output).unwrap();
|
tx.send(output).unwrap();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
99
src/app/debug_plot.rs
Normal file
99
src/app/debug_plot.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::mpsc::{self, Sender};
|
||||||
|
|
||||||
|
use egui::{Context, Ui};
|
||||||
|
use egui_plot::{Line, Plot, PlotBounds, PlotPoints};
|
||||||
|
use realfft::num_complex::Complex32;
|
||||||
|
|
||||||
|
pub enum PlotData {
|
||||||
|
U8(Vec<u8>),
|
||||||
|
//F32(Vec<f32>),
|
||||||
|
Bode32(Vec<Complex32>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DebugPlots {
|
||||||
|
plots: HashMap<&'static str, PlotData>,
|
||||||
|
plot_en: HashMap<&'static str, bool>,
|
||||||
|
rx: mpsc::Receiver<(&'static str, PlotData)>,
|
||||||
|
tx: mpsc::Sender<(&'static str, PlotData)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugPlots {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
DebugPlots {
|
||||||
|
plots: HashMap::new(),
|
||||||
|
plot_en: HashMap::new(),
|
||||||
|
rx,
|
||||||
|
tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_sender(&self) -> Sender<(&'static str, PlotData)> {
|
||||||
|
self.tx.clone()
|
||||||
|
}
|
||||||
|
pub fn update_plots(&mut self) {
|
||||||
|
while let Ok((key, plot)) = self.rx.try_recv() {
|
||||||
|
if self.plots.insert(key, plot).is_none() {
|
||||||
|
self.plot_en.insert(key, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn render_menu_buttons(&mut self, ui: &mut Ui) {
|
||||||
|
ui.menu_button("Debug Plots", |ui| {
|
||||||
|
for &k in self.plots.keys() {
|
||||||
|
if !self.plot_en.contains_key(k) {
|
||||||
|
self.plot_en.insert(k, false);
|
||||||
|
}
|
||||||
|
let enabled = self.plot_en.get_mut(k).unwrap();
|
||||||
|
ui.checkbox(enabled, k);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pub fn render_plot_windows(&mut self, ctx: &Context) {
|
||||||
|
for (key, plot) in self.plots.iter() {
|
||||||
|
let enabled = self.plot_en.get_mut(key).unwrap();
|
||||||
|
if *enabled {
|
||||||
|
DebugPlots::render_window(ctx, key, plot, enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render_window(ctx: &Context, title: &'static str, plot: &PlotData, open: &mut bool) {
|
||||||
|
egui::Window::new(title).open(open).show(ctx, |ui| {
|
||||||
|
ui.heading(title);
|
||||||
|
match plot {
|
||||||
|
PlotData::U8(v) => {
|
||||||
|
ui.heading("u8 Plot");
|
||||||
|
let line = Line::new(PlotPoints::from_iter(
|
||||||
|
v.iter().enumerate().map(|(i, y)| [i as f64, *y as f64]),
|
||||||
|
));
|
||||||
|
let plot = Plot::new(title);
|
||||||
|
plot.show(ui, |plot_ui| {
|
||||||
|
plot_ui.line(line);
|
||||||
|
plot_ui.set_plot_bounds(PlotBounds::from_min_max(
|
||||||
|
[-1.0, -1.0],
|
||||||
|
[(v.len() + 1) as f64, 256.0],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
PlotData::Bode32(v) => {
|
||||||
|
ui.heading("Bode Plot");
|
||||||
|
let mag_line =
|
||||||
|
Line::new(PlotPoints::from_iter(v.iter().enumerate().map(|(i, c)| {
|
||||||
|
[i as f64, ((c.re * c.re) + (c.im * c.im)).sqrt() as f64]
|
||||||
|
})));
|
||||||
|
let phase_line = Line::new(PlotPoints::from_iter(
|
||||||
|
v.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, c)| [i as f64, c.arg() as f64]),
|
||||||
|
));
|
||||||
|
let plot = Plot::new(title);
|
||||||
|
plot.show(ui, |plot_ui| {
|
||||||
|
plot_ui.line(mag_line);
|
||||||
|
plot_ui.line(phase_line);
|
||||||
|
});
|
||||||
|
ui.heading("TODO");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user