aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Chris Xiong <chirs241097@gmail.com> 2022-11-23 12:53:37 -0500
committerGravatar Chris Xiong <chirs241097@gmail.com> 2022-11-23 12:53:37 -0500
commit736033237b41f91bcc093c06780e47c4ad5febaa (patch)
tree8fa1cd0ae0b659a5bb5993d3d84f06ed116683a5
parent5405aea1f7dc629626b39e480ba459b5037af7f2 (diff)
downloadit2midi-736033237b41f91bcc093c06780e47c4ad5febaa.tar.xz
AAAArgh finally we read IT timing info.
-rw-r--r--src/convert.rs4
-rw-r--r--src/itfile.rs181
-rw-r--r--src/main.rs2
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) =>
{