old school music tracker audio backend
at dev 262 lines 9.7 kB view raw
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}