torque_tracker_engine/file/impulse_format/
header.rs1use crate::file::err::{self, LoadDefect};
2use std::{io::Read, num::NonZeroU32};
3
4use crate::channel::Pan;
5
6use crate::file::InFilePtr;
7
8#[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]>, pub instr_offsets: Box<[Option<InFilePtr>]>,
58 pub sample_offsets: Box<[Option<InFilePtr>]>,
59 pub pattern_offsets: Box<[Option<InFilePtr>]>,
61}
62
63impl ImpulseHeader {
65 pub(crate) const BASE_SIZE: usize = 0xC0; 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 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 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 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 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 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
209 } else if value <= Self::BASE_SIZE as u32 {
210 defect_handler(LoadDefect::OutOfBoundsPtr);
211 None
212 } else {
213 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}