use crate::core::io::AudioData;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AmplitudeError {
#[error("Invalid gain factor: {0}")]
InvalidGain(String),
#[error("Invalid signal: {0}")]
InvalidSignal(String),
}
pub fn amplify(signal: &AudioData, gain: f32) -> Result<AudioData, AmplitudeError> {
if gain <= 0.0 {
return Err(AmplitudeError::InvalidGain(
"Gain must be positive".to_string(),
));
}
let samples = signal.samples.iter().map(|&s| s * gain).collect();
Ok(AudioData {
samples,
sample_rate: signal.sample_rate,
channels: signal.channels,
})
}
pub fn attenuate(signal: &AudioData, gain: f32) -> Result<AudioData, AmplitudeError> {
if gain < 0.0 {
return Err(AmplitudeError::InvalidGain(
"Gain must be non-negative".to_string(),
));
}
let samples = signal.samples.iter().map(|&s| s * gain).collect();
Ok(AudioData {
samples,
sample_rate: signal.sample_rate,
channels: signal.channels,
})
}
pub fn normalize(signal: &AudioData, target: f32) -> NormalizeBuilder<'_> {
NormalizeBuilder { signal, target, mode: NormalizeMode::Peak }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum NormalizeMode {
#[default]
Peak,
Rms,
}
#[derive(Debug, Clone)]
pub struct NormalizeBuilder<'a> {
signal: &'a AudioData,
target: f32,
mode: NormalizeMode,
}
impl NormalizeBuilder<'_> {
#[must_use]
pub fn mode(mut self, mode: NormalizeMode) -> Self {
self.mode = mode;
self
}
pub fn compute(self) -> Result<AudioData, AmplitudeError> {
normalize_impl(self.signal, self.target, self.mode)
}
}
fn normalize_impl(
signal: &AudioData,
target: f32,
mode: NormalizeMode,
) -> Result<AudioData, AmplitudeError> {
if target <= 0.0 {
return Err(AmplitudeError::InvalidGain(
"Target level must be positive".to_string(),
));
}
if signal.samples.is_empty() {
return Err(AmplitudeError::InvalidSignal(
"Signal cannot be empty".to_string(),
));
}
let gain = match mode {
NormalizeMode::Peak => {
let max_amplitude = signal
.samples
.iter()
.map(|s| s.abs())
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or(0.0);
if max_amplitude == 0.0 {
return Err(AmplitudeError::InvalidSignal(
"Signal has no amplitude to normalize".to_string(),
));
}
target / max_amplitude
}
NormalizeMode::Rms => {
let rms = (signal.samples.iter().map(|&s| s * s).sum::<f32>() / signal.samples.len() as f32)
.sqrt();
if rms == 0.0 {
return Err(AmplitudeError::InvalidSignal(
"Signal has no RMS level to normalize".to_string(),
));
}
target / rms
}
};
let samples = signal.samples.iter().map(|&s| s * gain).collect();
Ok(AudioData {
samples,
sample_rate: signal.sample_rate,
channels: signal.channels,
})
}