Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
23 kB · 655 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656use alloc::{boxed::Box, vec::Vec};
use crate::{ file::{SansIO, SansIOWritten, err}, project::{ pan::PackedPan, pattern_order::PackedPatternOrder, song::{ChannelVolume, Volume}, },};use core::{ ffi::CStr, num::{NonZero, NonZeroU32}, ops::Deref,};
use core::mem;
use crate::file::InFilePtr;
// 26 bytes, so copy should be fine#[derive(Eq, Clone, Copy)]pub struct SongName([u8; 26]);
#[cfg(feature = "arbitrary")]impl<'a> arbitrary::Arbitrary<'a> for SongName { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { let mut data = [0; 26]; let mut idx = 0; u.arbitrary_loop(None, Some(25), |u| { use core::ops::ControlFlow;
data[idx] = u.arbitrary()?; idx += 1; Ok(ControlFlow::Continue(())) })?; Ok(Self(data)) } fn size_hint(_depth: usize) -> (usize, Option<usize>) { (0, Some(26)) }}
impl Deref for SongName { type Target = CStr;
fn deref(&self) -> &Self::Target { // could be unchecked CStr::from_bytes_until_nul(&self.0).unwrap() }}
impl PartialEq for SongName { fn eq(&self, other: &Self) -> bool { // C-String comparison, so that the end is ignored, although it should always be null CStr::eq(&self, other.deref()) }}
impl core::fmt::Debug for SongName { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { // debug this as a C-String f.debug_tuple("SongName").field(&self.deref()).finish() }}
impl SongName { /// returns None if value is longer than 26 bytes (including null) pub fn new(value: &CStr) -> Option<Self> { let value = value.to_bytes(); if value.len() > 25 { None } else { let mut data = [0; 26]; data.iter_mut() .zip(value) .for_each(|(base, name)| *base = *name); Some(Self(data)) } }}
#[derive(Debug, PartialEq, Eq)]#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]pub struct ImpulseHeader { // C-String pub song_name: SongName, pub philight: u16,
pub created_with: u16, pub compatible_with: u16, pub flags: u16, pub special: u16,
pub global_volume: Volume, pub mix_volume: Volume, pub initial_speed: NonZero<u8>, pub initial_tempo: NonZero<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: [ChannelVolume; 64],
// file_size: 1 pub orders: Box<[PackedPatternOrder]>, // length is oder_num
// file_size: 4 pub instr_offsets: Box<[InFilePtr]>, // file_size: 4 pub sample_offsets: Box<[InFilePtr]>, /// None means empty pattern // file_size: 4 pub pattern_offsets: Box<[Option<InFilePtr>]>,}
#[derive(Debug, PartialEq, Eq)]pub struct ParsedImpulseHeader { // C-String pub song_name: SongName, pub philight: u16,
pub created_with: u16, pub compatible_with: u16, pub flags: u16, pub special: u16,
pub global_volume: Volume, pub mix_volume: Volume, pub initial_speed: Option<NonZero<u8>>, pub initial_tempo: Option<NonZero<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: [ChannelVolume; 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>]>, /// other Option: if it was valid in the file /// inner Option: if it is there // file_size: 4 pub pattern_offsets: Box<[Option<Option<InFilePtr>>]>,}
impl TryFrom<ParsedImpulseHeader> for ImpulseHeader { type Error = ();
/// this should succeed if during parsing no defect was reported. TODO: defect reporting fn try_from(value: ParsedImpulseHeader) -> Result<Self, Self::Error> { if value.instr_offsets.iter().any(Option::is_none) || value.sample_offsets.iter().any(Option::is_none) || value.pattern_offsets.iter().any(Option::is_none) { return Err(()); } Ok(Self { song_name: value.song_name, philight: value.philight, created_with: value.created_with, compatible_with: value.compatible_with, flags: value.flags, special: value.special, global_volume: value.global_volume, mix_volume: value.mix_volume, initial_speed: value.initial_speed.ok_or(())?, initial_tempo: value.initial_tempo.ok_or(())?, pan_separation: value.pan_separation, pitch_wheel_depth: value.pitch_wheel_depth, message_length: value.message_length, message_offset: value.message_offset, channel_pan: value.channel_pan, channel_volume: value.channel_volume, orders: value.orders, instr_offsets: value .instr_offsets .iter() .copied() .map(Option::unwrap) .collect(), sample_offsets: value .sample_offsets .iter() .copied() .map(Option::unwrap) .collect(), pattern_offsets: value .pattern_offsets .iter() .copied() .map(Option::unwrap) .collect(), }) }}
// 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; self.initial_tempo = song.initial_tempo; 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(); }
pub fn write(&self, base: &mut [u8; Self::BASE_SIZE]) -> HeaderWriter { *base.first_chunk_mut().unwrap() = *b"IMPM"; // 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(&self.song_name.0) .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.instr_offsets.len()) .unwrap() .to_le_bytes(); u16_chunks[3] = u16::try_from(self.sample_offsets.len()) .unwrap() .to_le_bytes(); u16_chunks[4] = u16::try_from(self.pattern_offsets.len()) .unwrap() .to_le_bytes(); u16_chunks[5] = self.created_with.to_le_bytes(); u16_chunks[6] = self.compatible_with.to_le_bytes(); u16_chunks[7] = self.flags.to_le_bytes(); u16_chunks[8] = self.special.to_le_bytes();
base[0x30] = self.global_volume.get(); base[0x31] = self.mix_volume.get();
base[0x32] = self.initial_speed.get(); base[0x33] = self.initial_tempo.get(); 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.get_inner());
*base[0x80..0xC0].first_chunk_mut().unwrap() = self.channel_volume.map(ChannelVolume::get);
HeaderWriter { order_num: 0, orders: self.orders.clone(), instr_num: 0, instr_offsets: self.instr_offsets.clone(), sample_num: 0, sample_offsets: self.sample_offsets.clone(), 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 = self.initial_speed; song.initial_tempo = self.initial_tempo; 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 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 // can never be none, as it can't be too long SongName::new(c_str.unwrap_or_default()).unwrap() };
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]]);
// TODO: report defect let global_volume = Volume::new(base[0x30]).unwrap_or(const { Volume::new(64).unwrap() });
// TODO: report defect let mix_volume = Volume::new(base[0x31]).unwrap_or(const { Volume::new(64).unwrap() });
let initial_speed = NonZero::new(base[0x32]); if initial_speed == None { // report defect } let initial_tempo = NonZero::new(base[0x33]); if initial_tempo == None { // report defect } 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| PackedPan::new(pan).unwrap_or_default());
let channel_volume = <[u8; 64]>::try_from(&base[0x80..0xC0]) // .try_into() .unwrap() .map(|v| ChannelVolume::new(v).unwrap_or(const { ChannelVolume::new(64).unwrap() })); // 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: SongName, philight: u16,
created_with: u16, compatible_with: u16, flags: u16, special: u16,
global_volume: Volume, mix_volume: Volume, initial_speed: Option<NonZero<u8>>, initial_tempo: Option<NonZero<u8>>, pan_separation: u8, pitch_wheel_depth: u8, message_length: u16, message_offset: u32,
channel_pan: [PackedPan; 64], channel_volume: [ChannelVolume; 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, /// inner option comes from the file means empty pattern pattern_offsets: Vec<Option<Option<InFilePtr>>>,}
impl HeaderParser { /// needs at most 4 bytes at once pub fn parse(&mut self, mut buf: &[u8]) -> SansIO<ParsedImpulseHeader> { assert!(self.orders.len() <= self.order_num || self.order_num == 0); assert!(self.instr_offsets.len() <= self.instr_num || self.instr_num == 0); assert!(self.sample_offsets.len() <= self.sample_num || self.sample_num == 0); // lesser than here, if it is equal the parser is already done // if you hit this you drive the parser wrong. You shouldn't call parse if it returned Finished once. assert!(self.pattern_offsets.len() < self.pattern_num || self.pattern_num == 0);
let mut read = 0; while self.orders.len() < self.order_num { if let Some(&val) = buf.split_off_first() { read += 1; let order = if let Some(order) = PackedPatternOrder::new(val) { order } else { // report err PackedPatternOrder::default() }; 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 Some(None) } else if val <= ImpulseHeader::BASE_SIZE as u32 { // handle defect None } else { Some(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( ParsedImpulseHeader { 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].get_inner(); 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) }}