Skip to main content

elata_eeg_hal/
sample.rs

1//! EEG sample data types
2
3/// A single multi-channel EEG sample
4#[derive(Debug, Clone)]
5pub struct EegSample {
6    /// Timestamp in milliseconds since epoch
7    pub timestamp_ms: u64,
8    /// Sample values for each channel (in microvolts)
9    pub values: Vec<f32>,
10}
11
12impl EegSample {
13    pub fn new(timestamp_ms: u64, values: Vec<f32>) -> Self {
14        Self {
15            timestamp_ms,
16            values,
17        }
18    }
19
20    pub fn channel_count(&self) -> usize {
21        self.values.len()
22    }
23
24    pub fn get(&self, channel: usize) -> Option<f32> {
25        self.values.get(channel).copied()
26    }
27}
28
29/// A buffer of EEG samples for batch processing
30#[derive(Debug, Clone)]
31pub struct SampleBuffer {
32    /// Sample rate in Hz
33    pub sample_rate: u16,
34    /// Number of channels
35    pub channel_count: usize,
36    /// Samples stored in channel-major order: [ch0_samples..., ch1_samples..., ...]
37    data: Vec<f32>,
38    /// Timestamps for each sample
39    timestamps: Vec<u64>,
40}
41
42impl SampleBuffer {
43    pub fn new(sample_rate: u16, channel_count: usize) -> Self {
44        Self {
45            sample_rate,
46            channel_count,
47            data: Vec::new(),
48            timestamps: Vec::new(),
49        }
50    }
51
52    /// Create a buffer with pre-allocated capacity
53    pub fn with_capacity(sample_rate: u16, channel_count: usize, sample_capacity: usize) -> Self {
54        Self {
55            sample_rate,
56            channel_count,
57            data: Vec::with_capacity(channel_count * sample_capacity),
58            timestamps: Vec::with_capacity(sample_capacity),
59        }
60    }
61
62    /// Number of samples in the buffer
63    pub fn sample_count(&self) -> usize {
64        self.timestamps.len()
65    }
66
67    /// Duration of data in the buffer (in seconds)
68    pub fn duration_secs(&self) -> f32 {
69        self.sample_count() as f32 / self.sample_rate as f32
70    }
71
72    /// Clear all samples from the buffer
73    pub fn clear(&mut self) {
74        self.data.clear();
75        self.timestamps.clear();
76    }
77
78    /// Add a single sample to the buffer
79    pub fn push(&mut self, sample: &EegSample) {
80        assert_eq!(sample.channel_count(), self.channel_count);
81        self.timestamps.push(sample.timestamp_ms);
82        // Store in channel-major order for efficient per-channel processing
83        for (ch, &value) in sample.values.iter().enumerate() {
84            let idx = ch * self.sample_count() + (self.timestamps.len() - 1);
85            if idx >= self.data.len() {
86                self.data
87                    .resize(self.channel_count * self.timestamps.len(), 0.0);
88            }
89            self.data[ch * self.timestamps.len() + self.timestamps.len() - 1] = value;
90        }
91    }
92
93    /// Add samples from interleaved data (sample-major order)
94    /// Data format: [s0_ch0, s0_ch1, ..., s1_ch0, s1_ch1, ...]
95    pub fn push_interleaved(&mut self, data: &[f32], timestamp_start: u64, sample_rate: u16) {
96        let samples_per_channel = data.len() / self.channel_count;
97        let sample_interval_ms = 1000.0 / sample_rate as f64;
98
99        let start_idx = self.timestamps.len();
100
101        // Add timestamps
102        for i in 0..samples_per_channel {
103            let ts = timestamp_start + (i as f64 * sample_interval_ms) as u64;
104            self.timestamps.push(ts);
105        }
106
107        // Resize data array
108        let new_sample_count = self.timestamps.len();
109        self.data.resize(self.channel_count * new_sample_count, 0.0);
110
111        // Convert from interleaved to channel-major
112        for sample_idx in 0..samples_per_channel {
113            for ch in 0..self.channel_count {
114                let src_idx = sample_idx * self.channel_count + ch;
115                let dst_idx = ch * new_sample_count + start_idx + sample_idx;
116                self.data[dst_idx] = data[src_idx];
117            }
118        }
119    }
120
121    /// Get all samples for a specific channel
122    pub fn channel_data(&self, channel: usize) -> &[f32] {
123        let start = channel * self.sample_count();
124        let end = start + self.sample_count();
125        &self.data[start..end]
126    }
127
128    /// Get a mutable reference to channel data
129    pub fn channel_data_mut(&mut self, channel: usize) -> &mut [f32] {
130        let count = self.sample_count();
131        let start = channel * count;
132        let end = start + count;
133        &mut self.data[start..end]
134    }
135
136    /// Get timestamp for a sample index
137    pub fn timestamp(&self, sample_idx: usize) -> Option<u64> {
138        self.timestamps.get(sample_idx).copied()
139    }
140
141    /// Get the most recent N samples for a channel
142    pub fn recent_channel_data(&self, channel: usize, n: usize) -> &[f32] {
143        let data = self.channel_data(channel);
144        if data.len() <= n {
145            data
146        } else {
147            &data[data.len() - n..]
148        }
149    }
150
151    /// Keep only the most recent N samples
152    pub fn retain_recent(&mut self, n: usize) {
153        if self.sample_count() <= n {
154            return;
155        }
156
157        let remove_count = self.sample_count() - n;
158
159        // Shift timestamps
160        self.timestamps.drain(0..remove_count);
161
162        // Shift each channel's data
163        let new_count = self.timestamps.len();
164        for ch in 0..self.channel_count {
165            let old_start = ch * (new_count + remove_count) + remove_count;
166            let new_start = ch * new_count;
167            for i in 0..new_count {
168                self.data[new_start + i] = self.data[old_start + i];
169            }
170        }
171        self.data.truncate(self.channel_count * new_count);
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_sample_buffer_interleaved() {
181        let mut buf = SampleBuffer::new(256, 2);
182
183        // 3 samples, 2 channels, interleaved: [s0c0, s0c1, s1c0, s1c1, s2c0, s2c1]
184        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
185        buf.push_interleaved(&data, 1000, 256);
186
187        assert_eq!(buf.sample_count(), 3);
188        assert_eq!(buf.channel_data(0), &[1.0, 3.0, 5.0]);
189        assert_eq!(buf.channel_data(1), &[2.0, 4.0, 6.0]);
190    }
191
192    #[test]
193    fn test_recent_data() {
194        let mut buf = SampleBuffer::new(256, 1);
195        let data: Vec<f32> = (0..10).map(|i| i as f32).collect();
196        buf.push_interleaved(&data, 0, 256);
197
198        assert_eq!(buf.recent_channel_data(0, 3), &[7.0, 8.0, 9.0]);
199    }
200}