dasp_rs/signal_processing/mixing.rs
1use crate::core::io::AudioData;
2use thiserror::Error;
3
4/// Custom error types for signal mixing operations.
5///
6/// This enum defines errors specific to combining multiple audio signals, such as
7/// stereo mixing, multi-channel mixing, and dry/wet blending.
8#[derive(Error, Debug)]
9pub enum MixingError {
10 /// Error when signal lengths do not match.
11 #[error("Signal lengths mismatch: expected {0}, found {1}")]
12 LengthMismatch(usize, usize),
13
14 /// Error when the number of input signals is invalid for the operation.
15 #[error("Invalid number of signals: {0}")]
16 InvalidSignalCount(String),
17
18 /// Error when input parameters (e.g., mix factor, channels) are invalid.
19 #[error("Invalid parameter: {0}")]
20 InvalidParameter(String),
21
22 /// Error when an input signal has an incompatible format (e.g., not mono).
23 #[error("Incompatible signal format: {0}")]
24 IncompatibleFormat(String),
25}
26
27/// Combines two mono signals into a stereo signal.
28///
29/// This function takes two mono audio signals and interleaves them into a single
30/// stereo signal (left channel from the first signal, right channel from the second).
31/// Both signals must have the same length and sample rate, and be mono (1 channel).
32///
33/// # Arguments
34/// * `left` - The mono signal for the left channel.
35/// * `right` - The mono signal for the right channel.
36///
37/// # Returns
38/// Returns `Result<AudioData, MixingError>` containing the stereo signal or an error.
39///
40/// # Examples
41/// ```
42/// let left = AudioData { samples: vec![0.1, 0.2, 0.3], sample_rate: 44100, channels: 1 };
43/// let right = AudioData { samples: vec![0.4, 0.5, 0.6], sample_rate: 44100, channels: 1 };
44/// let stereo = stereo_mix(&left, &right)?;
45/// assert_eq!(stereo.samples, vec![0.1, 0.4, 0.2, 0.5, 0.3, 0.6]);
46/// assert_eq!(stereo.channels, 2);
47/// ```
48pub fn stereo_mix(left: &AudioData, right: &AudioData) -> Result<AudioData, MixingError> {
49 if left.channels != 1 || right.channels != 1 {
50 return Err(MixingError::IncompatibleFormat(
51 "Both signals must be mono".to_string(),
52 ));
53 }
54 if left.samples.len() != right.samples.len() {
55 return Err(MixingError::LengthMismatch(
56 left.samples.len(),
57 right.samples.len(),
58 ));
59 }
60 if left.sample_rate != right.sample_rate {
61 return Err(MixingError::InvalidParameter(
62 "Sample rates must match".to_string(),
63 ));
64 }
65
66 let mut samples = Vec::with_capacity(left.samples.len() * 2);
67 for (l, r) in left.samples.iter().zip(right.samples.iter()) {
68 samples.push(*l);
69 samples.push(*r);
70 }
71
72 Ok(AudioData {
73 samples,
74 sample_rate: left.sample_rate,
75 channels: 2,
76 })
77}
78
79/// Combines multiple signals into a multi-channel output.
80///
81/// This function takes a vector of mono signals and combines them into a single
82/// multi-channel signal with the specified number of channels. The number of signals
83/// must match the target channel count (e.g., 6 for 5.1 surround).
84/// All signals must be mono, have the same length, and same sample rate.
85///
86/// # Arguments
87/// * `signals` - A vector of mono audio signals.
88/// * `channels` - The target number of channels (e.g., 6 for 5.1).
89///
90/// # Returns
91/// Returns `Result<AudioData, MixingError>` containing the multi-channel signal or an error.
92///
93/// # Examples
94/// ```
95/// let signals = vec![
96/// AudioData { samples: vec![0.1, 0.2], sample_rate: 44100, channels: 1 }, // Front Left
97/// AudioData { samples: vec![0.3, 0.4], sample_rate: 44100, channels: 1 }, // Front Right
98/// ];
99/// let stereo = multi_channel_mix(&signals, 2)?;
100/// assert_eq!(stereo.samples, vec![0.1, 0.3, 0.2, 0.4]);
101/// assert_eq!(stereo.channels, 2);
102/// ```
103pub fn multi_channel_mix(signals: &[&AudioData], channels: u16) -> Result<AudioData, MixingError> {
104 if signals.is_empty() {
105 return Err(MixingError::InvalidSignalCount(
106 "At least one signal is required".to_string(),
107 ));
108 }
109 if signals.len() != channels as usize {
110 return Err(MixingError::InvalidSignalCount(format!(
111 "Number of signals ({}) must match target channels ({})",
112 signals.len(),
113 channels
114 )));
115 }
116
117 let length = signals[0].samples.len();
118 let sample_rate = signals[0].sample_rate;
119 for &signal in signals {
120 if signal.channels != 1 {
121 return Err(MixingError::IncompatibleFormat(
122 "All signals must be mono".to_string(),
123 ));
124 }
125 if signal.samples.len() != length {
126 return Err(MixingError::LengthMismatch(length, signal.samples.len()));
127 }
128 if signal.sample_rate != sample_rate {
129 return Err(MixingError::InvalidParameter(
130 "Sample rates must match".to_string(),
131 ));
132 }
133 }
134
135 let mut samples = Vec::with_capacity(length * channels as usize);
136 for i in 0..length {
137 for &signal in signals {
138 samples.push(signal.samples[i]);
139 }
140 }
141
142 Ok(AudioData {
143 samples,
144 sample_rate,
145 channels,
146 })
147}
148
149/// Blends a processed (wet) signal with the original (dry) signal.
150///
151/// This function combines the original signal with a processed version using a mix factor.
152/// A `wet_mix` of 0.0 returns the dry signal, 1.0 returns the wet signal, and values
153/// in between blend them proportionally. Signals must have the same length, sample rate,
154/// and channels.
155///
156/// # Arguments
157/// * `dry` - The original (unprocessed) signal.
158/// * `wet` - The processed signal.
159/// * `wet_mix` - The mix factor (0.0 = fully dry, 1.0 = fully wet).
160///
161/// # Returns
162/// Returns `Result<AudioData, MixingError>` containing the blended signal or an error.
163///
164/// # Examples
165/// ```
166/// let dry = AudioData { samples: vec![1.0, 1.0], sample_rate: 44100, channels: 1 };
167/// let wet = AudioData { samples: vec![2.0, 2.0], sample_rate: 44100, channels: 1 };
168/// let mixed = dry_wet_mix(&dry, &wet, 0.5)?;
169/// assert_eq!(mixed.samples, vec![1.5, 1.5]); // (1.0 * 0.5) + (2.0 * 0.5)
170/// ```
171pub fn dry_wet_mix(dry: &AudioData, wet: &AudioData, wet_mix: f32) -> Result<AudioData, MixingError> {
172 if !(0.0..=1.0).contains(&wet_mix) {
173 return Err(MixingError::InvalidParameter(
174 "Wet mix must be between 0.0 and 1.0".to_string(),
175 ));
176 }
177 if dry.samples.len() != wet.samples.len() {
178 return Err(MixingError::LengthMismatch(
179 dry.samples.len(),
180 wet.samples.len(),
181 ));
182 }
183 if dry.sample_rate != wet.sample_rate || dry.channels != wet.channels {
184 return Err(MixingError::InvalidParameter(
185 "Sample rate and channels must match".to_string(),
186 ));
187 }
188
189 let dry_mix = 1.0 - wet_mix;
190 let samples: Vec<f32> = dry
191 .samples
192 .iter()
193 .zip(&wet.samples)
194 .map(|(&d, &w)| d * dry_mix + w * wet_mix)
195 .collect();
196
197 Ok(AudioData {
198 samples,
199 sample_rate: dry.sample_rate,
200 channels: dry.channels,
201 })
202}