diff --git a/src/main.rs b/src/main.rs index 28e425e..d84a907 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use nannou::prelude::*; +use nannou::{geom::Range, prelude::*}; use crate::audio::AudioSample; @@ -16,7 +16,62 @@ struct Settings { struct Model { settings: Settings, window: Entity, - samples: Vec, + target_samples: Vec, + result_samples: Vec, + dft_result: Vec, +} + +const MAX_SAMPLES: usize = 1024; + +fn generate_sin(frequency: usize) -> Vec { + let factor = (frequency as f32 / MAX_SAMPLES as f32) * (2.0 * std::f32::consts::PI); + (0..MAX_SAMPLES) + .map(|i| f32::sin((i as f32) * factor)) + .collect() +} + +fn generate_cos(frequency: usize) -> Vec { + let factor = (frequency as f32 / MAX_SAMPLES as f32) * (2.0 * std::f32::consts::PI); + (0..MAX_SAMPLES) + .map(|i| f32::cos((i as f32) * factor)) + .collect() +} + +fn dot_product(a: &[f32], b: &[f32]) -> f32 { + a.iter().zip(b.iter()).map(|(a, b)| a * b).sum() +} + +fn naive_dft(samples: &[f32]) -> Vec { + let mut result = Vec::new(); + + for i in 0..(samples.len() / 2) { + let sin = generate_sin(i); + let cos = generate_cos(i); + let dot = dot_product(samples, &sin) + dot_product(samples, &cos); + + result.push(dot * (2. / MAX_SAMPLES as f32)); + } + + result +} + +fn inverse_dft(samples: &[f32]) -> Vec { + let mut result = Vec::new(); + + for i in 0..MAX_SAMPLES { + let mut sum = 0.0; + for (freq, amplitude) in samples.iter().enumerate() { + let sample = + (i as f32) * (freq as f32 / MAX_SAMPLES as f32) * (2.0 * std::f32::consts::PI); + + sum += f32::sin(sample) * amplitude; + sum += f32::cos(sample) * amplitude; + } + + result.push(sum); + } + + result } impl Model { @@ -36,20 +91,21 @@ impl Model { width: 1.0, offset: 0, }, - samples: sample.mono_data(), + target_samples: sample.mono_data(), + result_samples: vec![], + dft_result: vec![], } } pub fn current_samples(&self) -> Vec { - let len = self.samples.len(); + let len = self.target_samples.len(); let width = (self.settings.width * len as f32) as usize; let offset = self.settings.offset as usize; - let window: Vec<_> = self.samples[offset..(offset + width).min(len)] + let window: Vec<_> = self.target_samples[offset..(offset + width).min(len)] .iter() .collect(); - const MAX_SAMPLES: usize = 1024 * 4; let sample_compression = window.len() / MAX_SAMPLES; let mut samples = Vec::with_capacity(MAX_SAMPLES); @@ -70,18 +126,28 @@ impl Model { } fn update(app: &App, model: &mut Model) { + let current_samples = model.current_samples(); + let settings = &mut model.settings; + let result_samples = &mut model.result_samples; + let dft_result = &mut model.dft_result; let ctx = app.egui_for_window(model.window); + egui::Window::new("Settings").show(&ctx, |ui| { ui.label("Offset:"); ui.add(egui::Slider::new( &mut settings.offset, - 0..=model.samples.len() as u32, + 0..=model.target_samples.len() as u32, )); ui.label("Width:"); ui.add(egui::Slider::new(&mut settings.width, 0.0..=1.0)); + + if ui.button("Apply").clicked() { + *dft_result = naive_dft(¤t_samples); + *result_samples = inverse_dft(&dft_result); + } }); } @@ -92,16 +158,40 @@ fn view(app: &App, model: &Model) { let window = app.window(model.window); let window_rect = window.rect(); - let samples = model.current_samples(); - let sample_width = window_rect.w() / samples.len() as f32; + let height = window_rect.h() / 3.0; + let center_rect = Rect { + x: window_rect.x, + y: Range::new(height / 2., -height / 2.), + }; + + draw_samples_point(&draw, &model.current_samples(), center_rect.shift_y(height)); + draw_samples_point(&draw, &model.result_samples, center_rect); + draw_samples_bar(&draw, &model.dft_result, center_rect.shift_y(-height)); +} + +fn draw_samples_point(draw: &Draw, samples: &[f32], area: Rect) { + let sample_width = area.w() / samples.len() as f32; for (i, &sample) in samples.iter().enumerate() { draw.ellipse() .x_y( - i as f32 * sample_width + window_rect.left(), - sample * window_rect.h() / 2.0, + i as f32 * sample_width + area.left(), + sample * area.h() / 2.0 + area.mid_left().y, ) .radius(1.) .color(BLACK); } } + +fn draw_samples_bar(draw: &Draw, samples: &[f32], area: Rect) { + let sample_width = area.w() / samples.len() as f32; + + for (i, &sample) in samples.iter().enumerate() { + let left = i as f32 * sample_width + area.left(); + + draw.rect() + .x_y(left, area.bottom() + area.h() / 2.0) + .w_h(sample_width, sample.abs() * area.h()) + .color(BLACK); + } +}