aboutsummaryrefslogblamecommitdiff
path: root/src/itfile.rs
blob: 76c1997145180c8129ab03aa93e2d6a4e2d1a947 (plain) (tree)



















                                   





















                                                













































































































































                                                                                       
                  

















































                                                                                                      
                                                  
     
                                                
     
                                                                      















































































































































































































                                                                                                   
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
{
    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 & 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: u8,
    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: 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 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)
}