1use ferraus_core::resample::{resample, Quality};
27use ferraus_core::{AudioBuffer, Error, OpCtx, Result};
28use ferraus_ml::session::{load_for_hinted, ModelSession, TensorF32};
29use realfft::num_complex::Complex32;
30use realfft::{ComplexToReal, RealFftPlanner, RealToComplex};
31use std::path::Path;
32use std::sync::Arc;
33
34pub const MODEL: &str = "denoise/dfn3";
36pub const MEMBERS: [&str; 4] = ["enc.onnx", "erb_dec.onnx", "df_dec.onnx", "config.ini"];
38
39const MEAN_NORM_INIT: [f32; 2] = [-60.0, -90.0];
40const UNIT_NORM_INIT: [f32; 2] = [0.001, 0.0001];
41
42#[derive(Clone, Debug, PartialEq)]
44pub struct Params {
45 pub sr: usize,
46 pub fft_size: usize,
47 pub hop_size: usize,
48 pub nb_erb: usize,
49 pub nb_df: usize,
50 pub df_order: usize,
51 pub df_lookahead: usize,
52 pub conv_lookahead: usize,
53 pub min_nb_erb_freqs: usize,
54 pub norm_tau: f64,
55}
56
57impl Default for Params {
58 fn default() -> Self {
60 Params {
61 sr: 48_000,
62 fft_size: 960,
63 hop_size: 480,
64 nb_erb: 32,
65 nb_df: 96,
66 df_order: 5,
67 df_lookahead: 2,
68 conv_lookahead: 2,
69 min_nb_erb_freqs: 2,
70 norm_tau: 1.0,
71 }
72 }
73}
74
75impl Params {
76 pub fn from_ini(text: &str) -> Result<Params> {
78 let mut p = Params::default();
79 let mut section = String::new();
80 for line in text.lines() {
81 let line = line.trim();
82 if line.starts_with('[') && line.ends_with(']') {
83 section = line[1..line.len() - 1].trim().to_ascii_lowercase();
84 continue;
85 }
86 let Some((k, v)) = line.split_once('=') else { continue };
87 let (k, v) = (k.trim(), v.trim());
88 let num = || v.parse::<usize>().map_err(|_| Error::processing(format!("DFN3 config.ini: bad {k} = {v}")));
89 match (section.as_str(), k) {
90 ("df", "sr") => p.sr = num()?,
91 ("df", "fft_size") => p.fft_size = num()?,
92 ("df", "hop_size") => p.hop_size = num()?,
93 ("df", "nb_erb") => p.nb_erb = num()?,
94 ("df", "nb_df") => p.nb_df = num()?,
95 ("df", "df_order") => p.df_order = num()?,
96 ("df", "df_lookahead") => p.df_lookahead = num()?,
97 ("df", "min_nb_erb_freqs") => p.min_nb_erb_freqs = num()?,
98 ("df", "norm_tau") => {
99 p.norm_tau =
100 v.parse().map_err(|_| Error::processing(format!("DFN3 config.ini: bad norm_tau = {v}")))?
101 }
102 ("deepfilternet", "conv_lookahead") => p.conv_lookahead = num()?,
103 _ => {}
104 }
105 }
106 if p.hop_size * 2 != p.fft_size || p.nb_df > p.fft_size / 2 + 1 || p.df_lookahead >= p.df_order || p.nb_erb < 2
107 {
108 return Err(Error::unsupported(format!("DFN3 config.ini describes an unsupported layout: {p:?}")));
109 }
110 Ok(p)
111 }
112
113 pub fn alpha(&self) -> f32 {
115 let a = (-(self.hop_size as f64 / self.sr as f64) / self.norm_tau).exp();
116 let mut precision = 3;
117 loop {
118 let p = 10f64.powi(precision);
119 let r = (a * p).round() / p;
120 if r < 1.0 {
121 return r as f32;
122 }
123 precision += 1;
124 }
125 }
126
127 pub fn freq_bins(&self) -> usize {
128 self.fft_size / 2 + 1
129 }
130}
131
132fn freq2erb(f: f32) -> f32 {
133 9.265 * (f / (24.7 * 9.265)).ln_1p()
134}
135
136fn erb2freq(e: f32) -> f32 {
137 24.7 * 9.265 * ((e / 9.265).exp() - 1.0)
138}
139
140pub fn erb_widths(sr: usize, fft_size: usize, nb_bands: usize, min_nb_freqs: usize) -> Vec<usize> {
143 let nyq = (sr / 2) as f32;
144 let freq_width = sr as f32 / fft_size as f32;
145 let erb_low = freq2erb(0.0);
146 let erb_high = freq2erb(nyq);
147 let step = (erb_high - erb_low) / nb_bands as f32;
148 let min = min_nb_freqs as i32;
149 let mut erb = vec![0usize; nb_bands];
150 let (mut prev, mut over) = (0i32, 0i32);
151 for i in 1..=nb_bands {
152 let f = erb2freq(erb_low + i as f32 * step);
153 let fb = (f / freq_width).round() as i32;
154 let mut nb = fb - prev - over;
155 if nb < min {
156 over = min - nb;
157 nb = min;
158 } else {
159 over = 0;
160 }
161 erb[i - 1] = nb as usize;
162 prev = fb;
163 }
164 erb[nb_bands - 1] += 1;
165 let total: usize = erb.iter().sum();
166 let bins = fft_size / 2 + 1;
167 if total > bins {
168 erb[nb_bands - 1] -= total - bins;
169 }
170 erb
171}
172
173pub fn vorbis_window(n: usize) -> Vec<f32> {
175 let half = (n / 2) as f64;
176 (0..n)
177 .map(|i| {
178 let s = (0.5 * std::f64::consts::PI * (i as f64 + 0.5) / half).sin();
179 (0.5 * std::f64::consts::PI * s * s).sin() as f32
180 })
181 .collect()
182}
183
184pub struct Stft {
186 fwd: Arc<dyn RealToComplex<f32>>,
187 inv: Arc<dyn ComplexToReal<f32>>,
188 window: Vec<f32>,
189 wnorm: f32,
190 hop: usize,
191}
192
193impl Stft {
194 pub fn new(fft_size: usize, hop: usize) -> Stft {
195 let mut planner = RealFftPlanner::<f32>::new();
196 Stft {
197 fwd: planner.plan_fft_forward(fft_size),
198 inv: planner.plan_fft_inverse(fft_size),
199 window: vorbis_window(fft_size),
200 wnorm: 1.0 / ((fft_size * fft_size) as f32 / (2 * hop) as f32),
201 hop,
202 }
203 }
204
205 pub fn analysis(&self, x: &[f32]) -> Vec<Vec<Complex32>> {
207 let n = self.window.len();
208 let frames = x.len() / self.hop;
209 let mut buf = self.fwd.make_input_vec();
210 let mut scratch = self.fwd.make_scratch_vec();
211 let mut out = Vec::with_capacity(frames);
212 for t in 0..frames {
213 for (i, b) in buf.iter_mut().enumerate() {
214 let idx = (t * self.hop + i) as isize - (n - self.hop) as isize;
215 let v = if idx >= 0 { x[idx as usize] } else { 0.0 };
216 *b = v * self.window[i];
217 }
218 let mut spec = self.fwd.make_output_vec();
219 let _ = self.fwd.process_with_scratch(&mut buf, &mut spec, &mut scratch);
221 for s in spec.iter_mut() {
222 *s *= self.wnorm;
223 }
224 out.push(spec);
225 }
226 out
227 }
228
229 pub fn synthesis(&self, spec: &mut [Vec<Complex32>]) -> Vec<f32> {
231 let n = self.window.len();
232 let mut out = vec![0f32; spec.len() * self.hop];
233 let mut mem = vec![0f32; n - self.hop];
234 let mut buf = self.inv.make_output_vec();
235 let mut scratch = self.inv.make_scratch_vec();
236 for (t, s) in spec.iter_mut().enumerate() {
237 s[0].im = 0.0;
239 if let Some(last) = s.last_mut() {
240 last.im = 0.0;
241 }
242 let _ = self.inv.process_with_scratch(s, &mut buf, &mut scratch);
243 for (b, w) in buf.iter_mut().zip(&self.window) {
244 *b *= w;
245 }
246 for i in 0..self.hop {
247 out[t * self.hop + i] = buf[i] + mem[i];
248 }
249 mem.copy_from_slice(&buf[self.hop..]);
250 }
251 out
252 }
253}
254
255pub struct Features {
258 pub frames: usize,
259 pub erb: Vec<f32>,
260 pub spec: Vec<f32>,
261}
262
263pub fn features(spec: &[Vec<Complex32>], erb: &[usize], p: &Params) -> Features {
266 let t_n = spec.len();
267 let (ne, nd) = (p.nb_erb, p.nb_df);
268 let alpha = p.alpha();
269 let lin = |a: f32, b: f32, n: usize| -> Vec<f32> {
270 (0..n).map(|i| if n > 1 { a + (b - a) * i as f32 / (n - 1) as f32 } else { a }).collect()
271 };
272 let mut mean_state = lin(MEAN_NORM_INIT[0], MEAN_NORM_INIT[1], ne);
273 let mut unit_state = lin(UNIT_NORM_INIT[0], UNIT_NORM_INIT[1], nd);
274 let mut feat_erb = vec![0f32; t_n * ne];
275 let mut feat_spec = vec![0f32; 2 * t_n * nd];
276 let shift = p.conv_lookahead;
277 for (t, x) in spec.iter().enumerate() {
278 let dst = t.checked_sub(shift);
280 let mut start = 0;
281 for (b, (&w, s)) in erb.iter().zip(mean_state.iter_mut()).enumerate() {
282 let pow: f32 = x[start..start + w].iter().map(|c| c.norm_sqr()).sum::<f32>() / w as f32;
283 start += w;
284 let db = (pow + 1e-10).log10() * 10.0;
285 *s = db * (1.0 - alpha) + *s * alpha;
286 if let Some(d) = dst {
287 feat_erb[d * ne + b] = (db - *s) / 40.0;
288 }
289 }
290 for (f, s) in unit_state.iter_mut().enumerate() {
291 *s = x[f].norm() * (1.0 - alpha) + *s * alpha;
292 if let Some(d) = dst {
293 let v = x[f] / s.sqrt();
294 feat_spec[d * nd + f] = v.re;
295 feat_spec[t_n * nd + d * nd + f] = v.im;
296 }
297 }
298 }
299 Features { frames: t_n, erb: feat_erb, spec: feat_spec }
300}
301
302pub fn apply(spec: &[Vec<Complex32>], gains: &[f32], coefs: &[f32], erb: &[usize], p: &Params) -> Vec<Vec<Complex32>> {
305 let (ne, nd, order) = (p.nb_erb, p.nb_df, p.df_order);
306 let past = (order - 1 - p.df_lookahead) as isize;
307 let t_n = spec.len();
308 let mut out = Vec::with_capacity(t_n);
309 for t in 0..t_n {
310 let mut y = spec[t].clone();
311 let mut start = 0;
312 for (b, &w) in erb.iter().enumerate() {
313 let g = gains[t * ne + b];
314 for v in &mut y[start..start + w] {
315 *v *= g;
316 }
317 start += w;
318 }
319 let c = &coefs[t * nd * order * 2..(t + 1) * nd * order * 2];
320 for f in 0..nd {
321 let mut acc = Complex32::new(0.0, 0.0);
322 for n in 0..order {
323 let tt = t as isize - past + n as isize;
324 if tt < 0 || tt as usize >= t_n {
325 continue;
326 }
327 let k = (f * order + n) * 2;
328 acc += spec[tt as usize][f] * Complex32::new(c[k], c[k + 1]);
329 }
330 y[f] = acc;
331 }
332 out.push(y);
333 }
334 out
335}
336
337pub struct Dfn3 {
339 pub params: Params,
340 erb: Vec<usize>,
341 stft: Stft,
342 enc: Arc<ModelSession>,
343 erb_dec: Arc<ModelSession>,
344 df_dec: Arc<ModelSession>,
345 pub segment: usize,
347 warmup: usize,
348 crossfade: usize,
349}
350
351const SEGMENT: usize = 3000;
353const WARMUP: usize = 100;
354const CROSSFADE: usize = 50;
355const TAIL: usize = 8;
357
358impl Dfn3 {
359 pub fn load(ctx: &mut OpCtx) -> Result<Dfn3> {
362 let m = ferraus_models::ensure_model(ctx, MODEL)?;
363 let dir = m.path.with_file_name("extracted");
364 let paths = ferraus_ml::archive::extract_members(&m.path, &dir, &MEMBERS)?;
365 let hints = m.spec.provider_hints.clone();
366 let params = Params::from_ini(&std::fs::read_to_string(&paths[3])?)?;
367 let enc = load_for_hinted(ctx, &paths[0], &hints)?;
368 let erb_dec = load_for_hinted(ctx, &paths[1], &hints)?;
369 let df_dec = load_for_hinted(ctx, &paths[2], &hints)?;
370 Ok(Dfn3::new(params, enc, erb_dec, df_dec))
371 }
372
373 pub fn load_dir(dir: &Path, opts: &ferraus_ml::SessionOptions) -> Result<Dfn3> {
375 let params = Params::from_ini(&std::fs::read_to_string(dir.join("config.ini"))?)?;
376 let enc = ferraus_ml::load(&dir.join("enc.onnx"), opts)?.0;
377 let erb_dec = ferraus_ml::load(&dir.join("erb_dec.onnx"), opts)?.0;
378 let df_dec = ferraus_ml::load(&dir.join("df_dec.onnx"), opts)?.0;
379 Ok(Dfn3::new(params, enc, erb_dec, df_dec))
380 }
381
382 fn new(params: Params, enc: Arc<ModelSession>, erb_dec: Arc<ModelSession>, df_dec: Arc<ModelSession>) -> Dfn3 {
383 let erb = erb_widths(params.sr, params.fft_size, params.nb_erb, params.min_nb_erb_freqs);
384 let stft = Stft::new(params.fft_size, params.hop_size);
385 Dfn3 { params, erb, stft, enc, erb_dec, df_dec, segment: SEGMENT, warmup: WARMUP, crossfade: CROSSFADE }
386 }
387
388 fn infer(&self, feat: &Features, a: usize, b: usize) -> Result<(Vec<f32>, Vec<f32>)> {
391 let (ne, nd) = (self.params.nb_erb, self.params.nb_df);
392 let s = b - a;
393 let erb = feat.erb[a * ne..b * ne].to_vec();
394 let mut spec = Vec::with_capacity(2 * s * nd);
395 for ch in 0..2 {
396 let off = ch * feat.frames * nd;
397 spec.extend_from_slice(&feat.spec[off + a * nd..off + b * nd]);
398 }
399 let enc = self.enc.run(vec![
400 TensorF32::new("feat_erb", vec![1, 1, s as i64, ne as i64], erb),
401 TensorF32::new("feat_spec", vec![1, 2, s as i64, nd as i64], spec),
402 ])?;
403 let get = |name: &str| -> Result<TensorF32> {
404 enc.iter()
405 .find(|t| t.name == name)
406 .cloned()
407 .ok_or_else(|| Error::processing(format!("DFN3 encoder produced no '{name}' output")))
408 };
409 let m = self.erb_dec.run(vec![get("emb")?, get("e3")?, get("e2")?, get("e1")?, get("e0")?])?;
410 let c = self.df_dec.run(vec![get("emb")?, get("c0")?])?;
411 let gains = m.into_iter().next().ok_or_else(|| Error::processing("DFN3 ERB decoder has no output"))?;
412 let coefs = c
413 .into_iter()
414 .find(|t| t.name == "coefs")
415 .ok_or_else(|| Error::processing("DFN3 DF decoder produced no 'coefs' output"))?;
416 let want_c = s * nd * self.params.df_order * 2;
417 if gains.data.len() != s * ne || coefs.data.len() != want_c {
418 return Err(Error::processing(format!(
419 "DFN3 decoders returned shapes {:?} and {:?} for {s} frames",
420 gains.shape, coefs.shape
421 )));
422 }
423 Ok((gains.data, coefs.data))
424 }
425
426 fn infer_all(&self, feat: &Features, tick: &mut dyn FnMut(f64) -> Result<()>) -> Result<(Vec<f32>, Vec<f32>)> {
428 let t_n = feat.frames;
429 let (ne, nc) = (self.params.nb_erb, self.params.nb_df * self.params.df_order * 2);
430 if t_n <= self.segment {
431 let r = self.infer(feat, 0, t_n)?;
432 tick(1.0)?;
433 return Ok(r);
434 }
435 let mut gains = vec![0f32; t_n * ne];
436 let mut coefs = vec![0f32; t_n * nc];
437 let mut wsum = vec![0f32; t_n];
438 let step = self.segment - self.crossfade;
439 let mut start = 0;
440 while start < t_n {
441 let end = (start + self.segment).min(t_n);
442 let a = start.saturating_sub(self.warmup);
443 let b = (end + TAIL).min(t_n);
444 let (g, c) = self.infer(feat, a, b)?;
445 for t in start..end {
446 let mut w = 1.0f32;
448 if start > 0 && t < start + self.crossfade {
449 w = (t - start) as f32 / self.crossfade as f32 + 0.5 / self.crossfade as f32;
450 }
451 if end < t_n && t >= end - self.crossfade {
452 w = w.min((end - t) as f32 / self.crossfade as f32 - 0.5 / self.crossfade as f32);
453 }
454 let lt = t - a;
455 wsum[t] += w;
456 for (o, v) in gains[t * ne..(t + 1) * ne].iter_mut().zip(&g[lt * ne..(lt + 1) * ne]) {
457 *o += w * v;
458 }
459 for (o, v) in coefs[t * nc..(t + 1) * nc].iter_mut().zip(&c[lt * nc..(lt + 1) * nc]) {
460 *o += w * v;
461 }
462 }
463 tick(end as f64 / t_n as f64)?;
464 if end == t_n {
465 break;
466 }
467 start += step;
468 }
469 for t in 0..t_n {
470 let w = wsum[t].max(1e-6);
471 gains[t * ne..(t + 1) * ne].iter_mut().for_each(|v| *v /= w);
472 coefs[t * nc..(t + 1) * nc].iter_mut().for_each(|v| *v /= w);
473 }
474 Ok((gains, coefs))
475 }
476
477 pub fn enhance_channel(&self, x: &[f32], tick: &mut dyn FnMut(f64) -> Result<()>) -> Result<Vec<f32>> {
479 if x.is_empty() {
480 return Ok(Vec::new());
481 }
482 let p = &self.params;
483 let mut padded = x.to_vec();
484 padded.resize(x.len() + p.fft_size, 0.0);
485 let spec = self.stft.analysis(&padded);
486 let feat = features(&spec, &self.erb, p);
487 let (gains, coefs) = self.infer_all(&feat, tick)?;
488 let mut enhanced = apply(&spec, &gains, &coefs, &self.erb, p);
489 let y = self.stft.synthesis(&mut enhanced);
490 let d = p.fft_size - p.hop_size;
491 Ok((0..x.len()).map(|i| y.get(i + d).copied().unwrap_or(0.0)).collect())
492 }
493
494 pub fn enhance(&self, buf: &AudioBuffer, tick: &mut dyn FnMut(f64) -> Result<()>) -> Result<AudioBuffer> {
496 let rate = self.params.sr as u32;
497 let x = resample(buf, rate, Quality::High);
498 let channels = x.channels().max(1);
499 let mut data = Vec::with_capacity(x.channels());
500 for (c, ch) in x.data.iter().enumerate() {
501 let mut sub = |f: f64| tick((c as f64 + f) / channels as f64);
502 data.push(self.enhance_channel(ch, &mut sub)?);
503 }
504 let y = AudioBuffer::from_planar(rate, data);
505 let mut y = resample(&y, buf.sample_rate, Quality::High);
506 for c in &mut y.data {
507 c.resize(buf.frames(), 0.0);
508 }
509 Ok(y)
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516
517 #[test]
518 fn erb_bands_match_libdf() {
519 let p = Params::default();
520 let e = erb_widths(p.sr, p.fft_size, p.nb_erb, p.min_nb_erb_freqs);
521 assert_eq!(e.len(), 32);
522 assert_eq!(e.iter().sum::<usize>(), 481);
523 assert!(e.iter().all(|w| *w >= 2));
524 assert_eq!(&e[..4], &[2, 2, 2, 2]);
526 assert!(e[31] > 30, "{e:?}");
527 assert!(e.windows(2).all(|w| w[1] >= w[0]), "{e:?}");
528 }
529
530 #[test]
531 fn alpha_is_rounded_like_the_reference() {
532 assert_eq!(Params::default().alpha(), 0.99);
533 }
534
535 #[test]
536 fn config_ini_parses() {
537 let ini = "[train]\nseed = 43\n[df]\nsr = 48000\nfft_size = 960\nhop_size = 480\nnb_erb = 32\nnb_df = 96\nnorm_tau = 1\ndf_order = 5\ndf_lookahead = 2\n[deepfilternet]\nconv_lookahead = 2\n";
538 assert_eq!(Params::from_ini(ini).unwrap(), Params::default());
539 assert!(Params::from_ini("[df]\nhop_size = 240\n").is_err());
540 assert!(Params::from_ini("[df]\nnb_df = x\n").is_err());
541 }
542
543 #[test]
544 fn stft_reconstructs_perfectly() {
545 let p = Params::default();
546 let stft = Stft::new(p.fft_size, p.hop_size);
547 let x: Vec<f32> =
548 (0..48_000).map(|i| ((i as f32) * 0.031).sin() * 0.5 + ((i * 7 % 11) as f32 - 5.0) * 0.01).collect();
549 let mut padded = x.clone();
550 padded.resize(x.len() + p.fft_size, 0.0);
551 let mut spec = stft.analysis(&padded);
552 let y = stft.synthesis(&mut spec);
553 let d = p.fft_size - p.hop_size;
554 let err = x.iter().enumerate().map(|(i, v)| (v - y[i + d]).abs()).fold(0f32, f32::max);
555 assert!(err < 1e-5, "max reconstruction error {err}");
556 let f = 100.0 * 48_000.0 / 960.0;
558 let s: Vec<f32> =
559 (0..9600).map(|i| (2.0 * std::f64::consts::PI * f * i as f64 / 48_000.0).sin() as f32).collect();
560 let sp = stft.analysis(&s);
561 let mag = sp[5][100].norm();
562 let wsum: f32 = vorbis_window(960).iter().sum();
563 assert!((mag - 0.5 * wsum / 960.0).abs() < 1e-3, "{mag}");
564 }
565
566 #[test]
567 fn unity_gains_and_identity_filter_return_the_input() {
568 let p = Params::default();
569 let erb = erb_widths(p.sr, p.fft_size, p.nb_erb, p.min_nb_erb_freqs);
570 let stft = Stft::new(p.fft_size, p.hop_size);
571 let x: Vec<f32> = (0..9600).map(|i| ((i as f32) * 0.05).sin()).collect();
572 let spec = stft.analysis(&x);
573 let t = spec.len();
574 let gains = vec![1f32; t * p.nb_erb];
575 let mut coefs = vec![0f32; t * p.nb_df * p.df_order * 2];
577 let cur = p.df_order - 1 - p.df_lookahead;
578 for tt in 0..t {
579 for f in 0..p.nb_df {
580 coefs[((tt * p.nb_df + f) * p.df_order + cur) * 2] = 1.0;
581 }
582 }
583 let y = apply(&spec, &gains, &coefs, &erb, &p);
584 for (a, b) in y.iter().zip(&spec) {
585 for (u, v) in a.iter().zip(b) {
586 assert!((u - v).norm() < 1e-6);
587 }
588 }
589 let mut delay = vec![0f32; coefs.len()];
591 for tt in 0..t {
592 for f in 0..p.nb_df {
593 delay[((tt * p.nb_df + f) * p.df_order + cur - 1) * 2] = 1.0;
594 }
595 }
596 let y = apply(&spec, &vec![0.5; t * p.nb_erb], &delay, &erb, &p);
597 assert!((y[10][5] - spec[9][5]).norm() < 1e-6);
598 assert!((y[10][200] - spec[10][200] * 0.5).norm() < 1e-6);
599 }
600
601 #[test]
602 fn features_follow_the_reference_normalization() {
603 let p = Params::default();
604 let erb = erb_widths(p.sr, p.fft_size, p.nb_erb, p.min_nb_erb_freqs);
605 let spec = vec![vec![Complex32::new(0.01, 0.0); 481]; 2000];
607 let f = features(&spec, &erb, &p);
608 assert_eq!(f.erb.len(), 2000 * 32);
609 let late = &f.erb[1900 * 32..1901 * 32];
610 assert!(late.iter().all(|v| v.abs() < 1e-3), "{late:?}");
611 let db = (1e-4f32 + 1e-10).log10() * 10.0;
613 let s0 = -60.0f32;
614 let s1 = db * 0.01 + s0 * 0.99;
615 let s2 = db * 0.01 + s1 * 0.99;
616 let s3 = db * 0.01 + s2 * 0.99;
617 assert!((f.erb[0] - (db - s3) / 40.0).abs() < 1e-4, "{} vs {}", f.erb[0], (db - s3) / 40.0);
618 assert!(f.erb[1998 * 32..].iter().all(|v| *v == 0.0));
620 let re = f.spec[1900 * 96];
622 assert!((re - 0.01 / 0.01f32.sqrt()).abs() < 1e-3, "{re}");
623 assert_eq!(f.spec[2000 * 96 + 1900 * 96], 0.0);
624 }
625}