use crate::utils::stft;
use crate::MirResult;
pub struct HarmonicSeparator {
#[allow(dead_code)]
sample_rate: f32,
window_size: usize,
hop_size: usize,
}
impl HarmonicSeparator {
#[must_use]
pub fn new(sample_rate: f32, window_size: usize, hop_size: usize) -> Self {
Self {
sample_rate,
window_size,
hop_size,
}
}
pub fn separate(&self, signal: &[f32]) -> MirResult<(Vec<f32>, Vec<f32>)> {
let frames = stft(signal, self.window_size, self.hop_size)?;
let spectrogram: Vec<Vec<f32>> = frames
.iter()
.map(|frame| crate::utils::magnitude_spectrum(frame))
.collect();
let (harmonic_spec, percussive_spec) = self.median_filter_separation(&spectrogram);
let harmonic_energy: Vec<f32> = harmonic_spec
.iter()
.map(|frame| frame.iter().map(|m| m * m).sum())
.collect();
let percussive_energy: Vec<f32> = percussive_spec
.iter()
.map(|frame| frame.iter().map(|m| m * m).sum())
.collect();
Ok((harmonic_energy, percussive_energy))
}
fn median_filter_separation(&self, spectrogram: &[Vec<f32>]) -> (Vec<Vec<f32>>, Vec<Vec<f32>>) {
let num_frames = spectrogram.len();
if num_frames == 0 {
return (Vec::new(), Vec::new());
}
let num_bins = spectrogram[0].len();
let mut harmonic_spec = vec![vec![0.0; num_bins]; num_frames];
let mut percussive_spec = vec![vec![0.0; num_bins]; num_frames];
for (frame_idx, frame) in spectrogram.iter().enumerate() {
for (bin_idx, &magnitude) in frame.iter().enumerate() {
let temporal_stability = if frame_idx > 0 && frame_idx < num_frames - 1 {
let prev = spectrogram[frame_idx - 1][bin_idx];
let next = spectrogram[frame_idx + 1][bin_idx];
let avg = (prev + next) / 2.0;
1.0 - ((magnitude - avg).abs() / (magnitude + avg + 1e-10)).min(1.0)
} else {
0.5
};
let spectral_stability = if bin_idx > 0 && bin_idx < num_bins - 1 {
let prev = frame[bin_idx - 1];
let next = frame[bin_idx + 1];
let avg = (prev + next) / 2.0;
1.0 - ((magnitude - avg).abs() / (magnitude + avg + 1e-10)).min(1.0)
} else {
0.5
};
harmonic_spec[frame_idx][bin_idx] = magnitude * temporal_stability;
percussive_spec[frame_idx][bin_idx] = magnitude * spectral_stability;
}
}
(harmonic_spec, percussive_spec)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_harmonic_separator_creation() {
let separator = HarmonicSeparator::new(44100.0, 2048, 512);
assert_eq!(separator.sample_rate, 44100.0);
}
}