use dspk_core::{ComputeTarget, RecordingSource};
use dspk_ops::pipeline::{PipelineStage, PipelineWorkspace};
use dspk_ops::spatial::SpatialWhitening;
use dspk_ops::Pipeline;
use crate::PipelineError;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WhitenFit {
pub epsilon: f32,
pub fit_s: f64,
pub chunks: usize,
}
pub fn unfitted_whitening(channels: usize) -> SpatialWhitening {
let mut m = vec![0.0f32; channels * channels];
for c in 0..channels {
m[c * channels + c] = 1.0;
}
SpatialWhitening::from_matrix(channels, m)
}
pub fn fit_whitening(target: ComputeTarget, source: &dyn RecordingSource, channels: &[usize], before: &[PipelineStage], fit: &WhitenFit) -> Result<SpatialWhitening, PipelineError> {
let info = source.info();
let (total, fs, nch) = (info.samples, info.sample_rate_hz(), channels.len());
if nch == 0 || total == 0 {
return Err(PipelineError::Invalid("whiten: no channels or samples to fit on".into()));
}
let chunks = fit.chunks.max(1) as u64;
let len = (((fit.fit_s * fs) as u64) / chunks).clamp(1, total / chunks.max(1)).max(1);
let pipeline = Pipeline::with_stages(before.to_vec());
let (left, right) = pipeline.settling(fs).map_err(|e| PipelineError::Invalid(e.to_string()))?;
let client = target.client().map_err(|e| PipelineError::Compute(e.to_string()))?;
let mut workspace = if before.is_empty() {
None
} else {
let capacity = (len as usize + left + right).min(total as usize);
Some(PipelineWorkspace::<f32>::new(client.clone(), pipeline, nch, capacity, fs).map_err(|e| PipelineError::Invalid(e.to_string()))?)
};
let mut rows: Vec<Vec<f32>> = vec![Vec::with_capacity((len * chunks) as usize); nch];
for k in 0..chunks {
let centre = ((2 * k + 1) * total) / (2 * chunks);
let start = centre.saturating_sub(len / 2).min(total - len);
let read = start.saturating_sub(left as u64)..(start + len + right as u64).min(total);
let n = (read.end - read.start) as usize;
let mut values = vec![0.0f32; nch * n];
source.read(channels, read.clone(), &mut values).map_err(|e| PipelineError::Io(e.to_string()))?;
let processed = match workspace.as_mut() {
Some(ws) => {
let mut out = vec![0.0f32; nch * n];
ws.process_chunk(&values, n, &mut out);
out
}
None => values,
};
let a = (start - read.start) as usize;
for (c, row) in rows.iter_mut().enumerate() {
row.extend_from_slice(&processed[c * n + a..c * n + a + len as usize]);
}
}
let samples = rows[0].len();
let data: Vec<f32> = rows.into_iter().flatten().collect();
Ok(SpatialWhitening::fit_zca::<f32>(&client, &data, nch, samples, fit.epsilon))
}