//! Audio level calculation and visualization data.
/// Calculate RMS level from samples.
pub fn calculate_rms(samples: &[f32]) -> f32 {
if samples.is_empty() {
return 0.0;
}
let sum_sq: f32 = samples.iter().map(|s| s * s).sum();
(sum_sq / samples.len() as f32).sqrt()
}
/// Calculate peak level from samples.
pub fn calculate_peak(samples: &[f32]) -> f32 {
samples
.iter()
.map(|s| s.abs())
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(0.0)
}
/// Convert linear level to decibels.
pub fn linear_to_db(level: f32) -> f32 {
if level <= 0.0 {
-60.0
} else {
20.0 * level.log10()
}
}
/// Normalize dB value to 0.0-1.0 range.
pub fn db_to_normalized(db: f32, min_db: f32, max_db: f32) -> f32 {
((db - min_db) / (max_db - min_db)).clamp(0.0, 1.0)
}
/// Waveform data for visualization.
#[derive(Clone, Default)]
pub struct WaveformData {
/// Downsampled waveform points
pub points: Vec<f32>,
/// Peak level
pub peak: f32,
/// RMS level
pub rms: f32,
}
impl WaveformData {
/// Create waveform data from audio samples.
pub fn from_samples(samples: &[f32], num_points: usize) -> Self {
if samples.is_empty() || num_points == 0 {
return Self::default();
}
let chunk_size = samples.len() / num_points;
let points: Vec<f32> = if chunk_size > 0 {
samples
.chunks(chunk_size)
.take(num_points)
.map(|chunk| {
// Use max absolute value for each chunk
chunk
.iter()
.map(|s| s.abs())
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(0.0)
})
.collect()
} else {
samples.iter().copied().collect()
};
Self {
points,
peak: calculate_peak(samples),
rms: calculate_rms(samples),
}
}
}