diff --git a/src/app/debug_plot.rs b/src/app/debug_plot.rs index 9ade8f2..ae96393 100644 --- a/src/app/debug_plot.rs +++ b/src/app/debug_plot.rs @@ -7,7 +7,7 @@ use realfft::num_complex::Complex32; pub enum PlotData { U8(Vec), - //F32(Vec), + F32(Vec), Bode32(Vec), } #[derive(Clone)] @@ -94,6 +94,18 @@ impl DebugPlots { )); }); } + PlotData::F32(v) => { + ui.heading("f32 plot"); + let line = Line::new(PlotPoints::from_ys_f32(&v)); + 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, -2.0], + [(v.len() + 1) as f64, 2.0], + )); + }); + } PlotData::Bode32(v) => { ui.heading("Bode Plot"); let mag_line = diff --git a/src/backend/dummy.rs b/src/backend/dummy.rs new file mode 100644 index 0000000..3aadf1e --- /dev/null +++ b/src/backend/dummy.rs @@ -0,0 +1,109 @@ +use anyhow::Result; +use core::panic; +use std::{ + sync::mpsc::{self, RecvTimeoutError, SyncSender, TrySendError}, + time::{Duration, Instant}, + usize, +}; + +use crate::app::debug_plot::{DebugPlotSender, PlotData}; + +const LUT_LEN: usize = 4096; + +pub struct DummyDevice { + close: SyncSender<()>, +} +impl DummyDevice { + pub fn new( + sample_rate: usize, + fft_input: SyncSender>, + _plot_tx: DebugPlotSender, + ) -> Result { + let sin_lut: Vec = (0..LUT_LEN) + .map(|i| ((i as f32 / LUT_LEN as f32) * std::f32::consts::TAU).sin()) + .collect(); + let (close, close_rx) = mpsc::sync_channel(0); + let buffer_size: usize = 2048; + let loop_interval = Duration::from_secs_f32((1. / sample_rate as f32) * buffer_size as f32); + let freq = (sample_rate / 4) as f32; + let phase_delta = sin_lut.len() as f32 * (freq / sample_rate as f32); + std::thread::spawn(move || { + let mut phase = 0_f32; + loop { + let start = Instant::now(); + let samples: Vec = (0..buffer_size) + .map(|_i| { + phase = (phase + phase_delta) % sin_lut.len() as f32; + sin_lut[phase as usize] + }) + .collect(); + _plot_tx + .send("Dummy output", PlotData::F32(samples.clone())) + .unwrap(); + match fft_input.try_send(samples) { + Ok(_) => {} + Err(TrySendError::Full(_)) => log::warn!("Dummy Backend buffer full."), + Err(TrySendError::Disconnected(_)) => { + panic!("Dummy device lost connection to frontend!") + } + } + match close_rx.recv_timeout(loop_interval - start.elapsed()) { + Ok(_) => break, + Err(RecvTimeoutError::Disconnected) => { + panic!("Dummy device lost connection to frontend!") + } + Err(RecvTimeoutError::Timeout) => {} + } + } + }); + + Ok(Self { close }) + } +} +impl crate::backend::Device for DummyDevice { + fn show_settings(&mut self, ui: &mut egui::Ui) { + ui.label("TODO"); + } + + fn can_tune(&self) -> bool { + false + } + + fn tune(&mut self, _freq: usize) -> anyhow::Result<()> { + anyhow::bail!("Can't tune this device") + } + + fn close(self: Box) { + self.close.send(()).unwrap(); + } +} + +pub struct DummyBackend { + sample_rate: usize, +} +impl DummyBackend { + pub fn new() -> Self { + Self { sample_rate: 48000 } + } +} +impl super::Backend for DummyBackend { + fn display_text(&self) -> &'static str { + "Dummy" + } + + fn show_device_selection(&mut self, ui: &mut egui::Ui) { + ui.label("TODO"); + } + + fn build_device( + &mut self, + fft_input: SyncSender>, + _plot_tx: DebugPlotSender, + ) -> anyhow::Result> { + Ok(Box::new(DummyDevice::new( + self.sample_rate, + fft_input, + _plot_tx, + )?)) + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 345be17..daa1cba 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -4,6 +4,7 @@ use egui::Ui; use crate::app::debug_plot::DebugPlotSender; mod audio; +mod dummy; pub trait Device { fn show_settings(&mut self, ui: &mut Ui); fn can_tune(&self) -> bool; @@ -24,16 +25,23 @@ 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())]) + Backends(vec![ + Box::new(audio::AudioBackend::new()), + Box::new(dummy::DummyBackend::new()), + ]) } } #[cfg(target_arch = "wasm32")] impl Default for Backends { - fn default() -> Self {} + fn default() -> Self { + Backends(vec![Box::new(dummy::DummyBackend::new())]) + } } #[cfg(target_os = "android")] impl Default for Backends { - fn default() -> Self {} + fn default() -> Self { + Backends(vec![Box::new(dummy::DummyBackend::new())]) + } }