From 43947b23dd1020c5f1687a0565d0d58233303ea2 Mon Sep 17 00:00:00 2001 From: Sawyer McLane Date: Mon, 27 Jan 2025 10:39:24 -0700 Subject: [PATCH] Refactor audio processing methods to use slices for improved performance and add temperature range conversion --- src/audio.rs | 85 +++++++++++++++++++++---------------------------- src/products.rs | 10 ++++++ src/ui.rs | 19 ++++++----- 3 files changed, 55 insertions(+), 59 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index 8273b9e..0ac95a1 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -2,16 +2,13 @@ use cpal::{ traits::{DeviceTrait, HostTrait, StreamTrait}, Host, }; -use eframe::egui; +use eframe::egui::{self, remap_clamp}; use egui_plot::{Legend, Line, PlotPoints}; use lifx_core::HSBK; use rustfft::{num_complex::Complex, FftPlanner}; use std::sync::{Arc, Mutex}; -use crate::{ - color::{HSBKField, DEFAULT_KELVIN}, - products::KELVIN_RANGE, -}; +use crate::products::KELVIN_RANGE; pub const AUDIO_BUFFER_DEFAULT: usize = 48000; @@ -159,69 +156,59 @@ impl AudioManager { Ok(()) } - fn fft(samples_buffer: Vec) -> Vec> { - let mut buffer = to_complex(&samples_buffer); + fn fft(samples: &[f32]) -> Vec> { + let mut buffer = to_complex(samples); let mut planner = FftPlanner::new(); let fft = planner.plan_fft_forward(buffer.len()); fft.process(&mut buffer); buffer } - pub fn fft_real(spectrum: Vec) -> Vec { + pub fn fft_real(spectrum: &[f32]) -> Vec { let buffer = Self::fft(spectrum); to_real_f32(&buffer[0..buffer.len() / 2]) } - pub fn power_spectrum(samples: Vec) -> Vec { + pub fn power_spectrum(samples: &[f32]) -> Vec { let buffer = Self::fft(samples); buffer.iter().map(|value| value.norm_sqr()).collect() } - pub fn power(samples: Vec) -> u16 { + pub fn power(samples: &[f32]) -> u16 { let power_spectrum = Self::power_spectrum(samples); - let max_power = power_spectrum - .iter() - .fold(0.0, |acc: f32, value: &f32| acc.max(*value)); - (max_power.sqrt() * u16::MAX as f32) as u16 + let avg_power = power_spectrum.iter().sum::() / power_spectrum.len() as f32; + (avg_power.sqrt() * u16::MAX as f32) as u16 } - pub fn spectrum_to_hsbk(samples: Vec, field: HSBKField) -> HSBK { + pub fn samples_to_hsbk(samples: &[f32]) -> HSBK { let value = Self::power(samples); - let kelvin = match field { - HSBKField::Kelvin => { - ((value as f32 / u16::MAX as f32) * (KELVIN_RANGE.max - KELVIN_RANGE.min) as f32 - + KELVIN_RANGE.min as f32) as u16 - } - _ => DEFAULT_KELVIN, - }; - match field { - HSBKField::Hue => HSBK { - hue: value, - saturation: u16::MAX, - brightness: u16::MAX, - kelvin, - }, - HSBKField::Saturation => HSBK { - hue: u16::MAX, - saturation: value, - brightness: u16::MAX, - kelvin, - }, - HSBKField::Brightness => HSBK { - hue: u16::MAX, - saturation: u16::MAX, - brightness: value, - kelvin, - }, - HSBKField::Kelvin => HSBK { - hue: u16::MAX, - saturation: u16::MAX, - brightness: u16::MAX, - kelvin, - }, + let kelvin = remap_clamp( + value as f32, + 0.0..=u16::MAX as f32, + KELVIN_RANGE.to_range_f32(), + ) as u16; + + HSBK { + hue: Self::freq_to_hue(samples), + saturation: u16::MAX, + brightness: value, + kelvin, } } + pub fn freq_to_hue(samples: &[f32]) -> u16 { + let spectrum = Self::fft(samples); + let sample_rate = AUDIO_BUFFER_DEFAULT as f32; + let dominant_freq_hz = spectrum + .iter() + .enumerate() + .max_by(|(_, a), (_, b)| a.norm_sqr().partial_cmp(&b.norm_sqr()).unwrap()) + .map(|(index, _)| index as f32 * sample_rate / spectrum.len() as f32) + .unwrap_or_default(); + let max_freq = sample_rate / 2.0; + ((dominant_freq_hz / max_freq) * u16::MAX as f32) as u16 + } + pub fn devices(&self) -> Vec { self.host .output_devices() @@ -241,12 +228,12 @@ impl AudioManager { pub fn ui(&self, ui: &mut eframe::egui::Ui) { let audio_data = self.get_samples_data(); - let spectrum = Self::fft_real(audio_data.clone().unwrap_or_default()); if let Ok(ref data) = audio_data { + let spectrum = Self::fft_real(data); + let color = Self::power(data); egui::ScrollArea::vertical().show(ui, |ui| { // show current color - let color = Self::power(audio_data.clone().unwrap_or_default()); ui.label(format!("Current color: {:?}", color)); egui_plot::Plot::new("Audio Samples") .allow_zoom(false) diff --git a/src/products.rs b/src/products.rs index fbcd05e..8c56e0d 100644 --- a/src/products.rs +++ b/src/products.rs @@ -35,6 +35,16 @@ impl TemperatureRange { pub fn to_range_u16(&self) -> RangeInclusive { self.min as u16..=self.max as u16 } + + pub fn to_range_f32(&self) -> RangeInclusive { + self.min as f32..=self.max as f32 + } +} + +impl From for (u16, u16) { + fn from(range: TemperatureRange) -> Self { + (range.min as u16, range.max as u16) + } } #[derive(Clone, Debug, Serialize, Deserialize, Default)] diff --git a/src/ui.rs b/src/ui.rs index cbb704b..6ad96fb 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -588,9 +588,11 @@ pub fn kelvin_slider(ui: &mut Ui, kelvin: &mut u16, device: &DeviceInfo) -> egui RangeInclusive::new(range.min as u16, range.max as u16), "Kelvin", |v| { - let temp = (((v as f32 / u16::MAX as f32) - * (range.max - range.min) as f32) - + range.min as f32) as u16; + let temp: u16 = remap_clamp( + v as f32, + 0.0..=u16::MAX as f32, + KELVIN_RANGE.to_range_f32(), + ) as u16; kelvin_to_rgb(temp).into() }, ) @@ -607,9 +609,9 @@ pub fn kelvin_slider(ui: &mut Ui, kelvin: &mut u16, device: &DeviceInfo) -> egui RangeInclusive::new(KELVIN_RANGE.min as u16, KELVIN_RANGE.max as u16), "Kelvin", |v| { - let temp = (((v as f32 / u16::MAX as f32) - * (KELVIN_RANGE.max - KELVIN_RANGE.min) as f32) - + KELVIN_RANGE.min as f32) as u16; + let temp: u16 = + remap_clamp(v as f32, 0.0..=u16::MAX as f32, KELVIN_RANGE.to_range_f32()) + as u16; kelvin_to_rgb(temp).into() }, ), @@ -709,10 +711,7 @@ pub fn handle_audio(app: &mut MantleApp, ui: &mut Ui, device: &DeviceInfo) -> Op if let Some(waveform_trx) = app.waveform_channel.get_mut(&device.id()) { waveform_trx.handle = Some(thread::spawn(move || loop { let samples = buffer_clone.lock().unwrap().clone(); - let audio_color = AudioManager::spectrum_to_hsbk( - samples, - crate::color::HSBKField::Brightness, - ); + let audio_color = AudioManager::samples_to_hsbk(&samples); if let Err(err) = lifx_manager.set_color_by_id(device_id, audio_color) { eprintln!("Failed to set color: {}", err); }