use cubecl::prelude::*;
use dspk_core::compute::element_position;
use dspk_ops::core::{buffer, DspFloat};
use dspk_ops::resampler::fractional_delay_taps_device;
use dspk_core::compute::LaunchGeometry;
use crate::detection::DeduplicatedSpike;
use crate::extraction::{SINC_KERNEL_RADIUS, SnippetOptions, snippet_fits};
const PARABOLA_FLAT: f32 = 1e-12;
#[cube(launch)]
pub fn trough_shift_kernel<F: Float>(
trace: &[F],
primary_channels: &[u32],
center_samples: &[u32],
shifts: &mut [F],
num_samples: u32,
num_spikes: u32,
) {
let i = element_position() as u32;
if i < num_spikes {
let base = primary_channels[i as usize] * num_samples + center_samples[i as usize];
let y_prev = trace[(base - 1u32) as usize];
let y_mid = trace[base as usize];
let y_next = trace[(base + 1u32) as usize];
let denom = F::new(2.0f32) * (y_prev - F::new(2.0f32) * y_mid + y_next);
let mut shift = F::new(0.0f32);
if F::abs(denom) > F::new(PARABOLA_FLAT) {
shift = F::clamp((y_prev - y_next) / denom, F::new(-0.5f32), F::new(0.5f32));
}
shifts[i as usize] = shift;
}
}
#[cube(launch)]
pub fn extract_snippets_kernel<F: Float>(
trace: &[F],
knn_table: &[u32],
primary_channels: &[u32],
center_samples: &[u32],
taps: &[F],
out_snippets: &mut [F],
num_samples: u32,
num_spikes: u32,
k_neighbors: u32,
pre_samples: u32,
snippet_len: u32,
apply_shift: u32,
#[comptime] radius: u32,
) {
let tid = element_position() as u32;
let per_spike = k_neighbors * snippet_len;
if tid < num_spikes * per_spike {
let spike = tid / per_spike;
let rem = tid - spike * per_spike;
let k = rem / snippet_len;
let s = rem - k * snippet_len;
let primary = primary_channels[spike as usize];
let neighbour = knn_table[(primary * k_neighbors + k) as usize];
let pos = neighbour * num_samples + center_samples[spike as usize] - pre_samples + s;
if apply_shift == 0u32 {
out_snippets[tid as usize] = trace[pos as usize];
} else {
let n = 2 * radius + 1;
let tap_base = spike * n;
let mut sum = F::new(0.0f32);
#[unroll]
for m in 0..n {
sum += taps[(tap_base + m) as usize] * trace[(pos + m - radius) as usize];
}
out_snippets[tid as usize] = sum;
}
}
}
#[derive(Debug, Clone)]
pub struct VramSnippets {
pub snippets: cubecl::server::Handle,
pub primary_channels: cubecl::server::Handle,
pub primaries: Vec<u32>,
pub kept: Vec<usize>,
pub dropped: usize,
}
impl VramSnippets {
pub fn num_spikes(&self) -> usize {
self.kept.len()
}
}
#[allow(clippy::too_many_arguments)]
pub fn execute_extract_sinc_in_vram<F: DspFloat>(
client: &Client,
trace_handle: &cubecl::server::Handle,
knn_handle: &cubecl::server::Handle,
channels: usize,
samples: usize,
spikes: &[DeduplicatedSpike],
k_neighbors: usize,
pre_samples: usize,
post_samples: usize,
apply_sinc_shift: bool,
) -> Option<VramSnippets> {
let snippet_len = pre_samples + post_samples;
if snippet_len == 0 {
return None;
}
let kept: Vec<usize> = spikes
.iter()
.enumerate()
.filter(|(_, s)| {
s.primary_channel < channels
&& snippet_fits(s.sample_index as usize, samples, &SnippetOptions { k_neighbors: 1, pre_samples, post_samples, sinc_shift: apply_sinc_shift, subtract_mean: false })
})
.map(|(i, _)| i)
.collect();
let dropped = spikes.len() - kept.len();
let num_spikes = kept.len();
if num_spikes == 0 {
return None;
}
let primary_channels: Vec<u32> = kept.iter().map(|&i| spikes[i].primary_channel as u32).collect();
let center_samples: Vec<u32> = kept.iter().map(|&i| spikes[i].sample_index as u32).collect();
let prim_handle = buffer::upload(client, &primary_channels);
let center_handle = buffer::upload(client, ¢er_samples);
let total = num_spikes * k_neighbors * snippet_len;
let out_snippets_handle = buffer::empty::<F>(client, total);
let taps = if apply_sinc_shift {
let shifts = buffer::empty::<F>(client, num_spikes);
let per_spike = LaunchGeometry::elementwise(client, num_spikes);
unsafe {
trough_shift_kernel::launch::<F>(
client,
per_spike.cube_count,
per_spike.cube_dim,
BufferArg::from_raw_parts(trace_handle.clone(), channels * samples),
BufferArg::from_raw_parts(prim_handle.clone(), num_spikes),
BufferArg::from_raw_parts(center_handle.clone(), num_spikes),
BufferArg::from_raw_parts(shifts.clone(), num_spikes),
samples as u32,
num_spikes as u32,
);
}
fractional_delay_taps_device::<F>(client, &shifts, num_spikes, SINC_KERNEL_RADIUS)
} else {
buffer::empty::<F>(client, 1)
};
let tap_len = if apply_sinc_shift { num_spikes * (2 * SINC_KERNEL_RADIUS + 1) } else { 1 };
let geom = LaunchGeometry::elementwise(client, total);
unsafe {
extract_snippets_kernel::launch::<F>(
client,
geom.cube_count,
geom.cube_dim,
BufferArg::from_raw_parts(trace_handle.clone(), channels * samples),
BufferArg::from_raw_parts(knn_handle.clone(), channels * k_neighbors),
BufferArg::from_raw_parts(prim_handle.clone(), num_spikes),
BufferArg::from_raw_parts(center_handle, num_spikes),
BufferArg::from_raw_parts(taps, tap_len),
BufferArg::from_raw_parts(out_snippets_handle.clone(), total),
samples as u32,
num_spikes as u32,
k_neighbors as u32,
pre_samples as u32,
snippet_len as u32,
u32::from(apply_sinc_shift),
SINC_KERNEL_RADIUS as u32,
);
}
Some(VramSnippets { snippets: out_snippets_handle, primary_channels: prim_handle, primaries: primary_channels, kept, dropped })
}