use std::sync::{Arc, Mutex};
pub const NUM_BANDS: usize = 12;
const SMOOTHING: f32 = 0.5;
const GAIN: f32 = 0.85;
const FFT_SIZE: usize = 2048;
#[derive(Clone, Debug)]
pub struct SpectrumData {
pub bands: [f32; NUM_BANDS],
pub peak: f32,
}
impl Default for SpectrumData {
fn default() -> Self {
Self {
bands: [0.0; NUM_BANDS],
peak: 0.0,
}
}
}
pub struct AudioAnalyzer {
fft: Arc<dyn realfft::RealToComplex<f32>>,
sample_buffer: Vec<f32>,
fft_input: Vec<f32>,
fft_output: Vec<realfft::num_complex::Complex<f32>>,
spectrum: SpectrumData,
write_pos: usize,
}
impl AudioAnalyzer {
pub fn new() -> Self {
let mut planner = realfft::RealFftPlanner::new();
let fft = planner.plan_fft_forward(FFT_SIZE);
let fft_output_len = FFT_SIZE / 2 + 1;
Self {
fft,
sample_buffer: vec![0.0; FFT_SIZE],
fft_input: vec![0.0; FFT_SIZE],
fft_output: vec![realfft::num_complex::Complex::default(); fft_output_len],
spectrum: SpectrumData::default(),
write_pos: 0,
}
}
pub fn push_samples(&mut self, samples: &[f32]) {
for &sample in samples {
self.sample_buffer[self.write_pos] = sample;
self.write_pos = (self.write_pos + 1) % FFT_SIZE;
}
}
pub fn process(&mut self) -> SpectrumData {
for i in 0..FFT_SIZE {
let idx = (self.write_pos + i) % FFT_SIZE;
let window = 0.5 * (1.0 - (2.0 * std::f32::consts::PI * i as f32 / FFT_SIZE as f32).cos());
self.fft_input[i] = self.sample_buffer[idx] * window;
}
if self
.fft
.process(&mut self.fft_input, &mut self.fft_output)
.is_ok()
{
self.update_spectrum();
}
self.spectrum.clone()
}
fn update_spectrum(&mut self) {
let bin_count = self.fft_output.len();
let band_edges: [usize; NUM_BANDS + 1] = [
1,
2,
4,
8,
16,
32,
64,
128,
256,
384,
512,
768,
bin_count - 1,
];
let mut new_bands = [0.0f32; NUM_BANDS];
let mut max_magnitude = 0.0f32;
for band in 0..NUM_BANDS {
let start = band_edges[band];
let end = band_edges[band + 1].min(bin_count);
if start < end {
let mut sum = 0.0f32;
for i in start..end {
let magnitude = self.fft_output[i].norm();
sum += magnitude;
max_magnitude = max_magnitude.max(magnitude);
}
new_bands[band] = sum / (end - start) as f32;
}
}
const BAND_GAINS: [f32; NUM_BANDS] = [
0.7, 0.8, 0.9, 1.0, 1.0, 1.0, 1.1, 1.2, 1.3, 1.4, 1.6, 2.0, ];
if max_magnitude > 0.0 {
for (i, band) in new_bands.iter_mut().enumerate() {
let normalized = (*band / max_magnitude) * BAND_GAINS[i] * GAIN;
let scaled = normalized.sqrt();
*band = scaled.min(0.85);
}
}
for (i, new_band) in new_bands.iter().enumerate() {
self.spectrum.bands[i] = self.spectrum.bands[i] * SMOOTHING + *new_band * (1.0 - SMOOTHING);
if self.spectrum.bands[i] < 0.005 {
self.spectrum.bands[i] = 0.0;
}
}
let current_peak = new_bands.iter().cloned().fold(0.0f32, f32::max);
self.spectrum.peak = self.spectrum.peak * SMOOTHING + current_peak * (1.0 - SMOOTHING);
if self.spectrum.peak < 0.005 {
self.spectrum.peak = 0.0;
}
}
}
pub type SharedAnalyzer = Arc<Mutex<AudioAnalyzer>>;
pub fn create_shared_analyzer() -> SharedAnalyzer {
Arc::new(Mutex::new(AudioAnalyzer::new()))
}