1use crate::infer::GraphExt as _;
22use crate::op::Activation;
23use crate::{DType, Graph, NodeId, Op, Shape, fft::FftNorm};
24
25const FIR_DIRECT_MAX_TAPS: usize = 64;
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum FirMode {
35 Full,
37 Same,
39 Valid,
41 Causal,
44}
45
46pub fn iir_impulse_response(b: &[f32], a: &[f32], n: usize) -> Vec<f32> {
53 assert!(
54 !b.is_empty() && !a.is_empty(),
55 "iir_impulse_response: empty coeffs"
56 );
57 assert!(a[0] != 0.0, "iir_impulse_response: a0 must be non-zero");
58 let m = b.len().max(a.len());
59 let a0 = a[0];
60 let bn: Vec<f32> = (0..m)
61 .map(|i| b.get(i).copied().unwrap_or(0.0) / a0)
62 .collect();
63 let an: Vec<f32> = (0..m)
64 .map(|i| a.get(i).copied().unwrap_or(0.0) / a0)
65 .collect();
66 let s = m - 1; let mut w = vec![0f32; s];
68 let mut out = Vec::with_capacity(n);
69 for t in 0..n {
70 let x = if t == 0 { 1.0 } else { 0.0 };
71 let y = bn[0] * x + if s > 0 { w[0] } else { 0.0 };
72 let mut new_w = vec![0f32; s];
73 for i in 0..s {
74 let mut wi = bn[i + 1] * x - an[i + 1] * y;
75 if i + 1 < s {
76 wi += w[i + 1];
77 }
78 new_w[i] = wi;
79 }
80 w = new_w;
81 out.push(y);
82 }
83 out
84}
85
86impl Graph {
87 pub fn hilbert(&mut self, x: NodeId) -> (NodeId, NodeId) {
90 let shape = self.shape(x).clone();
91 let last = shape.rank() - 1;
92 let l = shape.dim(last).unwrap_static();
93 let n = crate::fft::next_pow2(l);
94
95 let (re, im) = self.fft_real(x, FftNorm::Backward); let mut h = vec![0f32; n];
100 h[0] = 1.0;
101 if n >= 2 {
102 h[n / 2] = 1.0;
103 for hk in h.iter_mut().take(n / 2).skip(1) {
104 *hk = 2.0;
105 }
106 }
107 let h_node = self.const_f32_tensor(h, &[n]);
108 let re_h = self.mul(re, h_node);
109 let im_h = self.mul(im, h_node);
110
111 let block = self.concat_(vec![re_h, im_h], last);
113 let full = self.fft_norm(block, true, FftNorm::Forward); let a_re = self.narrow_(full, last, 0, n);
115 let a_im = self.narrow_(full, last, n, n);
116 (
118 self.narrow_(a_re, last, 0, l),
119 self.narrow_(a_im, last, 0, l),
120 )
121 }
122
123 pub fn envelope(&mut self, x: NodeId) -> NodeId {
125 let (re, im) = self.hilbert(x);
126 self.complex_abs(re, im)
127 }
128
129 pub fn instantaneous_phase(&mut self, x: NodeId) -> NodeId {
135 let (re, im) = self.hilbert(x);
136 let r = self.complex_abs(re, im);
137 let denom = self.add(r, re);
138 let eps = self.constant(1e-12, DType::F32);
139 let denom = self.add(denom, eps);
140 let ratio = self.div(im, denom);
141 let s = crate::shape::unary_shape(self.shape(ratio));
142 let at = self.activation(Activation::Atan, ratio, s);
143 let two = self.constant(2.0, DType::F32);
144 self.mul(at, two)
145 }
146
147 pub fn fir_filtfilt(&mut self, x: NodeId, taps: &[f32]) -> NodeId {
154 let k = taps.len();
155 assert!(k > 0, "fir_filtfilt: need ≥1 tap");
156 let last = self.shape(x).rank() - 1;
157 let l = self.shape(x).dim(last).unwrap_static();
158 let h = self.const_f32_tensor(taps.to_vec(), &[k]);
159 let y1 = self.fir_conv_same(x, h, k, l);
160 let y2 = self.reverse(y1, vec![last]);
161 let y3 = self.fir_conv_same(y2, h, k, l);
162 self.reverse(y3, vec![last])
163 }
164
165 fn fir_conv_same(&mut self, x: NodeId, taps: NodeId, k: usize, l: usize) -> NodeId {
171 let full = self.fft_conv1d(x, taps, 0, FftNorm::Forward); let start = (k - 1) / 2;
173 let last = self.shape(full).rank() - 1;
174 self.narrow_(full, last, start, l)
175 }
176
177 pub fn biquad(&mut self, x: NodeId, b: [f32; 3], a: [f32; 3]) -> NodeId {
186 assert!(a[0] != 0.0, "biquad: a0 must be non-zero");
187 let (b0, b1, b2) = (b[0] / a[0], b[1] / a[0], b[2] / a[0]);
188 let (a1, a2) = (a[1] / a[0], a[2] / a[0]);
189
190 let xs_shape = self.shape(x).clone();
191 let orig_dims: Vec<i64> = xs_shape
192 .dims()
193 .iter()
194 .map(|d| d.unwrap_static() as i64)
195 .collect();
196 let rank = xs_shape.rank();
197 let n = xs_shape.dim(rank - 1).unwrap_static();
198 let p: usize = (0..rank - 1)
199 .map(|i| xs_shape.dim(i).unwrap_static())
200 .product();
201
202 let x_pn = self.reshape_(x, vec![p as i64, n as i64]);
204 let xs = self.transpose_(x_pn, vec![1, 0]); let mut body = Graph::new("biquad_body");
208 let carry = body.input("carry", Shape::new(&[p, 3], DType::F32));
209 let x_t = body.input("x_t", Shape::new(&[p], DType::F32));
210 let z1p = body.narrow_(carry, 1, 1, 1);
211 let z1p = body.reshape_(z1p, vec![p as i64]);
212 let z2p = body.narrow_(carry, 1, 2, 1);
213 let z2p = body.reshape_(z2p, vec![p as i64]);
214 let cb0 = body.constant(b0 as f64, DType::F32);
215 let cb1 = body.constant(b1 as f64, DType::F32);
216 let cb2 = body.constant(b2 as f64, DType::F32);
217 let ca1 = body.constant(a1 as f64, DType::F32);
218 let ca2 = body.constant(a2 as f64, DType::F32);
219 let b0x = body.mul(x_t, cb0);
220 let y = body.add(b0x, z1p); let b1x = body.mul(x_t, cb1);
222 let a1y = body.mul(y, ca1);
223 let z1_tmp = body.sub(b1x, a1y);
224 let z1 = body.add(z1_tmp, z2p); let b2x = body.mul(x_t, cb2);
226 let a2y = body.mul(y, ca2);
227 let z2 = body.sub(b2x, a2y); let yc = body.reshape_(y, vec![p as i64, 1]);
229 let z1c = body.reshape_(z1, vec![p as i64, 1]);
230 let z2c = body.reshape_(z2, vec![p as i64, 1]);
231 let next = body.concat_(vec![yc, z1c, z2c], 1); body.set_outputs(vec![next]);
233
234 let init = self.const_f32_tensor(vec![0.0; p * 3], &[p, 3]);
236 let traj = self.add_node(
237 Op::Scan {
238 body: Box::new(body),
239 length: n as u32,
240 save_trajectory: true,
241 num_bcast: 0,
242 num_xs: 1,
243 num_checkpoints: 0,
244 },
245 vec![init, xs],
246 Shape::new(&[n, p, 3], DType::F32),
247 );
248 let y_np = self.narrow_(traj, 2, 0, 1); let y_np = self.reshape_(y_np, vec![n as i64, p as i64]);
251 let y_pn = self.transpose_(y_np, vec![1, 0]); self.reshape_(y_pn, orig_dims)
253 }
254
255 pub fn sosfilt(&mut self, x: NodeId, sections: &[([f32; 3], [f32; 3])]) -> NodeId {
258 let mut y = x;
259 for &(b, a) in sections {
260 y = self.biquad(y, b, a);
261 }
262 y
263 }
264
265 pub fn sosfiltfilt(&mut self, x: NodeId, sections: &[([f32; 3], [f32; 3])]) -> NodeId {
269 let last = self.shape(x).rank() - 1;
270 let f1 = self.sosfilt(x, sections);
271 let r1 = self.reverse(f1, vec![last]);
272 let f2 = self.sosfilt(r1, sections);
273 self.reverse(f2, vec![last])
274 }
275
276 pub fn resample_poly(&mut self, x: NodeId, up: usize, down: usize, taps: &[f32]) -> NodeId {
284 assert!(up >= 1 && down >= 1, "resample_poly: up/down must be ≥1");
285 assert!(!taps.is_empty(), "resample_poly: need ≥1 tap");
286 let shape = self.shape(x).clone();
287 let rank = shape.rank();
288 let last = rank - 1;
289 let n = shape.dim(last).unwrap_static();
290 let lead: Vec<i64> = (0..last)
291 .map(|i| shape.dim(i).unwrap_static() as i64)
292 .collect();
293
294 let up_sig = if up == 1 {
296 x
297 } else {
298 let mut d1: Vec<i64> = lead.clone();
299 d1.push(n as i64);
300 d1.push(1);
301 let xr = self.reshape_(x, d1);
302 let mut zdims: Vec<usize> = shape
303 .dims()
304 .iter()
305 .take(last)
306 .map(|d| d.unwrap_static())
307 .collect();
308 zdims.push(n);
309 zdims.push(up - 1);
310 let zeros = self.const_f32_tensor(vec![0.0; zdims.iter().product()], &zdims);
311 let stacked = self.concat_(vec![xr, zeros], last + 1); let mut flat: Vec<i64> = lead.clone();
313 flat.push((n * up) as i64);
314 self.reshape_(stacked, flat)
315 };
316
317 let l = n * up;
319 let scaled: Vec<f32> = taps.iter().map(|&t| t * up as f32).collect();
320 let h = self.const_f32_tensor(scaled, &[taps.len()]);
321 let filtered = self.fir_conv_same(up_sig, h, taps.len(), l);
322
323 if down == 1 {
326 return filtered;
327 }
328 let out_len = l.div_ceil(down);
329 let padded = out_len * down;
330 let filtered = if padded > l {
331 let mut zdims: Vec<usize> = shape
332 .dims()
333 .iter()
334 .take(last)
335 .map(|d| d.unwrap_static())
336 .collect();
337 zdims.push(padded - l);
338 let zeros = self.const_f32_tensor(vec![0.0; zdims.iter().product()], &zdims);
339 self.concat_(vec![filtered, zeros], last)
340 } else {
341 filtered
342 };
343 let mut rdims: Vec<i64> = lead.clone();
344 rdims.push(out_len as i64);
345 rdims.push(down as i64);
346 let reshaped = self.reshape_(filtered, rdims); let col0 = self.narrow_(reshaped, last + 1, 0, 1); let mut odims: Vec<i64> = lead;
349 odims.push(out_len as i64);
350 self.reshape_(col0, odims)
351 }
352
353 pub fn fir_conv1d(&mut self, x: NodeId, taps: &[f32], mode: FirMode) -> NodeId {
362 let k = taps.len();
363 assert!(k > 0, "fir_conv1d: need ≥1 tap");
364 let last = self.shape(x).rank() - 1;
365 let l = self.shape(x).dim(last).unwrap_static();
366 let full = if k <= FIR_DIRECT_MAX_TAPS {
368 self.fir_full_direct(x, taps)
369 } else {
370 let h = self.const_f32_tensor(taps.to_vec(), &[k]);
371 self.fft_conv1d(x, h, 0, FftNorm::Forward)
372 };
373 match mode {
374 FirMode::Full => full,
375 FirMode::Causal => self.narrow_(full, last, 0, l),
376 FirMode::Same => {
377 let start = (k - 1) / 2;
378 self.narrow_(full, last, start, l)
379 }
380 FirMode::Valid => {
381 assert!(
382 l + 1 > k,
383 "fir_conv1d(Valid): signal ({l}) shorter than taps ({k})"
384 );
385 self.narrow_(full, last, k - 1, l - k + 1)
386 }
387 }
388 }
389
390 fn fir_full_direct(&mut self, x: NodeId, taps: &[f32]) -> NodeId {
393 let k = taps.len();
394 let last = self.shape(x).rank() - 1;
395 let l = self.shape(x).dim(last).unwrap_static();
396 let mut acc: Option<NodeId> = None;
397 for (j, &h) in taps.iter().enumerate() {
398 if h == 0.0 {
399 continue;
400 }
401 let c = self.constant(h as f64, DType::F32);
402 let scaled = self.mul(x, c);
403 let term = self.fir_pad_last(scaled, j, k - 1 - j);
404 acc = Some(match acc {
405 None => term,
406 Some(a) => self.add(a, term),
407 });
408 }
409 match acc {
410 Some(a) => a,
411 None => {
412 let mut dims: Vec<usize> = self
413 .shape(x)
414 .dims()
415 .iter()
416 .map(|d| d.unwrap_static())
417 .collect();
418 dims[last] = l + k - 1;
419 self.fir_zeros_dims(&dims)
420 }
421 }
422 }
423
424 pub fn partitioned_conv1d(&mut self, x: NodeId, ir: NodeId, block: usize) -> NodeId {
442 let xs = self.shape(x).clone();
443 let rank = xs.rank();
444 let last = rank - 1;
445 let l = xs.dim(last).unwrap_static();
446 let ir_shape = self.shape(ir).clone();
447 assert_eq!(ir_shape.rank(), 1, "partitioned_conv1d: ir must be rank-1");
448 let m = ir_shape.dim(0).unwrap_static();
449 assert!(m >= 1 && l >= 1, "partitioned_conv1d: empty input/ir");
450
451 let b = crate::fft::next_pow2(block.max(1));
452 let n = 2 * b; let kbins = n / 2 + 1;
454 let p = m.div_ceil(b);
455 let mf = (l + m - 1).div_ceil(b);
456
457 let ir_pad = self.fir_pad_last(ir, 0, p * b - m); let ir_blocks = self.reshape_(ir_pad, vec![p as i64, b as i64]); let ir_zeros = self.fir_zeros_dims(&[p, b]); let ir_frames = self.concat_(vec![ir_blocks, ir_zeros], 1); let (h_re, h_im) = self.rfft(ir_frames, FftNorm::Forward); let back = mf * b - l; let x_pad = self.fir_pad_last(x, b, back); let mut blk_dims: Vec<i64> = (0..last)
468 .map(|i| xs.dim(i).unwrap_static() as i64)
469 .collect();
470 blk_dims.push((mf + 1) as i64);
471 blk_dims.push(b as i64);
472 let blocks = self.reshape_(x_pad, blk_dims); let brank = self.shape(blocks).rank();
474 let baxis = brank - 2; let blast = brank - 1; let left = self.narrow_(blocks, baxis, 0, mf); let right = self.narrow_(blocks, baxis, 1, mf); let frames = self.concat_(vec![left, right], blast); let (x_re, x_im) = self.rfft(frames, FftNorm::Forward); let mf_axis = self.shape(x_re).rank() - 2;
483 let mut acc: Option<(NodeId, NodeId)> = None;
484 for pp in 0..p.min(mf) {
485 let hp_re = self.narrow_(h_re, 0, pp, 1);
486 let hp_re = self.reshape_(hp_re, vec![kbins as i64]); let hp_im = self.narrow_(h_im, 0, pp, 1);
488 let hp_im = self.reshape_(hp_im, vec![kbins as i64]); let (xr_s, xi_s) = if pp == 0 {
491 (x_re, x_im)
492 } else {
493 let keep = mf - pp;
494 let mut zdims: Vec<usize> = self
495 .shape(x_re)
496 .dims()
497 .iter()
498 .map(|d| d.unwrap_static())
499 .collect();
500 zdims[mf_axis] = pp;
501 let zre = self.fir_zeros_dims(&zdims);
502 let zim = self.fir_zeros_dims(&zdims);
503 let hr = self.narrow_(x_re, mf_axis, 0, keep);
504 let hi = self.narrow_(x_im, mf_axis, 0, keep);
505 (
506 self.concat_(vec![zre, hr], mf_axis),
507 self.concat_(vec![zim, hi], mf_axis),
508 )
509 };
510 let rr = self.mul(hp_re, xr_s);
512 let ii = self.mul(hp_im, xi_s);
513 let t_re = self.sub(rr, ii);
514 let ri = self.mul(hp_re, xi_s);
515 let ir2 = self.mul(hp_im, xr_s);
516 let t_im = self.add(ri, ir2);
517 acc = Some(match acc {
518 None => (t_re, t_im),
519 Some((ar, ai)) => (self.add(ar, t_re), self.add(ai, t_im)),
520 });
521 }
522 let (y_re, y_im) = acc.expect("partitioned_conv1d: no partitions");
523
524 let y_frames = self.irfft(y_re, y_im, n, FftNorm::Forward); let ylast = self.shape(y_frames).rank() - 1;
527 let valid = self.narrow_(y_frames, ylast, b, b); let mut out_dims: Vec<i64> = (0..last)
529 .map(|i| xs.dim(i).unwrap_static() as i64)
530 .collect();
531 out_dims.push((mf * b) as i64);
532 let flat = self.reshape_(valid, out_dims); let flast = self.shape(flat).rank() - 1;
534 self.narrow_(flat, flast, 0, l + m - 1) }
536
537 pub fn conv_reverb(&mut self, x: NodeId, ir: &[f32], block: usize) -> NodeId {
544 assert!(!ir.is_empty(), "conv_reverb: empty impulse response");
545 let ir_node = self.const_f32_tensor(ir.to_vec(), &[ir.len()]);
546 self.partitioned_conv1d(x, ir_node, block)
547 }
548
549 pub fn partitioned_conv(&mut self, x: NodeId, ir: NodeId, block: usize) -> NodeId {
555 let xs = self.shape(x).clone();
556 let last = xs.rank() - 1;
557 let l = xs.dim(last).unwrap_static();
558 let ir_shape = self.shape(ir).clone();
559 assert_eq!(ir_shape.rank(), 1, "partitioned_conv: ir must be rank-1");
560 let m = ir_shape.dim(0).unwrap_static();
561 let mut dims: Vec<usize> = xs.dims().iter().map(|d| d.unwrap_static()).collect();
562 dims[last] = l + m - 1;
563 let out_shape = Shape::new(&dims, xs.dtype());
564 self.push(Op::PartitionedConv { block }, vec![x, ir], out_shape, None)
565 }
566
567 pub fn partitioned_conv1d_gemm(&mut self, x: NodeId, ir: NodeId, block: usize) -> NodeId {
577 let xs = self.shape(x).clone();
578 let rank = xs.rank();
579 let last = rank - 1;
580 let l = xs.dim(last).unwrap_static();
581 let ir_shape = self.shape(ir).clone();
582 assert_eq!(
583 ir_shape.rank(),
584 1,
585 "partitioned_conv1d_gemm: ir must be rank-1"
586 );
587 let m = ir_shape.dim(0).unwrap_static();
588 assert!(m >= 1 && l >= 1, "partitioned_conv1d_gemm: empty input/ir");
589
590 let b = crate::fft::next_pow2(block.max(1));
591 let n = 2 * b;
592 let kbins = n / 2 + 1;
593 let p = m.div_ceil(b);
594 let mf = (l + m - 1).div_ceil(b);
595
596 let ir_pad = self.fir_pad_last(ir, 0, p * b - m);
598 let ir_blocks = self.reshape_(ir_pad, vec![p as i64, b as i64]);
599 let ir_zeros = self.fir_zeros_dims(&[p, b]);
600 let ir_frames = self.concat_(vec![ir_blocks, ir_zeros], 1);
601 let (h_re, h_im) = self.rfft(ir_frames, FftNorm::Forward); let back = mf * b - l;
605 let x_pad = self.fir_pad_last(x, b, back);
606 let mut blk_dims: Vec<i64> = (0..last)
607 .map(|i| xs.dim(i).unwrap_static() as i64)
608 .collect();
609 blk_dims.push((mf + 1) as i64);
610 blk_dims.push(b as i64);
611 let blocks = self.reshape_(x_pad, blk_dims);
612 let brank = self.shape(blocks).rank();
613 let baxis = brank - 2;
614 let blast = brank - 1;
615 let left = self.narrow_(blocks, baxis, 0, mf);
616 let right = self.narrow_(blocks, baxis, 1, mf);
617 let frames = self.concat_(vec![left, right], blast);
618 let (x_re, x_im) = self.rfft(frames, FftNorm::Forward); let xr = self.shape(x_re).rank();
623 let mf_axis = xr - 2;
624 let lead: Vec<usize> = (0..mf_axis)
625 .map(|i| self.shape(x_re).dim(i).unwrap_static())
626 .collect();
627 let rows: usize = lead.iter().product::<usize>() * mf;
628
629 let mut u_re_parts = Vec::with_capacity(p);
630 let mut u_im_parts = Vec::with_capacity(p);
631 for pp in 0..p {
632 let (sre, sim) = if pp == 0 {
633 (x_re, x_im)
634 } else {
635 let keep = mf - pp;
636 let mut zd: Vec<usize> = self
637 .shape(x_re)
638 .dims()
639 .iter()
640 .map(|d| d.unwrap_static())
641 .collect();
642 zd[mf_axis] = pp;
643 let zre = self.fir_zeros_dims(&zd);
644 let zim = self.fir_zeros_dims(&zd);
645 let hr = self.narrow_(x_re, mf_axis, 0, keep);
646 let hi = self.narrow_(x_im, mf_axis, 0, keep);
647 (
648 self.concat_(vec![zre, hr], mf_axis),
649 self.concat_(vec![zim, hi], mf_axis),
650 )
651 };
652 let mut d: Vec<i64> = self
654 .shape(sre)
655 .dims()
656 .iter()
657 .map(|x| x.unwrap_static() as i64)
658 .collect();
659 d.insert(d.len() - 1, 1);
660 u_re_parts.push(self.reshape_(sre, d.clone()));
661 u_im_parts.push(self.reshape_(sim, d));
662 }
663 let u_re = self.concat_(u_re_parts, mf_axis + 1); let u_im = self.concat_(u_im_parts, mf_axis + 1);
665
666 let u_re = self.reshape_(u_re, vec![rows as i64, p as i64, kbins as i64]);
668 let u_im = self.reshape_(u_im, vec![rows as i64, p as i64, kbins as i64]);
669 let u_re = self.transpose_(u_re, vec![2, 0, 1]); let u_im = self.transpose_(u_im, vec![2, 0, 1]);
671 let h_re_t = self.transpose_(h_re, vec![1, 0]); let h_im_t = self.transpose_(h_im, vec![1, 0]);
673 let h_re_c = self.reshape_(h_re_t, vec![kbins as i64, p as i64, 1]); let h_im_c = self.reshape_(h_im_t, vec![kbins as i64, p as i64, 1]);
675
676 let out_krp = Shape::new(&[kbins, rows, 1], DType::F32);
677 let rr = self.matmul(u_re, h_re_c, out_krp.clone());
678 let ii = self.matmul(u_im, h_im_c, out_krp.clone());
679 let y_re_k = self.sub(rr, ii); let ri = self.matmul(u_re, h_im_c, out_krp.clone());
681 let ir2 = self.matmul(u_im, h_re_c, out_krp);
682 let y_im_k = self.add(ri, ir2); let y_re = self.reshape_(y_re_k, vec![kbins as i64, rows as i64]);
686 let y_re = self.transpose_(y_re, vec![1, 0]);
687 let y_im = self.reshape_(y_im_k, vec![kbins as i64, rows as i64]);
688 let y_im = self.transpose_(y_im, vec![1, 0]);
689 let mut yd: Vec<i64> = lead.iter().map(|&d| d as i64).collect();
690 yd.push(mf as i64);
691 yd.push(kbins as i64);
692 let y_re = self.reshape_(y_re, yd.clone());
693 let y_im = self.reshape_(y_im, yd);
694
695 let y_frames = self.irfft(y_re, y_im, n, FftNorm::Forward);
697 let ylast = self.shape(y_frames).rank() - 1;
698 let valid = self.narrow_(y_frames, ylast, b, b);
699 let mut out_dims: Vec<i64> = (0..last)
700 .map(|i| xs.dim(i).unwrap_static() as i64)
701 .collect();
702 out_dims.push((mf * b) as i64);
703 let flat = self.reshape_(valid, out_dims);
704 let flast = self.shape(flat).rank() - 1;
705 self.narrow_(flat, flast, 0, l + m - 1)
706 }
707
708 pub fn iirfilt(&mut self, x: NodeId, b: &[f32], a: &[f32]) -> NodeId {
718 assert!(
719 !b.is_empty() && !a.is_empty(),
720 "iirfilt: empty coefficients"
721 );
722 assert!(a[0] != 0.0, "iirfilt: a0 must be non-zero");
723 let m = b.len().max(a.len());
724 let a0 = a[0];
725 let bn: Vec<f32> = (0..m)
726 .map(|i| b.get(i).copied().unwrap_or(0.0) / a0)
727 .collect();
728 let an: Vec<f32> = (0..m)
729 .map(|i| a.get(i).copied().unwrap_or(0.0) / a0)
730 .collect();
731 let s = m - 1; let xs_shape = self.shape(x).clone();
734 let orig_dims: Vec<i64> = xs_shape
735 .dims()
736 .iter()
737 .map(|d| d.unwrap_static() as i64)
738 .collect();
739 let rank = xs_shape.rank();
740 let nlen = xs_shape.dim(rank - 1).unwrap_static();
741 let pch: usize = (0..rank - 1)
742 .map(|i| xs_shape.dim(i).unwrap_static())
743 .product();
744
745 let x_pn = self.reshape_(x, vec![pch as i64, nlen as i64]);
747 let xs = self.transpose_(x_pn, vec![1, 0]); let cw = 1 + s;
751 let mut body = Graph::new("iir_body");
752 let carry = body.input("carry", Shape::new(&[pch, cw], DType::F32));
753 let x_t = body.input("x_t", Shape::new(&[pch], DType::F32));
754 let w: Vec<NodeId> = (0..s)
755 .map(|i| {
756 let wi = body.narrow_(carry, 1, 1 + i, 1);
757 body.reshape_(wi, vec![pch as i64])
758 })
759 .collect();
760 let cbn: Vec<NodeId> = (0..m)
761 .map(|i| body.constant(bn[i] as f64, DType::F32))
762 .collect();
763 let can: Vec<NodeId> = (0..m)
764 .map(|i| body.constant(an[i] as f64, DType::F32))
765 .collect();
766 let b0x = body.mul(x_t, cbn[0]);
768 let y = if s > 0 { body.add(b0x, w[0]) } else { b0x };
769 let mut new_w = Vec::with_capacity(s);
771 for i in 0..s {
772 let bx = body.mul(x_t, cbn[i + 1]);
773 let ay = body.mul(y, can[i + 1]);
774 let mut wi = body.sub(bx, ay);
775 if i + 1 < s {
776 wi = body.add(wi, w[i + 1]);
777 }
778 new_w.push(wi);
779 }
780 let mut cols = Vec::with_capacity(cw);
782 cols.push(body.reshape_(y, vec![pch as i64, 1]));
783 for wi in new_w {
784 cols.push(body.reshape_(wi, vec![pch as i64, 1]));
785 }
786 let next = body.concat_(cols, 1);
787 body.set_outputs(vec![next]);
788
789 let init = self.const_f32_tensor(vec![0.0; pch * cw], &[pch, cw]);
790 let traj = self.add_node(
791 Op::Scan {
792 body: Box::new(body),
793 length: nlen as u32,
794 save_trajectory: true,
795 num_bcast: 0,
796 num_xs: 1,
797 num_checkpoints: 0,
798 },
799 vec![init, xs],
800 Shape::new(&[nlen, pch, cw], DType::F32),
801 );
802 let y_np = self.narrow_(traj, 2, 0, 1);
804 let y_np = self.reshape_(y_np, vec![nlen as i64, pch as i64]);
805 let y_pn = self.transpose_(y_np, vec![1, 0]);
806 self.reshape_(y_pn, orig_dims)
807 }
808
809 pub fn iir_as_fir(
818 &mut self,
819 x: NodeId,
820 b: &[f32],
821 a: &[f32],
822 taps: usize,
823 mode: FirMode,
824 ) -> NodeId {
825 let h = iir_impulse_response(b, a, taps);
826 self.fir_conv1d(x, &h, mode)
827 }
828
829 fn fir_zeros_dims(&mut self, dims: &[usize]) -> NodeId {
831 self.const_f32_tensor(vec![0.0; dims.iter().product()], dims)
832 }
833
834 fn fir_pad_last(&mut self, x: NodeId, front: usize, back: usize) -> NodeId {
836 if front == 0 && back == 0 {
837 return x;
838 }
839 let last = self.shape(x).rank() - 1;
840 let mut zdims: Vec<usize> = self
841 .shape(x)
842 .dims()
843 .iter()
844 .map(|d| d.unwrap_static())
845 .collect();
846 let mut parts = Vec::new();
847 if front > 0 {
848 zdims[last] = front;
849 let z = self.fir_zeros_dims(&zdims);
850 parts.push(z);
851 }
852 parts.push(x);
853 if back > 0 {
854 zdims[last] = back;
855 let z = self.fir_zeros_dims(&zdims);
856 parts.push(z);
857 }
858 self.concat_(parts, last)
859 }
860
861 fn complex_abs(&mut self, re: NodeId, im: NodeId) -> NodeId {
862 let re2 = self.mul(re, re);
863 let im2 = self.mul(im, im);
864 let sum = self.add(re2, im2);
865 self.sqrt(sum)
866 }
867}
868
869#[cfg(test)]
870mod tests {
871 use super::*;
872
873 fn dims(g: &Graph, id: NodeId) -> Vec<usize> {
874 g.shape(id)
875 .dims()
876 .iter()
877 .map(|d| d.unwrap_static())
878 .collect()
879 }
880 fn input(g: &mut Graph, shape: &[usize]) -> NodeId {
881 g.input("x", Shape::new(shape, DType::F32))
882 }
883
884 #[test]
885 fn hilbert_preserves_shape() {
886 let mut g = Graph::new("hil");
887 let x = input(&mut g, &[3, 128]);
888 let (re, im) = g.hilbert(x);
889 assert_eq!(dims(&g, re), vec![3, 128]);
890 assert_eq!(dims(&g, im), vec![3, 128]);
891 }
892
893 #[test]
894 fn envelope_and_phase_shape() {
895 let mut g = Graph::new("env");
896 let x = input(&mut g, &[2, 100]);
897 let e = g.envelope(x);
898 let p = g.instantaneous_phase(x);
899 assert_eq!(dims(&g, e), vec![2, 100]);
900 assert_eq!(dims(&g, p), vec![2, 100]);
901 }
902
903 #[test]
904 fn filtfilt_same_length() {
905 let mut g = Graph::new("ff");
906 let x = input(&mut g, &[2, 200]);
907 let taps: Vec<f32> = (0..15).map(|i| 1.0 / (i as f32 + 1.0)).collect();
908 let y = g.fir_filtfilt(x, &taps);
909 assert_eq!(dims(&g, y), vec![2, 200]);
910 }
911
912 #[test]
913 fn biquad_and_sosfilt_same_length() {
914 let mut g = Graph::new("iir");
915 let x = input(&mut g, &[3, 128]);
916 let b = [0.2, 0.4, 0.2];
917 let a = [1.0, -0.3, 0.1];
918 let y = g.biquad(x, b, a);
919 assert_eq!(dims(&g, y), vec![3, 128]);
920 let sos = [(b, a), ([0.5, 0.0, 0.5], [1.0, 0.2, 0.05])];
921 let z = g.sosfiltfilt(x, &sos);
922 assert_eq!(dims(&g, z), vec![3, 128]);
923 }
924
925 #[test]
926 fn resample_poly_length() {
927 let mut g = Graph::new("rs");
928 let x = input(&mut g, &[2, 100]);
929 let taps: Vec<f32> = (0..13).map(|i| 1.0 / (i as f32 + 1.0)).collect();
930 let y = g.resample_poly(x, 3, 2, &taps);
932 assert_eq!(dims(&g, y), vec![2, 150]);
933 }
934
935 #[test]
936 fn fir_conv1d_modes_lengths() {
937 let taps: Vec<f32> = (0..9).map(|i| 1.0 / (i as f32 + 1.0)).collect();
938 for &(mode, want) in &[
939 (FirMode::Full, 64 + 9 - 1),
940 (FirMode::Same, 64),
941 (FirMode::Valid, 64 - 9 + 1),
942 (FirMode::Causal, 64),
943 ] {
944 let mut g = Graph::new("fir");
945 let x = input(&mut g, &[2, 64]);
946 let y = g.fir_conv1d(x, &taps, mode);
947 assert_eq!(dims(&g, y), vec![2, want], "mode {mode:?}");
948 }
949 }
950
951 #[test]
952 fn fir_conv1d_long_uses_fft_path() {
953 let taps: Vec<f32> = (0..100).map(|i| ((i as f32) * 0.01).cos()).collect();
955 let mut g = Graph::new("firfft");
956 let x = input(&mut g, &[130]);
957 let y = g.fir_conv1d(x, &taps, FirMode::Full);
958 assert_eq!(dims(&g, y), vec![130 + 100 - 1]);
959 }
960
961 #[test]
962 fn conv_reverb_full_length() {
963 let mut g = Graph::new("rir");
964 let x = input(&mut g, &[2, 100]);
965 let ir: Vec<f32> = (0..300).map(|i| (-(i as f32) * 0.02).exp()).collect();
966 let y = g.conv_reverb(x, &ir, 64);
967 assert_eq!(dims(&g, y), vec![2, 100 + 300 - 1]);
968 }
969
970 #[test]
971 fn partitioned_conv1d_node_ir() {
972 let mut g = Graph::new("pconv");
973 let x = input(&mut g, &[50]);
974 let ir = g.const_f32_tensor((0..70).map(|i| i as f32 * 0.1).collect(), &[70]);
975 let y = g.partitioned_conv1d(x, ir, 32);
976 assert_eq!(dims(&g, y), vec![50 + 70 - 1]);
977 }
978
979 #[test]
980 fn partitioned_conv1d_gemm_shape() {
981 let mut g = Graph::new("pgemm");
982 let x = input(&mut g, &[2, 50]);
983 let ir = g.const_f32_tensor((0..70).map(|i| i as f32 * 0.1).collect(), &[70]);
984 let y = g.partitioned_conv1d_gemm(x, ir, 32);
985 assert_eq!(dims(&g, y), vec![2, 50 + 70 - 1]);
986 }
987
988 #[test]
989 fn partitioned_conv_op_shape() {
990 let mut g = Graph::new("pop");
991 let x = input(&mut g, &[3, 40]);
992 let ir = g.const_f32_tensor((0..100).map(|i| i as f32 * 0.01).collect(), &[100]);
993 let y = g.partitioned_conv(x, ir, 32);
994 assert_eq!(dims(&g, y), vec![3, 40 + 100 - 1]);
996 }
997
998 #[test]
999 fn iirfilt_high_order_same_length() {
1000 let mut g = Graph::new("iirN");
1001 let x = input(&mut g, &[3, 128]);
1002 let b = [0.1, 0.2, 0.2, 0.1];
1004 let a = [1.0, -0.5, 0.3, -0.1];
1005 let y = g.iirfilt(x, &b, &a);
1006 assert_eq!(dims(&g, y), vec![3, 128]);
1007 }
1008
1009 #[test]
1010 fn iir_as_fir_causal_same_length() {
1011 let mut g = Graph::new("iirfir");
1012 let x = input(&mut g, &[2, 100]);
1013 let b = [0.2, 0.4, 0.2];
1014 let a = [1.0, -0.3, 0.1];
1015 let y = g.iir_as_fir(x, &b, &a, 48, FirMode::Causal);
1016 assert_eq!(dims(&g, y), vec![2, 100]);
1017 }
1018}