dasp_rs/features/
manipulation.rs

1use ndarray::{Array1, Array2, Axis};
2
3/// Stacks delayed copies of a 2D array for temporal context.
4///
5/// # Arguments
6/// * `data` - Input 2D array (features × time)
7/// * `n_steps` - Optional number of delayed copies (defaults to 2)
8/// * `delay` - Optional delay between steps in frames (defaults to 1)
9///
10/// # Returns
11/// Returns a 2D array of shape `(n_features * n_steps, n_frames)` containing stacked features.
12///
13/// # Examples
14/// ```
15/// use ndarray::Array2;
16/// let data = Array2::from_shape_vec((2, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap();
17/// let stacked = stack_memory(&data, None, None);
18/// ```
19pub fn stack_memory(
20    data: &Array2<f32>,
21    n_steps: Option<usize>,
22    delay: Option<usize>,
23) -> Array2<f32> {
24    let n_steps = n_steps.unwrap_or(2);
25    let delay = delay.unwrap_or(1);
26    let n_frames = data.shape()[1];
27    let n_features = data.shape()[0];
28    let mut stacked = Array2::zeros((n_features * n_steps, n_frames));
29    for step in 0..n_steps {
30        let offset = step * delay;
31        for t in 0..n_frames {
32            let src_t = (t as isize - offset as isize).max(0) as usize;
33            for f in 0..n_features {
34                stacked[[f + step * n_features, t]] = data[[f, src_t]];
35            }
36        }
37    }
38    stacked
39}
40
41/// Computes temporal kurtosis from audio or spectrogram.
42///
43/// Kurtosis measures the "tailedness" of the distribution in each frame.
44///
45/// # Arguments
46/// * `y` - Optional audio time series
47/// * `S` - Optional spectrogram (features × time)
48/// * `frame_length` - Optional frame length for audio (defaults to 2048)
49/// * `hop_length` - Optional hop length for audio (defaults to frame_length/4)
50///
51/// # Returns
52/// Returns a 1D array containing kurtosis values for each frame.
53///
54/// # Panics
55/// Panics if neither `y` nor `S` is provided, or if both are provided.
56///
57/// # Examples
58/// ```
59/// let y = vec![0.1, 0.2, 0.3, 0.4, 0.5];
60/// let kurtosis = temporal_kurtosis(Some(&y), None, None, None);
61/// ```
62pub fn temporal_kurtosis(
63    y: Option<&[f32]>,
64    s: Option<&Array2<f32>>,
65    frame_length: Option<usize>,
66    hop_length: Option<usize>,
67) -> Array1<f32> {
68    let frame_len = frame_length.unwrap_or(2048);
69    let hop = hop_length.unwrap_or(frame_len / 4);
70    match (y, s) {
71        (Some(y), None) => {
72            let n_frames = (y.len() - frame_len) / hop + 1;
73            let mut kurtosis = Array1::zeros(n_frames);
74            for i in 0..n_frames {
75                let start = i * hop;
76                let frame = &y[start..(start + frame_len).min(y.len())];
77                let mean = frame.iter().sum::<f32>() / frame.len() as f32;
78                let m2 = frame.iter().map(|&x| (x - mean).powi(2)).sum::<f32>() / frame.len() as f32;
79                let m4 = frame.iter().map(|&x| (x - mean).powi(4)).sum::<f32>() / frame.len() as f32;
80                kurtosis[i] = if m2 > 1e-10 { m4 / m2.powi(2) - 3.0 } else { 0.0 };
81            }
82            kurtosis
83        }
84        (None, Some(s)) => s.axis_iter(Axis(1)).map(|frame| {
85            let mean = frame.mean().unwrap_or(0.0);
86            let m2 = frame.mapv(|x| (x - mean).powi(2)).mean().unwrap_or(0.0);
87            let m4 = frame.mapv(|x| (x - mean).powi(4)).mean().unwrap_or(0.0);
88            if m2 > 1e-10 { m4 / m2.powi(2) - 3.0 } else { 0.0 }
89        }).collect(),
90        _ => panic!("Must provide either y or S"),
91    }
92}
93
94/// Computes zero-crossing rate from an audio signal.
95///
96/// Measures the rate at which the signal changes sign in each frame.
97///
98/// # Arguments
99/// * `y` - Input audio time series
100/// * `frame_length` - Optional frame length (defaults to 2048)
101/// * `hop_length` - Optional hop length (defaults to frame_length/4)
102///
103/// # Returns
104/// Returns a 1D array containing zero-crossing rates for each frame.
105///
106/// # Examples
107/// ```
108/// let y = vec![1.0, -1.0, 2.0, -2.0, 1.0];
109/// let zcr = zero_crossing_rate(&y, None, None);
110/// ```
111pub fn zero_crossing_rate(
112    y: &[f32],
113    frame_length: Option<usize>,
114    hop_length: Option<usize>,
115) -> Array1<f32> {
116    let frame_len = frame_length.unwrap_or(2048);
117    let hop = hop_length.unwrap_or(frame_len / 4);
118    let n_frames = (y.len() - frame_len) / hop + 1;
119    let mut zcr = Array1::zeros(n_frames);
120    for i in 0..n_frames {
121        let start = i * hop;
122        let slice = &y[start..(start + frame_len).min(y.len())];
123        let count = slice.windows(2).filter(|w| w[0] * w[1] < 0.0).count();
124        zcr[i] = count as f32 / frame_len as f32;
125    }
126    zcr
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use ndarray::array;
133
134    #[test]
135    fn test_stack_memory() {
136        let data = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
137        let result = stack_memory(&data, Some(2), Some(1));
138        let expected = array![
139            [1.0, 2.0, 3.0],
140            [4.0, 5.0, 6.0],
141            [1.0, 1.0, 2.0],
142            [4.0, 4.0, 5.0]
143        ];
144        assert_eq!(result, expected);
145    }
146
147    #[test]
148    fn test_temporal_kurtosis_y() {
149        let y = vec![1.0, -1.0, 1.0, -1.0];
150        let result = temporal_kurtosis(Some(&y), None, Some(4), Some(4));
151        let expected = array![-2.0];
152        assert_eq!(result, expected);
153    }
154
155    #[test]
156    fn test_temporal_kurtosis_s() {
157        let s = array![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]];
158        let result = temporal_kurtosis(None, Some(&s), None, None);
159        let expected = array![0.0, 0.0, 0.0];
160        assert_eq!(result, expected);
161    }
162
163    #[test]
164    fn test_zero_crossing_rate() {
165        let y = vec![1.0, -1.0, 2.0, -2.0, 1.0];
166        let result = zero_crossing_rate(&y, Some(2), Some(1));
167        let expected = array![0.5, 0.5, 0.5, 0.5];
168        assert_eq!(result, expected);
169    }
170
171    #[test]
172    fn test_zero_crossing_rate_full() {
173        let y = vec![1.0, -1.0, 2.0, -2.0, 1.0];
174        let result = zero_crossing_rate(&y, Some(5), Some(5));
175        let expected = array![0.8];
176        assert_eq!(result, expected);
177    }
178}