hmi2mid 0.1.1

Convert HMI files to standard MIDIs
Documentation
//! Convert HMI files to standard MIDIs.
//!
//! Based on the HMI2MID code found in Daggerfall Jukebox,
//! (<https://www.dfworkshop.net/downloads/old-tools/daggerfall-jukebox/>),
//! which is itself based on the WinRipper HMI to MID functions by Peter
//! Palowski (<http://www.blorp.com/~peter/>)

const Q_MAX      : usize = 128;
/// Note: the original code used half of the buffer for reading in input bytes,
/// and here we are providing the input as a complete byte vector up-front, so
/// the buffer may not really need to be this large
const BUF_SIZE   : usize = 2 * 1024 * 1024;
const HMP_TRACK0 : [u8; 19] = [
  b'M', b'T', b'r', b'k', 0, 0, 0, 11, 0, 0xFF, 0x51, 0x03, 0x18, 0x7F, 0xFF,
  0, 0xFF, 0x2F, 0
];

pub struct Hmi2mid {
  bw    : u32,
  buf   : Vec <u8>,
  q_rel : Vec <QRel>
}

#[derive(Clone, Default)]
struct QRel {
  pub tm : u32,
  pub ch : u8,
  pub n  : u8
}

impl Hmi2mid {
  pub fn new() -> Self {
    Hmi2mid {
      bw:    0,
      buf:   vec![0; BUF_SIZE],
      q_rel: vec![QRel::default(); Q_MAX]
    }
  }
  pub fn hmi_rip (&mut self, source : Vec <u8>) -> Result <Vec <u8>, ()> {
    let mut out = Vec::new();
    out.resize(14, 0);
    let bbuf = &source;
    let mut ptr = 0;
    loop {
      if ptr >= bbuf.len() {
        eprintln!("ptr >= bbuf.len()");
        return Err(())
      }
      if &bbuf[ptr..ptr+4] == &b"TRAC"[..] {
        break
      }
      ptr += 1;
    }
    ptr -= 8;
    let nft : u32 = u32::from_le_bytes([
      bbuf[0xE4],
      bbuf[0xE4+1],
      bbuf[0xE4+2],
      bbuf[0xE4+3]
    ]);
    let mut ntrax : u16 = 1;
    out.extend_from_slice (&HMP_TRACK0);
    for _ in 0..nft {
      if &bbuf[ptr..ptr+13] != &b"HMI-MIDITRACK"[..] {
        eprintln!(r#"&bbuf[ptr..ptr+13] != &b"HMI-MIDITRACK"[..]"#);
        return Err(())
      }
      ntrax += 1;
      ptr += bbuf[ptr + 0x57] as usize;
      let dd = self.do_track(&bbuf[ptr..]);
      if dd == std::usize::MAX {
        eprintln!("dd == std::usize::MAX");
        return Err(())
      }
      ptr += dd;
      out.extend_from_slice(&b"MTrk"[..]);
      out.extend_from_slice(&self.bw.to_be_bytes());
      out.extend_from_slice(&self.buf[0..self.bw as usize]);
    }
    &out[0..4].copy_from_slice(&b"MThd"[..]);
    &out[4..8].copy_from_slice(&0x06000000u32.to_le_bytes());
    // midi header
    out[8] = 0x00;
    out[9] = 0x01;
    &out[10..12].copy_from_slice(&ntrax.to_be_bytes());
    out[12] = 0x00;
    out[13] = 0xC0;

    Ok (out)
  }

  fn do_track (&mut self, t : &[u8]) -> usize {
    for n in 0..Q_MAX {
      self.q_rel[n].tm = std::u32::MAX;
    }
    let mut pt   : usize = 0;
    let mut ct   : u32   = 0;
    let mut tw   : u32   = 0;
    let mut run  : u8    = 0;
    let mut rrun : u8    = 0;
    self.bw = 0;
    loop {
      ct += self.read_delta (t, &mut pt);
      self.do_queue (ct, &mut tw, &mut rrun);
      let mut c : u8 = t[pt];
      if c == 0xFF {
        self.do_queue (std::u32::MAX-1, &mut tw, &mut rrun);
        if t[pt+1] == 0x2F {
          pt += 3;
          self.buf[self.bw as usize] = 0;
          self.bw += 1;
          self.buf[self.bw as usize] = 0xFF;
          self.bw += 1;
          self.buf[self.bw as usize] = 0x2F;
          self.bw += 1;
          self.buf[self.bw as usize] = 0;
          self.bw += 1;
          break
        }
        return std::usize::MAX
      } else if c == 0xF0 {
        self.bw += Self::write_delta (
          &mut self.buf[self.bw as usize..], ct - tw);
        tw = ct;
        while t[pt] != 0xF7 {
          self.buf[self.bw as usize] = t[pt];
          self.bw += 1;
          pt += 1;
        }
      } else if c == 0xFE {
        c = t[pt+1];
        if c == 0x10 {
          pt += t[pt+4] as usize + 9;
        } else if c == 0x14 {
          pt += 4;
        } else if c == 0x15 {
          pt += 8;
        } else {
          return std::usize::MAX
        }
      } else {
        self.bw += Self::write_delta (
          &mut self.buf[self.bw as usize..], ct - tw);
        tw = ct;
        if c & 0x80 != 0 {
          pt += 1;
          run = c;
        } else {
          c = run;
        }
        if c != rrun {
          self.buf[self.bw as usize] = c;
          self.bw += 1;
          rrun = c;
        }
        self.buf[self.bw as usize] = t[pt];
        self.bw += 1;
        pt += 1;
        let c1 : u8 = c & 0xF0;
        if c1 != 0xC0 && c1 != 0xD0 {
          self.buf[self.bw as usize] = t[pt];
          self.bw += 1;
          pt += 1;
        }
        if c1 == 0x90 {
          let b  : u8  = t[pt-2];
          let tt : u32 = ct + self.read_delta (t, &mut pt);
          self.q_add (c & 0xF, b, tt);
        }
      }
    }
    pt
  }

  fn q_add (&mut self, ch : u8, nt : u8, t : u32) {
    let mut n : usize = 0;
    while self.q_rel[n].tm != std::u32::MAX {
      n += 1;
    }
    self.q_rel[n] = QRel {
      tm: t,
      n: nt,
      ch
    };
  }

  fn write_delta (t : &mut [u8], dt : u32) -> u32 {
    let mut tl  : isize = 3;
    let mut pos : usize = 0;
    if dt != 0 {
      while dt >> (7*tl) == 0 {
        tl -= 1;
      }
      loop {
        t[pos] = (((dt >> (7*tl)) & 0x7F) | 0x80) as u8;
        tl -= 1;
        pos += 1;
        if tl < 0 {
          break
        }
      }
      t[pos-1] &= 0x7F;
    } else {
      t[pos] = 0;
      pos += 1;
    }
    pos as u32
  }

  fn read_delta (&self, t : &[u8], p : &mut usize) -> u32 {
    let mut d : u32 = 0;
    let mut b;
    loop {
      b = t[*p];
      *p += 1;
      d = (d << 7) | (b & 0x7F) as u32;
      if b & 0x80 == 0 {
        break
      }
    }
    d
  }

  fn do_queue (&mut self, ct : u32, tw : &mut u32, run : &mut u8) {
    let mut mt : u32;
    let mut nn : usize = std::usize::MAX;
    loop {
      mt = std::u32::MAX;
      for n in 0..Q_MAX {
        if self.q_rel[n].tm < mt {
          nn = n;
          mt = self.q_rel[n].tm;
        }
      }
      if mt > ct {
        return
      }
      debug_assert!(nn != std::usize::MAX);
      self.bw += Self::write_delta (
        &mut self.buf[self.bw as usize..], mt - *tw);
      *tw = mt;
      let e : u8 = self.q_rel[nn].ch | 0x90;
      if e != *run {
        self.buf[self.bw as usize] = e;
        self.bw += 1;
        *run = e;
      }
      self.buf[self.bw as usize] = self.q_rel[nn].n;
      self.bw += 1;
      self.buf[self.bw as usize] = 0;
      self.bw += 1;
      self.q_rel[nn].tm = std::u32::MAX;
    }
  }
}