const WAVEFORM_WIDTH: usize = 60;
const WAVEFORM_HEIGHT: usize = 8;
pub struct DecodeResult {
pub bytes: usize,
pub sample_count: usize,
pub sample_rate: u32,
pub channels: u8,
pub rms: f64,
pub waveform: Vec<f32>,
}
impl DecodeResult {
pub fn new(samples: &[i16], sample_rate: u32, channels: u8) -> Self {
let sample_count = samples.len();
let bytes = sample_count * 2;
let mut sum_of_squares = 0.0f64;
for &sample in samples {
let normalized = sample as f64 / 32768.0;
sum_of_squares += normalized * normalized;
}
let rms = if sample_count > 0 {
(sum_of_squares / sample_count as f64).sqrt()
} else {
0.0
};
let waveform = compute_waveform_peaks_i16(samples, WAVEFORM_WIDTH * 2);
Self {
bytes,
sample_count,
sample_rate,
channels,
rms,
waveform,
}
}
pub fn from_i32(samples: &[i32], sample_rate: u32, channels: u8) -> Self {
Self::from_i32_with_bits(samples, sample_rate, channels, 32)
}
pub fn from_i32_with_bits(
samples: &[i32],
sample_rate: u32,
channels: u8,
bits_per_sample: u8,
) -> Self {
let sample_count = samples.len();
let bytes = sample_count * 4;
let max_value = (1i64 << (bits_per_sample - 1)) as f64;
let mut sum_of_squares = 0.0f64;
for &sample in samples {
let normalized = sample as f64 / max_value;
sum_of_squares += normalized * normalized;
}
let rms = if sample_count > 0 {
(sum_of_squares / sample_count as f64).sqrt()
} else {
0.0
};
let waveform = compute_waveform_peaks_i32_bits(samples, WAVEFORM_WIDTH * 2, bits_per_sample);
Self {
bytes,
sample_count,
sample_rate,
channels,
rms,
waveform,
}
}
pub fn duration_secs(&self) -> f64 {
if self.sample_rate == 0 || self.channels == 0 {
return 0.0;
}
self.sample_count as f64 / self.channels as f64 / self.sample_rate as f64
}
pub fn rms_db(&self) -> f64 {
if self.rms > 0.0 {
20.0 * self.rms.log10()
} else {
-96.0
}
}
}
fn compute_waveform_peaks_i16(samples: &[i16], num_bins: usize) -> Vec<f32> {
if samples.is_empty() || num_bins == 0 {
return Vec::new();
}
let bin_size = (samples.len() + num_bins - 1) / num_bins;
samples
.chunks(bin_size)
.map(|chunk| {
let max_abs = chunk.iter().map(|&s| (s as f32).abs()).fold(0.0f32, f32::max);
max_abs / 32768.0
})
.collect()
}
fn compute_waveform_peaks_i32_bits(samples: &[i32], num_bins: usize, bits_per_sample: u8) -> Vec<f32> {
if samples.is_empty() || num_bins == 0 {
return Vec::new();
}
let max_value = (1i64 << (bits_per_sample - 1)) as f64;
let bin_size = (samples.len() + num_bins - 1) / num_bins;
samples
.chunks(bin_size)
.map(|chunk| {
let max_abs = chunk.iter().map(|&s| (s as f64).abs()).fold(0.0f64, f64::max);
(max_abs / max_value) as f32
})
.collect()
}
pub fn print_waveform_comparison(results: &[(&str, &DecodeResult)]) {
if results.is_empty() {
return;
}
println!();
println!(" Decoded Audio Waveforms");
println!(" {}", "═".repeat(70));
println!();
for (name, result) in results {
println!(
" {} ({:.2}s, {} Hz, {} ch, {:.1} dB)",
name,
result.duration_secs(),
result.sample_rate,
result.channels,
result.rms_db()
);
print_waveform(&result.waveform);
println!();
}
}
pub fn print_waveform_with_header(name: &str, result: &DecodeResult) {
println!();
println!(" {} Decode Test", name);
println!(" {}", "─".repeat(50));
println!(
" Duration: {:.2}s | Sample Rate: {} Hz | Channels: {} | RMS: {:.1} dB",
result.duration_secs(),
result.sample_rate,
result.channels,
result.rms_db()
);
print_waveform(&result.waveform);
println!();
}
fn print_waveform(peaks: &[f32]) {
if peaks.is_empty() {
println!(" (no audio data)");
return;
}
let chars = [' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
let display_peaks: Vec<f32> = if peaks.len() > WAVEFORM_WIDTH {
(0..WAVEFORM_WIDTH)
.map(|i| {
let start = i * peaks.len() / WAVEFORM_WIDTH;
let end = ((i + 1) * peaks.len() / WAVEFORM_WIDTH).min(peaks.len());
peaks[start..end]
.iter()
.map(|x| x.abs())
.fold(0.0f32, f32::max)
})
.collect()
} else {
peaks.iter().map(|x| x.abs()).collect()
};
let max_peak = display_peaks.iter().fold(0.0f32, |a, &b| a.max(b)).max(0.001);
let half_height = WAVEFORM_HEIGHT / 2;
for row in (0..half_height).rev() {
let threshold = (row as f32 + 0.5) / half_height as f32;
let line: String = display_peaks
.iter()
.map(|&p| {
let normalized = p / max_peak;
if normalized >= threshold {
let level =
((normalized - threshold) * half_height as f32 * (chars.len() - 1) as f32)
as usize;
chars[level.min(chars.len() - 1)]
} else {
' '
}
})
.collect();
println!(" │{}│", line);
}
println!(" ├{}┤", "─".repeat(display_peaks.len()));
for row in 0..half_height {
let threshold = (row as f32 + 0.5) / half_height as f32;
let line: String = display_peaks
.iter()
.map(|&p| {
let normalized = p / max_peak;
if normalized >= threshold {
let level =
((normalized - threshold) * half_height as f32 * (chars.len() - 1) as f32)
as usize;
chars[level.min(chars.len() - 1)]
} else {
' '
}
})
.collect();
println!(" │{}│", line);
}
}