torque_tracker_engine/file/impulse_format/
header.rs

1use crate::file::err::{self, LoadDefect};
2use std::{io::Read, num::NonZeroU32};
3
4use crate::channel::Pan;
5
6use crate::file::InFilePtr;
7
8/// maybe completely wrong
9#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
10pub enum PatternOrder {
11    Number(u8),
12    #[default]
13    EndOfSong,
14    SkipOrder,
15}
16
17impl TryFrom<u8> for PatternOrder {
18    type Error = u8;
19
20    fn try_from(value: u8) -> Result<Self, Self::Error> {
21        match value {
22            255 => Ok(Self::EndOfSong),
23            254 => Ok(Self::SkipOrder),
24            0..=199 => Ok(Self::Number(value)),
25            _ => Err(value),
26        }
27    }
28}
29
30#[derive(Debug)]
31pub struct ImpulseHeader {
32    pub song_name: String,
33    pub philight: u16,
34
35    pub created_with: u16,
36    pub compatible_with: u16,
37    pub flags: u16,
38    pub special: u16,
39
40    pub global_volume: u8,
41    pub mix_volume: u8,
42    pub initial_speed: u8,
43    pub initial_tempo: u8,
44    pub pan_separation: u8,
45    pub pitch_wheel_depth: u8,
46    pub message_length: u16,
47    pub message_offset: u32,
48
49    pub channel_pan: [Pan; 64],
50    pub channel_volume: [u8; 64],
51
52    pub orders: Box<[PatternOrder]>, // length is oder_num
53
54    /// all Offsets are verified to be point outside the header.
55    ///
56    /// Invalid offsets are replaced with None, so patterns or orders don't break, because the indexes change
57    pub instr_offsets: Box<[Option<InFilePtr>]>,
58    pub sample_offsets: Box<[Option<InFilePtr>]>,
59    /// here None could come from the file, which means an empty pattern
60    pub pattern_offsets: Box<[Option<InFilePtr>]>,
61}
62
63// https://github.com/schismtracker/schismtracker/wiki/ITTECH.TXT
64impl ImpulseHeader {
65    pub(crate) const BASE_SIZE: usize = 0xC0; // = 192
66
67    /// Reader position needs to be at the beginning of the Header.
68    ///
69    /// Header is stored at the beginning of the File. length isn't constant, but at least 192 bytes
70    /// when unable to load specific parts the function tries its best and communicates the failures in the BitFlags return value.
71    /// For some problems it wouldn't make sense to return an incomplete Header as so much would be missing. In those cases an Err is returned
72    pub fn parse<R: Read, H: FnMut(LoadDefect)>(
73        reader: &mut R,
74        defect_handler: &mut H,
75    ) -> Result<Self, err::LoadErr> {
76        let base = {
77            let mut base = [0; Self::BASE_SIZE];
78            reader.read_exact(&mut base)?;
79            base
80        };
81
82        // verify that the start matches
83        if !base.starts_with(b"IMPM") {
84            return Err(err::LoadErr::Invalid);
85        }
86
87        let song_name = {
88            let str = base[0x4..=0x1D].split(|b| *b == 0).next().unwrap().to_vec();
89            let str = String::from_utf8(str);
90            if str.is_err() {
91                defect_handler(LoadDefect::InvalidText)
92            }
93            str.unwrap_or_default()
94        };
95
96        let philight = u16::from_le_bytes([base[0x1E], base[0x1F]]);
97
98        let order_num = u16::from_le_bytes([base[0x20], base[0x21]]);
99        let instr_num = u16::from_le_bytes([base[0x22], base[0x23]]);
100        let sample_num = u16::from_le_bytes([base[0x24], base[0x25]]);
101        let pattern_num = u16::from_le_bytes([base[0x26], base[0x27]]);
102        let created_with = u16::from_le_bytes([base[0x28], base[0x29]]);
103        let compatible_with = u16::from_le_bytes([base[0x2A], base[0x2B]]);
104        let flags = u16::from_le_bytes([base[0x2C], base[0x2D]]);
105        let special = u16::from_le_bytes([base[0x2E], base[0x2F]]);
106
107        let global_volume = if base[0x30] <= 128 {
108            base[0x30]
109        } else {
110            defect_handler(LoadDefect::OutOfBoundsValue);
111            64
112        };
113
114        let mix_volume = if base[0x31] <= 128 {
115            base[0x31]
116        } else {
117            defect_handler(LoadDefect::OutOfBoundsValue);
118            64
119        };
120
121        let initial_speed = base[0x32];
122        let initial_tempo = base[0x33];
123        let pan_separation = base[0x34];
124        let pitch_wheel_depth = base[0x35];
125        let message_length = u16::from_le_bytes([base[0x36], base[0x37]]);
126        let message_offset = u32::from_le_bytes([base[0x38], base[0x39], base[0x3A], base[0x3B]]);
127        let _reserved = u32::from_le_bytes([base[0x3C], base[0x3D], base[0x3E], base[0x3F]]);
128
129        // can unwrap here, because the length is already checked at the beginning
130        let pan_vals: [u8; 64] = base[0x40..0x80].try_into().unwrap();
131        let channel_pan: [Pan; 64] = pan_vals.map(|pan| match Pan::try_from(pan) {
132            Ok(pan) => pan,
133            Err(_) => {
134                defect_handler(LoadDefect::OutOfBoundsValue);
135                Pan::default()
136            }
137        });
138
139        let channel_volume: [u8; 64] = {
140            // can unwrap here, because the length is already checked at the beginning
141            let mut vols: [u8; 64] = base[0x80..0xC0].try_into().unwrap();
142
143            vols.iter_mut().for_each(|vol| {
144                if *vol > 64 {
145                    defect_handler(LoadDefect::OutOfBoundsValue);
146                    *vol = 64
147                }
148            });
149            vols
150        };
151
152        let orders: Box<[PatternOrder]> = {
153            let mut data = vec![0; usize::from(order_num)].into_boxed_slice();
154            reader.read_exact(&mut data)?;
155            data.iter()
156                .map(|order| match PatternOrder::try_from(*order) {
157                    Ok(pat_order) => pat_order,
158                    Err(_) => {
159                        defect_handler(LoadDefect::OutOfBoundsValue);
160                        PatternOrder::SkipOrder
161                    }
162                })
163                .collect()
164        };
165
166        let instr_offsets = {
167            let mut data = vec![0; usize::from(instr_num)].into_boxed_slice();
168            reader.read_exact(&mut data)?;
169            data.chunks_exact(std::mem::size_of::<u32>())
170                .map(|chunk| {
171                    let value = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
172                    if value <= Self::BASE_SIZE as u32 {
173                        defect_handler(LoadDefect::OutOfBoundsPtr);
174                        None
175                    } else {
176                        // value is larger than Self::BASE_SIZE, so also larger than 0
177                        Some(InFilePtr(NonZeroU32::new(value).unwrap()))
178                    }
179                })
180                .collect()
181        };
182
183        let sample_offsets = {
184            let mut data = vec![0; usize::from(sample_num)].into_boxed_slice();
185            reader.read_exact(&mut data)?;
186            data.chunks_exact(std::mem::size_of::<u32>())
187                .map(|chunk| {
188                    let value = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
189                    if value <= Self::BASE_SIZE as u32 {
190                        defect_handler(LoadDefect::OutOfBoundsPtr);
191                        None
192                    } else {
193                        // value is larger than Self::BASE_SIZE, so also larger than 0
194                        Some(InFilePtr(NonZeroU32::new(value).unwrap()))
195                    }
196                })
197                .collect()
198        };
199
200        let pattern_offsets = {
201            let mut data = vec![0; usize::from(pattern_num)].into_boxed_slice();
202            reader.read_exact(&mut data)?;
203            data.chunks_exact(std::mem::size_of::<u32>())
204                .map(|chunk| {
205                    let value = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
206                    if value == 0 {
207                        // None is a valid value and assumed to be an empty pattern
208                        None
209                    } else if value <= Self::BASE_SIZE as u32 {
210                        defect_handler(LoadDefect::OutOfBoundsPtr);
211                        None
212                    } else {
213                        // value is larger than Self::BASE_SIZE, so also larger than 0
214                        Some(InFilePtr(NonZeroU32::new(value).unwrap()))
215                    }
216                })
217                .collect()
218        };
219
220        Ok(Self {
221            song_name,
222            philight,
223            created_with,
224            compatible_with,
225            flags,
226            special,
227            global_volume,
228            mix_volume,
229            initial_speed,
230            initial_tempo,
231            pan_separation,
232            pitch_wheel_depth,
233            message_length,
234            message_offset,
235            channel_pan,
236            channel_volume,
237            orders,
238            instr_offsets,
239            sample_offsets,
240            pattern_offsets,
241        })
242    }
243}