1use std::fmt;
11
12#[derive(Clone, Debug, PartialEq)]
14pub struct Wav {
15 pub sample_rate: u32,
16 pub channels: Vec<Vec<f32>>,
17 pub encoding: Encoding,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum Encoding {
24 Pcm(u16),
26 Float(u16),
28}
29
30impl Wav {
31 pub fn new(sample_rate: u32, channels: Vec<Vec<f32>>) -> Wav {
33 Wav { sample_rate, channels, encoding: Encoding::Float(32) }
34 }
35}
36
37#[derive(Debug, PartialEq, Eq)]
38pub enum WavError {
39 Format(String),
41 Unsupported(String),
43}
44
45impl fmt::Display for WavError {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
48 WavError::Format(s) => write!(f, "invalid WAV file: {s}"),
49 WavError::Unsupported(s) => write!(f, "unsupported WAV encoding: {s}"),
50 }
51 }
52}
53
54impl std::error::Error for WavError {}
55
56const PCM: u16 = 1;
57const FLOAT: u16 = 3;
58const EXTENSIBLE: u16 = 0xFFFE;
59
60fn u16_at(b: &[u8], i: usize) -> u16 {
61 u16::from_le_bytes([b[i], b[i + 1]])
62}
63
64fn u32_at(b: &[u8], i: usize) -> u32 {
65 u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]])
66}
67
68impl Wav {
69 pub fn parse(bytes: &[u8]) -> Result<Wav, WavError> {
72 let bad = |s: &str| WavError::Format(s.to_owned());
73 if bytes.len() < 12 || &bytes[0..4] != b"RIFF" || &bytes[8..12] != b"WAVE" {
74 return Err(bad("no RIFF/WAVE header"));
75 }
76 let (mut fmt, mut data) = (None, None);
77 let mut i = 12;
78 while i + 8 <= bytes.len() {
79 let (id, len) = (&bytes[i..i + 4], u32_at(bytes, i + 4) as usize);
80 let body = &bytes[i + 8..(i + 8 + len).min(bytes.len())];
81 match id {
82 b"fmt " => fmt = Some(body),
83 b"data" => data = Some(body),
85 _ => {}
86 }
87 i += 8 + len + (len & 1);
88 }
89 let fmt = fmt.ok_or_else(|| bad("no fmt chunk"))?;
90 let data = data.ok_or_else(|| bad("no data chunk"))?;
91 if fmt.len() < 16 {
92 return Err(bad("short fmt chunk"));
93 }
94 let mut tag = u16_at(fmt, 0);
95 let channels = u16_at(fmt, 2) as usize;
96 let sample_rate = u32_at(fmt, 4);
97 let bits = u16_at(fmt, 14) as usize;
98 if tag == EXTENSIBLE {
99 if fmt.len() < 26 {
100 return Err(bad("short extensible fmt chunk"));
101 }
102 tag = u16_at(fmt, 24); }
104 if channels == 0 || sample_rate == 0 {
105 return Err(bad("no channels or no sample rate"));
106 }
107 let width = bits.div_ceil(8);
108 let decode: fn(&[u8]) -> f32 = match (tag, width) {
109 (PCM, 1) => |s| (s[0] as f32 - 128.0) / 128.0,
110 (PCM, 2) => |s| i16::from_le_bytes([s[0], s[1]]) as f32 / 32768.0,
111 (PCM, 3) => |s| (i32::from_le_bytes([0, s[0], s[1], s[2]]) >> 8) as f32 / 8_388_608.0,
112 (PCM, 4) => |s| (i32::from_le_bytes([s[0], s[1], s[2], s[3]]) as f64 / 2_147_483_648.0) as f32,
113 (FLOAT, 4) => |s| f32::from_le_bytes([s[0], s[1], s[2], s[3]]),
114 (FLOAT, 8) => |s| f64::from_le_bytes(s.try_into().expect("8 bytes")) as f32,
115 _ => return Err(WavError::Unsupported(format!("format {tag:#06x}, {bits} bits"))),
116 };
117 let frame = width * channels;
118 let mut out = vec![Vec::with_capacity(data.len() / frame); channels];
119 for f in data.chunks_exact(frame) {
120 for (c, s) in f.chunks_exact(width).enumerate() {
121 out[c].push(decode(s));
122 }
123 }
124 let encoding = if tag == FLOAT { Encoding::Float(bits as u16) } else { Encoding::Pcm(bits as u16) };
125 Ok(Wav { sample_rate, channels: out, encoding })
126 }
127
128 pub fn load(path: impl AsRef<std::path::Path>) -> Result<Wav, Box<dyn std::error::Error + Send + Sync>> {
129 Ok(Wav::parse(&std::fs::read(path)?)?)
130 }
131}
132
133impl Wav {
134 pub fn to_bytes(&self, bits: u16, float: bool) -> Vec<u8> {
136 assert!(matches!((bits, float), (16 | 24 | 32, false) | (32, true)), "unsupported WAV encoding");
137 let ch = self.channels.len() as u16;
138 let frames = self.channels.first().map_or(0, Vec::len);
139 let width = bits as usize / 8;
140 let mut data = Vec::with_capacity(frames * ch as usize * width);
141 for i in 0..frames {
142 for c in &self.channels {
143 let v = c.get(i).copied().unwrap_or(0.0);
144 if float {
145 data.extend_from_slice(&v.to_le_bytes());
146 } else {
147 let full = (1i64 << (bits - 1)) as f64;
148 let q = (v as f64 * full).round().clamp(-full, full - 1.0) as i64;
149 data.extend_from_slice(&q.to_le_bytes()[..width]);
150 }
151 }
152 }
153 let mut b = Vec::with_capacity(44 + data.len());
154 b.extend_from_slice(b"RIFF");
155 b.extend_from_slice(&(36 + data.len() as u32).to_le_bytes());
156 b.extend_from_slice(b"WAVEfmt ");
157 b.extend_from_slice(&16u32.to_le_bytes());
158 b.extend_from_slice(&(if float { FLOAT } else { PCM }).to_le_bytes());
159 b.extend_from_slice(&ch.to_le_bytes());
160 b.extend_from_slice(&self.sample_rate.to_le_bytes());
161 b.extend_from_slice(&(self.sample_rate * ch as u32 * width as u32).to_le_bytes());
162 b.extend_from_slice(&(ch * width as u16).to_le_bytes());
163 b.extend_from_slice(&bits.to_le_bytes());
164 b.extend_from_slice(b"data");
165 b.extend_from_slice(&(data.len() as u32).to_le_bytes());
166 b.extend_from_slice(&data);
167 b
168 }
169}
170
171fn bessel_i0(x: f64) -> f64 {
176 let (mut sum, mut term, mut k) = (1.0, 1.0, 1.0);
177 while term > 1e-12 * sum {
178 term *= (x / (2.0 * k)).powi(2);
179 sum += term;
180 k += 1.0;
181 }
182 sum
183}
184
185pub fn resample(x: &[f32], from: u32, to: u32) -> Vec<f32> {
189 if from == to || x.is_empty() {
190 return x.to_vec();
191 }
192 const ZEROS: f64 = 32.0;
193 const BETA: f64 = 9.0;
194 let ratio = to as f64 / from as f64;
195 let fc = 0.5 * ratio.min(1.0) * 0.95;
197 let half = ZEROS / (2.0 * fc); let i0b = bessel_i0(BETA);
199 let len = (x.len() as u64 * to as u64).div_ceil(from as u64) as usize;
200 (0..len)
201 .map(|n| {
202 let t = n as f64 / ratio;
203 let (lo, hi) = (((t - half).ceil().max(0.0)) as usize, ((t + half).floor() as usize).min(x.len() - 1));
204 let mut acc = 0.0;
205 for (k, &v) in x.iter().enumerate().take(hi + 1).skip(lo) {
206 let d = k as f64 - t;
207 let u = d / half;
208 let w = bessel_i0(BETA * (1.0 - u * u).max(0.0).sqrt()) / i0b;
209 let a = std::f64::consts::PI * 2.0 * fc * d;
210 let sinc = if a.abs() < 1e-12 { 1.0 } else { a.sin() / a };
211 acc += v as f64 * 2.0 * fc * sinc * w;
212 }
213 acc as f32
214 })
215 .collect()
216}
217
218pub const IR_TAPS: usize = 2048;
223pub const PEDAL_IR_TAPS: usize = 384;
226pub const IR_SAMPLE_RATE: u32 = 48000;
228
229#[derive(Clone, Debug)]
231pub struct ConvertedIr {
232 pub data: Vec<f32>,
234 pub gain: f32,
236 pub ir: Vec<f32>,
238}
239
240fn juce_low_pass(ratio: f64) -> [f64; 5] {
243 let rate = if ratio > 1.0 { 0.5 / ratio } else { 0.5 * ratio };
244 let n = 1.0 / (std::f64::consts::PI * rate.max(0.001)).tan();
245 let n2 = n * n;
246 let c1 = 1.0 / (1.0 + std::f64::consts::SQRT_2 * n + n2);
247 [c1, c1 * 2.0, c1, c1 * 2.0 * (1.0 - n2), c1 * (1.0 - std::f64::consts::SQRT_2 * n + n2)]
248}
249
250fn juce_filter(x: &mut [f32], c: &[f64; 5]) {
252 let (mut x1, mut x2, mut y1, mut y2) = (0.0f64, 0.0f64, 0.0f64, 0.0f64);
253 for v in x {
254 let i = *v as f64;
255 let o = c[0] * i + c[1] * x1 + c[2] * x2 - c[3] * y1 - c[4] * y2;
256 (x2, x1, y2, y1) = (x1, i, y1, o);
257 *v = o as f32;
258 }
259}
260
261pub fn resample_like_app(x: &[f32], from: u32, to: u32) -> Vec<f32> {
265 let ratio = from as f64 / to as f64; let out_len = (x.len() as f64 / ratio) as usize;
267 let filter = juce_low_pass(ratio);
268 let mut src: Vec<f32> = x.to_vec();
270 src.resize(x.len().max((out_len as f64 * ratio) as usize + 4), 0.0);
271 if ratio > 1.0001 {
272 juce_filter(&mut src, &filter);
273 }
274 let (mut pos, mut sub) = (0usize, 0.0f64);
275 let mut out = Vec::with_capacity(out_len);
276 for _ in 0..out_len {
277 let alpha = sub as f32;
278 let (a, b) = (src.get(pos).copied().unwrap_or(0.0), src.get(pos + 1).copied().unwrap_or(0.0));
279 out.push(a + alpha * (b - a));
280 sub += ratio;
281 while sub >= 1.0 {
282 pos += 1;
283 sub -= 1.0;
284 }
285 }
286 if ratio < 0.9999 {
287 juce_filter(&mut out, &filter);
288 }
289 out
290}
291
292fn via_24_bit(x: &mut [f32]) {
294 for v in x {
295 let q = ((*v as f64).clamp(-1.0, 1.0) * 8_388_607.0).round_ties_even().clamp(-8_388_608.0, 8_388_607.0);
296 *v = (q / 8_388_608.0) as f32;
297 }
298}
299
300const GAIN_FFT: usize = 16384;
302const GAIN_BINS: std::ops::Range<usize> = 136..1024;
303
304pub fn app_gain(head: &[f32]) -> f64 {
307 let n = GAIN_FFT as f64;
308 let (mut lo, mut hi) = (f64::MAX, 0f64);
310 for k in GAIN_BINS {
311 let w = std::f64::consts::TAU * k as f64 / n;
312 let (c, s) = (w.cos(), w.sin());
313 let (mut re, mut im, mut cr, mut ci) = (0.0, 0.0, 1.0f64, 0.0f64);
314 for &v in head.iter().take(IR_TAPS) {
315 re += v as f64 * cr;
316 im -= v as f64 * ci;
317 (cr, ci) = (cr * c - ci * s, cr * s + ci * c);
318 }
319 let p = re * re + im * im;
320 (lo, hi) = (lo.min(p), hi.max(p));
321 }
322 if lo > 0.0 && hi.is_finite() { 1.0 / (lo.sqrt() * hi.sqrt()).sqrt() } else { 1.0 }
323}
324
325pub fn convert(wav: &Wav) -> ConvertedIr {
331 let ch = wav.channels.first().map(Vec::as_slice).unwrap_or(&[]);
332 let direct = wav.sample_rate == IR_SAMPLE_RATE && wav.channels.len() == 1 && wav.encoding == Encoding::Pcm(24);
333 let ir = if direct {
334 ch.to_vec()
335 } else {
336 let mut v = resample_like_app(ch, wav.sample_rate, IR_SAMPLE_RATE);
337 via_24_bit(&mut v);
338 v
339 };
340 let head = &ir[..ir.len().min(IR_TAPS)];
341 let gain = app_gain(head);
342 let mut data = vec![0.0f32; IR_TAPS];
343 for (k, &v) in head.iter().enumerate() {
344 data[IR_TAPS - 1 - k] = (gain * v as f64) as f32;
345 }
346 ConvertedIr { data, gain: gain as f32, ir }
347}
348
349#[cfg(feature = "nam")]
354const DESIGN_FFT: usize = 1 << 15;
355#[cfg(feature = "nam")]
357const SMOOTH: f64 = 1.059_463_094_359_295_3; #[cfg(feature = "nam")]
361fn smooth_power(p: &[f64]) -> Vec<f64> {
362 let mut pre = Vec::with_capacity(p.len() + 1);
363 pre.push(0.0);
364 for v in p {
365 pre.push(pre.last().unwrap() + v);
366 }
367 (0..p.len())
368 .map(|k| {
369 let lo = ((k as f64 / SMOOTH).floor() as usize).min(k);
370 let hi = ((k as f64 * SMOOTH).ceil() as usize).max(k).min(p.len() - 1);
371 (pre[hi + 1] - pre[lo]) / (hi + 1 - lo) as f64
372 })
373 .collect()
374}
375
376#[cfg(feature = "nam")]
378fn power_spectrum(x: &[f64], n: usize) -> Vec<f64> {
379 use rustfft::num_complex::Complex64;
380 let mut c: Vec<Complex64> = x.iter().take(n).map(|&v| Complex64::new(v, 0.0)).collect();
381 c.resize(n, Complex64::default());
382 rustfft::FftPlanner::new().plan_fft_forward(n).process(&mut c);
383 c[..n / 2 + 1].iter().map(|v| v.norm_sqr()).collect()
384}
385
386#[cfg(feature = "nam")]
389fn min_phase(mag: &[f64], taps: usize) -> Vec<f64> {
390 use rustfft::num_complex::Complex64;
391 let n = (mag.len() - 1) * 2;
392 let mut p = rustfft::FftPlanner::new();
393 let (fwd, inv) = (p.plan_fft_forward(n), p.plan_fft_inverse(n));
394 let floor = mag.iter().fold(0f64, |m, &v| m.max(v)) * 1e-6;
395 let mut c: Vec<Complex64> = (0..n).map(|k| Complex64::new(mag[k.min(n - k)].max(floor).ln(), 0.0)).collect();
396 inv.process(&mut c);
397 for (i, v) in c.iter_mut().enumerate() {
398 let w = match i {
399 0 => 1.0,
400 i if i < n / 2 => 2.0,
401 i if i == n / 2 => 1.0,
402 _ => 0.0,
403 };
404 *v = Complex64::new(v.re / n as f64 * w, 0.0);
405 }
406 fwd.process(&mut c);
407 for v in c.iter_mut() {
408 *v = v.exp();
409 }
410 inv.process(&mut c);
411 c[..taps].iter().map(|v| v.re / n as f64).collect()
412}
413
414#[cfg(feature = "nam")]
417const FIT_LO_HZ: f64 = 30.0;
418#[cfg(feature = "nam")]
419const FIT_HI_HZ: f64 = 16000.0;
420
421#[cfg(feature = "nam")]
424fn fit_bands(n: usize) -> Vec<std::ops::Range<usize>> {
425 let hz = |f: f64| f * n as f64 / IR_SAMPLE_RATE as f64;
426 std::iter::successors(Some(FIT_LO_HZ), |f| Some(f * 2f64.powf(1.0 / 12.0)))
427 .take_while(|&f| f <= FIT_HI_HZ)
428 .map(|f| {
429 let (lo, hi) = (hz(f / SMOOTH).floor() as usize, hz(f * SMOOTH).ceil() as usize);
430 lo..hi.max(lo + 1)
431 })
432 .collect()
433}
434
435#[cfg(feature = "nam")]
439fn refine(h0: &[f64], nf: usize, full_smoothed: &[f64]) -> Vec<f64> {
440 use rustfft::num_complex::Complex64;
441 let n = DESIGN_FFT;
442 let bands = fit_bands(n);
443 let step = nf / n;
445 let target: Vec<f64> = bands
446 .iter()
447 .map(|b| {
448 let p = b.clone().map(|k| full_smoothed[k * step]).sum::<f64>() / b.len() as f64;
449 10.0 * p.max(1e-30).log10()
450 })
451 .collect();
452 let mut planner = rustfft::FftPlanner::new();
453 let (fwd, inv) = (planner.plan_fft_forward(n), planner.plan_fft_inverse(n));
454 let scale = h0.iter().fold(0f64, |m, v| m.max(v.abs())).max(1e-12);
455 let mut h: Vec<f64> = h0.iter().map(|v| v / scale).collect();
456 let eval = |h: &[f64], grad: Option<&mut [f64]>| -> f64 {
457 let mut c: Vec<Complex64> = h.iter().map(|&v| Complex64::new(v, 0.0)).collect();
458 c.resize(n, Complex64::default());
459 fwd.process(&mut c);
460 let p: Vec<f64> = bands.iter().map(|b| b.clone().map(|k| c[k].norm_sqr()).sum::<f64>() / b.len() as f64).collect();
461 let d: Vec<f64> = p.iter().zip(&target).map(|(p, t)| 10.0 * p.max(1e-30).log10() - t).collect();
462 let mean = d.iter().sum::<f64>() / d.len() as f64;
463 let e = d.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / d.len() as f64;
464 if let Some(g) = grad {
465 let mut gk = vec![Complex64::default(); n];
467 for ((b, dv), pv) in bands.iter().zip(&d).zip(&p) {
468 let de_dp = 2.0 * (dv - mean) / d.len() as f64 * 10.0 / (std::f64::consts::LN_10 * pv.max(1e-30));
469 for k in b.clone() {
470 gk[k] += c[k] * (2.0 * de_dp / b.len() as f64);
471 }
472 }
473 inv.process(&mut gk);
474 for (gi, v) in g.iter_mut().zip(&gk) {
475 *gi = v.re;
476 }
477 }
478 e
479 };
480 let (b1, b2, eps, lr) = (0.9, 0.999, 1e-12, 2e-3);
481 let (mut m, mut v) = (vec![0.0; h.len()], vec![0.0; h.len()]);
482 let mut g = vec![0.0; h.len()];
483 let mut best = (eval(&h, None), h.clone());
484 let steps: usize = 400;
485 for it in 0..steps {
486 let e = eval(&h, Some(&mut g));
487 if e < best.0 {
488 best = (e, h.clone());
489 }
490 let t = (it + 1) as i32;
491 let rate = lr * 0.5 * (1.0 + (std::f64::consts::PI * it as f64 / steps as f64).cos());
492 for i in 0..h.len() {
493 m[i] = b1 * m[i] + (1.0 - b1) * g[i];
494 v[i] = b2 * v[i] + (1.0 - b2) * g[i] * g[i];
495 h[i] -= rate * (m[i] / (1.0 - f64::powi(b1, t))) / ((v[i] / (1.0 - f64::powi(b2, t))).sqrt() + eps);
496 }
497 }
498 let e = eval(&h, None);
499 if e < best.0 {
500 best = (e, h);
501 }
502 best.1.iter().map(|v| v * scale).collect()
503}
504
505#[cfg(feature = "nam")]
513pub fn convert_optimised(wav: &Wav) -> ConvertedIr {
514 let ch = wav.channels.first().map(Vec::as_slice).unwrap_or(&[]);
515 let ir = resample(ch, wav.sample_rate, IR_SAMPLE_RATE);
516 let x: Vec<f64> = ir.iter().map(|&v| v as f64).collect();
517 let nf = x.len().next_power_of_two().max(DESIGN_FFT);
518 let full = smooth_power(&power_spectrum(&x, nf));
519 let step = nf / DESIGN_FFT;
520 let target: Vec<f64> = (0..=DESIGN_FFT / 2).map(|k| full[k * step].sqrt()).collect();
522 let peak = target.iter().fold(0f64, |m, &v| m.max(v));
523 let target: Vec<f64> = target.iter().map(|&v| v.max(peak * 1e-6)).collect();
524
525 let fade = 48;
527 let window = |h: &mut [f64]| {
528 for (i, v) in h.iter_mut().enumerate().skip(PEDAL_IR_TAPS - fade) {
529 let t = (i - (PEDAL_IR_TAPS - fade)) as f64 / fade as f64;
530 *v *= 0.5 + 0.5 * (std::f64::consts::PI * t).cos();
531 }
532 };
533 let lo_bin = (30.0 * DESIGN_FFT as f64 / 48000.0) as usize;
536 let hi_bin = (16000.0 * DESIGN_FFT as f64 / 48000.0) as usize;
537 let probe: Vec<usize> = std::iter::successors(Some(lo_bin as f64), |b| Some(b * 2f64.powf(1.0 / 24.0)))
538 .map(|b| b.round() as usize)
539 .take_while(|&b| b <= hi_bin)
540 .collect();
541 let response = |h: &[f64]| -> Vec<f64> { smooth_power(&power_spectrum(h, DESIGN_FFT)).iter().map(|p| p.sqrt()).collect() };
542 let error = |r: &[f64]| {
543 let d: Vec<f64> = probe.iter().map(|&k| 20.0 * (r[k].max(1e-30) / target[k]).log10()).collect();
544 let mean = d.iter().sum::<f64>() / d.len() as f64;
545 (d.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / d.len() as f64).sqrt()
546 };
547 let mut design = target.clone();
548 let mut best: Option<(f64, Vec<f64>)> = None;
549 for _ in 0..12 {
550 let mut h = min_phase(&design, PEDAL_IR_TAPS);
551 window(&mut h);
552 let r = response(&h);
553 let e = error(&r);
554 if best.as_ref().is_none_or(|b| e < b.0) {
555 best = Some((e, h));
556 }
557 for k in 0..design.len() {
559 let corr = (target[k] / r[k].max(1e-30)).powf(0.8);
560 design[k] = (design[k] * corr).clamp(target[k] / 4.0, target[k] * 4.0);
561 }
562 }
563 let h0 = best.map(|b| b.1).unwrap_or_default();
564 let h = refine(&h0, nf, &full);
565 let peak_x = x.iter().fold(0f64, |m, v| m.max(v.abs()));
568 let onset = x.iter().position(|v| v.abs() > 0.01 * peak_x).unwrap_or(0).saturating_sub(8);
569 let head: Vec<f32> = x[onset..].iter().take(IR_TAPS).map(|&v| v as f32).collect();
570 let g_ref = app_gain(&head);
571 let bands = fit_bands(DESIGN_FFT);
572 let step = nf / DESIGN_FFT;
573 let ours = smooth_power(&power_spectrum(&h, DESIGN_FFT));
574 let (mut sum, mut count) = (0.0, 0);
575 for b in &bands {
576 let f = b.start as f64 * IR_SAMPLE_RATE as f64 / DESIGN_FFT as f64;
577 if (100.0..=8000.0).contains(&f) {
578 let o = b.clone().map(|k| ours[k]).sum::<f64>() / b.len() as f64;
579 let t = b.clone().map(|k| full[k * step]).sum::<f64>() / b.len() as f64 * g_ref * g_ref;
580 sum += 10.0 * (t.max(1e-30) / o.max(1e-30)).log10();
581 count += 1;
582 }
583 }
584 let gain = 10f64.powf(sum / count.max(1) as f64 / 20.0);
585 let h: Vec<f32> = h.iter().map(|&v| v as f32).collect();
586 let mut data = vec![0.0f32; IR_TAPS];
587 for (k, &v) in h.iter().enumerate() {
588 data[IR_TAPS - 1 - k] = (gain * v as f64) as f32;
589 }
590 ConvertedIr { data, gain: gain as f32, ir }
591}
592
593#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
595pub enum Method {
596 #[default]
598 App,
599 #[cfg(feature = "nam")]
601 Optimised,
602}
603
604pub fn convert_with(wav: &Wav, method: Method) -> ConvertedIr {
606 match method {
607 Method::App => convert(wav),
608 #[cfg(feature = "nam")]
609 Method::Optimised => convert_optimised(wav),
610 }
611}
612
613pub fn slot_name(path: &std::path::Path) -> String {
616 path.file_stem().map(|s| s.to_string_lossy().chars().take(16).collect()).unwrap_or_default()
617}
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622
623 fn wav(tag: u16, channels: u16, rate: u32, bits: u16, data: &[u8]) -> Vec<u8> {
625 let mut f = vec![];
626 f.extend_from_slice(&tag.to_le_bytes());
627 f.extend_from_slice(&channels.to_le_bytes());
628 f.extend_from_slice(&rate.to_le_bytes());
629 f.extend_from_slice(&(rate * channels as u32 * bits as u32 / 8).to_le_bytes());
630 f.extend_from_slice(&(channels * bits / 8).to_le_bytes());
631 f.extend_from_slice(&bits.to_le_bytes());
632 let mut b = b"RIFF\0\0\0\0WAVE".to_vec();
633 for (id, body) in [(b"fmt ", &f[..]), (b"LIST", &b"junk"[..]), (b"data", data)] {
634 b.extend_from_slice(id);
635 b.extend_from_slice(&(body.len() as u32).to_le_bytes());
636 b.extend_from_slice(body);
637 }
638 b
639 }
640
641 #[test]
642 fn pcm_and_float() {
643 let w = Wav::parse(&wav(PCM, 1, 48000, 24, &[0, 0, 0x40, 0, 0, 0xC0, 0xFF, 0xFF, 0x7F])).unwrap();
644 assert_eq!(w.sample_rate, 48000);
645 assert_eq!(w.channels, vec![vec![0.5, -0.5, 8_388_607.0 / 8_388_608.0]]);
646 let w = Wav::parse(&wav(PCM, 2, 44100, 16, &[0, 0x40, 0, 0xC0])).unwrap();
647 assert_eq!(w.channels, vec![vec![0.5], vec![-0.5]]);
648 let d: Vec<u8> = [0.25f32, -1.0].iter().flat_map(|v| v.to_le_bytes()).collect();
649 assert_eq!(Wav::parse(&wav(FLOAT, 1, 96000, 32, &d)).unwrap().channels, vec![vec![0.25, -1.0]]);
650 }
651
652 #[test]
653 fn write_and_read_back() {
654 let w = Wav::new(44100, vec![vec![0.5, -0.25, 0.0], vec![0.125, 1.0 - 1.0 / 8_388_608.0, -1.0]]);
655 for (bits, float, enc) in [(24, false, Encoding::Pcm(24)), (32, true, Encoding::Float(32))] {
656 assert_eq!(Wav::parse(&w.to_bytes(bits, float)).unwrap(), Wav { encoding: enc, ..w.clone() });
657 }
658 let r = Wav::parse(&w.to_bytes(16, false)).unwrap();
659 assert!(r.channels[0].iter().zip(&w.channels[0]).all(|(a, b)| (a - b).abs() < 1e-4));
660 }
661
662 #[test]
663 fn resampling_keeps_a_sine() {
664 let f = 1000.0;
665 let x: Vec<f32> = (0..4410).map(|n| (std::f64::consts::TAU * f * n as f64 / 44100.0).sin() as f32).collect();
666 let y = resample(&x, 44100, 48000);
667 assert_eq!(y.len(), 4800);
668 for (n, &v) in y.iter().enumerate().take(4400).skip(400) {
670 let want = (std::f64::consts::TAU * f * n as f64 / 48000.0).sin() as f32;
671 assert!((v - want).abs() < 1e-3, "sample {n}: {v} vs {want}");
672 }
673 assert_eq!(resample(&x, 48000, 48000), x);
674 assert_eq!((resample(&x[..441], 44100, 48000).len(), resample(&x[..442], 44100, 48000).len()), (480, 482));
675 }
676
677 #[test]
678 fn conversion_layout() {
679 let mut ch = vec![0.0f32; 3000];
680 (ch[0], ch[2500]) = (0.25, 0.9);
681 let mut w = Wav::new(48000, vec![ch, vec![1.0; 3000]]);
682 let c = convert(&w);
683 assert_eq!(c.data.len(), IR_TAPS);
684 assert!((c.data[IR_TAPS - 1] - 1.0).abs() < 1e-6 && (c.gain - 4.0).abs() < 1e-5);
686 assert_eq!(c.data.iter().filter(|v| **v != 0.0).count(), 1);
687 w = Wav { sample_rate: 48000, channels: vec![vec![-0.5]], encoding: Encoding::Pcm(24) };
689 let s = convert(&w);
690 assert_eq!((s.data[IR_TAPS - 1], s.data[0], s.ir.len()), (-1.0, 0.0, 1));
691 assert_eq!(slot_name(std::path::Path::new("/x/Delay And Reverb Ambient.wav")), "Delay And Reverb");
692 }
693
694 #[test]
695 fn app_gain_centres_the_mid_band() {
696 let mut x = vec![0.0f32; 2048];
699 (x[0], x[1000]) = (0.5, -0.25);
700 assert!((app_gain(&x) / 2.309391 - 1.0).abs() < 1e-6, "{}", app_gain(&x));
701 }
702
703 #[test]
704 fn app_resampler_basics() {
705 let x: Vec<f32> = (0..100).map(|i| (i as f32 * 0.1).sin()).collect();
707 assert_eq!(resample_like_app(&x, 48000, 48000), x);
708 assert_eq!((resample_like_app(&x, 44100, 48000).len(), resample_like_app(&x, 96000, 48000).len()), (108, 50));
709 let dc = resample_like_app(&[0.5; 4000], 44100, 48000);
711 assert!((dc[3000] - 0.5).abs() < 1e-4);
712 }
713
714 #[cfg(feature = "nam")]
715 #[test]
716 fn optimised_conversion_fits_a_delayed_ir_into_384_taps() {
717 let x: Vec<f32> = (0..6000)
720 .map(|n| if n < 1000 { 0.0 } else { let t = (n - 1000) as f32; (-t / 120.0).exp() * (t * 0.05).sin() + if n == 1000 { 0.5 } else { 0.0 } })
721 .collect();
722 let w = Wav::new(48000, vec![x]);
723 let c = convert_optimised(&w);
724 assert_eq!(c.data.len(), IR_TAPS);
725 assert!(c.data[..IR_TAPS - PEDAL_IR_TAPS].iter().all(|&v| v == 0.0));
726 let played: f32 = c.data[IR_TAPS - PEDAL_IR_TAPS..].iter().map(|v| v * v).sum();
727 let official = convert(&w);
728 let official_played: f32 = official.data[IR_TAPS - PEDAL_IR_TAPS..].iter().map(|v| v * v).sum();
729 assert!(c.data.iter().all(|v| v.is_finite()) && played > 1e3 * official_played, "{played} vs {official_played}");
730 assert_eq!(convert_with(&w, Method::App).data, official.data);
731 }
732
733 #[test]
734 fn errors() {
735 assert!(matches!(Wav::parse(b"nope"), Err(WavError::Format(_))));
736 assert!(matches!(Wav::parse(&wav(2, 1, 48000, 4, &[0; 8])), Err(WavError::Unsupported(_))));
737 }
738}