use crate::core::io::AudioData;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MixingError {
#[error("Signal lengths mismatch: expected {0}, found {1}")]
LengthMismatch(usize, usize),
#[error("Invalid number of signals: {0}")]
InvalidSignalCount(String),
#[error("Invalid parameter: {0}")]
InvalidParameter(String),
#[error("Incompatible signal format: {0}")]
IncompatibleFormat(String),
}
pub fn stereo_mix(left: &AudioData, right: &AudioData) -> Result<AudioData, MixingError> {
if left.channels != 1 || right.channels != 1 {
return Err(MixingError::IncompatibleFormat(
"Both signals must be mono".to_string(),
));
}
if left.samples.len() != right.samples.len() {
return Err(MixingError::LengthMismatch(
left.samples.len(),
right.samples.len(),
));
}
if left.sample_rate != right.sample_rate {
return Err(MixingError::InvalidParameter(
"Sample rates must match".to_string(),
));
}
let mut samples = Vec::with_capacity(left.samples.len() * 2);
for (l, r) in left.samples.iter().zip(right.samples.iter()) {
samples.push(*l);
samples.push(*r);
}
Ok(AudioData {
samples,
sample_rate: left.sample_rate,
channels: 2,
})
}
pub fn multi_channel_mix(signals: &[&AudioData], channels: u16) -> Result<AudioData, MixingError> {
if signals.is_empty() {
return Err(MixingError::InvalidSignalCount(
"At least one signal is required".to_string(),
));
}
if signals.len() != channels as usize {
return Err(MixingError::InvalidSignalCount(format!(
"Number of signals ({}) must match target channels ({})",
signals.len(),
channels
)));
}
let length = signals[0].samples.len();
let sample_rate = signals[0].sample_rate;
for &signal in signals {
if signal.channels != 1 {
return Err(MixingError::IncompatibleFormat(
"All signals must be mono".to_string(),
));
}
if signal.samples.len() != length {
return Err(MixingError::LengthMismatch(length, signal.samples.len()));
}
if signal.sample_rate != sample_rate {
return Err(MixingError::InvalidParameter(
"Sample rates must match".to_string(),
));
}
}
let mut samples = Vec::with_capacity(length * channels as usize);
for i in 0..length {
for &signal in signals {
samples.push(signal.samples[i]);
}
}
Ok(AudioData {
samples,
sample_rate,
channels,
})
}
pub fn dry_wet_mix(dry: &AudioData, wet: &AudioData, wet_mix: f32) -> Result<AudioData, MixingError> {
if !(0.0..=1.0).contains(&wet_mix) {
return Err(MixingError::InvalidParameter(
"Wet mix must be between 0.0 and 1.0".to_string(),
));
}
if dry.samples.len() != wet.samples.len() {
return Err(MixingError::LengthMismatch(
dry.samples.len(),
wet.samples.len(),
));
}
if dry.sample_rate != wet.sample_rate || dry.channels != wet.channels {
return Err(MixingError::InvalidParameter(
"Sample rate and channels must match".to_string(),
));
}
let dry_mix = 1.0 - wet_mix;
let samples: Vec<f32> = dry
.samples
.iter()
.zip(&wet.samples)
.map(|(&d, &w)| d * dry_mix + w * wet_mix)
.collect();
Ok(AudioData {
samples,
sample_rate: dry.sample_rate,
channels: dry.channels,
})
}