wfdb/record/
segment_reader.rs

1use crate::record::segment::SegmentManager;
2use crate::{Error, MultiSignalReader, Result, Sample, SegmentInfo};
3use std::path::PathBuf;
4
5/// Reader for multi-segment records with seeking support.
6///
7/// Handles automatic segment switching and provides unified access to signals
8/// across all segments in a multi-segment record.
9///
10/// # Examples
11///
12/// ```no_run
13/// use wfdb::Record;
14///
15/// # fn main() -> wfdb::Result<()> {
16/// let record = Record::open("data/multi_segment_record")?;
17/// let mut reader = record.segment_reader()?;
18///
19/// // Read frames across all segments
20/// while let Some(frame) = reader.read_frame()? {
21///     // Process frame...
22/// }
23/// # Ok(())
24/// # }
25/// ```
26pub struct SegmentReader {
27    /// Segment manager for handling segment metadata and switching.
28    segment_manager: SegmentManager,
29    /// Current multi-signal reader (for current segment).
30    current_reader: Option<MultiSignalReader>,
31    /// Total samples read across all segments.
32    samples_read: u64,
33}
34
35impl SegmentReader {
36    /// Create a new segment reader.
37    pub(crate) fn new(base_path: PathBuf, segments: Vec<SegmentInfo>) -> Self {
38        let segment_manager = SegmentManager::new(base_path, segments);
39
40        Self {
41            segment_manager,
42            current_reader: None,
43            samples_read: 0,
44        }
45    }
46
47    /// Read one frame (one sample from each signal).
48    ///
49    /// Returns `None` when all segments have been read.
50    ///
51    /// # Errors
52    ///
53    /// Returns an error if:
54    /// - A segment cannot be loaded
55    /// - A frame cannot be read
56    pub fn read_frame(&mut self) -> Result<Option<Vec<Sample>>> {
57        loop {
58            // Try to read from current reader
59            if let Some(reader) = &mut self.current_reader {
60                let frame = reader.read_frame()?;
61                if !frame.is_empty() {
62                    self.samples_read += 1;
63                    return Ok(Some(frame));
64                }
65                // Current segment exhausted, move to next
66            }
67
68            // Move to next segment
69            if !self.advance_segment()? {
70                return Ok(None); // No more segments
71            }
72        }
73    }
74
75    /// Read multiple frames.
76    ///
77    /// # Errors
78    ///
79    /// Returns an error if:
80    /// - A segment cannot be loaded
81    /// - Frames cannot be read
82    pub fn read_frames(&mut self, count: usize) -> Result<Vec<Vec<Sample>>> {
83        let mut frames = Vec::with_capacity(count);
84        for _ in 0..count {
85            match self.read_frame()? {
86                Some(frame) => frames.push(frame),
87                None => break,
88            }
89        }
90        Ok(frames)
91    }
92
93    /// Seek to a specific sample number across all segments.
94    ///
95    /// Automatically switches to the appropriate segment and positions
96    /// the reader at the target sample.
97    ///
98    /// Returns the actual sample position after seeking.
99    ///
100    /// # Errors
101    ///
102    /// Returns an error if:
103    /// - The segment containing the target sample cannot be loaded
104    /// - Seeking within the segment fails
105    pub fn seek_to_sample(&mut self, sample: u64) -> Result<u64> {
106        // Find which segment contains this sample
107        let segment_index = self.find_segment_for_sample(sample)?;
108
109        // Calculate offset within segment
110        let segment_start = if segment_index == 0 {
111            0
112        } else {
113            self.segment_manager
114                .segment_info(segment_index - 1)
115                .map_or(0, |s| s.num_samples)
116        };
117        let offset_in_segment = sample - segment_start;
118
119        // Switch to target segment
120        self.switch_to_segment(segment_index)?;
121
122        // Seek within segment
123        if let Some(reader) = &mut self.current_reader {
124            reader.seek_to_frame(offset_in_segment)?;
125        }
126
127        self.samples_read = sample;
128        Ok(sample)
129    }
130
131    /// Get current sample position across all segments.
132    #[must_use]
133    pub const fn position(&self) -> u64 {
134        self.samples_read
135    }
136
137    /// Get total number of samples across all segments.
138    #[must_use]
139    pub fn total_samples(&self) -> u64 {
140        self.segment_manager
141            .segment_info(self.segment_manager.num_segments() - 1)
142            .map_or(0, |s| s.num_samples)
143    }
144
145    /// Get current segment index.
146    #[must_use]
147    pub const fn current_segment(&self) -> usize {
148        self.segment_manager.current_index()
149    }
150
151    /// Get total number of segments.
152    #[must_use]
153    pub const fn num_segments(&self) -> usize {
154        self.segment_manager.num_segments()
155    }
156
157    // [Private helper methods]
158
159    /// Advance to the next segment.
160    ///
161    /// Returns `true` if successfully advanced, `false` if no more segments.
162    fn advance_segment(&mut self) -> Result<bool> {
163        let next_index = self.segment_manager.current_index() + 1;
164        if next_index >= self.segment_manager.num_segments() {
165            return Ok(false);
166        }
167
168        self.switch_to_segment(next_index)?;
169        Ok(true)
170    }
171
172    /// Switch to a specific segment.
173    fn switch_to_segment(&mut self, index: usize) -> Result<()> {
174        // Load segment data
175        self.segment_manager.load_segment(index)?;
176
177        // Get signals and base path for this segment
178        let signals = self.segment_manager.current_signals()?.to_vec();
179        let base_path = self.segment_manager.current_base_path()?.to_path_buf();
180
181        // Create new multi-signal reader for this segment
182        let reader = MultiSignalReader::new(&base_path, &signals)?;
183
184        self.current_reader = Some(reader);
185        Ok(())
186    }
187
188    /// Find which segment contains a given sample number.
189    fn find_segment_for_sample(&self, sample: u64) -> Result<usize> {
190        let mut cumulative = 0u64;
191        for i in 0..self.segment_manager.num_segments() {
192            let segment_info = self
193                .segment_manager
194                .segment_info(i)
195                .ok_or_else(|| Error::InvalidHeader("Segment not found".to_string()))?;
196
197            if sample < cumulative + segment_info.num_samples {
198                return Ok(i);
199            }
200            cumulative += segment_info.num_samples;
201        }
202
203        Err(Error::InvalidHeader(format!(
204            "Sample {sample} is beyond the end of the record"
205        )))
206    }
207}