use std::io; use std::io::prelude::*; use std::io::SeekFrom; use std::fs::File; use std::mem::{size_of, zeroed}; use crate::portmod::{Effect, Slide}; macro_rules! zero_default { ($t:ty) => { impl Default for $t { fn default() -> Self { unsafe { zeroed() } } } } } #[repr(C, packed)] pub struct Header { pub magic: [u8; 4], pub song_name: [u8; 26], pub pat_highlight: u16, //PHiligt pub nord: u16, //OrdNum pub ninst: u16, //InsNum pub nsamp: u16, //SmpNum pub npatt: u16, //PatNum pub tracker_version: u16, //Cwt/v pub format_version: u16, //Cmwt pub flags: u16, //Flags pub special: u16, //Special pub global_volume: u8, //GV pub mix_volume: u8, //MV pub speed: u8, //IS pub tempo: u8, //IT pub stereo_seperation: u8, //Sep pub wheel_depth: u8, //PWD pub msg_length: u16, //MsgLgth pub msg_offset: u32, //Message Offset pub reserved: u32, pub channel_pan: [u8; 64], pub 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 & 0x1 != 0 } fn time_signature(self: &Self) -> Option<(u32, u32)> { if self.special & 0x4 != 0 && self.pat_highlight != 0 { Some(((self.pat_highlight & 0xff) as u32, (self.pat_highlight >> 8) as u32)) } else { None } } } #[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() } pub fn is_compressed(self: &Self) -> bool { self.header.flag & 0x8 != 0 } pub fn num_channels(self: &Self) -> u8 { if self.header.flag & 0x4 != 0 { 2 } else { 1 }} pub fn bits_per_sample(self: &Self) -> u8 { if self.header.flag & 0x2 != 0 { 16 } else { 8 }} } #[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: u8, 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: nrows as u8, nch:64, data}) } pub fn cell_at(&self, r: u8, c: u8) -> &Cell { &self.data[((r as u16 * 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 Effect::from_it_vol(*vol) { Effect::SetVolume(vol) => print!("v{:02} ", vol), Effect::VolumeSlide(s) => match s { Slide::FineUp(x) => print!("a{:02} ", x), Slide::FineDown(x) => print!("b{:02} ", x), Slide::Up(x) => print!("c{:02} ", x), Slide::Down(x) => print!("d{:02} ", x), }, Effect::PortaDown(x) => print!("e{:02} ", x / 4), Effect::PortaUp(x) => print!("f{:02} ", x / 4), Effect::SetPan(p) => print!("p{:02} ", p), Effect::TonePorta(_) => print!("g{:02} ", vol - 193), Effect::Vibrato(_, x) => print!("h{:02} ", x), _ => 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!("=================================================="); } } #[derive(Debug)] #[derive(Default)] pub struct MPTTrackExt { pub rpb: Option, pub rpm: Option, pub nch: Option } pub struct ITFile { pub header: Header, pub orders: Vec, pub message: String, pub insts: Vec, pub samps: Vec, pub patterns: Vec, pub trkext: Option } impl ITFile { pub fn time_signature(self: &Self) -> Option<(u32, u32)> { if let Some((b, m)) = self.header.time_signature() { Some((b as u32, m as u32)) } else { if let Some(trex) = self.trkext.as_ref() { match (trex.rpb, trex.rpm) { (Some(b), Some(m)) => Some((b, m)), (_, _) => None } } else { None } } } } #[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_u32(f: &mut File) -> Result { let mut buf = [0u8; 4]; f.read_exact(&mut buf)?; Ok(u32::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) } fn check_magic(f: &mut File, m: &str) -> Result { let mb = m.as_bytes(); assert_eq!(mb.len(), 4); let mut buf = [0u8; 4]; f.read_exact(&mut buf)?; if mb != buf { f.seek(SeekFrom::Current(-4))?; } Ok(mb == buf) } fn read_magic(f: &mut File) -> Result { let mut buf = [0u8; 4]; f.read_exact(&mut buf)?; Ok(String::from_utf8_lossy(&buf).into_owned()) } pub fn read_inst_ext(f: &mut File, ninst: u16) -> Result<(), io::Error> //instr extension currently completely unused { println!("trying to read MPT Instr Extension at {:x}", f.stream_position()?); if check_magic(f, "XTPM")? { println!("MPT Instr Extension found at {:x}", f.stream_position()?); loop { if check_magic(f, "STPM")? { f.seek(SeekFrom::Current(-4))?; break; } read_u32(f)?; let chunk_len_inst = read_u16(f)? as i64; f.seek(SeekFrom::Current(chunk_len_inst * ninst as i64))?; } } Ok(()) } pub fn read_track_ext(f: &mut File) -> Result, io::Error> { if check_magic(f, "STPM")? { println!("MPT Track Extension found at {:x}", f.stream_position()?); let mut ret: MPTTrackExt = Default::default(); loop { let mr = read_magic(f); let mut dlen = 0; if mr.is_ok() { dlen = read_u16(f)?; } match mr.as_deref() { Err(_) => { break; }, Ok("...C") => { ret.nch = Some(read_u16(f)?); }, Ok(".BPR") => { ret.rpb = Some(read_u32(f)?); }, Ok(".MPR") => { ret.rpm = Some(read_u32(f)?); }, Ok(&_) => {f.seek(SeekFrom::Current(dlen.into()))?; } } } return Ok(Some(ret)); } Ok(None) } pub fn read_mpt_ext(f: &mut File, ninst: u16) -> Option { match read_inst_ext(f, ninst) { Err(_) => None, Ok(_) => { match read_track_ext(f) { Err(_) => None, Ok(r) => r } } } } 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 {:?}", orders); println!("inst offsets {: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 last_samp_off = 0u64; let mut last_samp_comp = false; let mut samps: Vec = Vec::new(); if header.nsamp > 0 { last_samp_off = (samp_offsets[header.nsamp as usize - 1] as usize + size_of::()) as u64; } 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); } f.seek(SeekFrom::Start(samp.header.samp_ptr.into()))?; last_samp_comp = samp.is_compressed(); if !last_samp_comp { let data_len = samp.header.length * samp.num_channels() as u32 * samp.bits_per_sample() as u32 / 8; f.seek(SeekFrom::Current(data_len.into()))?; } let dataend_off = f.stream_position()?; last_samp_off = std::cmp::max(last_samp_off, dataend_off); 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)?; let dataend_off = f.stream_position()?; last_samp_off = std::cmp::max(last_samp_off, dataend_off); patt.dump(); patterns.push(patt); } if last_samp_off > 0 { f.seek(SeekFrom::Start(last_samp_off))?; if last_samp_comp { loop { if read_u32(&mut f).is_err() { break; } else { f.seek(SeekFrom::Current(-4))?; } if check_magic(&mut f, "STPM").unwrap_or_default() || check_magic(&mut f, "XTPM").unwrap_or_default() { let id = read_u32(&mut f)?; f.seek(SeekFrom::Current(-8))?; if (id & 0x80808080 == 0) && (id & 0x60606060 != 0) { break; } } let chnklen = read_u16(&mut f)?; f.seek(SeekFrom::Current(chnklen as i64))?; } } } let trkext = read_mpt_ext(&mut f, header.ninst); let ret = ITFile{header, orders, message, insts, samps, patterns, trkext}; Ok(ret) }