ot_tools_io/
samples.rs

1/*
2SPDX-License-Identifier: GPL-3.0-or-later
3Copyright © 2024 Mike Robeson [dijksterhuis]
4*/
5
6//! Types and ser/de of `*.ot` binary data files.
7
8pub mod configs;
9pub mod options;
10pub mod slices;
11
12use crate::{
13    samples::options::{SampleAttributeTimestrechMode, SampleAttributeTrigQuantizationMode},
14    samples::{
15        configs::{SampleLoopConfig, SampleTrimConfig},
16        slices::{Slice, Slices},
17    },
18    CalculateChecksum, CheckChecksum, CheckHeader, CheckIntegrity, Decode, Encode,
19    OptionEnumValueConvert, RBoxErr,
20};
21use ot_tools_io_derive::Decodeable;
22use serde::{Deserialize, Serialize};
23use serde_big_array::BigArray;
24
25// in `hexdump -C` format:
26// ```
27// FORM....DPS1SMPA
28// ......
29// ```
30/// Standard header bytes in an sample 'attributes' file
31pub const SAMPLES_HEADER: [u8; 21] = [
32    0x46, 0x4F, 0x52, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x44, 0x50, 0x53, 0x31, 0x53, 0x4D, 0x50, 0x41,
33    0x00, 0x00, 0x00, 0x00, 0x00,
34];
35
36/// Current/supported datatype version for a sample attributes file
37pub const SAMPLES_FILE_VERSION: u8 = 2;
38
39// TODO move to lib.rs
40/// Trait for adding the `.swap_bytes()` method.
41pub trait SwapBytes {
42    /// Type for `Self`
43    type T;
44
45    /// Swap the bytes of all struct fields.
46    /// Must be applied to the `SampleAttributes` file to deal with litle-endian/big-endian systems.
47    fn swap_bytes(self) -> RBoxErr<Self::T>;
48}
49
50#[derive(Debug)]
51enum SampleAttributeErrors {
52    TempoOutOfBounds,
53    GainOutOfBounds,
54}
55impl std::fmt::Display for SampleAttributeErrors {
56    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
57        match self {
58            Self::GainOutOfBounds => write!(f, "invalid gain, must be in range -24.0 <= x <= 24.0"),
59            Self::TempoOutOfBounds => write!(
60                f,
61                "invalid tempo value, must be in range 30.0 <= x <= 300.0"
62            ),
63        }
64    }
65}
66impl std::error::Error for SampleAttributeErrors {}
67
68/// Convert from a human (device UI) representation of gain (-24.0 <= x <= 24.0)
69/// to binary data file representation of gain (0 <= x <= 96).
70/// Note that binary gain uses two bytes (hence u16).
71fn bin_gain_from_human(human_gain: &f32) -> RBoxErr<u16> {
72    // don't use contains range trick here. need to explicitly check range bounds.
73    #[allow(clippy::manual_range_contains)]
74    if human_gain < &-24.0 || human_gain > &24.0 {
75        Err(SampleAttributeErrors::GainOutOfBounds.into())
76    } else {
77        let new_gain_f32 = (2.0 * (10.0 * (human_gain + 24.0)).round()) * 0.1;
78        Ok(new_gain_f32 as u16)
79    }
80}
81
82/// Convert from a human (device UI) representation of tempo (30.0 <= x <= 300.0)
83/// to binary data file representation of gain (720 <= x <= 7200)
84/// Note that binary tempo uses four bytes (hence u32).
85fn bin_tempo_from_human(human_tempo: &f32) -> RBoxErr<u32> {
86    // don't use contains range trick here. need to explicitly check range bounds.
87    #[allow(clippy::manual_range_contains)]
88    if human_tempo < &30.0 || human_tempo > &300.0 {
89        Err(SampleAttributeErrors::TempoOutOfBounds.into())
90    } else {
91        let bin_tempo_f32 = human_tempo * 24.0;
92        Ok(bin_tempo_f32 as u32)
93    }
94}
95
96/// Struct to create a valid Octatrack `.ot` file.
97/// General metadata for the sample's configuration on the OT
98/// and the slice array with pointer positions for the sliced WAV.
99#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
100pub struct SampleAttributes {
101    /// Header
102    pub header: [u8; 21],
103
104    /// Datatype's version ID//
105    pub datatype_version: u8,
106
107    /// Unknown data
108    pub unknown: u8,
109
110    /// Tempo is always the machine UI's BPM multiplied by 24
111    pub tempo: u32,
112
113    /// Number of bars for the sample trim length marker.
114    /// By default, trim length should be equal to trim end,
115    /// and probably loop length too for drum hit sample chains.
116    pub trim_len: u32,
117
118    /// Number of bars for the sample loop length marker.
119    /// By default, loop length should be equal to trim length for sample chains.
120    pub loop_len: u32,
121
122    /// Default timestrech algorithm applied to the sample.
123    /// See the `ot_sample::options::SampleTimestrechModes` enum for suitable choices.
124    pub stretch: u32,
125
126    /// Default loop mode applied to the sample.
127    /// See the `ot_sample::options::SampleLoopModes` enum for suitable choices.
128    pub loop_mode: u32,
129
130    /// Gain of the sample.
131    /// -24.0 db <= x <= +24 db range in the machine's UI, with increments of 0.5 db changes.
132    /// 0 <= x <= 96 range in binary data file.
133    pub gain: u16,
134
135    /// Default trig quantization mode applied to the sample.
136    /// See the `ot_sample::options::SampleTrigQuantizationModes` enum for suitable choices.
137    pub quantization: u8,
138
139    /// Where the trim start marker is placed for the sample, measured in bars.
140    /// Default is 0 (start of sample).
141    /// TODO: measured in bars? or samples?
142    pub trim_start: u32,
143
144    /// Where the trim end marker is placed for the sample.
145    /// When the sample is being played in normal mode (i.e. not using slices),
146    /// the Octatrack will not play samples past this point.
147    /// By default, trim length should be equal to trim end,
148    /// and probably loop length too for drum hit sample chains.
149    /// TODO: measured in bars? or samples?
150    pub trim_end: u32,
151
152    /// Start position for any loops. Default should be the same as trim start.
153    /// Measured in bars.
154    /// A note from the Octatrack manual on loop point/start behaviour:
155    /// > If a loop point is set, the sample will play from the start point to the
156    /// > end point, then loop from the loop point to the end point
157    pub loop_start: u32,
158
159    /// 64 length array containing `Slice`s.
160    /// See the `Slice` struct for more details.
161    /// Any empty slice positions should have zero-valued struct fields.
162    #[serde(with = "BigArray")]
163    pub slices: [Slice; 64],
164
165    /// Number of usuable `Slice`s in this sample.
166    /// Used by the Octatrack to ignore zero-valued `Slice`s in the `slices` array when loading the sample.
167    pub slices_len: u32,
168
169    /// Checksum value for the struct.
170    /// This must be calculated **after** the struct is created on little-endian systems
171    /// (requires byte swapping all struct fields to get the correct checksum value).
172    pub checksum: u16,
173}
174
175impl SwapBytes for SampleAttributes {
176    /// Swaps the bytes on all struct fields.
177    /// **MUST BE CALLED BEFORE SERIALISATION WHEN SYSTEM IS LITTLE ENDIAN!**
178    type T = SampleAttributes;
179
180    /// Swap the bytes of all struct fields.
181    /// Must be applied to the `SampleAttributes` file to deal with litle-endian/big-endian systems.
182    fn swap_bytes(self) -> RBoxErr<Self::T> {
183        let mut bswapped_slices: [Slice; 64] = self.slices;
184
185        for (i, slice) in self.slices.iter().enumerate() {
186            bswapped_slices[i] = slice.swap_bytes()?;
187        }
188
189        let bswapped = Self {
190            header: SAMPLES_HEADER,
191            datatype_version: SAMPLES_FILE_VERSION,
192            unknown: self.unknown,
193            tempo: self.tempo.swap_bytes(),
194            trim_len: self.trim_len.swap_bytes(),
195            loop_len: self.loop_len.swap_bytes(),
196            stretch: self.stretch.swap_bytes(),
197            loop_mode: self.loop_mode.swap_bytes(),
198            gain: self.gain.swap_bytes(),
199            quantization: self.quantization.swap_bytes(),
200            trim_start: self.trim_start.swap_bytes(),
201            trim_end: self.trim_end.swap_bytes(),
202            loop_start: self.loop_start.swap_bytes(),
203            slices: bswapped_slices,
204            slices_len: self.slices_len.swap_bytes(),
205            checksum: self.checksum.swap_bytes(),
206        };
207
208        Ok(bswapped)
209    }
210}
211
212impl SampleAttributes {
213    /// Create a new `SampleAttributesFile` struct.
214    /// 'Human-readable' `f32` values for `gain` and `tempo` are converted to proper `u8` values in this method.
215    /// ```rust
216    /// # use std::array::from_fn;
217    /// # use ot_tools_io::samples::SampleAttributes;
218    /// # use ot_tools_io::samples::options::{
219    /// #    SampleAttributeTimestrechMode,
220    /// #    SampleAttributeTrigQuantizationMode,
221    /// #    SampleAttributeLoopMode,
222    /// # };
223    /// # use ot_tools_io::samples::configs::{
224    /// #    SampleLoopConfig,
225    /// #    SampleTrimConfig,
226    /// # };
227    /// # use ot_tools_io::samples::slices::{Slices, Slice};
228    /// # let slice_arr: [Slice; 64] = from_fn(|_| Slice::default());
229    /// # let slices: Slices = Slices { slices: slice_arr, count: 0 };
230    /// let x = SampleAttributes::new(
231    ///     &120.0,
232    ///     // ...
233    ///     # &SampleAttributeTimestrechMode::default(),
234    ///     # &SampleAttributeTrigQuantizationMode::default(),
235    ///     &0.0,
236    ///     // ...
237    ///     # &SampleTrimConfig { start: 0, end: 100, length: 25 },
238    ///     # &SampleLoopConfig { start: 0, length: 100, mode: SampleAttributeLoopMode::Off },
239    ///     # &slices,
240    /// ).unwrap();
241    /// assert_eq!(x.tempo, 2880);
242    /// assert_eq!(x.gain, 48);
243    /// ```
244    pub fn new(
245        tempo: &f32,
246        stretch: &SampleAttributeTimestrechMode,
247        quantization: &SampleAttributeTrigQuantizationMode,
248        gain: &f32,
249        trim_config: &SampleTrimConfig,
250        loop_config: &SampleLoopConfig,
251        slices: &Slices,
252    ) -> RBoxErr<Self> {
253        Ok(Self {
254            header: SAMPLES_HEADER,
255            datatype_version: SAMPLES_FILE_VERSION,
256            unknown: 0,
257            gain: bin_gain_from_human(gain)?,
258            stretch: stretch.value()?,
259            tempo: bin_tempo_from_human(tempo)?,
260            quantization: quantization.value()? as u8,
261            trim_start: trim_config.start,
262            trim_end: trim_config.end,
263            trim_len: trim_config.length, // bin lengths are multiplied by 100?
264            loop_start: loop_config.start,
265            loop_len: loop_config.length, // bin lengths are multiplied by 100?
266            loop_mode: loop_config.mode.value()?,
267            slices: slices.slices,
268            slices_len: slices.count,
269            checksum: 0,
270        })
271    }
272}
273
274// For samples, need to run special decode method as need to flip bytes depending on endianness
275impl Decode for SampleAttributes {
276    /// Decode raw bytes of a `.ot` data file into a new struct,
277    /// swap byte values if system is little-endian then do some minor
278    /// post-processing to get user-friendly settings values.
279    fn decode(bytes: &[u8]) -> RBoxErr<Self> {
280        let decoded: Self = bincode::deserialize(bytes)?;
281        let mut bswapd = decoded.clone();
282
283        // swapping bytes is one required when running on little-endian systems
284        if cfg!(target_endian = "little") {
285            bswapd = decoded.swap_bytes()?;
286        }
287
288        Ok(bswapd)
289    }
290}
291
292// For sample data, need swap bytes depending on endianness and calculate a checksum
293impl Encode for SampleAttributes {
294    /// Encodes struct data to binary representation, after some pre-processing.
295    ///
296    /// Before serializing, will:
297    /// 1. modify tempo and gain values to machine ranges
298    /// 2. swaps bytes of values (when current system is little-endian)
299    /// 3. generate checksum value
300    fn encode(&self) -> RBoxErr<Vec<u8>> {
301        Ok(bincode::serialize(self)?)
302    }
303}
304
305impl CalculateChecksum for SampleAttributes {
306    fn calculate_checksum(&self) -> RBoxErr<u16> {
307        let bytes = self.encode()?;
308
309        // skip header and checksum byte values
310        let checksum_bytes = &bytes[16..bytes.len() - 2];
311
312        let chk: u32 = checksum_bytes
313            .iter()
314            .map(|x| *x as u32)
315            .sum::<u32>()
316            .rem_euclid(u16::MAX as u32 + 1);
317
318        Ok(chk as u16)
319    }
320}
321
322impl CheckHeader for SampleAttributes {
323    fn check_header(&self) -> bool {
324        self.header == SAMPLES_HEADER
325    }
326}
327
328impl CheckChecksum for SampleAttributes {
329    fn check_checksum(&self) -> RBoxErr<bool> {
330        Ok(self.checksum == self.calculate_checksum()?)
331    }
332}
333
334impl CheckIntegrity for SampleAttributes {}
335
336/// Only really useful for debugging and / or reverse engineering purposes.
337#[derive(Debug, Serialize, Deserialize, Decodeable)]
338pub struct SampleAttributesRawBytes {
339    #[serde(with = "BigArray")]
340    pub data: [u8; 832],
341}
342
343#[cfg(test)]
344mod test {
345    use crate::samples::configs::{SampleLoopConfig, SampleTrimConfig};
346    use crate::samples::options::SampleAttributeLoopMode;
347    use crate::samples::slices::{Slice, Slices};
348
349    fn create_mock_configs_blank() -> (SampleTrimConfig, SampleLoopConfig, Slices) {
350        let trim_config = SampleTrimConfig {
351            start: 0,
352            end: 0,
353            length: 0,
354        };
355
356        let loop_config = SampleLoopConfig {
357            start: 0,
358            length: 0,
359            mode: SampleAttributeLoopMode::Normal,
360        };
361
362        let default_slice = Slice {
363            trim_start: 0,
364            trim_end: 0,
365            loop_start: 0xFFFFFFFF,
366        };
367
368        let slices: [Slice; 64] = [default_slice; 64];
369
370        let slice_conf = Slices { slices, count: 0 };
371
372        (trim_config, loop_config, slice_conf)
373    }
374
375    mod checksum {
376        /*
377        mod array_based {
378            use crate::CalculateChecksum;
379            use std::array::from_fn;
380
381            #[test]
382            #[ignore]
383            fn ok_no_overflow_all_255() {
384                let bytes: [u8; 832] = from_fn(|_| 255);
385                assert_eq!(
386                    get_checksum(&bytes).unwrap(),
387                    10962,
388                    "overflows should wrap"
389                );
390            }
391            #[test]
392            #[ignore]
393            fn ok_no_overflow() {
394                let bytes: [u8; 832] = from_fn(|_| 78);
395
396                assert_eq!(
397                    get_checksum(&bytes).unwrap(),
398                    63492,
399                    "random value that shouldn't overflow at all"
400                );
401            }
402
403            #[test]
404            #[ignore]
405            fn ok_64_slice_len() {
406                let bytes: [u8; 832] = from_fn(|_| 64);
407                assert_eq!(get_checksum(&bytes).unwrap(), 52096,);
408            }
409
410            #[test]
411            #[ignore]
412            fn ok_one_bytes() {
413                let bytes: [u8; 832] = from_fn(|_| 1);
414                assert_eq!(
415                    get_checksum(&bytes).unwrap(),
416                    814,
417                    "low values definitely don't overflow"
418                );
419            }
420
421            #[test]
422            #[ignore]
423            fn ok_zeroed() {
424                let bytes: [u8; 832] = from_fn(|_| 0);
425                assert_eq!(
426                    get_checksum(&bytes).unwrap(),
427                    0,
428                    "unknown if this is correct behaviour"
429                );
430            }
431        }
432        */
433
434        mod file_based {
435            use crate::read_type_from_bin_file;
436            use crate::samples::{CalculateChecksum, SampleAttributes};
437            use crate::test_utils::get_samples_dirpath;
438
439            #[test]
440            fn zero_slices_trig_quant_one_step() {
441                let path = get_samples_dirpath()
442                    .join("checksum")
443                    .join("0slices-tq1step.ot");
444                let data = read_type_from_bin_file::<SampleAttributes>(&path).unwrap();
445                assert_eq!(data.calculate_checksum().unwrap(), data.checksum);
446            }
447
448            #[test]
449            fn zero_slices() {
450                let path = get_samples_dirpath().join("checksum").join("0slices.ot");
451                let data = read_type_from_bin_file::<SampleAttributes>(&path).unwrap();
452                assert_eq!(data.calculate_checksum().unwrap(), data.checksum);
453            }
454
455            #[test]
456            fn one_slice() {
457                let path = get_samples_dirpath().join("checksum").join("1slices.ot");
458                let data = read_type_from_bin_file::<SampleAttributes>(&path).unwrap();
459                assert_eq!(data.calculate_checksum().unwrap(), data.checksum);
460            }
461            #[test]
462            fn two_slices() {
463                let path = get_samples_dirpath().join("checksum").join("2slices.ot");
464                let data = read_type_from_bin_file::<SampleAttributes>(&path).unwrap();
465                assert_eq!(data.calculate_checksum().unwrap(), data.checksum);
466            }
467            #[test]
468            fn four_slices() {
469                let path = get_samples_dirpath().join("checksum").join("4slices.ot");
470                let data = read_type_from_bin_file::<SampleAttributes>(&path).unwrap();
471                assert_eq!(data.calculate_checksum().unwrap(), data.checksum);
472            }
473            #[test]
474            fn eight_slices() {
475                let path = get_samples_dirpath().join("checksum").join("8slices.ot");
476                let data = read_type_from_bin_file::<SampleAttributes>(&path).unwrap();
477                assert_eq!(data.calculate_checksum().unwrap(), data.checksum);
478            }
479            #[test]
480            fn sixteen_slices() {
481                let path = get_samples_dirpath().join("checksum").join("16slices.ot");
482                let data = read_type_from_bin_file::<SampleAttributes>(&path).unwrap();
483                assert_eq!(data.calculate_checksum().unwrap(), data.checksum);
484            }
485            #[test]
486            fn thirty_two_slices() {
487                let path = get_samples_dirpath().join("checksum").join("32slices.ot");
488                let data = read_type_from_bin_file::<SampleAttributes>(&path).unwrap();
489                assert_eq!(data.calculate_checksum().unwrap(), data.checksum);
490            }
491            #[test]
492            fn forty_eight_slices() {
493                let path = get_samples_dirpath().join("checksum").join("48slices.ot");
494                let data = read_type_from_bin_file::<SampleAttributes>(&path).unwrap();
495                assert_eq!(data.calculate_checksum().unwrap(), data.checksum);
496            }
497            #[test]
498            fn sixty_two_slices() {
499                let path = get_samples_dirpath().join("checksum").join("62slices.ot");
500                let data = read_type_from_bin_file::<SampleAttributes>(&path).unwrap();
501                assert_eq!(data.calculate_checksum().unwrap(), data.checksum);
502            }
503            #[test]
504            fn sixty_three_slices() {
505                let path = get_samples_dirpath().join("checksum").join("63slices.ot");
506                let data = read_type_from_bin_file::<SampleAttributes>(&path).unwrap();
507                assert_eq!(data.calculate_checksum().unwrap(), data.checksum);
508            }
509            #[test]
510            fn sixty_four_slices_correct() {
511                let path = get_samples_dirpath().join("checksum").join("64slices.ot");
512                let data = read_type_from_bin_file::<SampleAttributes>(&path).unwrap();
513                assert_eq!(data.calculate_checksum().unwrap(), data.checksum);
514            }
515        }
516    }
517
518    mod bin_tempo_from_human {
519        use crate::samples::bin_tempo_from_human;
520
521        #[test]
522        fn err_oob_too_low() {
523            assert!(bin_tempo_from_human(&29.9).is_err());
524        }
525        #[test]
526        fn err_oob_too_high() {
527            assert!(bin_tempo_from_human(&300.1).is_err());
528        }
529
530        #[test]
531        fn ok_30() {
532            assert_eq!(bin_tempo_from_human(&30.0).unwrap(), 720);
533        }
534        #[test]
535        fn ok_300() {
536            assert_eq!(bin_tempo_from_human(&300.0).unwrap(), 7200);
537        }
538
539        #[test]
540        fn ok_120() {
541            assert_eq!(bin_tempo_from_human(&120.0).unwrap(), 2880);
542        }
543
544        #[test]
545        fn ok_100() {
546            assert_eq!(bin_tempo_from_human(&100.0).unwrap(), 2400);
547        }
548    }
549
550    mod bin_gain_from_human {
551        use crate::samples::bin_gain_from_human;
552
553        #[test]
554        fn err_oob_too_low() {
555            assert!(bin_gain_from_human(&-24.1).is_err());
556        }
557        #[test]
558        fn err_oob_too_high() {
559            assert!(bin_gain_from_human(&24.1).is_err());
560        }
561
562        #[test]
563        fn ok_negative_24() {
564            assert_eq!(bin_gain_from_human(&-24.0).unwrap(), 0);
565        }
566        #[test]
567        fn ok_positive_24() {
568            assert_eq!(bin_gain_from_human(&24.0).unwrap(), 96);
569        }
570
571        #[test]
572        fn ok_positive_23_half() {
573            assert_eq!(bin_gain_from_human(&23.5).unwrap(), 95);
574        }
575
576        #[test]
577        fn ok_negative_23_half() {
578            assert_eq!(bin_gain_from_human(&-23.5).unwrap(), 1);
579        }
580
581        #[test]
582        fn ok_zero() {
583            assert_eq!(bin_gain_from_human(&0.0).unwrap(), 48);
584        }
585
586        #[test]
587        fn ok_zero_half() {
588            assert_eq!(bin_gain_from_human(&0.5).unwrap(), 49);
589        }
590
591        #[test]
592        fn ok_zero_minus_half() {
593            assert_eq!(bin_gain_from_human(&-0.5).unwrap(), 47);
594        }
595    }
596
597    mod create_new {
598
599        use super::create_mock_configs_blank;
600        use crate::samples::options::{
601            SampleAttributeTimestrechMode, SampleAttributeTrigQuantizationMode,
602        };
603        use crate::samples::SampleAttributes;
604
605        #[test]
606        fn err_oob_tempo() {
607            let (trim_conf, loop_conf, slices) = create_mock_configs_blank();
608
609            let composed_chain = SampleAttributes::new(
610                &10000.0,
611                &SampleAttributeTimestrechMode::Off,
612                &SampleAttributeTrigQuantizationMode::PatternLength,
613                &0.0,
614                &trim_conf,
615                &loop_conf,
616                &slices,
617            );
618
619            assert!(composed_chain.is_err());
620        }
621
622        #[test]
623        fn err_invalid_gain() {
624            let (trim_conf, loop_conf, slices) = create_mock_configs_blank();
625
626            let composed_chain = SampleAttributes::new(
627                &125.0,
628                &SampleAttributeTimestrechMode::Off,
629                &SampleAttributeTrigQuantizationMode::PatternLength,
630                &300.0,
631                &trim_conf,
632                &loop_conf,
633                &slices,
634            );
635
636            assert!(composed_chain.is_err());
637        }
638    }
639
640    mod integrity {
641        use super::create_mock_configs_blank;
642        use crate::samples::options::{
643            SampleAttributeTimestrechMode, SampleAttributeTrigQuantizationMode,
644        };
645        use crate::samples::SampleAttributes;
646        use crate::CheckHeader;
647        #[test]
648        fn true_valid_header() {
649            let (trim_conf, loop_conf, slices) = create_mock_configs_blank();
650            let new = SampleAttributes::new(
651                &120.0,
652                &SampleAttributeTimestrechMode::Off,
653                &SampleAttributeTrigQuantizationMode::PatternLength,
654                &0.0,
655                &trim_conf,
656                &loop_conf,
657                &slices,
658            );
659            assert!(new.unwrap().check_header());
660        }
661
662        #[test]
663        fn false_invalid_header() {
664            let (trim_conf, loop_conf, slices) = create_mock_configs_blank();
665            let new = SampleAttributes::new(
666                &120.0,
667                &SampleAttributeTimestrechMode::Off,
668                &SampleAttributeTrigQuantizationMode::PatternLength,
669                &0.0,
670                &trim_conf,
671                &loop_conf,
672                &slices,
673            );
674            let mut mutated = new.unwrap();
675            mutated.header[0] = 0x00;
676            mutated.header[20] = 111;
677            assert!(!mutated.check_header());
678        }
679    }
680}