fluxfox/bitstream/
fm.rs

1/*
2    FluxFox
3    https://github.com/dbalsom/fluxfox
4
5    Copyright 2024 Daniel Balsom
6
7    Permission is hereby granted, free of charge, to any person obtaining a
8    copy of this software and associated documentation files (the “Software”),
9    to deal in the Software without restriction, including without limitation
10    the rights to use, copy, modify, merge, publish, distribute, sublicense,
11    and/or sell copies of the Software, and to permit persons to whom the
12    Software is furnished to do so, subject to the following conditions:
13
14    The above copyright notice and this permission notice shall be included in
15    all copies or substantial portions of the Software.
16
17    THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23    DEALINGS IN THE SOFTWARE.
24
25    --------------------------------------------------------------------------
26
27    src/fm.rs
28
29    Implements a wrapper around a BitVec to provide FM encoding and decoding.
30
31*/
32use crate::{
33    bitstream::{EncodingVariant, TrackCodec, TrackDataStreamT},
34    diskimage::TrackRegion,
35    io::{Error, ErrorKind, Read, Result, Seek, SeekFrom},
36    range_check::RangeChecker,
37    DiskDataEncoding,
38};
39use bit_vec::BitVec;
40use std::ops::Index;
41
42pub const FM_BYTE_LEN: usize = 16;
43pub const FM_MARKER_LEN: usize = 64;
44
45//pub const FM_MARKER_CLOCK_MASK: u64 = 0xAAAA_AAAA_AAAA_0000;
46pub const FM_MARKER_DATA_MASK: u64 = 0x0000_0000_0000_5555;
47
48pub const FM_MARKER_CLOCK_MASK: u64 = 0xAAAA_AAAA_AAAA_AAAA;
49pub const FM_MARKER_CLOCK_PATTERN: u64 = 0xAAAA_AAAA_AAAA_A02A;
50
51#[doc(hidden)]
52#[macro_export]
53macro_rules! fm_offset {
54    ($x:expr) => {
55        $x * 16
56    };
57}
58
59pub struct FmCodec {
60    bit_vec: BitVec,
61    clock_map: BitVec,
62    weak_enabled: bool,
63    weak_mask: BitVec,
64    initial_phase: usize,
65    bit_cursor: usize,
66    track_padding: usize,
67    data_ranges: RangeChecker,
68    data_ranges_filtered: RangeChecker,
69}
70
71pub enum FmEncodingType {
72    Data,
73    AddressMark,
74}
75
76pub fn get_fm_sync_offset(track: &BitVec) -> Option<bool> {
77    match find_sync(track, 0) {
78        Some(offset) => {
79            if offset % 2 == 0 {
80                Some(false)
81            }
82            else {
83                Some(true)
84            }
85        }
86        None => None,
87    }
88}
89
90pub fn find_sync(track: &BitVec, start_idx: usize) -> Option<usize> {
91    let mut shift_reg: u32 = 0;
92
93    for (i, bit) in track.into_iter().skip(start_idx).enumerate() {
94        shift_reg = shift_reg << 1 | (bit as u32);
95
96        if i >= 32 && shift_reg == 0xAA_AA_AA_AA {
97            return Some(i - 32);
98        }
99    }
100    None
101}
102
103impl TrackCodec for FmCodec {
104    fn encoding(&self) -> DiskDataEncoding {
105        DiskDataEncoding::Fm
106    }
107
108    fn len(&self) -> usize {
109        self.bit_vec.len()
110    }
111
112    fn is_empty(&self) -> bool {
113        self.bit_vec.is_empty()
114    }
115
116    fn replace(&mut self, new_bits: BitVec) {
117        self.bit_vec = new_bits;
118    }
119
120    fn data_bits(&self) -> &BitVec {
121        &self.bit_vec
122    }
123
124    fn data(&self) -> Vec<u8> {
125        self.bit_vec.to_bytes()
126    }
127
128    fn set_clock_map(&mut self, clock_map: BitVec) {
129        self.clock_map = clock_map;
130    }
131
132    fn clock_map(&self) -> &BitVec {
133        &self.clock_map
134    }
135
136    fn clock_map_mut(&mut self) -> &mut BitVec {
137        &mut self.clock_map
138    }
139
140    fn enable_weak(&mut self, enable: bool) {
141        self.weak_enabled = enable;
142    }
143
144    fn weak_mask(&self) -> &BitVec {
145        &self.weak_mask
146    }
147
148    fn has_weak_bits(&self) -> bool {
149        !self.detect_weak_bits(6).0 > 0
150    }
151
152    fn weak_data(&self) -> Vec<u8> {
153        self.weak_mask.to_bytes()
154    }
155
156    fn set_track_padding(&mut self) {
157        let mut wrap_buffer: [u8; 4] = [0; 4];
158
159        if self.bit_vec.len() % 8 == 0 {
160            // Track length was an even multiple of 8, so it is possible track data was padded to
161            // byte margins.
162
163            let found_pad = false;
164            // Read buffer across the end of the track and see if all bytes are the same.
165            for pad in 1..16 {
166                log::trace!(
167                    "bitcells: {} data bits: {} window_start: {}",
168                    self.bit_vec.len(),
169                    self.bit_vec.len() / 2,
170                    self.bit_vec.len() - (8 * 2)
171                );
172                let wrap_addr = (self.bit_vec.len() / 2) - (8 * 2);
173
174                self.track_padding = pad;
175
176                self.seek(SeekFrom::Start(wrap_addr as u64)).unwrap();
177                self.read_exact(&mut wrap_buffer).unwrap();
178
179                log::trace!(
180                    "set_track_padding(): wrap_buffer at {}, pad {}: {:02X?}",
181                    wrap_addr,
182                    pad,
183                    wrap_buffer
184                );
185            }
186
187            if !found_pad {
188                // No padding found
189                log::warn!("set_track_padding(): Unable to determine track padding.");
190                self.track_padding = 0;
191            }
192        }
193        else {
194            // Track length is not an even multiple of 8 - the only explanation is that there is no
195            // track padding.
196            self.track_padding = 0;
197        }
198    }
199
200    fn read_raw_byte(&self, index: usize) -> Option<u8> {
201        if index >= self.len() {
202            return None;
203        }
204
205        let mut byte = 0;
206        for bi in index..std::cmp::min(index + 8, self.bit_vec.len()) {
207            byte = (byte << 1) | self.bit_vec[bi] as u8;
208        }
209        Some(byte)
210    }
211
212    fn read_decoded_byte(&self, index: usize) -> Option<u8> {
213        if index >= self.bit_vec.len() || index >= self.clock_map.len() {
214            log::error!(
215                "read_decoded_byte(): index out of bounds: {} vec: {} clock_map:{}",
216                index,
217                self.bit_vec.len(),
218                self.clock_map.len()
219            );
220            return None;
221        }
222        let p_off: usize = self.clock_map[index] as usize;
223        let mut byte = 0;
224        for bi in (index..std::cmp::min(index + FM_BYTE_LEN, self.bit_vec.len()))
225            .skip(p_off)
226            .step_by(2)
227        {
228            byte = (byte << 1) | self.bit_vec[bi] as u8;
229        }
230        Some(byte)
231    }
232
233    fn write_buf(&mut self, buf: &[u8], offset: usize) -> Option<usize> {
234        let encoded_buf = Self::encode(buf, false, EncodingVariant::Data);
235
236        let mut copy_len = encoded_buf.len();
237        if self.bit_vec.len() < offset + encoded_buf.len() {
238            copy_len = self.bit_vec.len() - offset;
239        }
240
241        let mut bits_written = 0;
242
243        let phase = !self.clock_map[offset] as usize;
244        println!("write_buf(): offset: {} phase: {}", offset, phase);
245
246        for (i, bit) in encoded_buf.into_iter().enumerate().take(copy_len) {
247            self.bit_vec.set(offset + phase + i, bit);
248            bits_written += 1;
249        }
250
251        let bytes_written = bits_written + 7 / 8;
252        Some(bytes_written)
253    }
254
255    fn write_raw_buf(&mut self, buf: &[u8], offset: usize) -> usize {
256        let mut bytes_written = 0;
257        let mut offset = offset;
258
259        for byte in buf {
260            for bit_pos in (0..8).rev() {
261                let bit = byte & (0x01 << bit_pos) != 0;
262                self.bit_vec.set(offset, bit);
263                offset += 1;
264            }
265            bytes_written += 1;
266        }
267
268        bytes_written
269    }
270
271    fn encode(&self, data: &[u8], prev_bit: bool, encoding_type: EncodingVariant) -> BitVec {
272        let mut bitvec = BitVec::new();
273        let mut bit_count = 0;
274
275        for &byte in data {
276            for i in (0..8).rev() {
277                let bit = (byte & (1 << i)) != 0;
278                if bit {
279                    // 1 is encoded as 01
280                    bitvec.push(false);
281                    bitvec.push(true);
282                }
283                else {
284                    // 0 is encoded as 10 if previous bit was 0, otherwise 00
285                    let previous_bit = if bitvec.is_empty() {
286                        prev_bit
287                    }
288                    else {
289                        bitvec[bitvec.len() - 1]
290                    };
291
292                    if previous_bit {
293                        bitvec.push(false);
294                    }
295                    else {
296                        bitvec.push(true);
297                    }
298                    bitvec.push(false);
299                }
300
301                bit_count += 1;
302
303                // Omit clock bit between source bits 3 and 4 for address marks
304                if let EncodingVariant::AddressMark = encoding_type {
305                    if bit_count == 4 {
306                        // Clear the previous clock bit (which is between bit 3 and 4)
307                        bitvec.set(bitvec.len() - 2, false);
308                    }
309                }
310            }
311
312            // Reset bit_count for the next byte
313            bit_count = 0;
314        }
315
316        bitvec
317    }
318
319    fn find_marker(&self, marker: u64, mask: Option<u64>, start: usize, limit: Option<usize>) -> Option<(usize, u16)> {
320        //log::debug!("Fm::find_marker(): Searching for marker {:016X} at {}", marker, start);
321        if self.bit_vec.is_empty() {
322            return None;
323        }
324
325        let mask = mask.unwrap_or(!0);
326
327        let mut shift_reg: u64 = 0;
328        let mut shift_ct: u32 = 0;
329
330        let search_limit = if let Some(provided_limit) = limit {
331            std::cmp::min(provided_limit, self.bit_vec.len())
332        }
333        else {
334            self.bit_vec.len()
335        };
336
337        for bi in start..search_limit {
338            shift_reg = (shift_reg << 1) | self.bit_vec[bi] as u64;
339            shift_ct += 1;
340
341            let have_marker = (shift_reg & FM_MARKER_CLOCK_MASK) == FM_MARKER_CLOCK_PATTERN;
342            let have_data = (shift_reg & FM_MARKER_DATA_MASK & mask) == marker & FM_MARKER_DATA_MASK & mask;
343
344            if shift_ct >= 64 && have_marker {
345                log::debug!(
346                    "found marker clock at {}: Shift reg {:16X}: data: {:16X} mask: {:16X}, marker data: {:16X}",
347                    bi - 64,
348                    shift_reg & FM_MARKER_CLOCK_MASK,
349                    shift_reg & FM_MARKER_DATA_MASK,
350                    mask,
351                    marker & FM_MARKER_DATA_MASK
352                );
353            }
354
355            if shift_ct >= 64 && have_marker && have_data {
356                log::debug!(
357                    "Fm::find_marker(): Found marker at {} data match: {}",
358                    bi - 64,
359                    have_data
360                );
361                return Some(((bi - 64) + 1, (shift_reg & 0xFFFF) as u16));
362            }
363        }
364        log::debug!("Fm::find_marker(): Failed to find marker!");
365        None
366    }
367
368    fn set_data_ranges(&mut self, ranges: Vec<(usize, usize)>) {
369        // Don't set ranges for overlapping sectors. This avoids visual discontinuities during
370        // visualization.
371        let filtered_ranges = ranges
372            .iter()
373            .filter(|(start, end)| !(*start >= self.bit_vec.len() || *end >= self.bit_vec.len()))
374            .map(|(start, end)| (*start, *end))
375            .collect::<Vec<(usize, usize)>>();
376
377        self.data_ranges_filtered = RangeChecker::new(filtered_ranges);
378        self.data_ranges = RangeChecker::new(ranges);
379    }
380
381    fn is_data(&self, index: usize, wrapping: bool) -> bool {
382        if wrapping {
383            self.data_ranges.contains(index)
384        }
385        else {
386            self.data_ranges_filtered.contains(index)
387        }
388    }
389
390    fn debug_marker(&self, index: usize) -> String {
391        let mut shift_reg: u64 = 0;
392        for bi in index..std::cmp::min(index + 64, self.bit_vec.len()) {
393            shift_reg = (shift_reg << 1) | self.bit_vec[bi] as u64;
394        }
395        format!("{:16X}/{:064b}", shift_reg, shift_reg)
396    }
397
398    fn debug_decode(&self, index: usize) -> String {
399        let mut shift_reg: u32 = 0;
400        let start = index << 1;
401        for bi in (start..std::cmp::min(start + 64, self.bit_vec.len())).step_by(2) {
402            shift_reg = (shift_reg << 1) | self.bit_vec[self.initial_phase + bi] as u32;
403        }
404        format!("{:08X}/{:032b}", shift_reg, shift_reg)
405    }
406}
407
408impl FmCodec {
409    pub const WEAK_BIT_RUN: usize = 6;
410
411    pub fn new(mut bit_vec: BitVec, bit_ct: Option<usize>, weak_mask: Option<BitVec>) -> Self {
412        // If a bit count was provided, we can trim the bit vector to that length.
413        if let Some(bit_ct) = bit_ct {
414            bit_vec.truncate(bit_ct);
415        }
416
417        let encoding_sync = get_fm_sync_offset(&bit_vec).unwrap_or(false);
418        let sync = encoding_sync.into();
419
420        let clock_map = BitVec::from_elem(bit_vec.len(), encoding_sync);
421        let weak_mask = match weak_mask {
422            Some(mask) => mask,
423            None => BitVec::from_elem(bit_vec.len(), false),
424        };
425
426        if weak_mask.len() < bit_vec.len() {
427            panic!("Weak mask must be the same length as the bit vector");
428        }
429
430        FmCodec {
431            bit_vec,
432            clock_map,
433            weak_enabled: true,
434            weak_mask,
435            initial_phase: sync,
436            bit_cursor: sync,
437            track_padding: 0,
438            data_ranges: Default::default(),
439            data_ranges_filtered: Default::default(),
440        }
441    }
442
443    pub fn weak_data(&self) -> Vec<u8> {
444        self.weak_mask.to_bytes()
445    }
446
447    pub fn set_weak_mask(&mut self, weak_mask: BitVec) -> Result<()> {
448        if weak_mask.len() != self.bit_vec.len() {
449            return Err(Error::new(
450                ErrorKind::InvalidInput,
451                "Weak mask must be the same length as the bit vector",
452            ));
453        }
454        self.weak_mask = weak_mask;
455
456        Ok(())
457    }
458
459    pub fn encode(data: &[u8], prev_bit: bool, encoding_type: EncodingVariant) -> BitVec {
460        let mut bitvec = BitVec::new();
461        let mut bit_count = 0;
462
463        for &byte in data {
464            for i in (0..8).rev() {
465                let bit = (byte & (1 << i)) != 0;
466                if bit {
467                    // 1 is encoded as 01
468                    bitvec.push(false);
469                    bitvec.push(true);
470                }
471                else {
472                    // 0 is encoded as 10 if previous bit was 0, otherwise 00
473                    let previous_bit = if bitvec.is_empty() {
474                        prev_bit
475                    }
476                    else {
477                        bitvec[bitvec.len() - 1]
478                    };
479
480                    if previous_bit {
481                        bitvec.push(false);
482                    }
483                    else {
484                        bitvec.push(true);
485                    }
486                    bitvec.push(false);
487                }
488
489                bit_count += 1;
490
491                // Omit clock bit between source bits 3 and 4 for address marks
492                if let EncodingVariant::AddressMark = encoding_type {
493                    if bit_count == 4 {
494                        // Clear the previous clock bit (which is between bit 3 and 4)
495                        bitvec.set(bitvec.len() - 2, false);
496                    }
497                }
498            }
499
500            // Reset bit_count for the next byte
501            bit_count = 0;
502        }
503
504        bitvec
505    }
506
507    /// Encode an MFM address mark.
508    /// `data` must be a 4-byte slice.
509    /// Returns the encoded value in a u64 suitable for comparison to a shift register used to search
510    /// a BitVec.
511    pub fn encode_marker(data: &[u8]) -> u64 {
512        assert_eq!(data.len(), 4);
513
514        let mut accum: u64 = 0;
515        // A mark is always preceded by a SYNC block of 0's, so we know the previous bit will always
516        // be 0.
517        let mut previous_bit = false;
518
519        for &byte in data {
520            for i in (0..8).rev() {
521                let bit = (byte & (1 << i)) != 0;
522                if bit {
523                    // 1 is encoded as 01
524                    accum = (accum << 2) | 0b01;
525                }
526                else {
527                    // 0 is encoded as 10 if previous bit was 0, otherwise 00
528                    if !previous_bit {
529                        accum = (accum << 2) | 0b10;
530                    }
531                    else {
532                        accum <<= 2;
533                    }
534                }
535                previous_bit = bit;
536            }
537        }
538        accum
539    }
540
541    #[allow(dead_code)]
542    fn read_bit(self) -> Option<bool> {
543        if self.weak_enabled && self.weak_mask[self.bit_cursor] {
544            // Weak bits return random data
545            Some(rand::random())
546        }
547        else {
548            Some(self.bit_vec[self.bit_cursor])
549        }
550    }
551    #[allow(dead_code)]
552    fn read_bit_at(&self, index: usize) -> Option<bool> {
553        if self.weak_enabled && self.weak_mask[self.initial_phase + (index << 1)] {
554            // Weak bits return random data
555            Some(rand::random())
556        }
557        else {
558            Some(self.bit_vec[self.initial_phase + (index << 1)])
559        }
560    }
561
562    fn ref_bit_at(&self, index: usize) -> &bool {
563        let p_off: usize = self.clock_map[index] as usize;
564        if self.weak_enabled && self.weak_mask[p_off + (index << 1)] {
565            // Weak bits return random data
566            // TODO: precalculate random table and return reference to it.
567            &self.bit_vec[p_off + (index << 1)]
568        }
569        else {
570            &self.bit_vec[p_off + (index << 1)]
571        }
572    }
573
574    pub(crate) fn detect_weak_bits(&self, run: usize) -> (usize, usize) {
575        let mut region_ct = 0;
576        let mut weak_bit_ct = 0;
577        let mut zero_ct = 0;
578
579        for bit in self.bit_vec.iter() {
580            if !bit {
581                zero_ct += 1;
582            }
583            else {
584                if zero_ct >= run {
585                    region_ct += 1;
586                }
587                zero_ct = 0;
588            }
589
590            if zero_ct > 3 {
591                weak_bit_ct += 1;
592            }
593        }
594
595        (region_ct, weak_bit_ct)
596    }
597
598    #[allow(dead_code)]
599    pub(crate) fn detect_weak_regions(&self, run: usize) -> Vec<TrackRegion> {
600        let mut regions = Vec::new();
601
602        let mut zero_ct = 0;
603        let mut region_start = 0;
604        for (i, bit) in self.bit_vec.iter().enumerate() {
605            if !bit {
606                zero_ct += 1;
607            }
608            else {
609                if zero_ct >= run {
610                    regions.push(TrackRegion {
611                        start: region_start,
612                        end:   i - 1,
613                    });
614                }
615                zero_ct = 0;
616            }
617
618            if zero_ct == run {
619                region_start = i;
620            }
621        }
622
623        regions
624    }
625
626    /// Not every format will have a separate weak bit mask, but that doesn't mean weak bits cannot
627    /// be encoded. Formats can encode weak bits as a run of 4 or more zero bits. Here we detect
628    /// such runs and extract them into a weak bit mask as a BitVec.
629    #[allow(dead_code)]
630    pub(crate) fn create_weak_bit_mask(&self, run: usize) -> BitVec {
631        let mut weak_bitvec = BitVec::new();
632        let mut zero_ct = 0;
633        for bit in self.bit_vec.iter() {
634            if !bit {
635                zero_ct += 1;
636            }
637            else {
638                zero_ct = 0;
639            }
640
641            if zero_ct > run {
642                weak_bitvec.push(true);
643            }
644            else {
645                weak_bitvec.push(false);
646            }
647        }
648
649        assert_eq!(weak_bitvec.len(), self.bit_vec.len());
650
651        weak_bitvec
652    }
653}
654
655impl Iterator for FmCodec {
656    type Item = bool;
657
658    fn next(&mut self) -> Option<Self::Item> {
659        if self.bit_cursor >= (self.bit_vec.len() - 1) {
660            return None;
661        }
662
663        // The bit cursor should always be aligned to a clock bit.
664        // So retrieve the next bit which is the data bit, then point to the next clock.
665        let mut data_idx = self.bit_cursor + 1;
666        if data_idx > (self.bit_vec.len() - self.track_padding) {
667            // Wrap around to the beginning of the track
668            data_idx = 0;
669        }
670
671        let decoded_bit = if self.weak_enabled && self.weak_mask[data_idx] {
672            // Weak bits return random data
673            rand::random()
674        }
675        else {
676            self.bit_vec[data_idx]
677        };
678
679        let new_cursor = data_idx + 1;
680        if new_cursor >= (self.bit_vec.len() - self.track_padding) {
681            // Wrap around to the beginning of the track
682            self.bit_cursor = 0;
683        }
684        else {
685            self.bit_cursor = new_cursor;
686        }
687
688        Some(decoded_bit)
689    }
690}
691
692impl Seek for FmCodec {
693    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
694        let (base, offset) = match pos {
695            // TODO: avoid casting to isize
696            SeekFrom::Start(offset) => (0, offset as isize),
697            SeekFrom::End(offset) => (self.bit_vec.len() as isize, offset as isize),
698            SeekFrom::Current(offset) => (self.bit_cursor as isize, offset as isize),
699        };
700
701        let new_pos = base.checked_add(offset).ok_or(Error::new(
702            ErrorKind::InvalidInput,
703            "invalid seek to a negative or overflowed position",
704        ))?;
705
706        let mut new_cursor = (new_pos as usize) << 1;
707        /*
708        let mut debug_vec = Vec::new();
709        for i in 0..5 {
710            debug_vec.push(self.clock_map[new_cursor - 2 + i]);
711        }
712
713        log::debug!(
714            "seek() clock_map[{}]: {} {:?}",
715            new_cursor,
716            self.clock_map[new_cursor],
717            debug_vec
718        );
719        */
720
721        // If we have seeked to a data bit, nudge the bit cursor to the next clock bit.
722        if !self.clock_map[new_cursor] {
723            //log::trace!("seek(): nudging to next clock bit");
724            new_cursor += 1;
725        }
726
727        if new_cursor > self.bit_vec.len() {
728            return Err(Error::new(
729                ErrorKind::InvalidInput,
730                "invalid seek to a negative or overflowed position",
731            ));
732        }
733
734        self.bit_cursor = new_cursor;
735        //log::trace!("seek(): new_pos: {}", self.bit_cursor);
736
737        Ok(self.bit_cursor as u64)
738    }
739}
740
741impl Read for FmCodec {
742    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
743        let mut bytes_read = 0;
744        for byte in buf.iter_mut() {
745            let mut byte_val = 0;
746            for _ in 0..8 {
747                if let Some(bit) = self.next() {
748                    byte_val = (byte_val << 1) | bit as u8;
749                }
750                else {
751                    break;
752                }
753            }
754            *byte = byte_val;
755            bytes_read += 1;
756        }
757        Ok(bytes_read)
758    }
759}
760
761impl Index<usize> for FmCodec {
762    type Output = bool;
763
764    fn index(&self, index: usize) -> &Self::Output {
765        if index >= self.bit_vec.len() {
766            panic!("index out of bounds");
767        }
768
769        // Decode the bit here (implement the MFM decoding logic)
770        self.ref_bit_at(index)
771    }
772}
773
774impl TrackDataStreamT for FmCodec {}