old school music tracker audio backend
at main 243 lines 8.7 kB view raw
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}