1use crate::infer::GraphExt as _;
17use crate::op::Activation;
18use crate::{DType, Graph, NodeId, fft::FftNorm};
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum WindowKind {
23 Rectangular,
25 Hann,
27 Hamming,
29 Blackman,
31}
32
33impl WindowKind {
34 fn coeffs(self, n: usize) -> Vec<f32> {
35 use std::f32::consts::PI;
36 if n == 1 {
37 return vec![1.0];
38 }
39 let d = (n - 1) as f32;
40 (0..n)
41 .map(|i| {
42 let t = i as f32 / d;
43 match self {
44 WindowKind::Rectangular => 1.0,
45 WindowKind::Hann => 0.5 - 0.5 * (2.0 * PI * t).cos(),
46 WindowKind::Hamming => 0.54 - 0.46 * (2.0 * PI * t).cos(),
47 WindowKind::Blackman => {
48 0.42 - 0.5 * (2.0 * PI * t).cos() + 0.08 * (4.0 * PI * t).cos()
49 }
50 }
51 })
52 .collect()
53 }
54}
55
56impl Graph {
57 pub fn spectrogram(
65 &mut self,
66 x: NodeId,
67 frame_len: usize,
68 hop: usize,
69 window: WindowKind,
70 power: bool,
71 log: bool,
72 ) -> NodeId {
73 assert!(frame_len > 0 && hop > 0, "spectrogram: frame_len/hop > 0");
74 let shape = self.shape(x).clone();
75 let last = shape.rank() - 1;
76 let t = shape.dim(last).unwrap_static();
77 assert!(t >= frame_len, "spectrogram: T {t} < frame_len {frame_len}");
78 let n_frames = 1 + (t - frame_len) / hop;
79
80 let mut rows = Vec::with_capacity(n_frames);
82 for f in 0..n_frames {
83 let start = f * hop;
84 let frame = self.narrow_(x, last, start, frame_len);
85 let mut dims: Vec<i64> = self
86 .shape(frame)
87 .dims()
88 .iter()
89 .map(|d| d.unwrap_static() as i64)
90 .collect();
91 dims.insert(0, 1);
92 rows.push(self.reshape_(frame, dims));
93 }
94 let framed = if rows.len() == 1 {
95 rows.pop().unwrap()
96 } else {
97 self.concat_(rows, 0)
98 };
99
100 let framed = if window == WindowKind::Rectangular {
102 framed
103 } else {
104 let w = self.const_f32_tensor(window.coeffs(frame_len), &[frame_len]);
105 self.mul(framed, w)
106 };
107
108 let (re, im) = self.rfft(framed, FftNorm::Backward);
109 let re2 = self.mul(re, re);
110 let im2 = self.mul(im, im);
111 let mag2 = self.add(re2, im2);
112 let out = if power { mag2 } else { self.sqrt(mag2) };
113 if log { self.log_eps(out, 1e-8) } else { out }
114 }
115
116 pub fn band_power(&mut self, x: NodeId, sample_rate: f32, bands: &[(f32, f32)]) -> NodeId {
122 assert!(!bands.is_empty(), "band_power: need ≥1 band");
123 let shape = self.shape(x).clone();
124 let last = shape.rank() - 1;
125 let t = shape.dim(last).unwrap_static();
126 let n_pad = crate::fft::next_pow2(t);
127 let n_bins = n_pad / 2 + 1;
128
129 let (re, im) = self.rfft(x, FftNorm::Backward);
130 let re2 = self.mul(re, re);
131 let im2 = self.mul(im, im);
132 let power = self.add(re2, im2); let plast = self.shape(power).rank() - 1;
134
135 let hz_per_bin = sample_rate / n_pad as f32;
136 let mut cols = Vec::with_capacity(bands.len());
137 for &(lo, hi) in bands {
138 let mut b0 = (lo / hz_per_bin).ceil() as isize;
139 let mut b1 = (hi / hz_per_bin).floor() as isize;
140 b0 = b0.clamp(0, n_bins as isize - 1);
141 b1 = b1.clamp(0, n_bins as isize - 1);
142 let (b0, b1) = if b1 < b0 { (b0, b0) } else { (b0, b1) };
143 let seg = self.narrow_(power, plast, b0 as usize, (b1 - b0 + 1) as usize);
144 cols.push(self.sum(seg, vec![plast], true)); }
146 if cols.len() == 1 {
147 cols.pop().unwrap()
148 } else {
149 self.concat_(cols, plast)
150 }
151 }
152
153 pub fn differential_entropy(
159 &mut self,
160 x: NodeId,
161 sample_rate: f32,
162 bands: &[(f32, f32)],
163 ) -> NodeId {
164 let bp = self.band_power(x, sample_rate, bands);
165 let c = self.constant(2.0 * std::f64::consts::PI * std::f64::consts::E, DType::F32);
167 let scaled = self.mul(bp, c);
168 let logv = self.log_eps(scaled, 1e-8);
169 let half = self.constant(0.5, DType::F32);
170 self.mul(logv, half)
171 }
172
173 fn log_eps(&mut self, x: NodeId, eps: f32) -> NodeId {
174 let e = self.constant(eps as f64, DType::F32);
175 let shifted = self.add(x, e);
176 let s = crate::shape::unary_shape(self.shape(shifted));
177 self.activation(Activation::Log, shifted, s)
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::Shape;
185
186 fn dims(g: &Graph, id: NodeId) -> Vec<usize> {
187 g.shape(id)
188 .dims()
189 .iter()
190 .map(|d| d.unwrap_static())
191 .collect()
192 }
193 fn input(g: &mut Graph, shape: &[usize]) -> NodeId {
194 g.input("x", Shape::new(shape, DType::F32))
195 }
196
197 #[test]
198 fn spectrogram_shape() {
199 let mut g = Graph::new("spec");
200 let x = input(&mut g, &[2, 256]); let y = g.spectrogram(x, 64, 32, WindowKind::Hann, true, true);
203 assert_eq!(dims(&g, y), vec![7, 2, 33]);
204 }
205
206 #[test]
207 fn band_power_shape() {
208 let mut g = Graph::new("bp");
209 let x = input(&mut g, &[4, 512]); let bands = [
211 (0.5, 4.0),
212 (4.0, 8.0),
213 (8.0, 13.0),
214 (13.0, 30.0),
215 (30.0, 45.0),
216 ];
217 let y = g.band_power(x, 128.0, &bands);
218 assert_eq!(dims(&g, y), vec![4, 5]);
219 }
220
221 #[test]
222 fn differential_entropy_shape() {
223 let mut g = Graph::new("de");
224 let x = input(&mut g, &[4, 512]);
225 let bands = [(1.0, 4.0), (4.0, 8.0), (8.0, 14.0)];
226 let y = g.differential_entropy(x, 200.0, &bands);
227 assert_eq!(dims(&g, y), vec![4, 3]);
228 }
229}