use crate::spectral::SpectralAnalyzer;
use crate::{AnalysisConfig, Result};
pub struct EditDetector {
spectral_analyzer: SpectralAnalyzer,
hop_size: usize,
}
impl EditDetector {
#[must_use]
pub fn new(config: AnalysisConfig) -> Self {
let hop_size = config.hop_size;
Self {
spectral_analyzer: SpectralAnalyzer::new(config),
hop_size,
}
}
pub fn detect(&self, samples: &[f32], sample_rate: f32) -> Result<EditResult> {
let edit_times = self.detect_discontinuities(samples, sample_rate)?;
Ok(EditResult {
num_edits: edit_times.len(),
edit_times,
})
}
fn detect_discontinuities(&self, samples: &[f32], sample_rate: f32) -> Result<Vec<f32>> {
let window_size = 2048;
let mut edits = Vec::new();
if samples.len() < window_size * 2 {
return Ok(edits);
}
let num_frames = (samples.len() - window_size) / self.hop_size;
let mut spectral_centroids = Vec::new();
let mut energies = Vec::new();
for frame_idx in 0..num_frames {
let start = frame_idx * self.hop_size;
let end = (start + window_size).min(samples.len());
if end - start < window_size {
break;
}
let frame = &samples[start..end];
let energy: f32 = frame.iter().map(|&x| x * x).sum();
energies.push(energy);
let features = self.spectral_analyzer.analyze_frame(frame, sample_rate)?;
spectral_centroids.push(features.centroid);
}
let threshold = 3.0;
for i in 1..energies.len() {
let diff = (energies[i] - energies[i - 1]).abs();
let mean = (energies[i] + energies[i - 1]) / 2.0;
if mean > 0.0 && diff / mean > threshold {
let time = (i * self.hop_size) as f32 / sample_rate;
edits.push(time);
}
}
for i in 1..spectral_centroids.len() {
let diff = (spectral_centroids[i] - spectral_centroids[i - 1]).abs();
if diff > 500.0 {
let time = (i * self.hop_size) as f32 / sample_rate;
if !edits.contains(&time) {
edits.push(time);
}
}
}
edits.sort_by(|a, b| a.partial_cmp(b).unwrap());
Ok(edits)
}
}
#[derive(Debug, Clone)]
pub struct EditResult {
pub num_edits: usize,
pub edit_times: Vec<f32>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_edit_detector() {
let config = AnalysisConfig::default();
let detector = EditDetector::new(config);
let sample_rate = 44100.0;
let mut samples = Vec::new();
for i in 0..22050 {
samples.push((2.0 * std::f32::consts::PI * 440.0 * i as f32 / sample_rate).sin() * 0.5);
}
for i in 0..22050 {
samples.push((2.0 * std::f32::consts::PI * 880.0 * i as f32 / sample_rate).sin() * 0.5);
}
let result = detector.detect(&samples, sample_rate);
assert!(result.is_ok());
}
}