Spectrometer didn't seem to calculate the graph correctly so i fixed

this with good logic which does work
This commit is contained in:
JorySeverijnse 2026-01-18 19:19:46 +01:00
parent fa020e0b36
commit e3ff11d8a8

View File

@ -109,17 +109,17 @@ pub fn parse_rgb_hex(hex: &str) -> Result<image::Rgb<u8>> {
/// Compute frequency spectrum from audio samples using FFT.
fn compute_spectrum(audio_data: &AudioData, start_sample: usize, window_size: usize) -> Vec<f32> {
// Use shorter FFT for better temporal resolution
let fft_size = (window_size / 2).next_power_of_two().max(256);
// Use a larger FFT size for better frequency resolution, especially in the bass
let fft_size = 2048;
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(fft_size);
// Collect audio samples for this window (sum L+R for better bass representation)
// Collect audio samples for this window
let mut buffer: Vec<Complex<f32>> = (0..fft_size)
.map(|i| {
let sample_idx = start_sample + i;
if sample_idx < audio_data.left_channel.len() {
// Sum channels instead of averaging for stronger bass representation
// Sum channels for mono analysis
let sample = audio_data.left_channel[sample_idx] + audio_data.right_channel[sample_idx];
Complex::new(sample, 0.0)
} else {
@ -128,32 +128,86 @@ fn compute_spectrum(audio_data: &AudioData, start_sample: usize, window_size: us
})
.collect();
// Apply Hann window to reduce spectral leakage
// Apply Hann window
for (i, sample) in buffer.iter_mut().enumerate() {
let window = 0.5 * (1.0 - (2.0 * std::f32::consts::PI * i as f32 / (fft_size - 1) as f32).cos());
sample.re *= window;
sample.im *= window;
}
// Apply FFT
fft.process(&mut buffer);
// Compute magnitude spectrum (only positive frequencies, up to Nyquist)
let nyquist_bin = fft_size / 2;
let mut spectrum: Vec<f32> = buffer[0..nyquist_bin]
// Normalize and skip DC
let spectrum: Vec<f32> = buffer[1..nyquist_bin]
.iter()
.map(|c| c.norm())
.map(|c| c.norm() / (fft_size as f32))
.collect();
// Apply less aggressive logarithmic scaling to preserve dynamics
for mag in spectrum.iter_mut() {
// Use square root scaling instead of log for better dynamic range
*mag = (*mag).sqrt().min(1.0);
// Boost low frequencies slightly for better bass visibility
// (this is frequency-dependent scaling)
spectrum
}
/// Draw the spectrometer bars with logarithmic frequency mapping.
fn draw_spectrometer(
buffer: &mut ImageBuffer<image::Rgb<u8>, Vec<u8>>,
spectrum: &[f32],
x_offset: u32,
y_offset: u32,
width: u32,
height: u32,
num_bars: usize,
color: image::Rgb<u8>,
sample_rate: u32,
) {
let spacing = 1;
let bar_width = (width - (num_bars as u32 - 1) * spacing) / num_bars as u32;
let bar_width = bar_width.max(1);
// Logarithmic mapping parameters
let min_freq = 20.0f32;
let max_freq = 20000.0f32;
let nyquist = sample_rate as f32 / 2.0;
for i in 0..num_bars {
// Calculate frequency range for this bar (logarithmic)
let f_start = min_freq * (max_freq / min_freq).powf(i as f32 / num_bars as f32);
let f_end = min_freq * (max_freq / min_freq).powf((i + 1) as f32 / num_bars as f32);
// Map frequencies to FFT bin indices
let bin_start = (f_start / nyquist * spectrum.len() as f32) as usize;
let bin_end = (f_end / nyquist * spectrum.len() as f32) as usize;
let bin_end = bin_end.max(bin_start + 1).min(spectrum.len());
// Aggregate magnitude in this frequency range
let mut magnitude = 0.0f32;
if bin_start < spectrum.len() {
magnitude = spectrum[bin_start..bin_end].iter().fold(0.0f32, |acc, &x| acc.max(x));
}
spectrum
// Apply frequency-dependent boost (higher frequencies are naturally quieter)
// Boost highs by adding a linear factor based on frequency
let freq_factor = 1.0 + (f_start / max_freq) * 5.0;
let mut val = magnitude * freq_factor;
// Dynamic range compression/scaling
val = (val * 20.0).sqrt().min(1.0);
// Noise floor
if val < 0.05 { val = 0.0; }
let bar_height = (val * height as f32) as u32;
let x = x_offset + i as u32 * (bar_width + spacing);
for y in 0..bar_height {
let pixel_y = y_offset + height - 1 - y;
for dx in 0..bar_width {
let pixel_x = x + dx;
if pixel_x < buffer.width() && pixel_y < buffer.height() {
buffer.put_pixel(pixel_x, pixel_y, color);
}
}
}
}
}
/// Draw a single frame of the visualization.
@ -308,36 +362,20 @@ pub fn draw_frame(
let spec_x_offset = half_width;
let spec_y_offset = half_height;
let window_size = 512.min(samples_per_frame); // Shorter window for better temporal resolution
let window_size = 1024.min(samples_per_frame);
let spectrum = compute_spectrum(audio_data, start_sample, window_size);
// More bars with spacing for proper spectrum analyzer look
let num_bars = 32usize; // Fewer bars for better definition
let spacing = 1; // 1 pixel spacing between bars
let total_spacing = (num_bars - 1) * spacing;
let available_width = spec_width - total_spacing as u32;
let bar_width = (available_width / num_bars as u32).max(1);
for i in 0..num_bars {
// Map spectrum bins to bars (group multiple bins per bar)
let bin_start = (i * spectrum.len() / num_bars).min(spectrum.len());
let bin_end = ((i + 1) * spectrum.len() / num_bars).min(spectrum.len());
let magnitude = spectrum[bin_start..bin_end].iter().fold(0.0f32, |acc, &x| acc.max(x));
let bar_height = (magnitude * spec_height as f32 * 0.9) as u32; // Scale to 90% of quadrant height
let x = spec_x_offset + (i as u32) * (bar_width + spacing as u32);
// Draw vertical bar from bottom up within the quadrant
for y in 0..bar_height {
let pixel_y = spec_y_offset + spec_height - 1 - y; // Bottom to top in quadrant
for dx in 0..bar_width {
let pixel_x = x + dx;
if pixel_x < width && pixel_y < height && pixel_x >= spec_x_offset {
buffer.put_pixel(pixel_x, pixel_y, options.left_color);
}
}
}
}
draw_spectrometer(
&mut buffer,
&spectrum,
spec_x_offset,
spec_y_offset,
spec_width,
spec_height,
32,
options.left_color,
audio_data.sample_rate,
);
// Draw grid lines separating quadrants
for x in 0..width {
@ -348,37 +386,20 @@ pub fn draw_frame(
}
}
RenderMode::Spectrometer => {
// Use a window of samples for FFT
let window_size = 512.min(samples_per_frame); // Shorter window for better temporal resolution
let window_size = 1024.min(samples_per_frame);
let spectrum = compute_spectrum(audio_data, start_sample, window_size);
// Spectrum analyzer style with individual bars and spacing
let num_bars = 64usize; // Good number for full screen spectrum analyzer
let spacing = 2; // 2 pixel spacing between bars for classic look
let total_spacing = (num_bars - 1) * spacing;
let available_width = width - total_spacing as u32;
let bar_width = (available_width / num_bars as u32).max(1);
for i in 0..num_bars {
// Map spectrum bins to bars (group multiple bins per bar)
let bin_start = (i * spectrum.len() / num_bars).min(spectrum.len());
let bin_end = ((i + 1) * spectrum.len() / num_bars).min(spectrum.len());
let magnitude = spectrum[bin_start..bin_end].iter().fold(0.0f32, |acc, &x| acc.max(x));
let bar_height = (magnitude * height as f32 * 0.95) as u32; // Scale to 95% of screen height
let x = (i as u32) * (bar_width + spacing as u32);
// Draw vertical bar from bottom up
for y in 0..bar_height {
let pixel_y = height - 1 - y; // Bottom to top
for dx in 0..bar_width {
let pixel_x = x + dx;
if pixel_x < width && pixel_y < height {
buffer.put_pixel(pixel_x, pixel_y, options.left_color);
}
}
}
}
draw_spectrometer(
&mut buffer,
&spectrum,
0,
0,
width,
height,
64,
options.left_color,
audio_data.sample_rate,
);
}
}