1use ndarray::prelude::*;
2use ndrustfft::{R2cFftHandler, ndfft_r2c, ndifft_r2c};
3use num::complex::Complex64;
4
5use crate::signal::{SignalError, TimeSignal};
6
7use crate::dsp::window::{WindowFn, generate_window};
8
9#[derive(Debug, thiserror::Error)]
10pub enum FilterError {
11 #[error("overlap must be between 0 and 1, got {0}")]
12 InvalidOverlap(f64),
13 #[error("n_samples must be at least 2")]
14 InvalidNSamples,
15 #[error("no valid frequency bands found in range {0}..{1} Hz")]
16 NoBandsInRange(f64, f64),
17 #[error(transparent)]
18 Signal(#[from] SignalError),
19}
20
21pub fn fractional_octave_frequencies(
26 num_fractions: usize,
27 frequency_range: (f64, f64),
28) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
29 let (f_min, f_max) = frequency_range;
30 let nf = num_fractions as f64;
31
32 let k_min = (nf * (f_min / 1000.0).log2()).floor() as i32 - 1;
34 let k_max = (nf * (f_max / 1000.0).log2()).ceil() as i32 + 1;
35
36 let mut centers = Vec::new();
37 let mut lowers = Vec::new();
38 let mut uppers = Vec::new();
39
40 for k in k_min..=k_max {
41 let f_m = 1000.0 * 2.0_f64.powf(k as f64 / nf);
42 if f_m >= f_min * (1.0 - 1e-6) && f_m <= f_max * (1.0 + 1e-6) {
43 centers.push(f_m);
44 lowers.push(f_m * 2.0_f64.powf(-0.5 / nf));
45 uppers.push(f_m * 2.0_f64.powf(0.5 / nf));
46 }
47 }
48
49 (centers, lowers, uppers)
50}
51
52#[derive(Debug, Clone, Copy)]
53pub struct OctaveBandsConfig {
54 pub frequency_range: (f64, f64),
56 pub overlap: f64,
58 pub slope: usize,
60 pub n_samples: usize,
62}
63
64impl Default for OctaveBandsConfig {
65 fn default() -> Self {
66 Self {
67 frequency_range: (63.0, 16_000.0),
68 overlap: 1.0,
69 slope: 0,
70 n_samples: 4096,
71 }
72 }
73}
74
75pub struct OctaveBands {
90 pub impulse_responses: TimeSignal,
92 pub center_frequencies: Vec<f64>,
94}
95
96impl OctaveBands {
97 pub fn design(
104 num_fractions: usize,
105 config: &OctaveBandsConfig,
106 sample_rate: f64,
107 ) -> Result<Self, FilterError> {
108 let OctaveBandsConfig {
109 frequency_range,
110 overlap,
111 slope,
112 n_samples,
113 } = *config;
114
115 if !(0.0..=1.0).contains(&overlap) {
116 return Err(FilterError::InvalidOverlap(overlap));
117 }
118 if n_samples < 2 {
119 return Err(FilterError::InvalidNSamples);
120 }
121
122 let n_bins = n_samples / 2 + 1;
123 let (f_center, f_lower, f_upper) =
124 fractional_octave_frequencies(num_fractions, frequency_range);
125
126 let nyquist = sample_rate / 2.0;
128 let mut f_m = Vec::new();
129 let mut f_l = Vec::new();
130 let mut f_u = Vec::new();
131 for ((fc, fl), fu) in f_center.iter().zip(&f_lower).zip(&f_upper) {
132 if *fc < nyquist {
133 f_m.push(*fc);
134 f_l.push(*fl);
135 f_u.push(*fu);
136 }
137 }
138
139 let num_bands = f_m.len();
140 if num_bands == 0 {
141 return Err(FilterError::NoBandsInRange(
142 frequency_range.0,
143 frequency_range.1,
144 ));
145 }
146
147 let freq_to_bin = |f: f64| -> usize {
149 ((n_samples as f64 * f / sample_rate).round() as usize).min(n_bins - 1)
150 };
151
152 let k_1: Vec<usize> = f_l.iter().map(|&f| freq_to_bin(f)).collect(); let k_m: Vec<usize> = f_m.iter().map(|&f| freq_to_bin(f)).collect(); let k_2: Vec<usize> = f_u.iter().map(|&f| freq_to_bin(f)).collect(); let p: Vec<usize> = (0..num_bands)
159 .map(|b| ((overlap / 2.0) * (k_2[b] as f64 - k_m[b] as f64)).round() as usize)
160 .collect();
161
162 let mut g = Array2::<f64>::ones((num_bands, n_bins));
164
165 for b in 1..num_bands {
167 if p[b] > 0 {
168 let pb = p[b] as isize;
169 let k1b = k_1[b] as isize;
170
171 for pp in -pb..=pb {
172 let bin = k1b + pp;
173 if bin < 0 || bin as usize >= n_bins {
174 continue;
175 }
176 let idx = bin as usize;
177
178 let mut phi = pp as f64 / pb as f64;
180 for _ in 0..slope {
181 phi = (std::f64::consts::PI / 2.0 * phi).sin();
182 }
183 phi = 0.5 * (phi + 1.0); g[[b - 1, idx]] = (std::f64::consts::PI / 2.0 * phi).cos();
187 g[[b, idx]] = (std::f64::consts::PI / 2.0 * phi).sin();
188 }
189 }
190
191 let high = (k_1[b] + p[b]).min(n_bins - 1) + 1;
193 g.slice_mut(s![b - 1, high..]).fill(0.0);
194
195 let low = k_1[b].saturating_sub(p[b]);
196 g.slice_mut(s![b, ..low]).fill(0.0);
197 }
198
199 g.mapv_inplace(|v| v * v);
201
202 let g_complex = Array2::from_shape_fn((num_bands, n_bins), |(b, k)| {
205 let sign = if k % 2 == 0 { 1.0 } else { -1.0 };
206 Complex64::new(g[[b, k]] * sign, 0.0)
207 });
208
209 let fft_handler = R2cFftHandler::<f64>::new(n_samples);
211 let mut ir = Array2::<f64>::zeros((num_bands, n_samples));
212 ndifft_r2c(&g_complex.view(), &mut ir.view_mut(), &fft_handler, 1);
213
214 let window = generate_window(WindowFn::Hann, n_samples);
216 for b in 0..num_bands {
217 ir.slice_mut(s![b, ..])
218 .zip_mut_with(&window, |x, &w| *x *= w);
219 }
220
221 let impulse_responses = TimeSignal::new(ir, sample_rate)?;
222 Ok(Self {
223 impulse_responses,
224 center_frequencies: f_m,
225 })
226 }
227
228 pub fn apply(&self, signal: &TimeSignal) -> Result<Vec<TimeSignal>, FilterError> {
234 let n_sig = signal.num_time_steps();
235 let n_filt = self.impulse_responses.num_time_steps();
236 let n_out = n_sig + n_filt - 1;
237 let fft_size = n_out.next_power_of_two();
238 let n_freq = fft_size / 2 + 1;
239 let num_bands = self.impulse_responses.num_channels();
240 let num_ch = signal.num_channels();
241
242 let fft_handler = R2cFftHandler::<f64>::new(fft_size);
243
244 let mut filter_spectra = Array2::<Complex64>::zeros((num_bands, n_freq));
246 {
247 let mut padded = Array2::<f64>::zeros((num_bands, fft_size));
248 padded
249 .slice_mut(s![.., ..n_filt])
250 .assign(&self.impulse_responses.time_data());
251 ndfft_r2c(
252 &padded.view(),
253 &mut filter_spectra.view_mut(),
254 &fft_handler,
255 1,
256 );
257 }
258
259 let mut sig_spectra = Array2::<Complex64>::zeros((num_ch, n_freq));
261 {
262 let mut padded = Array2::<f64>::zeros((num_ch, fft_size));
263 padded
264 .slice_mut(s![.., ..n_sig])
265 .assign(&signal.time_data());
266 ndfft_r2c(&padded.view(), &mut sig_spectra.view_mut(), &fft_handler, 1);
267 }
268
269 let mut product = Array2::<Complex64>::zeros((num_ch, n_freq));
271 let mut ifft_buf = Array2::<f64>::zeros((num_ch, fft_size));
272
273 let mut bands = Vec::with_capacity(num_bands);
274 for b in 0..num_bands {
275 let h = filter_spectra.slice(s![b, ..]);
276 for ch in 0..num_ch {
277 for k in 0..n_freq {
278 product[[ch, k]] = sig_spectra[[ch, k]] * h[k];
279 }
280 }
281
282 ndifft_r2c(&product.view(), &mut ifft_buf.view_mut(), &fft_handler, 1);
283
284 let band_data = ifft_buf.slice(s![.., ..n_out]).to_owned();
285 bands.push(TimeSignal::new(band_data, signal.sample_rate())?);
286 }
287
288 Ok(bands)
289 }
290}
291
292pub fn reconstructing_fractional_octave_bands(
305 signal: &TimeSignal,
306 num_fractions: usize,
307 config: &OctaveBandsConfig,
308) -> Result<(Vec<TimeSignal>, Vec<f64>), FilterError> {
309 let fb = OctaveBands::design(num_fractions, config, signal.sample_rate())?;
310 let center_frequencies = fb.center_frequencies.clone();
311 let bands = fb.apply(signal)?;
312 Ok((bands, center_frequencies))
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use approx::assert_abs_diff_eq;
319
320 fn impulse(n: usize, sample_rate: f64) -> TimeSignal {
321 let mut data = Array2::<f64>::zeros((1, n));
322 data[[0, 0]] = 1.0;
323 TimeSignal::new(data, sample_rate).unwrap()
324 }
325
326 #[test]
327 fn fractional_octave_frequencies_octave() {
328 let (c, l, u) = fractional_octave_frequencies(1, (63.0, 16000.0));
329 assert!(!c.is_empty());
330 for &f in &c {
331 assert!((63.0 * (1.0 - 1e-5)..=16000.0 * (1.0 + 1e-5)).contains(&f));
332 }
333 for ((&fc, &fl), &fu) in c.iter().zip(&l).zip(&u) {
334 assert!(fl < fc && fc < fu);
335 }
336 }
337
338 #[test]
339 fn design_output_shape() {
340 let n_samples = 256;
341 let sample_rate = 44100.0;
342 let fb = OctaveBands::design(
343 1,
344 &OctaveBandsConfig {
345 frequency_range: (125.0, 8000.0),
346 n_samples,
347 ..Default::default()
348 },
349 sample_rate,
350 )
351 .unwrap();
352
353 let (c, _, _) = fractional_octave_frequencies(1, (125.0, 8000.0));
354 let expected_bands = c.iter().filter(|&&f| f < sample_rate / 2.0).count();
355
356 assert_eq!(fb.impulse_responses.num_channels(), expected_bands);
357 assert_eq!(fb.impulse_responses.num_time_steps(), n_samples);
358 assert_eq!(fb.center_frequencies.len(), expected_bands);
359 }
360
361 #[test]
362 fn reconstruction_impulse() {
363 let n = 512;
365 let sr = 44100.0;
366 let n_filt = 256_usize;
367
368 let x = impulse(n, sr);
369 let (bands, _) = reconstructing_fractional_octave_bands(
370 &x,
371 1,
372 &OctaveBandsConfig {
373 frequency_range: (125.0, 8000.0),
374 n_samples: n_filt,
375 ..Default::default()
376 },
377 )
378 .unwrap();
379
380 let n_out = n + n_filt - 1;
381 let mut sum = Array1::<f64>::zeros(n_out);
382 for band in &bands {
383 sum += &band.channel(0);
384 }
385
386 let peak_idx = sum
388 .iter()
389 .enumerate()
390 .max_by(|(_, a), (_, b)| a.abs().partial_cmp(&b.abs()).unwrap())
391 .map(|(i, _)| i)
392 .unwrap();
393
394 assert_eq!(peak_idx, n_filt / 2);
395 assert_abs_diff_eq!(sum[peak_idx], 1.0, epsilon = 0.05);
396 }
397}