//! LSL stream information and types use serde::{Deserialize, Serialize}; /// Channel format for LSL streams #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ChannelFormat { /// 32-bit floating point Float32, /// 64-bit floating point Float64, /// 32-bit signed integer Int32, /// 16-bit signed integer Int16, /// 8-bit signed integer Int8, /// Variable-length strings String, } impl Default for ChannelFormat { fn default() -> Self { Self::Float32 } } /// Information about an LSL stream #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LslStreamInfo { /// Stream name (e.g., "MyBiosemi") pub name: String, /// Stream type (e.g., "EEG", "MEG", "Markers") pub stream_type: String, /// Number of channels pub channel_count: usize, /// Nominal sampling rate in Hz (0 for irregular rate) pub nominal_srate: f64, /// Data format pub channel_format: ChannelFormat, /// Unique source identifier (e.g., machine ID + serial number) pub source_id: String, /// Hostname of the source machine pub hostname: String, /// Version of the stream pub version: i32, /// Creation timestamp pub created_at: f64, /// Session ID pub session_id: String, /// Unique identifier for this stream instance pub uid: String, } impl LslStreamInfo { /// Create a new stream info pub fn new(name: &str, stream_type: &str, channel_count: usize, srate: f64) -> Self { Self { name: name.to_string(), stream_type: stream_type.to_string(), channel_count, nominal_srate: srate, channel_format: ChannelFormat::Float32, source_id: String::new(), hostname: String::new(), version: 1, created_at: 0.0, session_id: String::new(), uid: uuid::Uuid::new_v4().to_string(), } } /// Check if this is an EEG stream pub fn is_eeg(&self) -> bool { self.stream_type.to_uppercase() == "EEG" } /// Check if this is an MEG stream pub fn is_meg(&self) -> bool { self.stream_type.to_uppercase() == "MEG" } /// Check if this is a marker/event stream pub fn is_markers(&self) -> bool { let t = self.stream_type.to_uppercase(); t == "MARKERS" || t == "EVENTS" || t == "STIM" } /// Get the expected sample size in bytes pub fn sample_size(&self) -> usize { let bytes_per_channel = match self.channel_format { ChannelFormat::Float32 => 4, ChannelFormat::Float64 => 8, ChannelFormat::Int32 => 4, ChannelFormat::Int16 => 2, ChannelFormat::Int8 => 1, ChannelFormat::String => 0, // Variable }; self.channel_count * bytes_per_channel } } /// A single sample from an LSL stream #[derive(Debug, Clone)] pub struct LslSample { /// Sample data (one value per channel) pub data: Vec, /// LSL timestamp of the sample pub timestamp: f64, } impl LslSample { /// Create a new sample pub fn new(data: Vec, timestamp: f64) -> Self { Self { data, timestamp } } /// Get the number of channels pub fn n_channels(&self) -> usize { self.data.len() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_stream_info_creation() { let info = LslStreamInfo::new("TestEEG", "EEG", 64, 500.0); assert_eq!(info.name, "TestEEG"); assert_eq!(info.channel_count, 64); assert_eq!(info.nominal_srate, 500.0); assert!(info.is_eeg()); assert!(!info.is_meg()); } #[test] fn test_sample_size() { let mut info = LslStreamInfo::new("Test", "EEG", 32, 256.0); info.channel_format = ChannelFormat::Float32; assert_eq!(info.sample_size(), 32 * 4); info.channel_format = ChannelFormat::Float64; assert_eq!(info.sample_size(), 32 * 8); info.channel_format = ChannelFormat::Int16; assert_eq!(info.sample_size(), 32 * 2); } }