Remove unsused variable
Added bytemuck for faster pcm decoding refactor spectrometer
This commit is contained in:
parent
e3ff11d8a8
commit
837be94e1c
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -704,6 +704,7 @@ name = "oscilloscope-video-gen"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytemuck",
|
||||
"clap",
|
||||
"hound",
|
||||
"image",
|
||||
|
||||
@ -31,6 +31,9 @@ anyhow = "1.0"
|
||||
# FFT processing for spectrometer
|
||||
rustfft = "6.1"
|
||||
|
||||
# Efficient byte casting
|
||||
bytemuck = "1.24"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
|
||||
12
src/audio.rs
12
src/audio.rs
@ -3,6 +3,7 @@
|
||||
//! Handles reading and decoding WAV files into normalized sample data.
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use bytemuck;
|
||||
use std::path::Path;
|
||||
|
||||
/// Normalized audio sample data.
|
||||
@ -49,12 +50,11 @@ impl AudioData {
|
||||
let mut left_channel = Vec::with_capacity(total_samples);
|
||||
let mut right_channel = Vec::with_capacity(total_samples);
|
||||
|
||||
for i in 0..total_samples {
|
||||
let offset = i * 2 * num_channels;
|
||||
|
||||
let left_val = i16::from_le_bytes([pcm_data[offset], pcm_data[offset + 1]]);
|
||||
let right_val = i16::from_le_bytes([pcm_data[offset + 2], pcm_data[offset + 3]]);
|
||||
|
||||
// Convert PCM data to f32 samples efficiently
|
||||
let samples: &[i16] = bytemuck::cast_slice(&pcm_data);
|
||||
for chunk in samples.chunks_exact(num_channels) {
|
||||
let left_val = chunk[0];
|
||||
let right_val = chunk[1];
|
||||
left_channel.push(left_val as f32 / 32768.0);
|
||||
right_channel.push(right_val as f32 / 32768.0);
|
||||
}
|
||||
|
||||
@ -78,12 +78,7 @@ pub fn draw_line(
|
||||
fn draw_graticule(
|
||||
buffer: &mut ImageBuffer<image::Rgb<u8>, Vec<u8>>,
|
||||
primary_color: image::Rgb<u8>,
|
||||
show_grid: bool,
|
||||
) {
|
||||
if !show_grid {
|
||||
return;
|
||||
}
|
||||
|
||||
let (width, height) = buffer.dimensions();
|
||||
|
||||
for x in 0..width {
|
||||
@ -108,7 +103,7 @@ 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> {
|
||||
fn compute_spectrum(audio_data: &AudioData, start_sample: usize) -> Vec<f32> {
|
||||
// Use a larger FFT size for better frequency resolution, especially in the bass
|
||||
let fft_size = 2048;
|
||||
let mut planner = FftPlanner::new();
|
||||
@ -159,19 +154,23 @@ fn draw_spectrometer(
|
||||
color: image::Rgb<u8>,
|
||||
sample_rate: u32,
|
||||
) {
|
||||
let spacing = 1;
|
||||
let bar_width = (width - (num_bars as u32 - 1) * spacing) / num_bars as u32;
|
||||
// Constants for logarithmic frequency mapping
|
||||
const MIN_FREQ: f32 = 20.0;
|
||||
const MAX_FREQ: f32 = 20000.0;
|
||||
const FREQ_BOOST_FACTOR: f32 = 5.0;
|
||||
const DYNAMIC_RANGE_SCALE: f32 = 20.0;
|
||||
const NOISE_FLOOR: f32 = 0.05;
|
||||
const BAR_SPACING: u32 = 1;
|
||||
|
||||
let bar_width = (width - (num_bars as u32 - 1) * BAR_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);
|
||||
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;
|
||||
@ -185,18 +184,17 @@ fn draw_spectrometer(
|
||||
}
|
||||
|
||||
// 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 freq_factor = 1.0 + (f_start / MAX_FREQ) * FREQ_BOOST_FACTOR;
|
||||
let mut val = magnitude * freq_factor;
|
||||
|
||||
// Dynamic range compression/scaling
|
||||
val = (val * 20.0).sqrt().min(1.0);
|
||||
val = (val * DYNAMIC_RANGE_SCALE).sqrt().min(1.0);
|
||||
|
||||
// Noise floor
|
||||
if val < 0.05 { val = 0.0; }
|
||||
if val < NOISE_FLOOR { val = 0.0; }
|
||||
|
||||
let bar_height = (val * height as f32) as u32;
|
||||
let x = x_offset + i as u32 * (bar_width + spacing);
|
||||
let x = x_offset + i as u32 * (bar_width + BAR_SPACING);
|
||||
|
||||
for y in 0..bar_height {
|
||||
let pixel_y = y_offset + height - 1 - y;
|
||||
@ -226,7 +224,7 @@ pub fn draw_frame(
|
||||
}
|
||||
|
||||
if options.show_grid {
|
||||
draw_graticule(&mut buffer, options.left_color, true);
|
||||
draw_graticule(&mut buffer, options.left_color);
|
||||
}
|
||||
|
||||
let end_sample = std::cmp::min(start_sample + samples_per_frame, audio_data.left_channel.len());
|
||||
@ -362,8 +360,7 @@ pub fn draw_frame(
|
||||
let spec_x_offset = half_width;
|
||||
let spec_y_offset = half_height;
|
||||
|
||||
let window_size = 1024.min(samples_per_frame);
|
||||
let spectrum = compute_spectrum(audio_data, start_sample, window_size);
|
||||
let spectrum = compute_spectrum(audio_data, start_sample);
|
||||
|
||||
draw_spectrometer(
|
||||
&mut buffer,
|
||||
@ -386,8 +383,7 @@ pub fn draw_frame(
|
||||
}
|
||||
}
|
||||
RenderMode::Spectrometer => {
|
||||
let window_size = 1024.min(samples_per_frame);
|
||||
let spectrum = compute_spectrum(audio_data, start_sample, window_size);
|
||||
let spectrum = compute_spectrum(audio_data, start_sample);
|
||||
|
||||
draw_spectrometer(
|
||||
&mut buffer,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user