Skip to main content

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}