Spectrometer didn't seem to calculate the graph correctly so i fixed
this with good logic which does work
This commit is contained in:
parent
fa020e0b36
commit
e3ff11d8a8
167
src/render.rs
167
src/render.rs
@ -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
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user