1use ndarray::{Array2, ArrayView1};
2use num_complex::Complex;
3use rayon::prelude::*;
4use thiserror::Error;
5
6#[derive(Error, Debug)]
11pub enum HarmonicsError {
12 #[error("Length mismatch: {0} vs {1}")]
14 LengthMismatch(usize, usize),
15
16 #[error("Invalid input: {0}")]
18 InvalidInput(String),
19
20 #[error("Computation failed: {0}")]
22 ComputationFailed(String),
23}
24
25pub fn interp_harmonics(x: &[f32], freqs: &[f32], harmonics: &[f32]) -> Result<Array2<f32>, HarmonicsError> {
44 if x.len() != freqs.len() {
45 return Err(HarmonicsError::LengthMismatch(x.len(), freqs.len()));
46 }
47 if x.is_empty() {
48 return Err(HarmonicsError::InvalidInput("Amplitude spectrum is empty".to_string()));
49 }
50 if !freqs.windows(2).all(|w| w[0] <= w[1]) {
51 return Err(HarmonicsError::InvalidInput("Frequency bins must be sorted".to_string()));
52 }
53
54 let n_bins = freqs.len();
55 let n_harmonics = harmonics.len();
56 let mut result = Array2::zeros((n_harmonics, n_bins));
57
58 result.axis_iter_mut(ndarray::Axis(0))
59 .into_par_iter()
60 .enumerate()
61 .for_each(|(h_idx, mut row)| {
62 let h = harmonics[h_idx];
63 for (bin, &f) in freqs.iter().enumerate() {
64 let target_freq = f * h;
65 if target_freq <= *freqs.last().unwrap() {
66 let left_idx = freqs.binary_search_by(|&x| x.partial_cmp(&target_freq).unwrap())
67 .unwrap_or_else(|e| e.saturating_sub(1));
68 let left_idx = left_idx.min(n_bins - 2);
69 let right_idx = left_idx + 1;
70 let left_freq = freqs[left_idx];
71 let right_freq = freqs[right_idx];
72 let alpha = if left_freq == right_freq {
73 0.0
74 } else {
75 (target_freq - left_freq) / (right_freq - left_freq)
76 };
77 row[bin] = x[left_idx] * (1.0 - alpha) + x[right_idx] * alpha;
78 }
79 }
80 });
81
82 Ok(result)
83}
84
85pub fn salience(s: &Array2<f32>, freqs: &[f32], harmonics: &[f32], weights: Option<&[f32]>) -> Result<Array2<f32>, HarmonicsError> {
104 let n_bins = s.shape()[0];
105 let n_frames = s.shape()[1];
106 if n_bins != freqs.len() {
107 return Err(HarmonicsError::LengthMismatch(n_bins, freqs.len()));
108 }
109 if n_bins == 0 || n_frames == 0 {
110 return Err(HarmonicsError::InvalidInput("Spectrogram is empty".to_string()));
111 }
112 if !freqs.windows(2).all(|w| w[0] <= w[1]) {
113 return Err(HarmonicsError::InvalidInput("Frequency bins must be sorted".to_string()));
114 }
115
116 let n_harmonics = harmonics.len();
117 let default_weights = vec![1.0; n_harmonics];
118 let weights = weights.unwrap_or(&default_weights);
119 if weights.len() != n_harmonics {
120 return Err(HarmonicsError::LengthMismatch(weights.len(), n_harmonics));
121 }
122
123 let mut salience_map = Array2::zeros((n_bins, n_frames));
124 salience_map.axis_iter_mut(ndarray::Axis(1))
125 .into_par_iter()
126 .enumerate()
127 .for_each(|(frame, mut col)| {
128 for (bin, &f) in freqs.iter().enumerate() {
129 let mut total = 0.0;
130 for (h_idx, &h) in harmonics.iter().enumerate() {
131 let harmonic_freq = f * h;
132 if harmonic_freq <= freqs[n_bins - 1] {
133 let left_idx = freqs.binary_search_by(|&x| x.partial_cmp(&harmonic_freq).unwrap())
134 .unwrap_or_else(|e| e.saturating_sub(1));
135 let left_idx = left_idx.min(n_bins - 2);
136 let right_idx = left_idx + 1;
137 let alpha = (harmonic_freq - freqs[left_idx]) / (freqs[right_idx] - freqs[left_idx]);
138 let interp = s[[left_idx, frame]] * (1.0 - alpha) + s[[right_idx, frame]] * alpha;
139 total += interp * weights[h_idx];
140 }
141 }
142 col[bin] = total;
143 }
144 });
145
146 Ok(salience_map)
147}
148
149pub fn f0_harmonics(x: &[f32], f0: &[f32], freqs: &[f32], harmonics: &[f32]) -> Result<Array2<f32>, HarmonicsError> {
168 if x.len() != freqs.len() {
169 return Err(HarmonicsError::LengthMismatch(x.len(), freqs.len()));
170 }
171 if x.is_empty() || f0.is_empty() {
172 return Err(HarmonicsError::InvalidInput("Input arrays cannot be empty".to_string()));
173 }
174 if !freqs.windows(2).all(|w| w[0] <= w[1]) {
175 return Err(HarmonicsError::InvalidInput("Frequency bins must be sorted".to_string()));
176 }
177
178 let n_frames = f0.len();
179 let n_harmonics = harmonics.len();
180 let mut result = Array2::zeros((n_harmonics, n_frames));
181
182 result.axis_iter_mut(ndarray::Axis(1))
183 .into_par_iter()
184 .enumerate()
185 .for_each(|(frame, mut col)| {
186 let fund = f0[frame];
187 for (h_idx, &h) in harmonics.iter().enumerate() {
188 let target_freq = fund * h;
189 if target_freq <= freqs[freqs.len() - 1] {
190 let left_idx = freqs.binary_search_by(|&x| x.partial_cmp(&target_freq).unwrap())
191 .unwrap_or_else(|e| e.saturating_sub(1));
192 let left_idx = left_idx.min(freqs.len() - 2);
193 let right_idx = left_idx + 1;
194 let alpha = (target_freq - freqs[left_idx]) / (freqs[right_idx] - freqs[left_idx]);
195 col[h_idx] = x[left_idx] * (1.0 - alpha) + x[right_idx] * alpha;
196 }
197 }
198 });
199
200 Ok(result)
201}
202
203pub fn phase_vocoder(
222 d: &Array2<Complex<f32>>,
223 rate: f32,
224 hop_length: Option<usize>,
225 n_fft: Option<usize>,
226) -> Result<Array2<Complex<f32>>, HarmonicsError> {
227 if rate <= 0.0 {
228 return Err(HarmonicsError::InvalidInput("Rate must be positive".to_string()));
229 }
230 let n_bins = d.shape()[0];
231 let orig_frames = d.shape()[1];
232 if n_bins == 0 || orig_frames == 0 {
233 return Err(HarmonicsError::InvalidInput("Spectrogram is empty".to_string()));
234 }
235
236 let n_fft = n_fft.unwrap_or((n_bins - 1) * 2);
237 let hop = hop_length.unwrap_or(n_fft / 4);
238 if hop == 0 {
239 return Err(HarmonicsError::InvalidInput("Hop length must be positive".to_string()));
240 }
241
242 let new_frames = ((orig_frames as f32 * hop as f32) / rate / hop as f32).ceil() as usize;
243 let mut output = Array2::zeros((n_bins, new_frames));
244 let mut phase_acc = Array2::zeros((n_bins, 1));
245
246 let omega = (0..n_bins).map(|k| 2.0 * std::f32::consts::PI * k as f32 / n_fft as f32).collect::<Vec<f32>>();
247
248 for t in 0..new_frames {
249 let orig_t = (t as f32 * rate * hop as f32 / hop as f32) as usize;
250 let orig_t_next = ((t + 1) as f32 * rate * hop as f32 / hop as f32) as usize;
251
252 if orig_t >= orig_frames || orig_t_next >= orig_frames {
253 continue;
254 }
255
256 let mag = d.column(orig_t).mapv(|c| c.norm());
257 let phase = d.column(orig_t).mapv(|c| c.arg());
258 let phase_next = d.column(orig_t_next).mapv(|c| c.arg());
259 let delta_phase = phase_next - phase - &ArrayView1::from(&omega) * hop as f32;
260
261 let delta_phase_unwrapped: Vec<f32> = delta_phase.iter()
262 .map(|&dp| dp - 2.0 * std::f32::consts::PI * (dp / (2.0 * std::f32::consts::PI)).round())
263 .collect();
264
265 let phase_advance = ArrayView1::from(&delta_phase_unwrapped).mapv(|x| x / hop as f32 * (hop as f32 * rate));
266 phase_acc = phase_acc + phase_advance;
267
268 output.column_mut(t).assign(&mag.mapv(|m| Complex::from_polar(m, phase_acc[[0, 0]])));
269 }
270
271 Ok(output)
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use ndarray::array;
278
279 #[test]
280 fn test_interp_harmonics() {
281 let x = vec![0.1, 0.2, 0.3, 0.4];
282 let freqs = vec![0.0, 100.0, 200.0, 300.0];
283 let harmonics = vec![1.0, 2.0];
284 let result = interp_harmonics(&x, &freqs, &harmonics).unwrap();
285 assert_eq!(result.shape(), &[2, 4]);
286 assert_eq!(result[[0, 0]], 0.1);
287 assert_eq!(result[[1, 0]], 0.1);
288 assert_eq!(result[[0, 1]], 0.2);
289 assert_eq!(result[[1, 1]], 0.3);
290 }
291
292 #[test]
293 fn test_interp_harmonics_mismatch() {
294 let x = vec![0.1, 0.2];
295 let freqs = vec![0.0, 100.0, 200.0];
296 let harmonics = vec![1.0];
297 let result = interp_harmonics(&x, &freqs, &harmonics);
298 assert!(matches!(result, Err(HarmonicsError::LengthMismatch(2, 3))));
299 }
300
301 #[test]
302 fn test_salience() {
303 let s = array![[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9], [1.0, 1.1, 1.2]];
304 let freqs = vec![0.0, 100.0, 200.0, 300.0];
305 let harmonics = vec![1.0, 2.0];
306 let weights: Option<&[f32]> = Some(&[1.0, 0.5]);
307 let result = salience(&s, &freqs, &harmonics, weights).unwrap();
308 assert_eq!(result.shape(), &[4, 3]);
309 assert_eq!(result[[0, 0]], 0.1 + 0.1 * 0.5);
310 assert_eq!(result[[1, 0]], 0.4 + 0.35);
311 }
312
313 #[test]
314 fn test_salience_weight_mismatch() {
315 let s = array![[0.1, 0.2], [0.3, 0.4]];
316 let freqs = vec![0.0, 100.0];
317 let harmonics = vec![1.0, 2.0];
318 let weights: Option<&[f32]> = Some(&[1.0]);
319 let result = salience(&s, &freqs, &harmonics, weights);
320 assert!(matches!(result, Err(HarmonicsError::LengthMismatch(1, 2))));
321 }
322
323 #[test]
324 fn test_f0_harmonics() {
325 let x = vec![0.1, 0.2, 0.3, 0.4];
326 let f0 = vec![100.0, 150.0];
327 let freqs = vec![0.0, 100.0, 200.0, 300.0];
328 let harmonics = vec![1.0, 2.0];
329 let result = f0_harmonics(&x, &f0, &freqs, &harmonics).unwrap();
330 assert_eq!(result.shape(), &[2, 2]);
331 assert_eq!(result[[0, 0]], 0.2);
332 assert_eq!(result[[1, 0]], 0.3);
333 assert_eq!(result[[0, 1]], 0.25);
334 }
335
336 #[test]
337 fn test_f0_harmonics_empty() {
338 let x = vec![];
339 let f0 = vec![100.0];
340 let freqs = vec![];
341 let harmonics = vec![1.0];
342 let result = f0_harmonics(&x, &f0, &freqs, &harmonics);
343 assert!(matches!(result, Err(HarmonicsError::InvalidInput(_))));
344 }
345
346 #[test]
347 fn test_phase_vocoder() {
348 let d = array![
349 [Complex::new(1.0, 0.0), Complex::new(2.0, 0.0), Complex::new(3.0, 0.0), Complex::new(4.0, 0.0)],
350 [Complex::new(5.0, 0.0), Complex::new(6.0, 0.0), Complex::new(7.0, 0.0), Complex::new(8.0, 0.0)],
351 ];
352 let result = phase_vocoder(&d, 0.5, Some(1), Some(4)).unwrap();
353 assert_eq!(result.shape(), &[2, 8]);
354 assert_eq!(result[[0, 0]].norm(), 1.0);
355 }
356
357 #[test]
358 fn test_phase_vocoder_invalid_rate() {
359 let d = array![[Complex::new(1.0, 0.0)]];
360 let result = phase_vocoder(&d, 0.0, None, None);
361 assert!(matches!(result, Err(HarmonicsError::InvalidInput(_))));
362 }
363}