Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
19 kB · 518 lines
Rust
at dev
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519use alloc::{boxed::Box, vec::Vec};
use crate::{ file::{SansIO, SansIOWritten, err}, project::song::{PackedPan, PackedPatternOrder, Pan, PatternOrder},};use core::{ ffi::CStr, num::{NonZero, NonZeroU32},};
use core::mem;
use crate::file::InFilePtr;
#[derive(Debug, PartialEq, Eq)]pub struct ImpulseHeader { // C-String pub song_name: [u8; 26], pub philight: u16,
pub created_with: u16, pub compatible_with: u16, pub flags: u16, pub special: u16,
pub global_volume: u8, pub mix_volume: u8, pub initial_speed: u8, pub initial_tempo: u8, pub pan_separation: u8, pub pitch_wheel_depth: u8, pub message_length: u16, pub message_offset: u32,
pub channel_pan: [PackedPan; 64], pub channel_volume: [u8; 64],
// file_size: 1 pub orders: Box<[PackedPatternOrder]>, // length is oder_num
/// all Offsets are verified to be point outside the header. /// /// Invalid offsets are replaced with None, so patterns or orders don't break, because the indexes change // file_size: 4 pub instr_offsets: Box<[Option<InFilePtr>]>, // file_size: 4 pub sample_offsets: Box<[Option<InFilePtr>]>, /// here None could come from the file, which means an empty pattern // file_size: 4 pub pattern_offsets: Box<[Option<InFilePtr>]>,}
// https://github.com/schismtracker/schismtracker/wiki/ITTECH.TXTimpl ImpulseHeader { pub const BASE_SIZE: usize = 0xC0; // = 192
/// compute the lenght of the header if written into a file. /// Should be used to place everything else first, as the header needs the position of /// the other stuff. pub fn file_len(&self) -> usize { Self::BASE_SIZE + self.orders.len() + (self.instr_offsets.len() + self.sample_offsets.len() + self.pattern_offsets.len()) * 4 }
pub fn copy_from_song(&mut self, song: &crate::project::song::Song) { // keep in sync with `copy_values_into_song` self.global_volume = song.global_volume; self.initial_speed = song.initial_speed.get(); self.initial_tempo = song.initial_tempo.get(); self.mix_volume = song.mix_volume; self.pan_separation = song.pan_separation; self.pitch_wheel_depth = song.pitch_wheel_depth; self.channel_pan = song.pan; self.channel_volume = song.volume;
self.orders = song .pattern_order .iter() .copied() .collect::<Vec<PackedPatternOrder>>() .into_boxed_slice(); }
/// This panics if the Header is invalid in some way // should return a HeaderWriter of some sort pub fn write(&self, base: &mut [u8; Self::BASE_SIZE]) -> HeaderWriter { *base.first_chunk_mut().unwrap() = *b"IMPM"; let song_name = CStr::from_bytes_until_nul(&self.song_name).unwrap(); // max 26 bytes written two times, maybe llvm fixes this maybe it doesn't base[0x4..=0x1D].fill(0); base[0x4..=0x1D] .iter_mut() .zip(song_name.to_bytes()) .for_each(|(base, name)| *base = *name); let (u16_chunks, []) = base[0x1E..=0x2F].as_chunks_mut() else { panic!() }; u16_chunks[0] = self.philight.to_le_bytes(); u16_chunks[1] = u16::try_from(self.orders.len()).unwrap().to_le_bytes(); u16_chunks[2] = u16::try_from(self.sample_offsets.len()) .unwrap() .to_le_bytes(); u16_chunks[3] = u16::try_from(self.pattern_offsets.len()) .unwrap() .to_le_bytes(); u16_chunks[4] = self.created_with.to_le_bytes(); u16_chunks[5] = self.compatible_with.to_le_bytes(); u16_chunks[6] = self.flags.to_le_bytes(); u16_chunks[7] = self.special.to_le_bytes();
assert!(self.global_volume <= 128); base[0x30] = self.global_volume; assert!(self.mix_volume <= 128); base[0x31] = self.mix_volume;
base[0x32] = self.initial_speed; base[0x33] = self.initial_tempo; base[0x34] = self.pan_separation; base[0x35] = self.pitch_wheel_depth;
*base[0x36..=0x37].first_chunk_mut().unwrap() = self.message_length.to_le_bytes(); *base[0x38..=0x3B].first_chunk_mut().unwrap() = self.message_offset.to_le_bytes(); // reserved base[0x3C] = 0; base[0x3D] = 0; base[0x3E] = 0; base[0x3F] = 0;
base[0x40..0x80] .iter_mut() .zip(self.channel_pan) .for_each(|(base, pan)| *base = pan.0);
*base[0x80..0xC0].first_chunk_mut().unwrap() = self.channel_volume;
HeaderWriter { order_num: 0, orders: self.orders.clone(), instr_num: 0, instr_offsets: self .instr_offsets .iter() .copied() .map(Option::unwrap) .collect(), sample_num: 0, sample_offsets: self .sample_offsets .iter() .copied() .map(Option::unwrap) .collect(), pattern_num: 0, pattern_offsets: self.pattern_offsets.clone(), } }
/// takes the values that are included in Song from the header and write them to the song. /// /// Mostly applies to metadata about the song. /// Samples, patterns, instruments are not filled as they are not included in the header pub fn copy_values_into_song(&self, song: &mut crate::project::song::Song) { // keep in sync with `copy_values_from_song` song.global_volume = self.global_volume; // TODO: figure out if i want to error here or when parsing the Header song.initial_speed = NonZero::new(self.initial_speed).unwrap(); song.initial_tempo = NonZero::new(self.initial_tempo).unwrap(); song.mix_volume = self.mix_volume; song.pan_separation = self.pan_separation; song.pitch_wheel_depth = self.pitch_wheel_depth;
song.pan = self.channel_pan; song.volume = self.channel_volume;
self.orders .iter() .copied() .zip(song.pattern_order.iter_mut()) .for_each(|(header, song)| *song = header); }
pub fn parse(base: &[u8; Self::BASE_SIZE]) -> Result<HeaderParser, err::LoadErr> { // verify that the start matches if !base.starts_with(b"IMPM") { return Err(err::LoadErr::Invalid); }
let song_name = { let mut name = [0; _]; let c_str = CStr::from_bytes_until_nul(&base[0x4..=0x1D]); if c_str.is_err() { // defect_handler(LoadDefect::InvalidText) } // if there is an error use an empty string let c_str = c_str.unwrap_or_default().to_bytes(); // copy the name into the output buffer name.iter_mut().zip(c_str).for_each(|(t, s)| *t = *s); name };
let philight = u16::from_le_bytes([base[0x1E], base[0x1F]]);
let order_num = usize::from(u16::from_le_bytes([base[0x20], base[0x21]])); let instr_num = usize::from(u16::from_le_bytes([base[0x22], base[0x23]])); let sample_num = usize::from(u16::from_le_bytes([base[0x24], base[0x25]])); let pattern_num = usize::from(u16::from_le_bytes([base[0x26], base[0x27]])); let created_with = u16::from_le_bytes([base[0x28], base[0x29]]); let compatible_with = u16::from_le_bytes([base[0x2A], base[0x2B]]); let flags = u16::from_le_bytes([base[0x2C], base[0x2D]]); let special = u16::from_le_bytes([base[0x2E], base[0x2F]]);
let global_volume = if base[0x30] <= 128 { base[0x30] } else { // defect_handler(LoadDefect::OutOfBoundsValue); 64 };
let mix_volume = if base[0x31] <= 128 { base[0x31] } else { // defect_handler(LoadDefect::OutOfBoundsValue); 64 };
let initial_speed = base[0x32]; let initial_tempo = base[0x33]; let pan_separation = base[0x34]; let pitch_wheel_depth = base[0x35]; let message_length = u16::from_le_bytes([base[0x36], base[0x37]]); let message_offset = u32::from_le_bytes([base[0x38], base[0x39], base[0x3A], base[0x3B]]); let _reserved = u32::from_le_bytes([base[0x3C], base[0x3D], base[0x3E], base[0x3F]]);
// can unwrap here, because the length is const let pan_vals: [u8; 64] = base[0x40..0x80].try_into().unwrap(); let channel_pan: [PackedPan; 64] = pan_vals.map(|pan| match pan { PackedPan::DISABLED => Pan::Disabled.into(), PackedPan::SURROUND => Pan::Surround.into(), 0..=PackedPan::MAX_VALID => Pan::Value(pan).into(), // report err _ => Pan::default().into(), });
let channel_volume: [u8; 64] = { // can unwrap here, because the length is already checked at the beginning let mut vols: [u8; 64] = base[0x80..0xC0].try_into().unwrap();
vols.iter_mut().for_each(|vol| { if *vol > 64 { // defect_handler(LoadDefect::OutOfBoundsValue); *vol = 64 } }); vols };
Ok(HeaderParser { song_name, philight, created_with, compatible_with, flags, special, global_volume, mix_volume, initial_speed, initial_tempo, pan_separation, pitch_wheel_depth, message_length, message_offset, channel_pan, channel_volume, orders: Vec::with_capacity(order_num), instr_offsets: Vec::with_capacity(instr_num), sample_offsets: Vec::with_capacity(sample_num), pattern_offsets: Vec::with_capacity(pattern_num), order_num, instr_num, sample_num, pattern_num, }) }}
pub struct HeaderParser { // C-String song_name: [u8; 26], philight: u16,
created_with: u16, compatible_with: u16, flags: u16, special: u16,
global_volume: u8, mix_volume: u8, initial_speed: u8, initial_tempo: u8, pan_separation: u8, pitch_wheel_depth: u8, message_length: u16, message_offset: u32,
channel_pan: [PackedPan; 64], channel_volume: [u8; 64],
order_num: usize, orders: Vec<PackedPatternOrder>, // length is oder_num
/// all Offsets are verified to be point outside the header. /// /// Invalid offsets are replaced with None, so patterns or orders don't break, because the indexes change instr_num: usize, instr_offsets: Vec<Option<InFilePtr>>, sample_num: usize, sample_offsets: Vec<Option<InFilePtr>>, pattern_num: usize, /// here None could come from the file, which means an empty pattern pattern_offsets: Vec<Option<InFilePtr>>,}
impl HeaderParser { /// needs at most 4 bytes at once pub fn parse(&mut self, mut buf: &[u8]) -> SansIO<ImpulseHeader> { assert!(self.orders.len() <= self.order_num); assert!(self.instr_offsets.len() <= self.instr_num); assert!(self.sample_offsets.len() <= self.sample_num); // lesser than here, if it is equal the parser is already done assert!(self.pattern_offsets.len() < self.pattern_num);
let mut read = 0; while self.orders.len() < self.order_num { if let Some(&val) = buf.split_off_first() { read += 1; let order = match val { PackedPatternOrder::END_OF_SONG => PatternOrder::EndOfSong.into(), PackedPatternOrder::SKIP_ORDER => PatternOrder::SkipOrder.into(), 0..=PackedPatternOrder::MAX_VALID => PatternOrder::Number(val).into(), // report Err _ => PatternOrder::default().into(), }; self.orders.push(order); } else { return SansIO::Pending(read); } }
while self.instr_offsets.len() < self.instr_num { if let Some((val, new_buf)) = buf.split_first_chunk() { buf = new_buf; read += val.len(); let val = u32::from_le_bytes(*val); let opt = if val <= ImpulseHeader::BASE_SIZE as u32 { // handle defect None } else { Some(InFilePtr(NonZeroU32::new(val).unwrap())) }; self.instr_offsets.push(opt); } else { return SansIO::Pending(read); } }
while self.sample_offsets.len() < self.sample_num { if let Some((val, new_buf)) = buf.split_first_chunk() { buf = new_buf; read += val.len(); let val = u32::from_le_bytes(*val); let opt = if val <= ImpulseHeader::BASE_SIZE as u32 { // handle defect None } else { Some(InFilePtr(NonZeroU32::new(val).unwrap())) }; self.sample_offsets.push(opt); } else { return SansIO::Pending(read); } }
while self.pattern_offsets.len() < self.pattern_num { if let Some((val, new_buf)) = buf.split_first_chunk() { buf = new_buf; read += val.len(); let val = u32::from_le_bytes(*val); let opt = if val == 0 { // not a defect 0 is valid here None } else if val <= ImpulseHeader::BASE_SIZE as u32 { // handle defect None } else { Some(InFilePtr(NonZeroU32::new(val).unwrap())) }; self.pattern_offsets.push(opt); } else { return SansIO::Pending(read); } }
assert!(self.orders.len() == self.order_num); assert!(self.instr_offsets.len() == self.instr_num); assert!(self.sample_offsets.len() == self.sample_num); assert!(self.pattern_offsets.len() == self.pattern_num); SansIO::Finished( ImpulseHeader { song_name: self.song_name, philight: self.philight, created_with: self.created_with, compatible_with: self.compatible_with, flags: self.flags, special: self.special, global_volume: self.global_volume, mix_volume: self.mix_volume, initial_speed: self.initial_speed, initial_tempo: self.initial_tempo, pan_separation: self.pan_separation, pitch_wheel_depth: self.pitch_wheel_depth, message_length: self.message_length, message_offset: self.message_offset, channel_pan: self.channel_pan, channel_volume: self.channel_volume, orders: mem::take(&mut self.orders).into_boxed_slice(), instr_offsets: mem::take(&mut self.instr_offsets).into_boxed_slice(), sample_offsets: mem::take(&mut self.sample_offsets).into_boxed_slice(), pattern_offsets: mem::take(&mut self.pattern_offsets).into_boxed_slice(), }, read, ) }}
pub struct HeaderWriter { order_num: usize, orders: Box<[PackedPatternOrder]>,
instr_num: usize, instr_offsets: Box<[InFilePtr]>, sample_num: usize, sample_offsets: Box<[InFilePtr]>, /// here None should go into the pattern, which means an empty pattern // file_size: 4 pattern_num: usize, pattern_offsets: Box<[Option<InFilePtr>]>,}
impl HeaderWriter { pub fn write(&mut self, mut buf: &mut [u8]) -> SansIOWritten { assert!(self.order_num <= self.orders.len()); assert!(self.instr_num <= self.instr_offsets.len()); assert!(self.sample_num <= self.sample_offsets.len()); // lesser than here, if it is equal the parser is already done assert!(self.pattern_num <= self.pattern_offsets.len());
let mut read = 0; while self.order_num < self.orders.len() { if let Some(w) = buf.split_off_first_mut() { read += 1; *w = self.orders[self.order_num].0; self.order_num += 1; } else { return SansIO::Pending(read); } }
while self.instr_num < self.instr_offsets.len() { if let Some((w, new_buf)) = buf.split_first_chunk_mut() { buf = new_buf; read += 4; *w = self.instr_offsets[self.instr_num].get().get().to_le_bytes(); self.instr_num += 1; } else { return SansIO::Pending(read); } }
while self.sample_num < self.sample_offsets.len() { if let Some((w, new_buf)) = buf.split_first_chunk_mut() { buf = new_buf; read += 4; *w = self.sample_offsets[self.sample_num] .get() .get() .to_le_bytes(); self.sample_num += 1; } else { return SansIO::Pending(read); } }
while self.pattern_num < self.pattern_offsets.len() { if let Some((w, new_buf)) = buf.split_first_chunk_mut() { buf = new_buf; read += 4; *w = if let Some(ptr) = self.pattern_offsets[self.pattern_num] { ptr.get().get() } else { 0 } .to_le_bytes(); self.pattern_num += 1; } else { return SansIO::Pending(read); } }
assert!(self.orders.len() == self.order_num); assert!(self.instr_offsets.len() == self.instr_num); assert!(self.sample_offsets.len() == self.sample_num); assert!(self.pattern_offsets.len() == self.pattern_num); SansIO::Finished((), read) }}