Skip to main content

lsl_core/
sample.rs

1//! Sample types and serialization matching liblsl protocol 1.10.
2//!
3//! Samples are the unit of data transfer. Each contains a timestamp and channel data.
4
5use crate::types::*;
6use std::io::{Error as IoError, ErrorKind, Read, Result as IoResult};
7
8/// A timestamped multi-channel sample.
9#[derive(Debug, Clone)]
10pub struct Sample {
11    pub timestamp: f64,
12    pub pushthrough: bool,
13    pub data: SampleData,
14}
15
16/// The payload of a sample, typed by channel format.
17#[derive(Debug, Clone)]
18pub enum SampleData {
19    Float32(Vec<f32>),
20    Double64(Vec<f64>),
21    Int32(Vec<i32>),
22    Int16(Vec<i16>),
23    Int8(Vec<i8>),
24    Int64(Vec<i64>),
25    StringData(Vec<String>),
26}
27
28impl Sample {
29    /// Create a new sample with the given format, channel count, timestamp, and default data.
30    pub fn new(fmt: ChannelFormat, num_channels: u32, timestamp: f64) -> Self {
31        let n = num_channels as usize;
32        let data = match fmt {
33            ChannelFormat::Float32 => SampleData::Float32(vec![0.0; n]),
34            ChannelFormat::Double64 => SampleData::Double64(vec![0.0; n]),
35            ChannelFormat::Int32 => SampleData::Int32(vec![0; n]),
36            ChannelFormat::Int16 => SampleData::Int16(vec![0; n]),
37            ChannelFormat::Int8 => SampleData::Int8(vec![0; n]),
38            ChannelFormat::Int64 => SampleData::Int64(vec![0; n]),
39            ChannelFormat::String | ChannelFormat::Undefined => {
40                SampleData::StringData(vec![String::new(); n])
41            }
42        };
43        Sample {
44            timestamp,
45            pushthrough: true,
46            data,
47        }
48    }
49
50    /// Assign float data
51    pub fn assign_f32(&mut self, src: &[f32]) {
52        match &mut self.data {
53            SampleData::Float32(d) => d.copy_from_slice(src),
54            SampleData::Double64(d) => {
55                for (dst, s) in d.iter_mut().zip(src) {
56                    *dst = *s as f64;
57                }
58            }
59            SampleData::Int32(d) => {
60                for (dst, s) in d.iter_mut().zip(src) {
61                    *dst = *s as i32;
62                }
63            }
64            SampleData::Int16(d) => {
65                for (dst, s) in d.iter_mut().zip(src) {
66                    *dst = *s as i16;
67                }
68            }
69            SampleData::Int8(d) => {
70                for (dst, s) in d.iter_mut().zip(src) {
71                    *dst = *s as i8;
72                }
73            }
74            SampleData::Int64(d) => {
75                for (dst, s) in d.iter_mut().zip(src) {
76                    *dst = *s as i64;
77                }
78            }
79            SampleData::StringData(d) => {
80                for (dst, s) in d.iter_mut().zip(src) {
81                    *dst = s.to_string();
82                }
83            }
84        }
85    }
86
87    /// Retrieve float data
88    pub fn retrieve_f32(&self, dst: &mut [f32]) {
89        match &self.data {
90            SampleData::Float32(d) => dst.copy_from_slice(d),
91            SampleData::Double64(d) => {
92                for (o, s) in dst.iter_mut().zip(d) {
93                    *o = *s as f32;
94                }
95            }
96            SampleData::Int32(d) => {
97                for (o, s) in dst.iter_mut().zip(d) {
98                    *o = *s as f32;
99                }
100            }
101            SampleData::Int16(d) => {
102                for (o, s) in dst.iter_mut().zip(d) {
103                    *o = *s as f32;
104                }
105            }
106            SampleData::Int8(d) => {
107                for (o, s) in dst.iter_mut().zip(d) {
108                    *o = *s as f32;
109                }
110            }
111            SampleData::Int64(d) => {
112                for (o, s) in dst.iter_mut().zip(d) {
113                    *o = *s as f32;
114                }
115            }
116            SampleData::StringData(d) => {
117                for (o, s) in dst.iter_mut().zip(d) {
118                    *o = s.parse().unwrap_or(0.0);
119                }
120            }
121        }
122    }
123
124    /// Assign double data
125    pub fn assign_f64(&mut self, src: &[f64]) {
126        match &mut self.data {
127            SampleData::Double64(d) => d.copy_from_slice(src),
128            SampleData::Float32(d) => {
129                for (dst, s) in d.iter_mut().zip(src) {
130                    *dst = *s as f32;
131                }
132            }
133            SampleData::Int32(d) => {
134                for (dst, s) in d.iter_mut().zip(src) {
135                    *dst = *s as i32;
136                }
137            }
138            SampleData::Int16(d) => {
139                for (dst, s) in d.iter_mut().zip(src) {
140                    *dst = *s as i16;
141                }
142            }
143            SampleData::Int8(d) => {
144                for (dst, s) in d.iter_mut().zip(src) {
145                    *dst = *s as i8;
146                }
147            }
148            SampleData::Int64(d) => {
149                for (dst, s) in d.iter_mut().zip(src) {
150                    *dst = *s as i64;
151                }
152            }
153            SampleData::StringData(d) => {
154                for (dst, s) in d.iter_mut().zip(src) {
155                    *dst = s.to_string();
156                }
157            }
158        }
159    }
160
161    /// Retrieve double data
162    pub fn retrieve_f64(&self, dst: &mut [f64]) {
163        match &self.data {
164            SampleData::Double64(d) => dst.copy_from_slice(d),
165            SampleData::Float32(d) => {
166                for (o, s) in dst.iter_mut().zip(d) {
167                    *o = *s as f64;
168                }
169            }
170            SampleData::Int32(d) => {
171                for (o, s) in dst.iter_mut().zip(d) {
172                    *o = *s as f64;
173                }
174            }
175            SampleData::Int16(d) => {
176                for (o, s) in dst.iter_mut().zip(d) {
177                    *o = *s as f64;
178                }
179            }
180            SampleData::Int8(d) => {
181                for (o, s) in dst.iter_mut().zip(d) {
182                    *o = *s as f64;
183                }
184            }
185            SampleData::Int64(d) => {
186                for (o, s) in dst.iter_mut().zip(d) {
187                    *o = *s as f64;
188                }
189            }
190            SampleData::StringData(d) => {
191                for (o, s) in dst.iter_mut().zip(d) {
192                    *o = s.parse().unwrap_or(0.0);
193                }
194            }
195        }
196    }
197
198    pub fn assign_i32(&mut self, src: &[i32]) {
199        match &mut self.data {
200            SampleData::Int32(d) => d.copy_from_slice(src),
201            SampleData::Float32(d) => {
202                for (dst, s) in d.iter_mut().zip(src) {
203                    *dst = *s as f32;
204                }
205            }
206            SampleData::Double64(d) => {
207                for (dst, s) in d.iter_mut().zip(src) {
208                    *dst = *s as f64;
209                }
210            }
211            SampleData::Int16(d) => {
212                for (dst, s) in d.iter_mut().zip(src) {
213                    *dst = *s as i16;
214                }
215            }
216            SampleData::Int8(d) => {
217                for (dst, s) in d.iter_mut().zip(src) {
218                    *dst = *s as i8;
219                }
220            }
221            SampleData::Int64(d) => {
222                for (dst, s) in d.iter_mut().zip(src) {
223                    *dst = *s as i64;
224                }
225            }
226            SampleData::StringData(d) => {
227                for (dst, s) in d.iter_mut().zip(src) {
228                    *dst = s.to_string();
229                }
230            }
231        }
232    }
233
234    pub fn retrieve_i32(&self, dst: &mut [i32]) {
235        match &self.data {
236            SampleData::Int32(d) => dst.copy_from_slice(d),
237            SampleData::Float32(d) => {
238                for (o, s) in dst.iter_mut().zip(d) {
239                    *o = *s as i32;
240                }
241            }
242            SampleData::Double64(d) => {
243                for (o, s) in dst.iter_mut().zip(d) {
244                    *o = *s as i32;
245                }
246            }
247            SampleData::Int16(d) => {
248                for (o, s) in dst.iter_mut().zip(d) {
249                    *o = *s as i32;
250                }
251            }
252            SampleData::Int8(d) => {
253                for (o, s) in dst.iter_mut().zip(d) {
254                    *o = *s as i32;
255                }
256            }
257            SampleData::Int64(d) => {
258                for (o, s) in dst.iter_mut().zip(d) {
259                    *o = *s as i32;
260                }
261            }
262            SampleData::StringData(d) => {
263                for (o, s) in dst.iter_mut().zip(d) {
264                    *o = s.parse().unwrap_or(0);
265                }
266            }
267        }
268    }
269
270    pub fn assign_i64(&mut self, src: &[i64]) {
271        match &mut self.data {
272            SampleData::Int64(d) => d.copy_from_slice(src),
273            SampleData::Float32(d) => {
274                for (dst, s) in d.iter_mut().zip(src) {
275                    *dst = *s as f32;
276                }
277            }
278            SampleData::Double64(d) => {
279                for (dst, s) in d.iter_mut().zip(src) {
280                    *dst = *s as f64;
281                }
282            }
283            SampleData::Int32(d) => {
284                for (dst, s) in d.iter_mut().zip(src) {
285                    *dst = *s as i32;
286                }
287            }
288            SampleData::Int16(d) => {
289                for (dst, s) in d.iter_mut().zip(src) {
290                    *dst = *s as i16;
291                }
292            }
293            SampleData::Int8(d) => {
294                for (dst, s) in d.iter_mut().zip(src) {
295                    *dst = *s as i8;
296                }
297            }
298            SampleData::StringData(d) => {
299                for (dst, s) in d.iter_mut().zip(src) {
300                    *dst = s.to_string();
301                }
302            }
303        }
304    }
305
306    pub fn retrieve_i64(&self, dst: &mut [i64]) {
307        match &self.data {
308            SampleData::Int64(d) => dst.copy_from_slice(d),
309            SampleData::Float32(d) => {
310                for (o, s) in dst.iter_mut().zip(d) {
311                    *o = *s as i64;
312                }
313            }
314            SampleData::Double64(d) => {
315                for (o, s) in dst.iter_mut().zip(d) {
316                    *o = *s as i64;
317                }
318            }
319            SampleData::Int32(d) => {
320                for (o, s) in dst.iter_mut().zip(d) {
321                    *o = *s as i64;
322                }
323            }
324            SampleData::Int16(d) => {
325                for (o, s) in dst.iter_mut().zip(d) {
326                    *o = *s as i64;
327                }
328            }
329            SampleData::Int8(d) => {
330                for (o, s) in dst.iter_mut().zip(d) {
331                    *o = *s as i64;
332                }
333            }
334            SampleData::StringData(d) => {
335                for (o, s) in dst.iter_mut().zip(d) {
336                    *o = s.parse().unwrap_or(0);
337                }
338            }
339        }
340    }
341
342    pub fn assign_i16(&mut self, src: &[i16]) {
343        match &mut self.data {
344            SampleData::Int16(d) => d.copy_from_slice(src),
345            SampleData::Float32(d) => {
346                for (dst, s) in d.iter_mut().zip(src) {
347                    *dst = *s as f32;
348                }
349            }
350            SampleData::Double64(d) => {
351                for (dst, s) in d.iter_mut().zip(src) {
352                    *dst = *s as f64;
353                }
354            }
355            SampleData::Int32(d) => {
356                for (dst, s) in d.iter_mut().zip(src) {
357                    *dst = *s as i32;
358                }
359            }
360            SampleData::Int8(d) => {
361                for (dst, s) in d.iter_mut().zip(src) {
362                    *dst = *s as i8;
363                }
364            }
365            SampleData::Int64(d) => {
366                for (dst, s) in d.iter_mut().zip(src) {
367                    *dst = *s as i64;
368                }
369            }
370            SampleData::StringData(d) => {
371                for (dst, s) in d.iter_mut().zip(src) {
372                    *dst = s.to_string();
373                }
374            }
375        }
376    }
377
378    pub fn retrieve_i16(&self, dst: &mut [i16]) {
379        match &self.data {
380            SampleData::Int16(d) => dst.copy_from_slice(d),
381            SampleData::Float32(d) => {
382                for (o, s) in dst.iter_mut().zip(d) {
383                    *o = *s as i16;
384                }
385            }
386            SampleData::Double64(d) => {
387                for (o, s) in dst.iter_mut().zip(d) {
388                    *o = *s as i16;
389                }
390            }
391            SampleData::Int32(d) => {
392                for (o, s) in dst.iter_mut().zip(d) {
393                    *o = *s as i16;
394                }
395            }
396            SampleData::Int8(d) => {
397                for (o, s) in dst.iter_mut().zip(d) {
398                    *o = *s as i16;
399                }
400            }
401            SampleData::Int64(d) => {
402                for (o, s) in dst.iter_mut().zip(d) {
403                    *o = *s as i16;
404                }
405            }
406            SampleData::StringData(d) => {
407                for (o, s) in dst.iter_mut().zip(d) {
408                    *o = s.parse().unwrap_or(0);
409                }
410            }
411        }
412    }
413
414    pub fn assign_i8(&mut self, src: &[i8]) {
415        match &mut self.data {
416            SampleData::Int8(d) => d.copy_from_slice(src),
417            SampleData::Float32(d) => {
418                for (dst, s) in d.iter_mut().zip(src) {
419                    *dst = *s as f32;
420                }
421            }
422            SampleData::Double64(d) => {
423                for (dst, s) in d.iter_mut().zip(src) {
424                    *dst = *s as f64;
425                }
426            }
427            SampleData::Int32(d) => {
428                for (dst, s) in d.iter_mut().zip(src) {
429                    *dst = *s as i32;
430                }
431            }
432            SampleData::Int16(d) => {
433                for (dst, s) in d.iter_mut().zip(src) {
434                    *dst = *s as i16;
435                }
436            }
437            SampleData::Int64(d) => {
438                for (dst, s) in d.iter_mut().zip(src) {
439                    *dst = *s as i64;
440                }
441            }
442            SampleData::StringData(d) => {
443                for (dst, s) in d.iter_mut().zip(src) {
444                    *dst = s.to_string();
445                }
446            }
447        }
448    }
449
450    pub fn retrieve_i8(&self, dst: &mut [i8]) {
451        match &self.data {
452            SampleData::Int8(d) => dst.copy_from_slice(d),
453            SampleData::Float32(d) => {
454                for (o, s) in dst.iter_mut().zip(d) {
455                    *o = *s as i8;
456                }
457            }
458            SampleData::Double64(d) => {
459                for (o, s) in dst.iter_mut().zip(d) {
460                    *o = *s as i8;
461                }
462            }
463            SampleData::Int32(d) => {
464                for (o, s) in dst.iter_mut().zip(d) {
465                    *o = *s as i8;
466                }
467            }
468            SampleData::Int16(d) => {
469                for (o, s) in dst.iter_mut().zip(d) {
470                    *o = *s as i8;
471                }
472            }
473            SampleData::Int64(d) => {
474                for (o, s) in dst.iter_mut().zip(d) {
475                    *o = *s as i8;
476                }
477            }
478            SampleData::StringData(d) => {
479                for (o, s) in dst.iter_mut().zip(d) {
480                    *o = s.parse().unwrap_or(0);
481                }
482            }
483        }
484    }
485
486    pub fn assign_strings(&mut self, src: &[String]) {
487        if let SampleData::StringData(d) = &mut self.data {
488            d.clone_from_slice(src);
489        }
490    }
491
492    pub fn retrieve_strings(&self) -> Vec<String> {
493        match &self.data {
494            SampleData::StringData(d) => d.clone(),
495            SampleData::Float32(d) => d.iter().map(|v| v.to_string()).collect(),
496            SampleData::Double64(d) => d.iter().map(|v| v.to_string()).collect(),
497            SampleData::Int32(d) => d.iter().map(|v| v.to_string()).collect(),
498            SampleData::Int16(d) => d.iter().map(|v| v.to_string()).collect(),
499            SampleData::Int8(d) => d.iter().map(|v| v.to_string()).collect(),
500            SampleData::Int64(d) => d.iter().map(|v| v.to_string()).collect(),
501        }
502    }
503
504    /// Assign raw bytes (for numeric non-string formats)
505    pub fn assign_raw(&mut self, data: &[u8]) {
506        match &mut self.data {
507            SampleData::Float32(d) => {
508                for (i, v) in d.iter_mut().enumerate() {
509                    let off = i * 4;
510                    if off + 4 <= data.len() {
511                        *v = f32::from_le_bytes([
512                            data[off],
513                            data[off + 1],
514                            data[off + 2],
515                            data[off + 3],
516                        ]);
517                    }
518                }
519            }
520            SampleData::Double64(d) => {
521                for (i, v) in d.iter_mut().enumerate() {
522                    let off = i * 8;
523                    if off + 8 <= data.len() {
524                        *v = f64::from_le_bytes(data[off..off + 8].try_into().unwrap());
525                    }
526                }
527            }
528            SampleData::Int32(d) => {
529                for (i, v) in d.iter_mut().enumerate() {
530                    let off = i * 4;
531                    if off + 4 <= data.len() {
532                        *v = i32::from_le_bytes([
533                            data[off],
534                            data[off + 1],
535                            data[off + 2],
536                            data[off + 3],
537                        ]);
538                    }
539                }
540            }
541            SampleData::Int16(d) => {
542                for (i, v) in d.iter_mut().enumerate() {
543                    let off = i * 2;
544                    if off + 2 <= data.len() {
545                        *v = i16::from_le_bytes([data[off], data[off + 1]]);
546                    }
547                }
548            }
549            SampleData::Int8(d) => {
550                for (i, v) in d.iter_mut().enumerate() {
551                    if i < data.len() {
552                        *v = data[i] as i8;
553                    }
554                }
555            }
556            SampleData::Int64(d) => {
557                for (i, v) in d.iter_mut().enumerate() {
558                    let off = i * 8;
559                    if off + 8 <= data.len() {
560                        *v = i64::from_le_bytes(data[off..off + 8].try_into().unwrap());
561                    }
562                }
563            }
564            _ => {}
565        }
566    }
567
568    /// Retrieve raw bytes
569    pub fn retrieve_raw(&self) -> Vec<u8> {
570        match &self.data {
571            SampleData::Float32(d) => d.iter().flat_map(|v| v.to_le_bytes()).collect(),
572            SampleData::Double64(d) => d.iter().flat_map(|v| v.to_le_bytes()).collect(),
573            SampleData::Int32(d) => d.iter().flat_map(|v| v.to_le_bytes()).collect(),
574            SampleData::Int16(d) => d.iter().flat_map(|v| v.to_le_bytes()).collect(),
575            SampleData::Int8(d) => d.iter().map(|v| *v as u8).collect(),
576            SampleData::Int64(d) => d.iter().flat_map(|v| v.to_le_bytes()).collect(),
577            SampleData::StringData(_) => Vec::new(),
578        }
579    }
580
581    /// Serialize a sample to bytes (protocol 1.10, little-endian, no byte-order swap).
582    pub fn serialize_110(&self, buf: &mut Vec<u8>) {
583        if self.timestamp == DEDUCED_TIMESTAMP {
584            buf.push(TAG_DEDUCED_TIMESTAMP);
585        } else {
586            buf.push(TAG_TRANSMITTED_TIMESTAMP);
587            buf.extend_from_slice(&self.timestamp.to_le_bytes());
588        }
589
590        match &self.data {
591            SampleData::StringData(strings) => {
592                for s in strings {
593                    let len = s.len();
594                    if len <= 0xFF {
595                        buf.push(1); // sizeof(u8)
596                        buf.push(len as u8);
597                    } else if len <= 0xFFFFFFFF {
598                        buf.push(4); // sizeof(u32)
599                        buf.extend_from_slice(&(len as u32).to_le_bytes());
600                    } else {
601                        buf.push(8); // sizeof(u64)
602                        buf.extend_from_slice(&(len as u64).to_le_bytes());
603                    }
604                    buf.extend_from_slice(s.as_bytes());
605                }
606            }
607            _ => {
608                buf.extend_from_slice(&self.retrieve_raw());
609            }
610        }
611    }
612
613    /// Deserialize a sample from a reader (protocol 1.10, little-endian).
614    pub fn deserialize_110<R: Read>(
615        reader: &mut R,
616        fmt: ChannelFormat,
617        num_channels: u32,
618    ) -> IoResult<Sample> {
619        let mut tag = [0u8; 1];
620        reader.read_exact(&mut tag)?;
621
622        let timestamp = if tag[0] == TAG_DEDUCED_TIMESTAMP {
623            DEDUCED_TIMESTAMP
624        } else {
625            let mut ts_bytes = [0u8; 8];
626            reader.read_exact(&mut ts_bytes)?;
627            f64::from_le_bytes(ts_bytes)
628        };
629
630        let n = num_channels as usize;
631        let data = match fmt {
632            ChannelFormat::Float32 => {
633                let mut raw = vec![0u8; n * 4];
634                reader.read_exact(&mut raw)?;
635                SampleData::Float32(
636                    raw.chunks_exact(4)
637                        .map(|c| f32::from_le_bytes(c.try_into().unwrap()))
638                        .collect(),
639                )
640            }
641            ChannelFormat::Double64 => {
642                let mut raw = vec![0u8; n * 8];
643                reader.read_exact(&mut raw)?;
644                SampleData::Double64(
645                    raw.chunks_exact(8)
646                        .map(|c| f64::from_le_bytes(c.try_into().unwrap()))
647                        .collect(),
648                )
649            }
650            ChannelFormat::Int32 => {
651                let mut raw = vec![0u8; n * 4];
652                reader.read_exact(&mut raw)?;
653                SampleData::Int32(
654                    raw.chunks_exact(4)
655                        .map(|c| i32::from_le_bytes(c.try_into().unwrap()))
656                        .collect(),
657                )
658            }
659            ChannelFormat::Int16 => {
660                let mut raw = vec![0u8; n * 2];
661                reader.read_exact(&mut raw)?;
662                SampleData::Int16(
663                    raw.chunks_exact(2)
664                        .map(|c| i16::from_le_bytes(c.try_into().unwrap()))
665                        .collect(),
666                )
667            }
668            ChannelFormat::Int8 => {
669                let mut raw = vec![0u8; n];
670                reader.read_exact(&mut raw)?;
671                SampleData::Int8(raw.into_iter().map(|b| b as i8).collect())
672            }
673            ChannelFormat::Int64 => {
674                let mut raw = vec![0u8; n * 8];
675                reader.read_exact(&mut raw)?;
676                SampleData::Int64(
677                    raw.chunks_exact(8)
678                        .map(|c| i64::from_le_bytes(c.try_into().unwrap()))
679                        .collect(),
680                )
681            }
682            ChannelFormat::String | ChannelFormat::Undefined => {
683                let mut strings = Vec::with_capacity(n);
684                for _ in 0..n {
685                    let mut lenbytes = [0u8; 1];
686                    reader.read_exact(&mut lenbytes)?;
687                    let len: usize = match lenbytes[0] {
688                        1 => {
689                            let mut b = [0u8; 1];
690                            reader.read_exact(&mut b)?;
691                            b[0] as usize
692                        }
693                        4 => {
694                            let mut b = [0u8; 4];
695                            reader.read_exact(&mut b)?;
696                            u32::from_le_bytes(b) as usize
697                        }
698                        8 => {
699                            let mut b = [0u8; 8];
700                            reader.read_exact(&mut b)?;
701                            u64::from_le_bytes(b) as usize
702                        }
703                        _ => {
704                            return Err(IoError::new(ErrorKind::InvalidData, "invalid varlen int"))
705                        }
706                    };
707                    let mut sbuf = vec![0u8; len];
708                    reader.read_exact(&mut sbuf)?;
709                    strings.push(String::from_utf8_lossy(&sbuf).into_owned());
710                }
711                SampleData::StringData(strings)
712            }
713        };
714
715        Ok(Sample {
716            timestamp,
717            pushthrough: true,
718            data,
719        })
720    }
721
722    /// Serialize a sample to bytes (protocol 1.00).
723    /// Protocol 1.00: every sample has an 8-byte timestamp (no tag byte),
724    /// strings use 4-byte length prefix.
725    pub fn serialize_100(&self, buf: &mut Vec<u8>) {
726        // Always write the full 8-byte timestamp
727        buf.extend_from_slice(&self.timestamp.to_le_bytes());
728
729        match &self.data {
730            SampleData::StringData(strings) => {
731                for s in strings {
732                    let len = s.len() as u32;
733                    buf.extend_from_slice(&len.to_le_bytes());
734                    buf.extend_from_slice(s.as_bytes());
735                }
736            }
737            _ => {
738                buf.extend_from_slice(&self.retrieve_raw());
739            }
740        }
741    }
742
743    /// Deserialize a sample from a reader (protocol 1.00).
744    pub fn deserialize_100<R: Read>(
745        reader: &mut R,
746        fmt: ChannelFormat,
747        num_channels: u32,
748    ) -> IoResult<Sample> {
749        // Always read 8-byte timestamp
750        let mut ts_bytes = [0u8; 8];
751        reader.read_exact(&mut ts_bytes)?;
752        let timestamp = f64::from_le_bytes(ts_bytes);
753
754        let n = num_channels as usize;
755        let data = match fmt {
756            ChannelFormat::Float32 => {
757                let mut raw = vec![0u8; n * 4];
758                reader.read_exact(&mut raw)?;
759                SampleData::Float32(
760                    raw.chunks_exact(4)
761                        .map(|c| f32::from_le_bytes(c.try_into().unwrap()))
762                        .collect(),
763                )
764            }
765            ChannelFormat::Double64 => {
766                let mut raw = vec![0u8; n * 8];
767                reader.read_exact(&mut raw)?;
768                SampleData::Double64(
769                    raw.chunks_exact(8)
770                        .map(|c| f64::from_le_bytes(c.try_into().unwrap()))
771                        .collect(),
772                )
773            }
774            ChannelFormat::Int32 => {
775                let mut raw = vec![0u8; n * 4];
776                reader.read_exact(&mut raw)?;
777                SampleData::Int32(
778                    raw.chunks_exact(4)
779                        .map(|c| i32::from_le_bytes(c.try_into().unwrap()))
780                        .collect(),
781                )
782            }
783            ChannelFormat::Int16 => {
784                let mut raw = vec![0u8; n * 2];
785                reader.read_exact(&mut raw)?;
786                SampleData::Int16(
787                    raw.chunks_exact(2)
788                        .map(|c| i16::from_le_bytes(c.try_into().unwrap()))
789                        .collect(),
790                )
791            }
792            ChannelFormat::Int8 => {
793                let mut raw = vec![0u8; n];
794                reader.read_exact(&mut raw)?;
795                SampleData::Int8(raw.into_iter().map(|b| b as i8).collect())
796            }
797            ChannelFormat::Int64 => {
798                let mut raw = vec![0u8; n * 8];
799                reader.read_exact(&mut raw)?;
800                SampleData::Int64(
801                    raw.chunks_exact(8)
802                        .map(|c| i64::from_le_bytes(c.try_into().unwrap()))
803                        .collect(),
804                )
805            }
806            ChannelFormat::String | ChannelFormat::Undefined => {
807                let mut strings = Vec::with_capacity(n);
808                for _ in 0..n {
809                    let mut len_bytes = [0u8; 4];
810                    reader.read_exact(&mut len_bytes)?;
811                    let len = u32::from_le_bytes(len_bytes) as usize;
812                    let mut sbuf = vec![0u8; len];
813                    reader.read_exact(&mut sbuf)?;
814                    strings.push(String::from_utf8_lossy(&sbuf).into_owned());
815                }
816                SampleData::StringData(strings)
817            }
818        };
819
820        Ok(Sample {
821            timestamp,
822            pushthrough: true,
823            data,
824        })
825    }
826
827    /// Generate a test pattern matching liblsl's assign_test_pattern
828    pub fn assign_test_pattern(&mut self, offset: i32) {
829        self.timestamp = 123456.789;
830        self.pushthrough = true;
831        match &mut self.data {
832            SampleData::Float32(d) => {
833                for (k, v) in d.iter_mut().enumerate() {
834                    let val = (k as i32 + offset) as f32;
835                    *v = if k % 2 == 0 { val } else { -val };
836                }
837            }
838            SampleData::Double64(d) => {
839                for (k, v) in d.iter_mut().enumerate() {
840                    let val = (k as i64 + offset as i64 + 16777217) as f64;
841                    *v = if k % 2 == 0 { val } else { -val };
842                }
843            }
844            SampleData::Int32(d) => {
845                for (k, v) in d.iter_mut().enumerate() {
846                    let val = (k as i32 + offset + 65537) % i32::MAX;
847                    *v = if k % 2 == 0 { val } else { -val };
848                }
849            }
850            SampleData::Int16(d) => {
851                for (k, v) in d.iter_mut().enumerate() {
852                    let val = ((k as i32 + offset + 257) % i16::MAX as i32) as i16;
853                    *v = if k % 2 == 0 { val } else { -val };
854                }
855            }
856            SampleData::Int8(d) => {
857                for (k, v) in d.iter_mut().enumerate() {
858                    let val = ((k as i32 + offset + 1) % i8::MAX as i32) as i8;
859                    *v = if k % 2 == 0 { val } else { -val };
860                }
861            }
862            SampleData::Int64(d) => {
863                for (k, v) in d.iter_mut().enumerate() {
864                    let val = k as i64 + 2147483649i64 + offset as i64;
865                    *v = if k % 2 == 0 { val } else { -val };
866                }
867            }
868            SampleData::StringData(d) => {
869                for (k, v) in d.iter_mut().enumerate() {
870                    let val = (k as i32 + 10) * if k % 2 == 0 { 1 } else { -1 };
871                    *v = val.to_string();
872                }
873            }
874        }
875    }
876
877    pub fn num_channels(&self) -> usize {
878        match &self.data {
879            SampleData::Float32(d) => d.len(),
880            SampleData::Double64(d) => d.len(),
881            SampleData::Int32(d) => d.len(),
882            SampleData::Int16(d) => d.len(),
883            SampleData::Int8(d) => d.len(),
884            SampleData::Int64(d) => d.len(),
885            SampleData::StringData(d) => d.len(),
886        }
887    }
888
889    pub fn format(&self) -> ChannelFormat {
890        match &self.data {
891            SampleData::Float32(_) => ChannelFormat::Float32,
892            SampleData::Double64(_) => ChannelFormat::Double64,
893            SampleData::Int32(_) => ChannelFormat::Int32,
894            SampleData::Int16(_) => ChannelFormat::Int16,
895            SampleData::Int8(_) => ChannelFormat::Int8,
896            SampleData::Int64(_) => ChannelFormat::Int64,
897            SampleData::StringData(_) => ChannelFormat::String,
898        }
899    }
900}
901
902impl PartialEq for Sample {
903    fn eq(&self, other: &Self) -> bool {
904        if self.timestamp != other.timestamp {
905            return false;
906        }
907        match (&self.data, &other.data) {
908            (SampleData::Float32(a), SampleData::Float32(b)) => a == b,
909            (SampleData::Double64(a), SampleData::Double64(b)) => a == b,
910            (SampleData::Int32(a), SampleData::Int32(b)) => a == b,
911            (SampleData::Int16(a), SampleData::Int16(b)) => a == b,
912            (SampleData::Int8(a), SampleData::Int8(b)) => a == b,
913            (SampleData::Int64(a), SampleData::Int64(b)) => a == b,
914            (SampleData::StringData(a), SampleData::StringData(b)) => a == b,
915            _ => false,
916        }
917    }
918}