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"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bytemuck",
|
||||||
"clap",
|
"clap",
|
||||||
"hound",
|
"hound",
|
||||||
"image",
|
"image",
|
||||||
|
|||||||
@ -31,6 +31,9 @@ anyhow = "1.0"
|
|||||||
# FFT processing for spectrometer
|
# FFT processing for spectrometer
|
||||||
rustfft = "6.1"
|
rustfft = "6.1"
|
||||||
|
|
||||||
|
# Efficient byte casting
|
||||||
|
bytemuck = "1.24"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
12
src/audio.rs
12
src/audio.rs
@ -3,6 +3,7 @@
|
|||||||
//! Handles reading and decoding WAV files into normalized sample data.
|
//! Handles reading and decoding WAV files into normalized sample data.
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use bytemuck;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
/// Normalized audio sample data.
|
/// Normalized audio sample data.
|
||||||
@ -49,12 +50,11 @@ impl AudioData {
|
|||||||
let mut left_channel = Vec::with_capacity(total_samples);
|
let mut left_channel = Vec::with_capacity(total_samples);
|
||||||
let mut right_channel = Vec::with_capacity(total_samples);
|
let mut right_channel = Vec::with_capacity(total_samples);
|
||||||
|
|
||||||
for i in 0..total_samples {
|
// Convert PCM data to f32 samples efficiently
|
||||||
let offset = i * 2 * num_channels;
|
let samples: &[i16] = bytemuck::cast_slice(&pcm_data);
|
||||||
|
for chunk in samples.chunks_exact(num_channels) {
|
||||||
let left_val = i16::from_le_bytes([pcm_data[offset], pcm_data[offset + 1]]);
|
let left_val = chunk[0];
|
||||||
let right_val = i16::from_le_bytes([pcm_data[offset + 2], pcm_data[offset + 3]]);
|
let right_val = chunk[1];
|
||||||
|
|
||||||
left_channel.push(left_val as f32 / 32768.0);
|
left_channel.push(left_val as f32 / 32768.0);
|
||||||
right_channel.push(right_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(
|
fn draw_graticule(
|
||||||
buffer: &mut ImageBuffer<image::Rgb<u8>, Vec<u8>>,
|
buffer: &mut ImageBuffer<image::Rgb<u8>, Vec<u8>>,
|
||||||
primary_color: image::Rgb<u8>,
|
primary_color: image::Rgb<u8>,
|
||||||
show_grid: bool,
|
|
||||||
) {
|
) {
|
||||||
if !show_grid {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (width, height) = buffer.dimensions();
|
let (width, height) = buffer.dimensions();
|
||||||
|
|
||||||
for x in 0..width {
|
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.
|
/// 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
|
// Use a larger FFT size for better frequency resolution, especially in the bass
|
||||||
let fft_size = 2048;
|
let fft_size = 2048;
|
||||||
let mut planner = FftPlanner::new();
|
let mut planner = FftPlanner::new();
|
||||||
@ -159,19 +154,23 @@ fn draw_spectrometer(
|
|||||||
color: image::Rgb<u8>,
|
color: image::Rgb<u8>,
|
||||||
sample_rate: u32,
|
sample_rate: u32,
|
||||||
) {
|
) {
|
||||||
let spacing = 1;
|
// Constants for logarithmic frequency mapping
|
||||||
let bar_width = (width - (num_bars as u32 - 1) * spacing) / num_bars as u32;
|
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);
|
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;
|
let nyquist = sample_rate as f32 / 2.0;
|
||||||
|
|
||||||
for i in 0..num_bars {
|
for i in 0..num_bars {
|
||||||
// Calculate frequency range for this bar (logarithmic)
|
// 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_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_end = MIN_FREQ * (MAX_FREQ / MIN_FREQ).powf((i + 1) as f32 / num_bars as f32);
|
||||||
|
|
||||||
// Map frequencies to FFT bin indices
|
// Map frequencies to FFT bin indices
|
||||||
let bin_start = (f_start / nyquist * spectrum.len() as f32) as usize;
|
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)
|
// 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) * FREQ_BOOST_FACTOR;
|
||||||
let freq_factor = 1.0 + (f_start / max_freq) * 5.0;
|
|
||||||
let mut val = magnitude * freq_factor;
|
let mut val = magnitude * freq_factor;
|
||||||
|
|
||||||
// Dynamic range compression/scaling
|
// Dynamic range compression/scaling
|
||||||
val = (val * 20.0).sqrt().min(1.0);
|
val = (val * DYNAMIC_RANGE_SCALE).sqrt().min(1.0);
|
||||||
|
|
||||||
// Noise floor
|
// 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 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 {
|
for y in 0..bar_height {
|
||||||
let pixel_y = y_offset + height - 1 - y;
|
let pixel_y = y_offset + height - 1 - y;
|
||||||
@ -226,7 +224,7 @@ pub fn draw_frame(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if options.show_grid {
|
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());
|
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_x_offset = half_width;
|
||||||
let spec_y_offset = half_height;
|
let spec_y_offset = half_height;
|
||||||
|
|
||||||
let window_size = 1024.min(samples_per_frame);
|
let spectrum = compute_spectrum(audio_data, start_sample);
|
||||||
let spectrum = compute_spectrum(audio_data, start_sample, window_size);
|
|
||||||
|
|
||||||
draw_spectrometer(
|
draw_spectrometer(
|
||||||
&mut buffer,
|
&mut buffer,
|
||||||
@ -386,8 +383,7 @@ pub fn draw_frame(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
RenderMode::Spectrometer => {
|
RenderMode::Spectrometer => {
|
||||||
let window_size = 1024.min(samples_per_frame);
|
let spectrum = compute_spectrum(audio_data, start_sample);
|
||||||
let spectrum = compute_spectrum(audio_data, start_sample, window_size);
|
|
||||||
|
|
||||||
draw_spectrometer(
|
draw_spectrometer(
|
||||||
&mut buffer,
|
&mut buffer,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user