use ndarray::{Array1, Array2};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ManipulationError {
#[error("Invalid input: {0}")]
InvalidInput(String),
}
pub fn stack_memory(
data: &Array2<f32>,
n_steps: Option<usize>,
delay: Option<usize>,
) -> Array2<f32> {
let n_steps = n_steps.unwrap_or(2);
let delay = delay.unwrap_or(1);
let n_frames = data.shape()[1];
let n_features = data.shape()[0];
let mut stacked = Array2::zeros((n_features * n_steps, n_frames));
for step in 0..n_steps {
let offset = step * delay;
for t in 0..n_frames {
let src_t = (t as isize - offset as isize).max(0) as usize;
for f in 0..n_features {
stacked[[f + step * n_features, t]] = data[[f, src_t]];
}
}
}
stacked
}
pub fn temporal_kurtosis(y: &[f32]) -> TemporalKurtosisBuilder<'_> {
TemporalKurtosisBuilder {
y,
frame_length: 2048,
hop_length: 512,
}
}
#[derive(Debug, Clone)]
pub struct TemporalKurtosisBuilder<'a> {
y: &'a [f32],
frame_length: usize,
hop_length: usize,
}
impl<'a> TemporalKurtosisBuilder<'a> {
pub fn frame_length(mut self, frame_length: usize) -> Self {
self.frame_length = frame_length;
self
}
pub fn hop_length(mut self, hop_length: usize) -> Self {
self.hop_length = hop_length;
self
}
pub fn compute(self) -> Result<Array1<f32>, ManipulationError> {
temporal_kurtosis_impl(self.y, self.frame_length, self.hop_length)
}
}
fn temporal_kurtosis_impl(
y: &[f32],
frame_length: usize,
hop_length: usize,
) -> Result<Array1<f32>, ManipulationError> {
let frame_len = frame_length;
let hop = hop_length;
let n_frames = (y.len() - frame_len) / hop + 1;
let mut kurtosis = Array1::zeros(n_frames);
for i in 0..n_frames {
let start = i * hop;
let frame = &y[start..(start + frame_len).min(y.len())];
let mean = frame.iter().sum::<f32>() / frame.len() as f32;
let m2 = frame.iter().map(|&x| (x - mean).powi(2)).sum::<f32>() / frame.len() as f32;
let m4 = frame.iter().map(|&x| (x - mean).powi(4)).sum::<f32>() / frame.len() as f32;
kurtosis[i] = if m2 > 1e-10 { m4 / m2.powi(2) - 3.0 } else { 0.0 };
}
Ok(kurtosis)
}
pub fn zero_crossing_rate(y: &[f32]) -> ZeroCrossingRateBuilder<'_> {
ZeroCrossingRateBuilder {
y,
frame_length: 2048,
hop_length: 512,
}
}
#[derive(Debug, Clone)]
pub struct ZeroCrossingRateBuilder<'a> {
y: &'a [f32],
frame_length: usize,
hop_length: usize,
}
impl<'a> ZeroCrossingRateBuilder<'a> {
pub fn frame_length(mut self, frame_length: usize) -> Self {
self.frame_length = frame_length;
self
}
pub fn hop_length(mut self, hop_length: usize) -> Self {
self.hop_length = hop_length;
self
}
pub fn compute(self) -> Array1<f32> {
zero_crossing_rate_impl(self.y, self.frame_length, self.hop_length)
}
}
fn zero_crossing_rate_impl(
y: &[f32],
frame_length: usize,
hop_length: usize,
) -> Array1<f32> {
let frame_len = frame_length;
let hop = hop_length;
let n_frames = (y.len() - frame_len) / hop + 1;
let mut zcr = Array1::zeros(n_frames);
for i in 0..n_frames {
let start = i * hop;
let slice = &y[start..(start + frame_len).min(y.len())];
let count = slice.windows(2).filter(|w| w[0] * w[1] < 0.0).count();
zcr[i] = count as f32 / frame_len as f32;
}
zcr
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::array;
#[test]
fn test_stack_memory() {
let data = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
let result = stack_memory(&data, Some(2), Some(1));
let expected = array![
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[1.0, 1.0, 2.0],
[4.0, 4.0, 5.0]
];
assert_eq!(result, expected);
}
#[test]
fn test_temporal_kurtosis_y() {
let y = vec![1.0, -1.0, 1.0, -1.0];
let result = temporal_kurtosis(&y)
.frame_length(4)
.hop_length(4)
.compute()
.unwrap();
let expected = array![-2.0];
assert_eq!(result, expected);
}
#[test]
fn test_zero_crossing_rate() {
let y = vec![1.0, -1.0, 2.0, -2.0, 1.0];
let result = zero_crossing_rate(&y)
.frame_length(2)
.hop_length(1)
.compute();
let expected = array![0.5, 0.5, 0.5, 0.5];
assert_eq!(result, expected);
}
#[test]
fn test_zero_crossing_rate_full() {
let y = vec![1.0, -1.0, 2.0, -2.0, 1.0];
let result = zero_crossing_rate(&y)
.frame_length(5)
.hop_length(5)
.compute();
let expected = array![0.8];
assert_eq!(result, expected);
}
}