use cubecl::prelude::*;
use super::session::PipelineWorkspace;
use crate::core::DspFloat;
use super::stage::PipelineStage;
use crate::filter::design::OpsError;
#[derive(Debug, Clone, Default)]
pub struct Pipeline {
stages: Vec<PipelineStage>,
}
impl Pipeline {
pub fn new() -> Self {
Self { stages: Vec::new() }
}
pub fn with_stages(stages: Vec<PipelineStage>) -> Self {
Self { stages }
}
pub fn add(&mut self, stage: PipelineStage) -> &mut Self {
self.stages.push(stage);
self
}
pub fn stages(&self) -> &[PipelineStage] {
&self.stages
}
pub fn len(&self) -> usize {
self.stages.len()
}
pub fn is_empty(&self) -> bool {
self.stages.is_empty()
}
pub fn settling(&self, sample_rate: f64) -> Result<(usize, usize), OpsError> {
self.stages.iter().try_fold((0, 0), |(l, r), stage| {
let (sl, sr) = stage.settling(sample_rate)?;
Ok((l + sl, r + sr))
})
}
pub fn validate(&self, sample_rate: f64) -> Result<(), OpsError> {
self.settling(sample_rate).map(|_| ())
}
pub fn execute<F: DspFloat>(
&self,
client: &Client,
input_handle: &cubecl::server::Handle,
channels: usize,
samples: usize,
sample_rate: f64,
) -> Result<cubecl::server::Handle, OpsError> {
if self.stages.is_empty() {
return Ok(input_handle.clone());
}
let mut workspace = PipelineWorkspace::<F>::new(
client.clone(),
self.clone(),
channels,
samples,
sample_rate,
)?;
Ok(workspace.process_handle(input_handle, samples))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::buffer;
fn chained(client: &Client) {
let channels = 32;
let samples = 500;
let total = channels * samples;
let in_handle = buffer::upload(client, &vec![100.0f32; total]);
let mut pipeline = Pipeline::new();
pipeline
.add(PipelineStage::Scale { alpha: 0.195, beta: 0.0 })
.add(PipelineStage::CommonAverageReference)
.add(PipelineStage::notch(60.0, 30.0));
assert_eq!(pipeline.len(), 3);
let out_handle = pipeline.execute::<f32>(client, &in_handle, channels, samples, 30000.0).unwrap();
let out = buffer::download::<f32>(client, out_handle);
assert_eq!(out.len(), total);
assert!(out.iter().all(|v| v.abs() < 1e-3), "{}", client.name());
}
runtime_test!(test_pipeline_chained, chained);
#[test]
fn test_settling_sums_both_sides() {
let fs = 30_000.0;
let mut pipeline = Pipeline::new();
pipeline.add(PipelineStage::bandpass(300.0, 6000.0)).add(PipelineStage::median9());
let (l, r) = pipeline.settling(fs).unwrap();
let (bl, br) = PipelineStage::bandpass(300.0, 6000.0).settling(fs).unwrap();
assert_eq!((l, r), (bl + 4, br + 4));
assert_eq!(bl, br);
}
#[test]
fn test_invalid_cutoff_is_an_error() {
let pipeline = Pipeline::with_stages(vec![PipelineStage::bandpass(6000.0, 300.0)]);
assert!(pipeline.validate(30_000.0).is_err());
let pipeline = Pipeline::with_stages(vec![PipelineStage::lowpass(20_000.0)]);
assert!(pipeline.validate(30_000.0).is_err());
}
}