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<Inst, self::Error>
{
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<Samp, self::Error>
{
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<Cell>
}
impl Patt
{
pub fn load(f: &mut File) -> Result<Patt, self::Error>
{
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<Cell> = 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<u8>,
pub message: String,
pub insts: Vec<Inst>,
pub samps: Vec<Samp>,
pub patterns: Vec<Patt>
}
#[derive(Debug)]
pub enum Error
{
IOError(io::Error),
InvalidHeader,
UnsupportedVersion,
CorruptPatternData
}
impl From<io::Error> 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<u8, io::Error>
{
let mut buf = [0u8; 1];
f.read_exact(&mut buf)?;
Ok(buf[0])
}
fn read_u16(f: &mut File) -> Result<u16, io::Error>
{
let mut buf = [0u8; 2];
f.read_exact(&mut buf)?;
Ok(u16::from_le_bytes(buf))
}
fn read_vec<T>(f: &mut File, len: usize) -> Result<Vec<T>, io::Error>
where T: Clone,
{
let mut ret: Vec<T> = 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::<T>()) };
f.read_exact(buf)?;
unsafe { ret.set_len(len) };
Ok(ret)
}
fn read_struct<T>(f: &mut File) -> Result<T, io::Error>
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::<T>()) };
f.read_exact(buf)?;
Ok(ret)
}
pub fn load(filename: &str) -> Result<ITFile, self::Error>
{
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<u8> = read_vec(&mut f, header.nord as usize)?;
let inst_offsets: Vec<u32> = read_vec(&mut f, header.ninst as usize)?;
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);
let mut message = String::new();
if header.has_message()
{
f.seek(SeekFrom::Start(header.msg_offset.into()))?;
let msg_raw: Vec<u8> = 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<Inst> = 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<Samp> = 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<Patt> = 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)
}