Files
rustytorch/crates/specialized/rtx-neuro-lsl/src/buffer.rs
T
2026-03-04 00:08:42 +00:00

206 lines
5.4 KiB
Rust

//! Ring buffer for continuous streaming data
use std::collections::VecDeque;
use crate::stream::LslSample;
/// Ring buffer for storing streaming samples
pub struct StreamBuffer {
/// Sample data storage (each Vec is one sample)
data: VecDeque<Vec<f64>>,
/// Timestamps for each sample
timestamps: VecDeque<f64>,
/// Maximum capacity in samples
capacity: usize,
/// Sampling frequency in Hz
sfreq: f64,
/// Total samples received (including dropped)
total_received: usize,
/// Samples dropped due to buffer overflow
dropped: usize,
}
impl StreamBuffer {
/// Create a new buffer with given capacity
pub fn new(capacity: usize, sfreq: f64) -> Self {
Self {
data: VecDeque::with_capacity(capacity),
timestamps: VecDeque::with_capacity(capacity),
capacity,
sfreq,
total_received: 0,
dropped: 0,
}
}
/// Create a buffer sized for a given duration
pub fn for_duration(duration_sec: f64, sfreq: f64) -> Self {
let capacity = (duration_sec * sfreq).ceil() as usize;
Self::new(capacity.max(1), sfreq)
}
/// Push a new sample into the buffer
pub fn push_sample(&mut self, sample: Vec<f64>, timestamp: f64) {
self.total_received += 1;
// Drop oldest sample if at capacity
if self.data.len() >= self.capacity {
self.data.pop_front();
self.timestamps.pop_front();
self.dropped += 1;
}
self.data.push_back(sample);
self.timestamps.push_back(timestamp);
}
/// Push a sample from LslSample struct
pub fn push(&mut self, sample: LslSample) {
self.push_sample(sample.data, sample.timestamp);
}
/// Get the most recent samples covering the specified duration
pub fn get_recent(&self, duration_sec: f64) -> (Vec<Vec<f64>>, Vec<f64>) {
let n_samples = (duration_sec * self.sfreq).ceil() as usize;
let n_available = self.data.len();
let n_to_return = n_samples.min(n_available);
let start_idx = n_available.saturating_sub(n_to_return);
let data: Vec<Vec<f64>> = self.data.iter().skip(start_idx).cloned().collect();
let timestamps: Vec<f64> = self.timestamps.iter().skip(start_idx).copied().collect();
(data, timestamps)
}
/// Get all samples in the buffer
pub fn get_all(&self) -> (Vec<Vec<f64>>, Vec<f64>) {
(
self.data.iter().cloned().collect(),
self.timestamps.iter().copied().collect(),
)
}
/// Clear the buffer
pub fn clear(&mut self) {
self.data.clear();
self.timestamps.clear();
}
/// Get the number of samples currently in the buffer
pub fn len(&self) -> usize {
self.data.len()
}
/// Check if buffer is empty
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
/// Get buffer capacity
pub fn capacity(&self) -> usize {
self.capacity
}
/// Get total samples received
pub fn total_received(&self) -> usize {
self.total_received
}
/// Get number of dropped samples
pub fn dropped(&self) -> usize {
self.dropped
}
/// Get the sampling frequency
pub fn sfreq(&self) -> f64 {
self.sfreq
}
/// Get duration of buffered data in seconds
pub fn duration(&self) -> f64 {
if self.data.is_empty() {
0.0
} else {
self.data.len() as f64 / self.sfreq
}
}
/// Get the latest timestamp
pub fn latest_timestamp(&self) -> Option<f64> {
self.timestamps.back().copied()
}
/// Get the earliest timestamp
pub fn earliest_timestamp(&self) -> Option<f64> {
self.timestamps.front().copied()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_buffer_creation() {
let buf = StreamBuffer::new(100, 256.0);
assert_eq!(buf.capacity(), 100);
assert_eq!(buf.sfreq(), 256.0);
assert!(buf.is_empty());
}
#[test]
fn test_buffer_for_duration() {
let buf = StreamBuffer::for_duration(2.0, 256.0);
assert_eq!(buf.capacity(), 512);
}
#[test]
fn test_push_and_get() {
let mut buf = StreamBuffer::new(10, 100.0);
for i in 0..5 {
buf.push_sample(vec![i as f64], i as f64 * 0.01);
}
assert_eq!(buf.len(), 5);
let (data, times) = buf.get_all();
assert_eq!(data.len(), 5);
assert_eq!(times.len(), 5);
assert_eq!(data[0][0], 0.0);
assert_eq!(data[4][0], 4.0);
}
#[test]
fn test_buffer_overflow() {
let mut buf = StreamBuffer::new(5, 100.0);
for i in 0..10 {
buf.push_sample(vec![i as f64], i as f64 * 0.01);
}
assert_eq!(buf.len(), 5);
assert_eq!(buf.dropped(), 5);
assert_eq!(buf.total_received(), 10);
let (data, _) = buf.get_all();
assert_eq!(data[0][0], 5.0); // First 5 samples were dropped
}
#[test]
fn test_get_recent() {
let mut buf = StreamBuffer::new(100, 10.0);
// Push 50 samples (5 seconds at 10 Hz)
for i in 0..50 {
buf.push_sample(vec![i as f64], i as f64 * 0.1);
}
// Get last 2 seconds (20 samples)
let (data, _) = buf.get_recent(2.0);
assert_eq!(data.len(), 20);
assert_eq!(data[0][0], 30.0); // Start from sample 30
}
}