old school music tracker audio backend
1use crate::{
2 file::err,
3 project::song::{Pan, PatternOrder},
4};
5use std::{
6 io::Read,
7 num::{NonZero, NonZeroU32},
8};
9
10use crate::file::InFilePtr;
11
12fn order_try_from(value: u8) -> Option<PatternOrder> {
13 match value {
14 255 => Some(PatternOrder::EndOfSong),
15 254 => Some(PatternOrder::SkipOrder),
16 0..=199 => Some(PatternOrder::Number(value)),
17 _ => None,
18 }
19}
20
21fn pan_try_from(value: u8) -> Option<Pan> {
22 match value {
23 100 => Some(Pan::Surround),
24 128 => Some(Pan::Disabled),
25 0..=64 => Some(Pan::Value(value)),
26 _ => None,
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 /// takes the values that are included in Song from the header and write them to the song.
66 ///
67 /// Mostly applies to metadata about the song.
68 /// Samples, patterns, instruments are not filled as they are not included in the header
69 pub fn copy_values_into_song(&self, song: &mut crate::project::song::Song) {
70 song.global_volume = self.global_volume;
71 // TODO: figure out if i want to error here or when parsing the self
72 song.initial_speed = NonZero::new(self.initial_speed).unwrap();
73 song.initial_tempo = NonZero::new(self.initial_tempo).unwrap();
74 song.mix_volume = self.mix_volume;
75 song.pan_separation = self.pan_separation;
76 song.pitch_wheel_depth = self.pitch_wheel_depth;
77
78 song.pan = self.channel_pan;
79 song.volume = self.channel_volume;
80
81 for (idx, order) in self.orders.iter().enumerate() {
82 song.pattern_order[idx] = *order;
83 }
84 }
85
86 pub(crate) const BASE_SIZE: usize = 0xC0; // = 192
87
88 /// Reader position needs to be at the beginning of the Header.
89 ///
90 /// Header is stored at the beginning of the File. length isn't constant, but at least 192 bytes
91 /// 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
92 pub fn parse<R: Read>(reader: &mut R) -> Result<Self, err::LoadErr> {
93 let base = {
94 let mut base = [0; Self::BASE_SIZE];
95 reader.read_exact(&mut base)?;
96 base
97 };
98
99 // verify that the start matches
100 if !base.starts_with(b"IMPM") {
101 return Err(err::LoadErr::Invalid);
102 }
103
104 let song_name = {
105 let str = base[0x4..=0x1D].split(|b| *b == 0).next().unwrap().to_vec();
106 let str = String::from_utf8(str);
107 if str.is_err() {
108 // defect_handler(LoadDefect::InvalidText)
109 }
110 str.unwrap_or_default()
111 };
112
113 let philight = u16::from_le_bytes([base[0x1E], base[0x1F]]);
114
115 let order_num = u16::from_le_bytes([base[0x20], base[0x21]]);
116 let instr_num = u16::from_le_bytes([base[0x22], base[0x23]]);
117 let sample_num = u16::from_le_bytes([base[0x24], base[0x25]]);
118 let pattern_num = u16::from_le_bytes([base[0x26], base[0x27]]);
119 let created_with = u16::from_le_bytes([base[0x28], base[0x29]]);
120 let compatible_with = u16::from_le_bytes([base[0x2A], base[0x2B]]);
121 let flags = u16::from_le_bytes([base[0x2C], base[0x2D]]);
122 let special = u16::from_le_bytes([base[0x2E], base[0x2F]]);
123
124 let global_volume = if base[0x30] <= 128 {
125 base[0x30]
126 } else {
127 // defect_handler(LoadDefect::OutOfBoundsValue);
128 64
129 };
130
131 let mix_volume = if base[0x31] <= 128 {
132 base[0x31]
133 } else {
134 // defect_handler(LoadDefect::OutOfBoundsValue);
135 64
136 };
137
138 let initial_speed = base[0x32];
139 let initial_tempo = base[0x33];
140 let pan_separation = base[0x34];
141 let pitch_wheel_depth = base[0x35];
142 let message_length = u16::from_le_bytes([base[0x36], base[0x37]]);
143 let message_offset = u32::from_le_bytes([base[0x38], base[0x39], base[0x3A], base[0x3B]]);
144 let _reserved = u32::from_le_bytes([base[0x3C], base[0x3D], base[0x3E], base[0x3F]]);
145
146 // can unwrap here, because the length is already checked at the beginning
147 let pan_vals: [u8; 64] = base[0x40..0x80].try_into().unwrap();
148 // let channel_pan: [Pan; 64] = pan_vals.map(|pan| match Pan::try_from(pan) {
149 // Ok(pan) => pan,
150 // Err(_) => {
151 // // defect_handler(LoadDefect::OutOfBoundsValue);
152 // Pan::default()
153 // }
154 // });
155 let channel_pan: [Pan; 64] = pan_vals.map(|pan| pan_try_from(pan).unwrap_or_default());
156
157 let channel_volume: [u8; 64] = {
158 // can unwrap here, because the length is already checked at the beginning
159 let mut vols: [u8; 64] = base[0x80..0xC0].try_into().unwrap();
160
161 vols.iter_mut().for_each(|vol| {
162 if *vol > 64 {
163 // defect_handler(LoadDefect::OutOfBoundsValue);
164 *vol = 64
165 }
166 });
167 vols
168 };
169
170 let orders: Box<[PatternOrder]> = {
171 let mut data = vec![0; usize::from(order_num)].into_boxed_slice();
172 reader.read_exact(&mut data)?;
173 data.iter()
174 .map(|order| match order_try_from(*order) {
175 Some(pat_order) => pat_order,
176 None => {
177 // defect_handler(LoadDefect::OutOfBoundsValue);
178 PatternOrder::SkipOrder
179 }
180 })
181 .collect()
182 };
183
184 let instr_offsets = {
185 let mut data = vec![0; usize::from(instr_num)].into_boxed_slice();
186 reader.read_exact(&mut data)?;
187 data.chunks_exact(std::mem::size_of::<u32>())
188 .map(|chunk| {
189 let value = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
190 if value <= Self::BASE_SIZE as u32 {
191 // defect_handler(LoadDefect::OutOfBoundsPtr);
192 None
193 } else {
194 // value is larger than Self::BASE_SIZE, so also larger than 0
195 Some(InFilePtr(NonZeroU32::new(value).unwrap()))
196 }
197 })
198 .collect()
199 };
200
201 let sample_offsets = {
202 let mut data = vec![0; usize::from(sample_num)].into_boxed_slice();
203 reader.read_exact(&mut data)?;
204 data.chunks_exact(std::mem::size_of::<u32>())
205 .map(|chunk| {
206 let value = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
207 if value <= Self::BASE_SIZE as u32 {
208 // TODO: if i put defect handles pack i probably need to handle 0
209 // defect_handler(LoadDefect::OutOfBoundsPtr);
210 None
211 } else {
212 // value is larger than Self::BASE_SIZE, so also larger than 0
213 Some(InFilePtr(NonZeroU32::new(value).unwrap()))
214 }
215 })
216 .collect()
217 };
218
219 let pattern_offsets = {
220 let mut data = vec![0; usize::from(pattern_num)].into_boxed_slice();
221 reader.read_exact(&mut data)?;
222 data.chunks_exact(std::mem::size_of::<u32>())
223 .map(|chunk| {
224 let value = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
225 if value == 0 {
226 // None is a valid value and assumed to be an empty pattern
227 None
228 } else if value <= Self::BASE_SIZE as u32 {
229 // defect_handler(LoadDefect::OutOfBoundsPtr);
230 None
231 } else {
232 // value is larger than Self::BASE_SIZE, so also larger than 0
233 Some(InFilePtr(NonZeroU32::new(value).unwrap()))
234 }
235 })
236 .collect()
237 };
238
239 Ok(Self {
240 song_name,
241 philight,
242 created_with,
243 compatible_with,
244 flags,
245 special,
246 global_volume,
247 mix_volume,
248 initial_speed,
249 initial_tempo,
250 pan_separation,
251 pitch_wheel_depth,
252 message_length,
253 message_offset,
254 channel_pan,
255 channel_volume,
256 orders,
257 instr_offsets,
258 sample_offsets,
259 pattern_offsets,
260 })
261 }
262}