use crate::core::io::AudioData;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum PanningError {
#[error("Signal must be mono: {0} channels found")]
NotMono(u16),
#[error("Invalid panning parameter: {0}")]
InvalidParameter(String),
#[error("Unsupported channel count: {0}")]
UnsupportedChannels(u16),
}
pub fn stereo_pan(signal: &AudioData, pan: f32) -> Result<AudioData, PanningError> {
if signal.channels != 1 {
return Err(PanningError::NotMono(signal.channels));
}
if !(-1.0..=1.0).contains(&pan) {
return Err(PanningError::InvalidParameter(
"Pan value must be between -1.0 and 1.0".to_string(),
));
}
let left_gain = (1.0 - pan) / 2.0;
let right_gain = (pan + 1.0) / 2.0;
let mut samples = Vec::with_capacity(signal.samples.len() * 2);
for &sample in &signal.samples {
samples.push(sample * left_gain);
samples.push(sample * right_gain);
}
Ok(AudioData {
samples,
sample_rate: signal.sample_rate,
channels: 2,
})
}
pub fn multi_channel_pan(
signal: &AudioData,
azimuth: f32,
channels: u16,
) -> Result<AudioData, PanningError> {
if signal.channels != 1 {
return Err(PanningError::NotMono(signal.channels));
}
if !matches!(channels, 2 | 4 | 6) {
return Err(PanningError::UnsupportedChannels(channels));
}
let azimuth = (azimuth % 360.0 + 360.0) % 360.0;
let mut gains = vec![0.0; channels as usize];
match channels {
2 => {
let pan = if azimuth <= 90.0 {
azimuth / 90.0
} else if azimuth <= 180.0 {
1.0 - (azimuth - 90.0) / 90.0
} else if azimuth <= 270.0 {
-(azimuth - 180.0) / 90.0
} else {
-1.0 + (azimuth - 270.0) / 90.0
};
gains[0] = (1.0 - pan) / 2.0;
gains[1] = (pan + 1.0) / 2.0;
}
4 => {
if azimuth <= 90.0 {
gains[0] = 1.0 - azimuth / 90.0;
gains[1] = azimuth / 90.0;
} else if azimuth <= 180.0 {
gains[1] = 1.0 - (azimuth - 90.0) / 90.0;
gains[3] = (azimuth - 90.0) / 90.0;
} else if azimuth <= 270.0 {
gains[3] = 1.0 - (azimuth - 180.0) / 90.0;
gains[2] = (azimuth - 180.0) / 90.0;
} else {
gains[2] = 1.0 - (azimuth - 270.0) / 90.0;
gains[0] = (azimuth - 270.0) / 90.0;
}
}
6 => {
if azimuth <= 45.0 {
gains[2] = 1.0 - azimuth / 45.0; gains[1] = azimuth / 45.0; } else if azimuth <= 135.0 {
gains[1] = 1.0; } else if azimuth <= 225.0 {
gains[1] = 1.0 - (azimuth - 135.0) / 90.0; gains[5] = (azimuth - 135.0) / 90.0; } else if azimuth <= 315.0 {
gains[5] = 1.0 - (azimuth - 225.0) / 90.0; gains[4] = (azimuth - 225.0) / 90.0; } else {
gains[4] = 1.0 - (azimuth - 315.0) / 45.0; gains[0] = (azimuth - 315.0) / 45.0; }
if azimuth >= 315.0 {
gains[2] = (azimuth - 315.0) / 45.0; } else if azimuth >= 225.0 {
gains[0] = (azimuth - 225.0) / 90.0; } else if (45.0..=135.0).contains(&azimuth) {
gains[0] = 0.0; } else if azimuth <= 45.0 {
gains[0] = 0.0; }
gains[3] = 0.0; }
_ => unreachable!(),
}
let mut samples = Vec::with_capacity(signal.samples.len() * channels as usize);
for &sample in &signal.samples {
for gain in &gains {
samples.push(sample * gain);
}
}
Ok(AudioData {
samples,
sample_rate: signal.sample_rate,
channels,
})
}