diff options
author | Chris Xiong <chirs241097@gmail.com> | 2022-11-23 12:53:37 -0500 |
---|---|---|
committer | Chris Xiong <chirs241097@gmail.com> | 2022-11-23 12:53:37 -0500 |
commit | 736033237b41f91bcc093c06780e47c4ad5febaa (patch) | |
tree | 8fa1cd0ae0b659a5bb5993d3d84f06ed116683a5 | |
parent | 5405aea1f7dc629626b39e480ba459b5037af7f2 (diff) | |
download | it2midi-736033237b41f91bcc093c06780e47c4ad5febaa.tar.xz |
AAAArgh finally we read IT timing info.
-rw-r--r-- | src/convert.rs | 4 | ||||
-rw-r--r-- | src/itfile.rs | 181 | ||||
-rw-r--r-- | src/main.rs | 2 |
3 files changed, 180 insertions, 7 deletions
diff --git a/src/convert.rs b/src/convert.rs index ca45e4a..884708d 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -15,6 +15,7 @@ struct PlayerState current_tick: u8, speed: u8, tempo: u8, + rpb: u8, loop_start: u8, loop_ctr: u8, row_extension: u8, @@ -172,6 +173,7 @@ impl<'a> Player<'a> current_tick: 0, speed: self.it.header.speed, tempo: self.it.header.tempo, + rpb: self.it.time_signature().unwrap_or((4, 16)).0 as u8, loop_start: 0, loop_ctr: !0, row_extension: 0, @@ -231,7 +233,7 @@ impl<'a> CellHandler for ConvertCellHandler<'a> let itfile::Cell{mask, note, inst, vol, efx, fxp} = cell; if !ch == 0 { - self.miditick += <Rational as From<u32>>::from(960) / (ps.speed as u16 * 4).into(); + self.miditick += <Rational as From<u32>>::from(960) / (ps.speed as u16 * ps.rpb as u16).into(); } ps } diff --git a/src/itfile.rs b/src/itfile.rs index 76c1997..12788b3 100644 --- a/src/itfile.rs +++ b/src/itfile.rs @@ -49,7 +49,13 @@ 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 } + { 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 >> 8) as u32, (self.pat_highlight & 0xff) as u32)) } + else { None } + } } #[repr(C, packed)] @@ -167,6 +173,9 @@ impl Samp { 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)] @@ -304,6 +313,15 @@ impl Patt } } +#[derive(Debug)] +#[derive(Default)] +pub struct MPTTrackExt +{ + pub rpb: Option<u32>, + pub rpm: Option<u32>, + pub nch: Option<u16> +} + pub struct ITFile { pub header: Header, @@ -311,7 +329,30 @@ pub struct ITFile pub message: String, pub insts: Vec<Inst>, pub samps: Vec<Samp>, - pub patterns: Vec<Patt> + pub patterns: Vec<Patt>, + pub trkext: Option<MPTTrackExt> +} + +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)] @@ -352,6 +393,13 @@ fn read_u16(f: &mut File) -> Result<u16, io::Error> Ok(u16::from_le_bytes(buf)) } +fn read_u32(f: &mut File) -> Result<u32, io::Error> +{ + let mut buf = [0u8; 4]; + f.read_exact(&mut buf)?; + Ok(u32::from_le_bytes(buf)) +} + fn read_vec<T>(f: &mut File, len: usize) -> Result<Vec<T>, io::Error> where T: Clone, { @@ -373,6 +421,86 @@ where T: Default, Ok(ret) } +fn check_magic(f: &mut File, m: &str) -> Result<bool, io::Error> +{ + 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<String, io::Error> +{ + 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<Option<MPTTrackExt>, 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<MPTTrackExt> +{ + 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<ITFile, self::Error> { let mut f = File::open(filename)?; @@ -396,8 +524,8 @@ pub fn load(filename: &str) -> Result<ITFile, self::Error> let samp_offsets: Vec<u32> = read_vec(&mut f, header.nsamp as usize)?; let patt_offsets: Vec<u32> = read_vec(&mut f, header.npatt as usize)?; - println!("{:?}", orders); - println!("{:x?}",inst_offsets); + println!("orders {:?}", orders); + println!("inst offsets {:x?}",inst_offsets); let mut message = String::new(); if header.has_message() @@ -421,7 +549,13 @@ pub fn load(filename: &str) -> Result<ITFile, self::Error> insts.push(inst); } + let mut last_samp_off = 0u64; + let mut last_samp_comp = false; let mut samps: Vec<Samp> = Vec::new(); + if header.nsamp > 0 + { + last_samp_off = (samp_offsets[header.nsamp as usize - 1] as usize + size_of::<SampHeader>()) as u64; + } for samp_offset in samp_offsets { f.seek(SeekFrom::Start(samp_offset.into()))?; @@ -430,6 +564,15 @@ pub fn load(filename: &str) -> Result<ITFile, self::Error> { 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); } @@ -438,11 +581,39 @@ pub fn load(filename: &str) -> Result<ITFile, self::Error> { 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); } - let ret = ITFile{header, orders, message, insts, samps, patterns}; + 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) } diff --git a/src/main.rs b/src/main.rs index ff3c025..0b76033 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ mod convert; mod utils; fn main() -> Result<(), itfile::Error> { - match itfile::load("/home/chrisoft/Music/mods/rr_e.it") + match itfile::load("/home/chrisoft/Music/mods/p/rr_e.it") { Ok(f) => { |