use std::io; use std::io::prelude::*; use std::io::SeekFrom; use std::fs::File; use std::mem::{size_of, zeroed}; macro_rules! zero_default { ($t:ty) => { impl Default for $t { fn default() -> Self { unsafe { zeroed() } } } } } #[repr(C, packed)] pub struct Header { magic: [u8; 4], song_name: [u8; 26], pat_highlight: u16, //PHiligt nord: u16, //OrdNum ninst: u16, //InsNum nsamp: u16, //SmpNum npatt: u16, //PatNum tracker_version: u16, //Cwt/v format_version: u16, //Cmwt flags: u16, //Flags special: u16, //Special global_volume: u8, //GV mix_volume: u8, //MV speed: u8, //IS tempo: u8, //IT stereo_seperation: u8, //Sep wheel_depth: u8, //PWD msg_length: u16, //MsgLgth msg_offset: u32, //Message Offset reserved: u32, channel_pan: [u8; 64], channel_vol: [u8; 64] } zero_default!{Header} impl Header { fn song_name(self: &Self) -> String { String::from_utf8_lossy(terminate_cstr(&self.song_name)).into_owned() } fn has_message(self: &Self) -> bool { self.special & 1 != 0 } } #[repr(C, packed)] pub struct InstHeader { magic: [u8; 4], dos_filename: [u8; 12], zero: u8, //00h override_action: u8, //NNA duplicate_check: u8, //DCT duplicate_action: u8, //DCA fade_out: u16, //FadeOut pitch_pan_separa: u8, //PPS pitch_pan_center: u8, //PPC global_volume: u8, //GbV default_pan: u8, //DfP rand_vol: u8, //RV rand_pan: u8, //RP tracker_version: u16, //TrkVers nsamples: u8, //NoS reserved: u8, //x inst_name: [u8; 26], filter_cutoff: u8, //IFC filter_reso: u8, //IFR midi_ch: u8, //MCh midi_pc: u8, //MPr midi_bank: u16, //MIDIBnk sample_table: [u8; 240] } zero_default!{InstHeader} #[repr(C, packed)] #[derive(Default)] pub struct EnvelopeNode {pub y: u8, pub t: u16} #[repr(C, packed)] #[derive(Default)] pub struct Envelope { pub flag: u8, //Flg pub nnodes: u8, //Num pub loop_begin: u8, //LpB pub loop_end: u8, //LpE pub sust_begin: u8, //SLB pub sust_end: u8, //SLE pub nodes: [EnvelopeNode; 25] } pub struct Inst { pub header: InstHeader, pub env_vol: Envelope, pub env_pan: Envelope, pub env_pit: Envelope } impl Inst { pub fn load(f: &mut File) -> Result { let header: InstHeader = read_struct(f)?; let env_vol: Envelope = read_struct(f)?; f.seek(SeekFrom::Start(1))?; let env_pan: Envelope = read_struct(f)?; f.seek(SeekFrom::Start(1))?; let env_pit: Envelope = read_struct(f)?; Ok(Inst{header, env_vol, env_pan, env_pit}) } pub fn inst_name(self: &Self) -> String { String::from_utf8_lossy(terminate_cstr(&self.header.inst_name)).into_owned() } pub fn filename(self: &Self) -> String { String::from_utf8_lossy(terminate_cstr(&self.header.dos_filename)).into_owned() } } #[repr(C, packed)] #[derive(Default)] pub struct SampHeader { magic: [u8; 4], dos_filename: [u8; 12], zero: u8, global_volume: u8, flag: u8, default_volume: u8, sample_name: [u8; 26], convert: u8, default_pan: u8, length: u32, loop_begin: u32, loop_end: u32, c5_speed: u32, sust_begin: u32, sust_end: u32, samp_ptr: u32, vib_speed: u8, vib_depth: u8, vib_type: u8, vib_rate: u8 } pub struct Samp { pub header: SampHeader } impl Samp { pub fn load(f: &mut File) -> Result { let header: SampHeader = read_struct(f)?; Ok(Samp{header}) } pub fn sample_name(self: &Self) -> String { String::from_utf8_lossy(terminate_cstr(&self.header.sample_name)).into_owned() } pub fn filename(self: &Self) -> String { String::from_utf8_lossy(terminate_cstr(&self.header.dos_filename)).into_owned() } } #[derive(Copy, Clone, Default)] pub struct Cell { pub mask: u8, pub note: u8, pub inst: u8, pub vol : u8, pub efx : u8, pub fxp : u8 } pub struct Patt { pub nrows: u16, pub nch: u8, pub data: Vec } impl Patt { pub fn load(f: &mut File) -> Result { let mut chmem: [Cell; 64] = [Default::default(); 64]; let datalen = read_u16(f)?; let nrows = read_u16(f)?; let nch: u8 = 64; f.seek(SeekFrom::Current(4))?; let mut data: Vec = Vec::new(); data.resize_with((nrows * (nch as u16)).into(), Default::default); let ofst = f.stream_position()?; for currow in 0..nrows { loop { let chvar = read_u8(f)?; if chvar == 0 { break; } let ch = ((chvar - 1) & 0x3f) as usize; let Cell{mut mask, ..} = chmem[ch]; if (chvar >> 7) != 0 { mask = read_u8(f)?; chmem[ch].mask = mask; } let mut note = 0u8; let mut inst = 0u8; let mut vol = 0u8; let mut efx = 0u8; let mut fxp = 0u8; if (mask & 0x01) != 0 { note = read_u8(f)?; chmem[ch] = Cell{note, ..chmem[ch]}; } if (mask & 0x02) != 0 { inst = read_u8(f)?; chmem[ch] = Cell{inst, ..chmem[ch]}; } if (mask & 0x04) != 0 { vol = read_u8(f)?; chmem[ch] = Cell{vol , ..chmem[ch]}; } if (mask & 0x08) != 0 { efx = read_u8(f)?; fxp = read_u8(f)?; chmem[ch] = Cell{efx, fxp, ..chmem[ch]}; } if (mask & 0x10) != 0 { Cell{note, ..} = chmem[ch]; } if (mask & 0x20) != 0 { Cell{inst, ..} = chmem[ch]; } if (mask & 0x40) != 0 { Cell{vol, ..} = chmem[ch]; } if (mask & 0x80) != 0 { Cell{efx, fxp, ..} = chmem[ch]; } data[currow as usize * nch as usize + ch] = Cell{mask, note, inst, vol, efx, fxp}; } } if f.stream_position()? - ofst != datalen as u64 { return Err(self::Error::CorruptPatternData); } Ok(Patt{nrows, nch:64, data}) } pub fn cell_at(&self, r: u16, c: u8) -> &Cell { &self.data[((r * self.nch as u16) + c as u16) as usize] } pub fn dump(&self) { const NOTES: &str = "C-C#D-D#E-F-F#G-G#A-A#B-"; println!("=================================================="); for r in 0..self.nrows { print!("|"); for ch in 0..self.nch { let Cell{mask, note, inst, vol, efx, fxp} = self.cell_at(r, ch); if mask & 0x11 != 0 { match note { 0xff => print!("== "), 0xfe => print!("^^ "), 0x78..=0xfd => print!("~~ "), 0 => print!("... "), _ => { let p = (note % 12 * 2) as usize; print!("{}{:1} ", &NOTES[p..p + 2], note / 12); } } } else { print!("... "); } if mask & 0x22 != 0 { match inst { 0 => print!(".. "), _ => print!("{:02X} ", inst) } } else { print!(".. "); } if mask & 0x44 != 0 { match vol { 0..=64 => print!("v{:02} ", vol), 65..=74 => print!("a{:02} ", vol - 65), 75..=84 => print!("b{:02} ", vol - 75), 85..=94 => print!("c{:02} ", vol - 85), 95..=104 => print!("d{:02} ", vol - 95), 105..=114 => print!("e{:02} ", vol - 105), 115..=124 => print!("f{:02} ", vol - 115), 128..=192 => print!("p{:02} ", vol - 128), 193..=202 => print!("g{:02} ", vol - 193), 203..=212 => print!("h{:02} ", vol - 203), _ => print!("??? ") } } else { print!("... "); } if mask & 0x88 != 0 { match efx { 0 => print!("...|"), _ => print!("{}{:02X}|", (('A' as u8) - 1 + efx) as char, fxp) } } else { print!("...|"); } } println!(""); } println!("=================================================="); } } pub struct ITFile { pub header: Header, pub orders: Vec, pub message: String, pub insts: Vec, pub samps: Vec, pub patterns: Vec } #[derive(Debug)] pub enum Error { IOError(io::Error), InvalidHeader, UnsupportedVersion, CorruptPatternData } impl From for self::Error { fn from(e: io::Error) -> Self { self::Error::IOError(e) } } fn terminate_cstr(s: &[u8]) -> &[u8] { match s.iter().position(|&x| x==0) { Some(p) => &s[0..p], None => s } } fn read_u8(f: &mut File) -> Result { let mut buf = [0u8; 1]; f.read_exact(&mut buf)?; Ok(buf[0]) } fn read_u16(f: &mut File) -> Result { let mut buf = [0u8; 2]; f.read_exact(&mut buf)?; Ok(u16::from_le_bytes(buf)) } fn read_vec(f: &mut File, len: usize) -> Result, io::Error> where T: Clone, { let mut ret: Vec = Vec::with_capacity(len); let buf: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(ret.as_mut_ptr() as *mut u8, len * size_of::()) }; f.read_exact(buf)?; unsafe { ret.set_len(len) }; Ok(ret) } fn read_struct(f: &mut File) -> Result where T: Default, { let mut ret = T::default(); let buf: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(&mut ret as *mut T as *mut u8, size_of::()) }; f.read_exact(buf)?; Ok(ret) } pub fn load(filename: &str) -> Result { let mut f = File::open(filename)?; let header: Header = read_struct(&mut f)?; if header.magic != "IMPM".as_bytes() { return Err(self::Error::InvalidHeader); } if cfg!(debug_assertions) { println!("song name: {}", header.song_name()); } if header.format_version < 0x200 { return Err(self::Error::UnsupportedVersion); } { let (ninst, nsamp, npatt) = (header.ninst, header.nsamp, header.npatt); println!("{} instruments, {} samples, {} patterns", ninst, nsamp, npatt); } let orders: Vec = read_vec(&mut f, header.nord as usize)?; let inst_offsets: Vec = read_vec(&mut f, header.ninst as usize)?; let samp_offsets: Vec = read_vec(&mut f, header.nsamp as usize)?; let patt_offsets: Vec = read_vec(&mut f, header.npatt as usize)?; println!("{:?}", orders); println!("{:x?}",inst_offsets); let mut message = String::new(); if header.has_message() { f.seek(SeekFrom::Start(header.msg_offset.into()))?; let msg_raw: Vec = read_vec(&mut f, header.msg_length.into())?; message = String::from_utf8_lossy(terminate_cstr(&msg_raw[..])).replace("\r", "\n"); } println!("{}", message); let mut insts: Vec = Vec::new(); for inst_offset in inst_offsets { f.seek(SeekFrom::Start(inst_offset.into()))?; let inst = Inst::load(&mut f)?; if inst.header.magic != "IMPI".as_bytes() { return Err(self::Error::InvalidHeader); } println!("{}", inst.inst_name()); insts.push(inst); } let mut samps: Vec = Vec::new(); for samp_offset in samp_offsets { f.seek(SeekFrom::Start(samp_offset.into()))?; let samp = Samp::load(&mut f)?; if samp.header.magic != "IMPS".as_bytes() { return Err(self::Error::InvalidHeader); } println!("{}", samp.sample_name()); samps.push(samp); } let mut patterns: Vec = Vec::new(); for patt_offset in patt_offsets { f.seek(SeekFrom::Start(patt_offset.into()))?; let patt = Patt::load(&mut f)?; patt.dump(); patterns.push(patt); } let ret = ITFile{header, orders, message, insts, samps, patterns}; Ok(ret) }