1use crate::infer::GraphExt as _;
33use crate::op::Activation;
34use crate::{DType, Graph, NodeId, Op, Shape, fft::FftNorm};
35
36const FIR_DIRECT_MAX_TAPS: usize = 64;
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum FirMode {
46 Full,
48 Same,
50 Valid,
52 Causal,
55}
56
57pub fn iir_impulse_response(b: &[f32], a: &[f32], n: usize) -> Vec<f32> {
64 assert!(
65 !b.is_empty() && !a.is_empty(),
66 "iir_impulse_response: empty coeffs"
67 );
68 assert!(a[0] != 0.0, "iir_impulse_response: a0 must be non-zero");
69 let m = b.len().max(a.len());
70 let a0 = a[0];
71 let bn: Vec<f32> = (0..m)
72 .map(|i| b.get(i).copied().unwrap_or(0.0) / a0)
73 .collect();
74 let an: Vec<f32> = (0..m)
75 .map(|i| a.get(i).copied().unwrap_or(0.0) / a0)
76 .collect();
77 let s = m - 1; let mut w = vec![0f32; s];
79 let mut out = Vec::with_capacity(n);
80 for t in 0..n {
81 let x = if t == 0 { 1.0 } else { 0.0 };
82 let y = bn[0] * x + if s > 0 { w[0] } else { 0.0 };
83 let mut new_w = vec![0f32; s];
84 for i in 0..s {
85 let mut wi = bn[i + 1] * x - an[i + 1] * y;
86 if i + 1 < s {
87 wi += w[i + 1];
88 }
89 new_w[i] = wi;
90 }
91 w = new_w;
92 out.push(y);
93 }
94 out
95}
96
97impl Graph {
98 pub fn hilbert(&mut self, x: NodeId) -> (NodeId, NodeId) {
101 let shape = self.shape(x).clone();
102 let last = shape.rank() - 1;
103 let l = shape.dim(last).unwrap_static();
104 let n = crate::fft::next_pow2(l);
105
106 let (re, im) = self.fft_real(x, FftNorm::Backward); let mut h = vec![0f32; n];
111 h[0] = 1.0;
112 if n >= 2 {
113 h[n / 2] = 1.0;
114 for hk in h.iter_mut().take(n / 2).skip(1) {
115 *hk = 2.0;
116 }
117 }
118 let h_node = self.const_f32_tensor(h, &[n]);
119 let re_h = self.mul(re, h_node);
120 let im_h = self.mul(im, h_node);
121
122 let block = self.concat_(vec![re_h, im_h], last);
124 let full = self.fft_norm(block, true, FftNorm::Forward); let a_re = self.narrow_(full, last, 0, n);
126 let a_im = self.narrow_(full, last, n, n);
127 (
129 self.narrow_(a_re, last, 0, l),
130 self.narrow_(a_im, last, 0, l),
131 )
132 }
133
134 pub fn envelope(&mut self, x: NodeId) -> NodeId {
136 let (re, im) = self.hilbert(x);
137 self.complex_abs(re, im)
138 }
139
140 pub fn instantaneous_phase(&mut self, x: NodeId) -> NodeId {
146 let (re, im) = self.hilbert(x);
147 let r = self.complex_abs(re, im);
148 let denom = self.add(r, re);
149 let eps = self.constant(1e-12, DType::F32);
150 let denom = self.add(denom, eps);
151 let ratio = self.div(im, denom);
152 let s = crate::shape::unary_shape(self.shape(ratio));
153 let at = self.activation(Activation::Atan, ratio, s);
154 let two = self.constant(2.0, DType::F32);
155 self.mul(at, two)
156 }
157
158 pub fn fir_filtfilt(&mut self, x: NodeId, taps: &[f32]) -> NodeId {
165 let k = taps.len();
166 assert!(k > 0, "fir_filtfilt: need ≥1 tap");
167 let last = self.shape(x).rank() - 1;
168 let l = self.shape(x).dim(last).unwrap_static();
169 let h = self.const_f32_tensor(taps.to_vec(), &[k]);
170 let y1 = self.fir_conv_same(x, h, k, l);
171 let y2 = self.reverse(y1, vec![last]);
172 let y3 = self.fir_conv_same(y2, h, k, l);
173 self.reverse(y3, vec![last])
174 }
175
176 fn fir_conv_same(&mut self, x: NodeId, taps: NodeId, k: usize, l: usize) -> NodeId {
182 let full = self.fft_conv1d(x, taps, 0, FftNorm::Forward); let start = (k - 1) / 2;
184 let last = self.shape(full).rank() - 1;
185 self.narrow_(full, last, start, l)
186 }
187
188 pub fn biquad(&mut self, x: NodeId, b: [f32; 3], a: [f32; 3]) -> NodeId {
197 assert!(a[0] != 0.0, "biquad: a0 must be non-zero");
198 let (b0, b1, b2) = (b[0] / a[0], b[1] / a[0], b[2] / a[0]);
199 let (a1, a2) = (a[1] / a[0], a[2] / a[0]);
200
201 let xs_shape = self.shape(x).clone();
202 let orig_dims: Vec<i64> = xs_shape
203 .dims()
204 .iter()
205 .map(|d| d.unwrap_static() as i64)
206 .collect();
207 let rank = xs_shape.rank();
208 let n = xs_shape.dim(rank - 1).unwrap_static();
209 let p: usize = (0..rank - 1)
210 .map(|i| xs_shape.dim(i).unwrap_static())
211 .product();
212
213 let x_pn = self.reshape_(x, vec![p as i64, n as i64]);
215 let xs = self.transpose_(x_pn, vec![1, 0]); let mut body = Graph::new("biquad_body");
219 let carry = body.input("carry", Shape::new(&[p, 3], DType::F32));
220 let x_t = body.input("x_t", Shape::new(&[p], DType::F32));
221 let z1p = body.narrow_(carry, 1, 1, 1);
222 let z1p = body.reshape_(z1p, vec![p as i64]);
223 let z2p = body.narrow_(carry, 1, 2, 1);
224 let z2p = body.reshape_(z2p, vec![p as i64]);
225 let cb0 = body.constant(b0 as f64, DType::F32);
226 let cb1 = body.constant(b1 as f64, DType::F32);
227 let cb2 = body.constant(b2 as f64, DType::F32);
228 let ca1 = body.constant(a1 as f64, DType::F32);
229 let ca2 = body.constant(a2 as f64, DType::F32);
230 let b0x = body.mul(x_t, cb0);
231 let y = body.add(b0x, z1p); let b1x = body.mul(x_t, cb1);
233 let a1y = body.mul(y, ca1);
234 let z1_tmp = body.sub(b1x, a1y);
235 let z1 = body.add(z1_tmp, z2p); let b2x = body.mul(x_t, cb2);
237 let a2y = body.mul(y, ca2);
238 let z2 = body.sub(b2x, a2y); let yc = body.reshape_(y, vec![p as i64, 1]);
240 let z1c = body.reshape_(z1, vec![p as i64, 1]);
241 let z2c = body.reshape_(z2, vec![p as i64, 1]);
242 let next = body.concat_(vec![yc, z1c, z2c], 1); body.set_outputs(vec![next]);
244
245 let init = self.const_f32_tensor(vec![0.0; p * 3], &[p, 3]);
247 let traj = self.add_node(
248 Op::Scan {
249 body: Box::new(body),
250 length: n as u32,
251 save_trajectory: true,
252 num_bcast: 0,
253 num_xs: 1,
254 num_checkpoints: 0,
255 },
256 vec![init, xs],
257 Shape::new(&[n, p, 3], DType::F32),
258 );
259 let y_np = self.narrow_(traj, 2, 0, 1); let y_np = self.reshape_(y_np, vec![n as i64, p as i64]);
262 let y_pn = self.transpose_(y_np, vec![1, 0]); self.reshape_(y_pn, orig_dims)
264 }
265
266 pub fn sosfilt(&mut self, x: NodeId, sections: &[([f32; 3], [f32; 3])]) -> NodeId {
269 let mut y = x;
270 for &(b, a) in sections {
271 y = self.biquad(y, b, a);
272 }
273 y
274 }
275
276 pub fn sosfiltfilt(&mut self, x: NodeId, sections: &[([f32; 3], [f32; 3])]) -> NodeId {
280 let last = self.shape(x).rank() - 1;
281 let f1 = self.sosfilt(x, sections);
282 let r1 = self.reverse(f1, vec![last]);
283 let f2 = self.sosfilt(r1, sections);
284 self.reverse(f2, vec![last])
285 }
286
287 pub fn resample_poly(&mut self, x: NodeId, up: usize, down: usize, taps: &[f32]) -> NodeId {
295 assert!(up >= 1 && down >= 1, "resample_poly: up/down must be ≥1");
296 assert!(!taps.is_empty(), "resample_poly: need ≥1 tap");
297 let shape = self.shape(x).clone();
298 let rank = shape.rank();
299 let last = rank - 1;
300 let n = shape.dim(last).unwrap_static();
301 let lead: Vec<i64> = (0..last)
302 .map(|i| shape.dim(i).unwrap_static() as i64)
303 .collect();
304
305 let up_sig = if up == 1 {
307 x
308 } else {
309 let mut d1: Vec<i64> = lead.clone();
310 d1.push(n as i64);
311 d1.push(1);
312 let xr = self.reshape_(x, d1);
313 let mut zdims: Vec<usize> = shape
314 .dims()
315 .iter()
316 .take(last)
317 .map(|d| d.unwrap_static())
318 .collect();
319 zdims.push(n);
320 zdims.push(up - 1);
321 let zeros = self.const_f32_tensor(vec![0.0; zdims.iter().product()], &zdims);
322 let stacked = self.concat_(vec![xr, zeros], last + 1); let mut flat: Vec<i64> = lead.clone();
324 flat.push((n * up) as i64);
325 self.reshape_(stacked, flat)
326 };
327
328 let l = n * up;
330 let scaled: Vec<f32> = taps.iter().map(|&t| t * up as f32).collect();
331 let h = self.const_f32_tensor(scaled, &[taps.len()]);
332 let filtered = self.fir_conv_same(up_sig, h, taps.len(), l);
333
334 if down == 1 {
337 return filtered;
338 }
339 let out_len = l.div_ceil(down);
340 let padded = out_len * down;
341 let filtered = if padded > l {
342 let mut zdims: Vec<usize> = shape
343 .dims()
344 .iter()
345 .take(last)
346 .map(|d| d.unwrap_static())
347 .collect();
348 zdims.push(padded - l);
349 let zeros = self.const_f32_tensor(vec![0.0; zdims.iter().product()], &zdims);
350 self.concat_(vec![filtered, zeros], last)
351 } else {
352 filtered
353 };
354 let mut rdims: Vec<i64> = lead.clone();
355 rdims.push(out_len as i64);
356 rdims.push(down as i64);
357 let reshaped = self.reshape_(filtered, rdims); let col0 = self.narrow_(reshaped, last + 1, 0, 1); let mut odims: Vec<i64> = lead;
360 odims.push(out_len as i64);
361 self.reshape_(col0, odims)
362 }
363
364 pub fn fir_conv1d(&mut self, x: NodeId, taps: &[f32], mode: FirMode) -> NodeId {
373 let k = taps.len();
374 assert!(k > 0, "fir_conv1d: need ≥1 tap");
375 let last = self.shape(x).rank() - 1;
376 let l = self.shape(x).dim(last).unwrap_static();
377 let full = if k <= FIR_DIRECT_MAX_TAPS {
379 self.fir_full_direct(x, taps)
380 } else {
381 let h = self.const_f32_tensor(taps.to_vec(), &[k]);
382 self.fft_conv1d(x, h, 0, FftNorm::Forward)
383 };
384 match mode {
385 FirMode::Full => full,
386 FirMode::Causal => self.narrow_(full, last, 0, l),
387 FirMode::Same => {
388 let start = (k - 1) / 2;
389 self.narrow_(full, last, start, l)
390 }
391 FirMode::Valid => {
392 assert!(
393 l + 1 > k,
394 "fir_conv1d(Valid): signal ({l}) shorter than taps ({k})"
395 );
396 self.narrow_(full, last, k - 1, l - k + 1)
397 }
398 }
399 }
400
401 fn fir_full_direct(&mut self, x: NodeId, taps: &[f32]) -> NodeId {
404 let k = taps.len();
405 let last = self.shape(x).rank() - 1;
406 let l = self.shape(x).dim(last).unwrap_static();
407 let mut acc: Option<NodeId> = None;
408 for (j, &h) in taps.iter().enumerate() {
409 if h == 0.0 {
410 continue;
411 }
412 let c = self.constant(h as f64, DType::F32);
413 let scaled = self.mul(x, c);
414 let term = self.fir_pad_last(scaled, j, k - 1 - j);
415 acc = Some(match acc {
416 None => term,
417 Some(a) => self.add(a, term),
418 });
419 }
420 match acc {
421 Some(a) => a,
422 None => {
423 let mut dims: Vec<usize> = self
424 .shape(x)
425 .dims()
426 .iter()
427 .map(|d| d.unwrap_static())
428 .collect();
429 dims[last] = l + k - 1;
430 self.fir_zeros_dims(&dims)
431 }
432 }
433 }
434
435 pub fn partitioned_conv1d(&mut self, x: NodeId, ir: NodeId, block: usize) -> NodeId {
453 let xs = self.shape(x).clone();
454 let rank = xs.rank();
455 let last = rank - 1;
456 let l = xs.dim(last).unwrap_static();
457 let ir_shape = self.shape(ir).clone();
458 assert_eq!(ir_shape.rank(), 1, "partitioned_conv1d: ir must be rank-1");
459 let m = ir_shape.dim(0).unwrap_static();
460 assert!(m >= 1 && l >= 1, "partitioned_conv1d: empty input/ir");
461
462 let b = crate::fft::next_pow2(block.max(1));
463 let n = 2 * b; let kbins = n / 2 + 1;
465 let p = m.div_ceil(b);
466 let mf = (l + m - 1).div_ceil(b);
467
468 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)
479 .map(|i| xs.dim(i).unwrap_static() as i64)
480 .collect();
481 blk_dims.push((mf + 1) as i64);
482 blk_dims.push(b as i64);
483 let blocks = self.reshape_(x_pad, blk_dims); let brank = self.shape(blocks).rank();
485 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;
494 let mut acc: Option<(NodeId, NodeId)> = None;
495 for pp in 0..p.min(mf) {
496 let hp_re = self.narrow_(h_re, 0, pp, 1);
497 let hp_re = self.reshape_(hp_re, vec![kbins as i64]); let hp_im = self.narrow_(h_im, 0, pp, 1);
499 let hp_im = self.reshape_(hp_im, vec![kbins as i64]); let (xr_s, xi_s) = if pp == 0 {
502 (x_re, x_im)
503 } else {
504 let keep = mf - pp;
505 let mut zdims: Vec<usize> = self
506 .shape(x_re)
507 .dims()
508 .iter()
509 .map(|d| d.unwrap_static())
510 .collect();
511 zdims[mf_axis] = pp;
512 let zre = self.fir_zeros_dims(&zdims);
513 let zim = self.fir_zeros_dims(&zdims);
514 let hr = self.narrow_(x_re, mf_axis, 0, keep);
515 let hi = self.narrow_(x_im, mf_axis, 0, keep);
516 (
517 self.concat_(vec![zre, hr], mf_axis),
518 self.concat_(vec![zim, hi], mf_axis),
519 )
520 };
521 let rr = self.mul(hp_re, xr_s);
523 let ii = self.mul(hp_im, xi_s);
524 let t_re = self.sub(rr, ii);
525 let ri = self.mul(hp_re, xi_s);
526 let ir2 = self.mul(hp_im, xr_s);
527 let t_im = self.add(ri, ir2);
528 acc = Some(match acc {
529 None => (t_re, t_im),
530 Some((ar, ai)) => (self.add(ar, t_re), self.add(ai, t_im)),
531 });
532 }
533 let (y_re, y_im) = acc.expect("partitioned_conv1d: no partitions");
534
535 let y_frames = self.irfft(y_re, y_im, n, FftNorm::Forward); let ylast = self.shape(y_frames).rank() - 1;
538 let valid = self.narrow_(y_frames, ylast, b, b); let mut out_dims: Vec<i64> = (0..last)
540 .map(|i| xs.dim(i).unwrap_static() as i64)
541 .collect();
542 out_dims.push((mf * b) as i64);
543 let flat = self.reshape_(valid, out_dims); let flast = self.shape(flat).rank() - 1;
545 self.narrow_(flat, flast, 0, l + m - 1) }
547
548 pub fn conv_reverb(&mut self, x: NodeId, ir: &[f32], block: usize) -> NodeId {
555 assert!(!ir.is_empty(), "conv_reverb: empty impulse response");
556 let ir_node = self.const_f32_tensor(ir.to_vec(), &[ir.len()]);
557 self.partitioned_conv1d(x, ir_node, block)
558 }
559
560 pub fn partitioned_conv(&mut self, x: NodeId, ir: NodeId, block: usize) -> NodeId {
566 let xs = self.shape(x).clone();
567 let last = xs.rank() - 1;
568 let l = xs.dim(last).unwrap_static();
569 let ir_shape = self.shape(ir).clone();
570 assert_eq!(ir_shape.rank(), 1, "partitioned_conv: ir must be rank-1");
571 let m = ir_shape.dim(0).unwrap_static();
572 let mut dims: Vec<usize> = xs.dims().iter().map(|d| d.unwrap_static()).collect();
573 dims[last] = l + m - 1;
574 let out_shape = Shape::new(&dims, xs.dtype());
575 self.push(Op::PartitionedConv { block }, vec![x, ir], out_shape, None)
576 }
577
578 pub fn partitioned_conv1d_gemm(&mut self, x: NodeId, ir: NodeId, block: usize) -> NodeId {
588 let xs = self.shape(x).clone();
589 let rank = xs.rank();
590 let last = rank - 1;
591 let l = xs.dim(last).unwrap_static();
592 let ir_shape = self.shape(ir).clone();
593 assert_eq!(
594 ir_shape.rank(),
595 1,
596 "partitioned_conv1d_gemm: ir must be rank-1"
597 );
598 let m = ir_shape.dim(0).unwrap_static();
599 assert!(m >= 1 && l >= 1, "partitioned_conv1d_gemm: empty input/ir");
600
601 let b = crate::fft::next_pow2(block.max(1));
602 let n = 2 * b;
603 let kbins = n / 2 + 1;
604 let p = m.div_ceil(b);
605 let mf = (l + m - 1).div_ceil(b);
606
607 let ir_pad = self.fir_pad_last(ir, 0, p * b - m);
609 let ir_blocks = self.reshape_(ir_pad, vec![p as i64, b as i64]);
610 let ir_zeros = self.fir_zeros_dims(&[p, b]);
611 let ir_frames = self.concat_(vec![ir_blocks, ir_zeros], 1);
612 let (h_re, h_im) = self.rfft(ir_frames, FftNorm::Forward); let back = mf * b - l;
616 let x_pad = self.fir_pad_last(x, b, back);
617 let mut blk_dims: Vec<i64> = (0..last)
618 .map(|i| xs.dim(i).unwrap_static() as i64)
619 .collect();
620 blk_dims.push((mf + 1) as i64);
621 blk_dims.push(b as i64);
622 let blocks = self.reshape_(x_pad, blk_dims);
623 let brank = self.shape(blocks).rank();
624 let baxis = brank - 2;
625 let blast = brank - 1;
626 let left = self.narrow_(blocks, baxis, 0, mf);
627 let right = self.narrow_(blocks, baxis, 1, mf);
628 let frames = self.concat_(vec![left, right], blast);
629 let (x_re, x_im) = self.rfft(frames, FftNorm::Forward); let xr = self.shape(x_re).rank();
634 let mf_axis = xr - 2;
635 let lead: Vec<usize> = (0..mf_axis)
636 .map(|i| self.shape(x_re).dim(i).unwrap_static())
637 .collect();
638 let rows: usize = lead.iter().product::<usize>() * mf;
639
640 let mut u_re_parts = Vec::with_capacity(p);
641 let mut u_im_parts = Vec::with_capacity(p);
642 for pp in 0..p {
643 let (sre, sim) = if pp == 0 {
644 (x_re, x_im)
645 } else {
646 let keep = mf - pp;
647 let mut zd: Vec<usize> = self
648 .shape(x_re)
649 .dims()
650 .iter()
651 .map(|d| d.unwrap_static())
652 .collect();
653 zd[mf_axis] = pp;
654 let zre = self.fir_zeros_dims(&zd);
655 let zim = self.fir_zeros_dims(&zd);
656 let hr = self.narrow_(x_re, mf_axis, 0, keep);
657 let hi = self.narrow_(x_im, mf_axis, 0, keep);
658 (
659 self.concat_(vec![zre, hr], mf_axis),
660 self.concat_(vec![zim, hi], mf_axis),
661 )
662 };
663 let mut d: Vec<i64> = self
665 .shape(sre)
666 .dims()
667 .iter()
668 .map(|x| x.unwrap_static() as i64)
669 .collect();
670 d.insert(d.len() - 1, 1);
671 u_re_parts.push(self.reshape_(sre, d.clone()));
672 u_im_parts.push(self.reshape_(sim, d));
673 }
674 let u_re = self.concat_(u_re_parts, mf_axis + 1); let u_im = self.concat_(u_im_parts, mf_axis + 1);
676
677 let u_re = self.reshape_(u_re, vec![rows as i64, p as i64, kbins as i64]);
679 let u_im = self.reshape_(u_im, vec![rows as i64, p as i64, kbins as i64]);
680 let u_re = self.transpose_(u_re, vec![2, 0, 1]); let u_im = self.transpose_(u_im, vec![2, 0, 1]);
682 let h_re_t = self.transpose_(h_re, vec![1, 0]); let h_im_t = self.transpose_(h_im, vec![1, 0]);
684 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]);
686
687 let out_krp = Shape::new(&[kbins, rows, 1], DType::F32);
688 let rr = self.matmul(u_re, h_re_c, out_krp.clone());
689 let ii = self.matmul(u_im, h_im_c, out_krp.clone());
690 let y_re_k = self.sub(rr, ii); let ri = self.matmul(u_re, h_im_c, out_krp.clone());
692 let ir2 = self.matmul(u_im, h_re_c, out_krp);
693 let y_im_k = self.add(ri, ir2); let y_re = self.reshape_(y_re_k, vec![kbins as i64, rows as i64]);
697 let y_re = self.transpose_(y_re, vec![1, 0]);
698 let y_im = self.reshape_(y_im_k, vec![kbins as i64, rows as i64]);
699 let y_im = self.transpose_(y_im, vec![1, 0]);
700 let mut yd: Vec<i64> = lead.iter().map(|&d| d as i64).collect();
701 yd.push(mf as i64);
702 yd.push(kbins as i64);
703 let y_re = self.reshape_(y_re, yd.clone());
704 let y_im = self.reshape_(y_im, yd);
705
706 let y_frames = self.irfft(y_re, y_im, n, FftNorm::Forward);
708 let ylast = self.shape(y_frames).rank() - 1;
709 let valid = self.narrow_(y_frames, ylast, b, b);
710 let mut out_dims: Vec<i64> = (0..last)
711 .map(|i| xs.dim(i).unwrap_static() as i64)
712 .collect();
713 out_dims.push((mf * b) as i64);
714 let flat = self.reshape_(valid, out_dims);
715 let flast = self.shape(flat).rank() - 1;
716 self.narrow_(flat, flast, 0, l + m - 1)
717 }
718
719 pub fn iirfilt(&mut self, x: NodeId, b: &[f32], a: &[f32]) -> NodeId {
729 assert!(
730 !b.is_empty() && !a.is_empty(),
731 "iirfilt: empty coefficients"
732 );
733 assert!(a[0] != 0.0, "iirfilt: a0 must be non-zero");
734 let m = b.len().max(a.len());
735 let a0 = a[0];
736 let bn: Vec<f32> = (0..m)
737 .map(|i| b.get(i).copied().unwrap_or(0.0) / a0)
738 .collect();
739 let an: Vec<f32> = (0..m)
740 .map(|i| a.get(i).copied().unwrap_or(0.0) / a0)
741 .collect();
742 let s = m - 1; let xs_shape = self.shape(x).clone();
745 let orig_dims: Vec<i64> = xs_shape
746 .dims()
747 .iter()
748 .map(|d| d.unwrap_static() as i64)
749 .collect();
750 let rank = xs_shape.rank();
751 let nlen = xs_shape.dim(rank - 1).unwrap_static();
752 let pch: usize = (0..rank - 1)
753 .map(|i| xs_shape.dim(i).unwrap_static())
754 .product();
755
756 let x_pn = self.reshape_(x, vec![pch as i64, nlen as i64]);
758 let xs = self.transpose_(x_pn, vec![1, 0]); let cw = 1 + s;
762 let mut body = Graph::new("iir_body");
763 let carry = body.input("carry", Shape::new(&[pch, cw], DType::F32));
764 let x_t = body.input("x_t", Shape::new(&[pch], DType::F32));
765 let w: Vec<NodeId> = (0..s)
766 .map(|i| {
767 let wi = body.narrow_(carry, 1, 1 + i, 1);
768 body.reshape_(wi, vec![pch as i64])
769 })
770 .collect();
771 let cbn: Vec<NodeId> = (0..m)
772 .map(|i| body.constant(bn[i] as f64, DType::F32))
773 .collect();
774 let can: Vec<NodeId> = (0..m)
775 .map(|i| body.constant(an[i] as f64, DType::F32))
776 .collect();
777 let b0x = body.mul(x_t, cbn[0]);
779 let y = if s > 0 { body.add(b0x, w[0]) } else { b0x };
780 let mut new_w = Vec::with_capacity(s);
782 for i in 0..s {
783 let bx = body.mul(x_t, cbn[i + 1]);
784 let ay = body.mul(y, can[i + 1]);
785 let mut wi = body.sub(bx, ay);
786 if i + 1 < s {
787 wi = body.add(wi, w[i + 1]);
788 }
789 new_w.push(wi);
790 }
791 let mut cols = Vec::with_capacity(cw);
793 cols.push(body.reshape_(y, vec![pch as i64, 1]));
794 for wi in new_w {
795 cols.push(body.reshape_(wi, vec![pch as i64, 1]));
796 }
797 let next = body.concat_(cols, 1);
798 body.set_outputs(vec![next]);
799
800 let init = self.const_f32_tensor(vec![0.0; pch * cw], &[pch, cw]);
801 let traj = self.add_node(
802 Op::Scan {
803 body: Box::new(body),
804 length: nlen as u32,
805 save_trajectory: true,
806 num_bcast: 0,
807 num_xs: 1,
808 num_checkpoints: 0,
809 },
810 vec![init, xs],
811 Shape::new(&[nlen, pch, cw], DType::F32),
812 );
813 let y_np = self.narrow_(traj, 2, 0, 1);
815 let y_np = self.reshape_(y_np, vec![nlen as i64, pch as i64]);
816 let y_pn = self.transpose_(y_np, vec![1, 0]);
817 self.reshape_(y_pn, orig_dims)
818 }
819
820 pub fn iir_as_fir(
829 &mut self,
830 x: NodeId,
831 b: &[f32],
832 a: &[f32],
833 taps: usize,
834 mode: FirMode,
835 ) -> NodeId {
836 let h = iir_impulse_response(b, a, taps);
837 self.fir_conv1d(x, &h, mode)
838 }
839
840 fn fir_zeros_dims(&mut self, dims: &[usize]) -> NodeId {
842 self.const_f32_tensor(vec![0.0; dims.iter().product()], dims)
843 }
844
845 fn fir_pad_last(&mut self, x: NodeId, front: usize, back: usize) -> NodeId {
847 if front == 0 && back == 0 {
848 return x;
849 }
850 let last = self.shape(x).rank() - 1;
851 let mut zdims: Vec<usize> = self
852 .shape(x)
853 .dims()
854 .iter()
855 .map(|d| d.unwrap_static())
856 .collect();
857 let mut parts = Vec::new();
858 if front > 0 {
859 zdims[last] = front;
860 let z = self.fir_zeros_dims(&zdims);
861 parts.push(z);
862 }
863 parts.push(x);
864 if back > 0 {
865 zdims[last] = back;
866 let z = self.fir_zeros_dims(&zdims);
867 parts.push(z);
868 }
869 self.concat_(parts, last)
870 }
871
872 fn complex_abs(&mut self, re: NodeId, im: NodeId) -> NodeId {
873 let re2 = self.mul(re, re);
874 let im2 = self.mul(im, im);
875 let sum = self.add(re2, im2);
876 self.sqrt(sum)
877 }
878}
879
880#[cfg(test)]
881mod tests {
882 use super::*;
883
884 fn dims(g: &Graph, id: NodeId) -> Vec<usize> {
885 g.shape(id)
886 .dims()
887 .iter()
888 .map(|d| d.unwrap_static())
889 .collect()
890 }
891 fn input(g: &mut Graph, shape: &[usize]) -> NodeId {
892 g.input("x", Shape::new(shape, DType::F32))
893 }
894
895 #[test]
896 fn hilbert_preserves_shape() {
897 let mut g = Graph::new("hil");
898 let x = input(&mut g, &[3, 128]);
899 let (re, im) = g.hilbert(x);
900 assert_eq!(dims(&g, re), vec![3, 128]);
901 assert_eq!(dims(&g, im), vec![3, 128]);
902 }
903
904 #[test]
905 fn envelope_and_phase_shape() {
906 let mut g = Graph::new("env");
907 let x = input(&mut g, &[2, 100]);
908 let e = g.envelope(x);
909 let p = g.instantaneous_phase(x);
910 assert_eq!(dims(&g, e), vec![2, 100]);
911 assert_eq!(dims(&g, p), vec![2, 100]);
912 }
913
914 #[test]
915 fn filtfilt_same_length() {
916 let mut g = Graph::new("ff");
917 let x = input(&mut g, &[2, 200]);
918 let taps: Vec<f32> = (0..15).map(|i| 1.0 / (i as f32 + 1.0)).collect();
919 let y = g.fir_filtfilt(x, &taps);
920 assert_eq!(dims(&g, y), vec![2, 200]);
921 }
922
923 #[test]
924 fn biquad_and_sosfilt_same_length() {
925 let mut g = Graph::new("iir");
926 let x = input(&mut g, &[3, 128]);
927 let b = [0.2, 0.4, 0.2];
928 let a = [1.0, -0.3, 0.1];
929 let y = g.biquad(x, b, a);
930 assert_eq!(dims(&g, y), vec![3, 128]);
931 let sos = [(b, a), ([0.5, 0.0, 0.5], [1.0, 0.2, 0.05])];
932 let z = g.sosfiltfilt(x, &sos);
933 assert_eq!(dims(&g, z), vec![3, 128]);
934 }
935
936 #[test]
937 fn resample_poly_length() {
938 let mut g = Graph::new("rs");
939 let x = input(&mut g, &[2, 100]);
940 let taps: Vec<f32> = (0..13).map(|i| 1.0 / (i as f32 + 1.0)).collect();
941 let y = g.resample_poly(x, 3, 2, &taps);
943 assert_eq!(dims(&g, y), vec![2, 150]);
944 }
945
946 #[test]
947 fn fir_conv1d_modes_lengths() {
948 let taps: Vec<f32> = (0..9).map(|i| 1.0 / (i as f32 + 1.0)).collect();
949 for &(mode, want) in &[
950 (FirMode::Full, 64 + 9 - 1),
951 (FirMode::Same, 64),
952 (FirMode::Valid, 64 - 9 + 1),
953 (FirMode::Causal, 64),
954 ] {
955 let mut g = Graph::new("fir");
956 let x = input(&mut g, &[2, 64]);
957 let y = g.fir_conv1d(x, &taps, mode);
958 assert_eq!(dims(&g, y), vec![2, want], "mode {mode:?}");
959 }
960 }
961
962 #[test]
963 fn fir_conv1d_long_uses_fft_path() {
964 let taps: Vec<f32> = (0..100).map(|i| ((i as f32) * 0.01).cos()).collect();
966 let mut g = Graph::new("firfft");
967 let x = input(&mut g, &[130]);
968 let y = g.fir_conv1d(x, &taps, FirMode::Full);
969 assert_eq!(dims(&g, y), vec![130 + 100 - 1]);
970 }
971
972 #[test]
973 fn conv_reverb_full_length() {
974 let mut g = Graph::new("rir");
975 let x = input(&mut g, &[2, 100]);
976 let ir: Vec<f32> = (0..300).map(|i| (-(i as f32) * 0.02).exp()).collect();
977 let y = g.conv_reverb(x, &ir, 64);
978 assert_eq!(dims(&g, y), vec![2, 100 + 300 - 1]);
979 }
980
981 #[test]
982 fn partitioned_conv1d_node_ir() {
983 let mut g = Graph::new("pconv");
984 let x = input(&mut g, &[50]);
985 let ir = g.const_f32_tensor((0..70).map(|i| i as f32 * 0.1).collect(), &[70]);
986 let y = g.partitioned_conv1d(x, ir, 32);
987 assert_eq!(dims(&g, y), vec![50 + 70 - 1]);
988 }
989
990 #[test]
991 fn partitioned_conv1d_gemm_shape() {
992 let mut g = Graph::new("pgemm");
993 let x = input(&mut g, &[2, 50]);
994 let ir = g.const_f32_tensor((0..70).map(|i| i as f32 * 0.1).collect(), &[70]);
995 let y = g.partitioned_conv1d_gemm(x, ir, 32);
996 assert_eq!(dims(&g, y), vec![2, 50 + 70 - 1]);
997 }
998
999 #[test]
1000 fn partitioned_conv_op_shape() {
1001 let mut g = Graph::new("pop");
1002 let x = input(&mut g, &[3, 40]);
1003 let ir = g.const_f32_tensor((0..100).map(|i| i as f32 * 0.01).collect(), &[100]);
1004 let y = g.partitioned_conv(x, ir, 32);
1005 assert_eq!(dims(&g, y), vec![3, 40 + 100 - 1]);
1007 }
1008
1009 #[test]
1010 fn iirfilt_high_order_same_length() {
1011 let mut g = Graph::new("iirN");
1012 let x = input(&mut g, &[3, 128]);
1013 let b = [0.1, 0.2, 0.2, 0.1];
1015 let a = [1.0, -0.5, 0.3, -0.1];
1016 let y = g.iirfilt(x, &b, &a);
1017 assert_eq!(dims(&g, y), vec![3, 128]);
1018 }
1019
1020 #[test]
1021 fn iir_as_fir_causal_same_length() {
1022 let mut g = Graph::new("iirfir");
1023 let x = input(&mut g, &[2, 100]);
1024 let b = [0.2, 0.4, 0.2];
1025 let a = [1.0, -0.3, 0.1];
1026 let y = g.iir_as_fir(x, &b, &a, 48, FirMode::Causal);
1027 assert_eq!(dims(&g, y), vec![2, 100]);
1028 }
1029}