1use std::{f64::consts::PI, ops::Range};
2
3use ndarray::Array1;
4use thiserror::Error;
5
6use crate::signal::{FreqSignal, SignalError, TimeSignal};
7
8use crate::dsp::{PadMode, pad_zeros};
9
10#[derive(Debug, Error)]
11pub enum DeconvolutionError {
12 #[error("sample rates do not match: output has {output} Hz, input has {input} Hz")]
13 SampleRateMismatch { output: f64, input: f64 },
14 #[error(
15 "channel counts do not match: output has {output} channels, input has {input} channels"
16 )]
17 ChannelMismatch { output: usize, input: usize },
18 #[error(
19 "fft length ({fft_len}) is shorter than signal length (output: {output_len}, input: {input_len})"
20 )]
21 FftLengthTooShort {
22 fft_len: usize,
23 output_len: usize,
24 input_len: usize,
25 },
26 #[error(
27 "invalid frequency range: {start} Hz to {end} Hz (must satisfy 0 <= start < end <= {nyquist} Hz)"
28 )]
29 InvalidFrequencyRange { start: f64, end: f64, nyquist: f64 },
30 #[error("signals must not be empty")]
31 EmptySignal,
32 #[error(transparent)]
33 Signal(#[from] SignalError),
34}
35
36#[derive(Debug, Clone, PartialEq)]
37pub struct DeconvolutionConfig {
38 fft_len: Option<usize>,
39 regularized_band: Option<Range<f64>>,
40 regularization_outside_band: f64,
41 regularization_inside_band: f64,
42}
43
44impl Default for DeconvolutionConfig {
45 fn default() -> Self {
46 Self {
47 fft_len: None,
48 regularized_band: None,
49 regularization_outside_band: 1.0,
50 regularization_inside_band: 10_f64.powf(-200.0 / 20.0),
51 }
52 }
53}
54
55impl DeconvolutionConfig {
56 pub fn with_fft_len(mut self, fft_len: usize) -> Self {
57 self.fft_len = Some(fft_len);
58 self
59 }
60
61 pub fn with_regularized_band(mut self, regularized_band: Range<f64>) -> Self {
62 self.regularized_band = Some(regularized_band);
63 self
64 }
65
66 pub fn with_frequency_range(self, frequency_range: Range<f64>) -> Self {
67 self.with_regularized_band(frequency_range)
68 }
69
70 pub fn with_regularization(mut self, outside_band: f64, inside_band: f64) -> Self {
71 self.regularization_outside_band = outside_band;
72 self.regularization_inside_band = inside_band;
73 self
74 }
75}
76
77pub fn deconvolve(
78 system_output: &TimeSignal,
79 system_input: &TimeSignal,
80 config: &DeconvolutionConfig,
81) -> Result<TimeSignal, DeconvolutionError> {
82 if system_output.sample_rate() != system_input.sample_rate() {
83 return Err(DeconvolutionError::SampleRateMismatch {
84 output: system_output.sample_rate(),
85 input: system_input.sample_rate(),
86 });
87 }
88
89 if system_output.num_channels() != system_input.num_channels() {
90 return Err(DeconvolutionError::ChannelMismatch {
91 output: system_output.num_channels(),
92 input: system_input.num_channels(),
93 });
94 }
95
96 let output_len = system_output.num_time_steps();
97 let input_len = system_input.num_time_steps();
98 if output_len == 0 || input_len == 0 {
99 return Err(DeconvolutionError::EmptySignal);
100 }
101
102 let fft_len = config.fft_len.unwrap_or(output_len.max(input_len));
103 if fft_len < output_len || fft_len < input_len {
104 return Err(DeconvolutionError::FftLengthTooShort {
105 fft_len,
106 output_len,
107 input_len,
108 });
109 }
110
111 let padded_output = pad_zeros(system_output, fft_len - output_len, PadMode::After)?;
112 let padded_input = pad_zeros(system_input, fft_len - input_len, PadMode::After)?;
113
114 let output_freq = padded_output.into_freq();
115 let inverse_input = regularized_spectrum_inversion(&padded_input.into_freq(), config)?;
116 let mut response_freq = FreqSignal::zeros(
117 output_freq.num_channels(),
118 output_freq.num_freq_bins(),
119 output_freq.sample_rate(),
120 Some(fft_len),
121 )?;
122
123 for ((mut out_channel, output_channel), inverse_channel) in response_freq
124 .channel_iter_mut()
125 .zip(output_freq.channel_iter())
126 .zip(inverse_input.channel_iter())
127 {
128 out_channel.assign(&(&output_channel * &inverse_channel));
129 }
130
131 Ok(response_freq.into_time())
132}
133
134fn regularized_spectrum_inversion(
135 signal: &FreqSignal,
136 config: &DeconvolutionConfig,
137) -> Result<FreqSignal, DeconvolutionError> {
138 let nyquist = signal.nyquist();
139 let regularized_band = config.regularized_band.clone().unwrap_or(0.0..nyquist);
140
141 validate_frequency_range(®ularized_band, nyquist)?;
142
143 let mut regularization =
144 Array1::from_elem(signal.num_freq_bins(), config.regularization_inside_band);
145
146 let outside = Array1::from_elem(signal.num_freq_bins(), config.regularization_outside_band);
147 let lower_crossfade_start = signal.nearest_freq_index(regularized_band.start / 2_f64.sqrt());
148 let lower_crossfade_end = signal.nearest_freq_index(regularized_band.start);
149 regularization = crossfade(
150 &outside,
151 ®ularization,
152 lower_crossfade_start..lower_crossfade_end,
153 );
154
155 if regularized_band.end < nyquist {
156 let upper_crossfade_start = signal.nearest_freq_index(regularized_band.end);
157 let upper_crossfade_end =
158 signal.nearest_freq_index((regularized_band.end * 2_f64.sqrt()).min(nyquist));
159 regularization = crossfade(
160 ®ularization,
161 &outside,
162 upper_crossfade_start..upper_crossfade_end,
163 );
164 }
165
166 let max_squared_magnitude = max_squared_magnitude(signal);
167 regularization *= max_squared_magnitude;
168
169 let mut inverse = FreqSignal::zeros(
170 signal.num_channels(),
171 signal.num_freq_bins(),
172 signal.sample_rate(),
173 Some(signal.num_time_steps()),
174 )?;
175
176 for ((mut out_channel, in_channel), reg_channel) in inverse
177 .channel_iter_mut()
178 .zip(signal.channel_iter())
179 .zip(std::iter::repeat(®ularization))
180 {
181 for ((out_bin, in_bin), reg) in out_channel
182 .iter_mut()
183 .zip(in_channel.iter())
184 .zip(reg_channel.iter())
185 {
186 *out_bin = in_bin.conj() / (in_bin.norm_sqr() + *reg);
187 }
188 }
189
190 Ok(inverse)
191}
192
193fn validate_frequency_range(range: &Range<f64>, nyquist: f64) -> Result<(), DeconvolutionError> {
194 if range.start < 0.0 || range.start >= range.end || range.end > nyquist {
195 return Err(DeconvolutionError::InvalidFrequencyRange {
196 start: range.start,
197 end: range.end,
198 nyquist,
199 });
200 }
201
202 Ok(())
203}
204
205fn max_squared_magnitude(signal: &FreqSignal) -> f64 {
206 signal
207 .freq_data()
208 .iter()
209 .map(|value| value.norm_sqr())
210 .fold(0.0_f64, f64::max)
211}
212
213fn crossfade(first: &Array1<f64>, second: &Array1<f64>, range: Range<usize>) -> Array1<f64> {
214 assert_eq!(first.len(), second.len());
215 assert!(range.start <= first.len());
216 assert!(range.end <= first.len());
217
218 let len = first.len();
219 let crossfade_len = range.end.saturating_sub(range.start);
220 let mut output = Array1::zeros(len);
221
222 for i in 0..range.start {
223 output[i] = first[i];
224 }
225
226 if crossfade_len > 0 {
227 for step in 0..crossfade_len {
228 let position = (step + 1) as f64 / (crossfade_len + 1) as f64;
229 let second_weight = 0.5 - 0.5 * (PI * position).cos();
230 let first_weight = 1.0 - second_weight;
231 let idx = range.start + step;
232 output[idx] = first[idx] * first_weight + second[idx] * second_weight;
233 }
234 }
235
236 for i in range.end..len {
237 output[i] = second[i];
238 }
239
240 output
241}
242
243#[cfg(test)]
244mod tests {
245 use approx::assert_abs_diff_eq;
246 use ndarray::{arr1, arr2};
247
248 use super::*;
249
250 #[test]
251 fn deconvolve_recovers_known_response_for_impulse_input() {
252 let system_input = TimeSignal::new(arr2(&[[1.0, 0.0, 0.0, 0.0]]), 48_000.0).unwrap();
253 let expected_response =
254 TimeSignal::new(arr2(&[[0.5, -0.25, 0.125, 0.0]]), 48_000.0).unwrap();
255
256 let recovered = deconvolve(
257 &expected_response,
258 &system_input,
259 &DeconvolutionConfig::default(),
260 )
261 .unwrap();
262
263 assert_abs_diff_eq!(
264 recovered.channel(0),
265 expected_response.channel(0),
266 epsilon = 1e-9
267 );
268 }
269
270 #[test]
271 fn deconvolve_rejects_sample_rate_mismatch() {
272 let system_output = TimeSignal::zeros(1, 16, 44_100.0).unwrap();
273 let system_input = TimeSignal::zeros(1, 16, 48_000.0).unwrap();
274
275 let result = deconvolve(
276 &system_output,
277 &system_input,
278 &DeconvolutionConfig::default(),
279 );
280
281 assert!(matches!(
282 result,
283 Err(DeconvolutionError::SampleRateMismatch {
284 output: 44_100.0,
285 input: 48_000.0
286 })
287 ));
288 }
289
290 #[test]
291 fn deconvolve_rejects_channel_mismatch() {
292 let system_output = TimeSignal::zeros(2, 16, 48_000.0).unwrap();
293 let system_input = TimeSignal::zeros(1, 16, 48_000.0).unwrap();
294
295 let result = deconvolve(
296 &system_output,
297 &system_input,
298 &DeconvolutionConfig::default(),
299 );
300
301 assert!(matches!(
302 result,
303 Err(DeconvolutionError::ChannelMismatch {
304 output: 2,
305 input: 1
306 })
307 ));
308 }
309
310 #[test]
311 fn deconvolve_rejects_fft_lengths_shorter_than_inputs() {
312 let system_output = TimeSignal::zeros(1, 16, 48_000.0).unwrap();
313 let system_input = TimeSignal::zeros(1, 8, 48_000.0).unwrap();
314
315 let result = deconvolve(
316 &system_output,
317 &system_input,
318 &DeconvolutionConfig::default().with_fft_len(15),
319 );
320
321 assert!(matches!(
322 result,
323 Err(DeconvolutionError::FftLengthTooShort {
324 fft_len: 15,
325 output_len: 16,
326 input_len: 8
327 })
328 ));
329 }
330
331 #[test]
332 fn deconvolve_rejects_invalid_frequency_ranges() {
333 let system_output = TimeSignal::zeros(1, 16, 48_000.0).unwrap();
334 let system_input = TimeSignal::zeros(1, 16, 48_000.0).unwrap();
335
336 let result = deconvolve(
337 &system_output,
338 &system_input,
339 &DeconvolutionConfig::default().with_regularized_band(2_000.0..1_000.0),
340 );
341 assert!(matches!(
342 result,
343 Err(DeconvolutionError::InvalidFrequencyRange { .. })
344 ));
345
346 let result = deconvolve(
347 &system_output,
348 &system_input,
349 &DeconvolutionConfig::default().with_regularized_band(100.0..30_000.0),
350 );
351 assert!(matches!(
352 result,
353 Err(DeconvolutionError::InvalidFrequencyRange { .. })
354 ));
355 }
356
357 #[test]
358 fn spectrum_inversion_matches_complex_formula() {
359 use num::complex::Complex64;
360
361 let signal = TimeSignal::new(
362 arr2(&[[0.8, -0.3, 0.5, 0.1, -0.2, 0.05, 0.0, 0.3]]),
363 48_000.0,
364 )
365 .unwrap()
366 .into_freq();
367 let factor = 1e-2;
369 let config = DeconvolutionConfig::default()
370 .with_regularized_band(3_000.0..12_000.0)
371 .with_regularization(factor, factor);
372
373 let inverse = regularized_spectrum_inversion(&signal, &config).unwrap();
374
375 let reg = factor * max_squared_magnitude(&signal);
376 for (h, inv) in signal.channel(0).iter().zip(inverse.channel(0).iter()) {
377 let expected = h.conj() / (h.conj() * *h + Complex64::from(reg));
378 assert_abs_diff_eq!(inv.re, expected.re, epsilon = 1e-12);
379 assert_abs_diff_eq!(inv.im, expected.im, epsilon = 1e-12);
380 }
381 }
382
383 #[test]
384 fn crossfade_transitions_between_arrays() {
385 let first = arr1(&[1.0, 1.0, 1.0, 1.0, 1.0]);
386 let second = arr1(&[0.0, 0.0, 0.0, 0.0, 0.0]);
387
388 let output = crossfade(&first, &second, 1..4);
389
390 assert_abs_diff_eq!(output[0], 1.0, epsilon = 1e-12);
391 assert!(output[1] < 1.0 && output[1] > output[2]);
392 assert_abs_diff_eq!(output[2], 0.5, epsilon = 1e-12);
393 assert!(output[3] > 0.0 && output[3] < output[2]);
394 assert_abs_diff_eq!(output[4], 0.0, epsilon = 1e-12);
395 }
396}