aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Chris Xiong <chirs241097@gmail.com> 2022-11-25 23:34:51 -0500
committerGravatar Chris Xiong <chirs241097@gmail.com> 2022-11-25 23:34:51 -0500
commitf8cf52fa4b2bbaff5627fa397a26589070084d3e (patch)
tree6ffc67fbba825eed811409732ce8e6cbda0dc38f /src
parent736033237b41f91bcc093c06780e47c4ad5febaa (diff)
downloadit2midi-f8cf52fa4b2bbaff5627fa397a26589070084d3e.tar.xz
Actually produce some valid midi output.
Major restructure in the IT player: No more custom trait objects, let's just use closures instead... Basic note handling. Fixes to the midi file modules so the output is now valid. Other fixes to appease the Rust Gods. Current converter handles no IT effects (except timing effects like Axx, Bxx, Cxx, SBx and SEx, as those are handled as a part of the player). But this is a start.
Diffstat (limited to 'src')
-rw-r--r--src/convert.rs219
-rw-r--r--src/itfile.rs2
-rw-r--r--src/main.rs6
-rw-r--r--src/midifile.rs27
-rw-r--r--src/portmod.rs4
-rw-r--r--src/utils.rs5
6 files changed, 157 insertions, 106 deletions
diff --git a/src/convert.rs b/src/convert.rs
index 884708d..a683772 100644
--- a/src/convert.rs
+++ b/src/convert.rs
@@ -1,11 +1,13 @@
use std::collections::{BTreeMap, BTreeSet};
-use std::any::Any;
use std::cell::RefCell;
-use crate::itfile;
+use std::rc::Rc;
+use crate::itfile::{ITFile, Cell};
use crate::utils::Rational;
-use crate::midifile;
+use crate::midifile::{MidiEvent, TimedMidiEvent, MidiTrack, MidiFile};
use crate::portmod::Effect;
+#[derive(Debug)]
+#[derive(Clone)]
struct PlayerState
{
skip_row: u8,
@@ -22,55 +24,37 @@ struct PlayerState
in_rep: bool
}
-struct Player<'a>
-{
- h: Box<RefCell<dyn CellHandler>>,
- it: &'a itfile::ITFile
-}
-
-pub struct Converter<'a>
-{
- instch: Option<BTreeMap<(u8, u8), usize>>,
- it: &'a itfile::ITFile
-}
+type CellHandlerFn<'a> = dyn 'a + FnMut(u8, Cell, PlayerState) -> PlayerState;
+type EfxHandlerFn<'a> = dyn 'a + FnMut(u8, Cell, Rc<RefCell<ChannelMemory>>, PlayerState, Rational) -> Vec<(TimedMidiEvent, bool)>;
-trait CellHandler
+struct Player<'a, 'b>
{
- fn process(&mut self, ch: u8, cell: itfile::Cell, ps: PlayerState) -> PlayerState;
- fn result_data(&mut self) -> Box<dyn Any>;
-}
-
-trait EffectHandler
-{
- fn process(&mut self, ch: u8, cell: itfile::Cell, chmem: &mut ChannelMemory, ps: PlayerState, mt: Rational) -> Vec<midifile::TimedMidiEvent>;
- fn handled_effects(&self) -> Vec<u8>;
-}
-
-struct PrePassCellHandler
-{
- instchmap: Option<BTreeSet<(u8, u8)>>,
- chinst: [u8; 64]
+ h: Box<RefCell<CellHandlerFn<'b>>>,
+ it: &'a ITFile
}
#[derive(Default)]
struct ChannelMemory
{
note: u8,
+ postnote: u8,
+ inst: u8,
vol: u8,
efxmem: [u8; 32],
pitch: Rational
}
-struct ConvertCellHandler<'a>
+pub struct Converter<'a>
{
miditick: Rational,
- chmem: [ChannelMemory; 64],
- fx_handlers: Vec<Box<RefCell<dyn EffectHandler + 'a>>>
+ chmem: Vec<Rc<RefCell<ChannelMemory>>>,
+ trks: Vec<MidiTrack>,
+ fx_handlers: Vec<Box<RefCell<EfxHandlerFn<'a>>>>
}
-impl<'a> Player<'a>
+impl<'a, 'b> Player<'a, 'b>
{
- fn new(handler: Box<RefCell<dyn CellHandler>>, it: &'a itfile::ITFile) -> Player<'a>
+ fn new(handler: Box<RefCell<CellHandlerFn<'b>>>, it: &'a ITFile) -> Player<'a, 'b>
{
Player
{
@@ -78,7 +62,7 @@ impl<'a> Player<'a>
it
}
}
- fn process_timingfx(&self, cell: itfile::Cell, ps: PlayerState) -> PlayerState
+ fn process_timingfx(&self, cell: Cell, ps: PlayerState) -> PlayerState
{
let e = Effect::from_it_efx((cell.efx, cell.fxp));
if ps.current_tick == 0
@@ -129,7 +113,7 @@ impl<'a> Player<'a>
fn process_pattern(&self, pat: usize, st: PlayerState) -> PlayerState
{
let skip_row = if !st.skip_row == 0 { 0 } else { st.skip_row };
- let mut ret = st;
+ let mut ret = PlayerState{skip_row: !0, ..st};
for r in skip_row..self.it.patterns[pat].nrows
{
ret.current_row = r;
@@ -139,12 +123,12 @@ impl<'a> Player<'a>
for c in 0..64
{
let cell = *self.it.patterns[pat].cell_at(r, c);
- ret = (&self.h).borrow_mut().process(c as u8, cell, ret);
+ ret = (self.h.borrow_mut())(c as u8, cell, ret);
ret = self.process_timingfx(cell, ret);
}
ret.current_tick += 1;
}
- ret = (&self.h).borrow_mut().process(!0, itfile::Cell::default(), ret);
+ ret = (self.h.borrow_mut())(!0, Cell::default(), ret);
ret.row_extension = 0;
if (!ret.skip_row) != 0 || (!ret.skip_ord) != 0 { return ret; }
}
@@ -155,6 +139,7 @@ impl<'a> Player<'a>
/// passing !0 to row or ord if it's unused
fn skip_to(&self, row: u8, ord: u8, ps: PlayerState) -> PlayerState
{
+ println!("skip to row {} of ord #{}", row, ord);
PlayerState {
skip_row: if !row != 0 { row } else { ps.skip_row },
skip_ord: if !ord != 0 { ord } else {
@@ -200,70 +185,126 @@ impl<'a> Player<'a>
}
}
-impl CellHandler for PrePassCellHandler
+impl<'a> Converter<'a>
{
- fn process(&mut self, ch: u8, cell: itfile::Cell, ps: PlayerState) -> PlayerState
+ pub fn new() -> Converter<'a>
{
- if (ch == 0xff) || (ps.current_tick != 0) { return ps; }
- let itfile::Cell{mask, mut inst, ..} = cell;
- if mask & 0x22 != 0
- { self.chinst[ch as usize] = inst; }
- else
- { inst = self.chinst[ch as usize]; }
- if mask & 0x11 != 0
- { self.instchmap.as_mut().unwrap().insert((ch, inst)); }
- ps
+ let mut ret = Converter
+ {
+ miditick: 0u32.into(),
+ chmem: Vec::new(),
+ trks: Vec::new(),
+ fx_handlers: Vec::new()
+ };
+ for _ in 0..256
+ { ret.chmem.push(Rc::new(RefCell::new(Default::default()))); }
+ ret.setup_fx_handlers();
+ ret
}
- fn result_data(&mut self) -> Box<dyn Any>
- { Box::new(self.instchmap.take().unwrap()) }
-}
-
-impl<'a> ConvertCellHandler<'a>
-{
- fn register_fx_handler(&mut self, hndlr: Box<RefCell<dyn EffectHandler + 'a>>)
+ fn setup_fx_handlers<'x, 'y>(&mut self)
{
- self.fx_handlers.push(hndlr);
+ let nonfx = |ch: u8, cell, chmem: Rc<RefCell<ChannelMemory>>, ps: PlayerState, t: Rational| {
+ if !ch == 0 { return Vec::new(); }
+ let mut ret = Vec::new();
+ let Cell { mask, note, .. } = cell;
+ if mask & 0x11 != 0 && ps.current_tick == 0
+ {
+ match note
+ {
+ 0x78..=0xff =>
+ if chmem.borrow().postnote != 0xff
+ {
+ ret.push((TimedMidiEvent{t: t.as_int_trunc() as u32, e: MidiEvent::NoteOff{ch: 0, key: chmem.borrow().postnote, vel: 0x40}}, false));
+ chmem.borrow_mut().postnote = 0xff;
+ },
+ 0 => (),
+ _ =>
+ {
+ if chmem.borrow().postnote != 0xff
+ {
+ ret.push((TimedMidiEvent{t: t.as_int_trunc() as u32, e: MidiEvent::NoteOff{ch: 0, key: chmem.borrow().postnote, vel: 0x40}}, false));
+ }
+ ret.push((TimedMidiEvent{t: t.as_int_trunc() as u32, e: MidiEvent::NoteOn{ch: 0, key: note, vel: 0x40}}, false));
+ chmem.borrow_mut().postnote = note;
+ }
+ }
+ }
+ ret
+ };
+ self.fx_handlers.push(Box::new(RefCell::new(nonfx)));
}
-}
-
-impl<'a> CellHandler for ConvertCellHandler<'a>
-{
- fn process(&mut self, ch: u8, cell: itfile::Cell, ps: PlayerState) -> PlayerState
+ fn pre_pass(it: &ITFile) -> BTreeMap<(u8, u8), usize>
{
- let itfile::Cell{mask, note, inst, vol, efx, fxp} = cell;
- if !ch == 0
+ let mut chinst = [0u8; 64];
+ let mut instchmap: BTreeSet<(u8, u8)> = BTreeSet::new();
{
- self.miditick += <Rational as From<u32>>::from(960) / (ps.speed as u16 * ps.rpb as u16).into();
+ let h = |ch, cell, ps: PlayerState| {
+ if (ch == 0xff) || (ps.current_tick != 0) { return ps; }
+ let Cell{mask, mut inst, ..} = cell;
+ if mask & 0x22 != 0
+ { chinst[ch as usize] = inst; }
+ else
+ { inst = chinst[ch as usize]; }
+ if mask & 0x11 != 0
+ { instchmap.insert((ch, inst)); }
+ ps
+ };
+ let p = Player::new(Box::new(RefCell::new(h)), it);
+ p.process_orders();
}
- ps
+ let mut instch: BTreeMap<(u8, u8), usize> = BTreeMap::new();
+ instchmap.iter().enumerate().for_each(
+ |(i, p)| { instch.insert(*p, i + 1); } );
+ instch
}
- fn result_data(&mut self) -> Box<dyn Any>
- { Box::new(()) }
-}
-
-impl<'a> Converter<'a>
-{
- pub fn new(it: &itfile::ITFile) -> Converter
- {
- Converter{instch: None, it}
- }
- fn pre_pass(&'a mut self)
+ pub fn convert(&mut self, it: &ITFile)
{
- let h = PrePassCellHandler{instchmap: Some(BTreeSet::new()), chinst: [0; 64]};
- let p = Player::new(Box::new(RefCell::new(h)), self.it);
- p.process_orders();
- let Player{h, ..} = p;
- if let Ok(m) = h.borrow_mut().result_data().downcast::<BTreeSet<(u8, u8)>>()
+ let instch = Converter::pre_pass(it);
+ println!("{:?}", instch);
+ self.trks.resize_with(instch.len() + 1, Default::default);
+ self.trks[0].push(TimedMidiEvent{ t: 0, e: MidiEvent::MetaTimeSig { n: 1, d_pot: 2 }});
+ let initspd = it.header.speed as f64;
+ let inittpo = it.header.tempo as f64;
+ let rpb = it.time_signature().unwrap_or((4, 4)).0 as f64;
+ self.trks[0].push(TimedMidiEvent{ t: 0, e: MidiEvent::MetaTempo(inittpo * 24. / rpb / initspd)});
+ for ((ch, inst), trkn) in instch.iter()
+ {
+ let tn = format!("instr #{} @ ch{}", *inst, *ch);
+ self.trks[*trkn].push(TimedMidiEvent{ t:0, e: MidiEvent::MetaTrackName(tn) });
+ }
+
{
- let mut instch = BTreeMap::new();
- m.iter().enumerate().for_each(
- |(i, p)| { instch.insert(*p, i + 1); } );
- self.instch = Some(instch);
+ let h = |ch:u8, cell, ps: PlayerState|
+ {
+ let Cell{mask, note, inst, vol: _, efx: _, fxp: _} = cell;
+ if !ch == 0
+ {
+ self.miditick += <Rational as From<u32>>::from(960) / (ps.rpb as u16).into();
+ }
+ if mask & 0x11 != 0 { self.chmem[ch as usize].borrow_mut().note = note; }
+ if mask & 0x22 != 0 { self.chmem[ch as usize].borrow_mut().inst = inst; }
+ self.fx_handlers.iter().for_each(
+ |h| {
+ let ev = (h.borrow_mut())(ch, cell, self.chmem[ch as usize].clone(), ps.clone(), self.miditick);
+ ev.iter().for_each(
+ |(e, is_common)| {
+ let target_track = instch.get(&(ch, self.chmem[ch as usize].borrow().inst)).unwrap();
+ (if *is_common { &mut self.trks[0] } else { &mut self.trks[*target_track] }).push(e.clone());
+ }
+ );
+ });
+ ps
+ };
+ let p = Player::new(Box::new(RefCell::new(h)), it);
+ p.process_orders();
}
- println!("{:?}", self.instch);
+
+ self.trks.iter_mut().for_each(|t| t.push(TimedMidiEvent{
+ t: t.last().unwrap_or(&TimedMidiEvent{t: 0, e: MidiEvent::MetaEndOfTrack}).t,
+ e: MidiEvent::MetaEndOfTrack}));
}
- pub fn convert(&'a mut self)
+ pub fn result(self) -> MidiFile
{
- self.pre_pass();
+ MidiFile{div: 960, tracks: self.trks}
}
}
diff --git a/src/itfile.rs b/src/itfile.rs
index 12788b3..57d235d 100644
--- a/src/itfile.rs
+++ b/src/itfile.rs
@@ -53,7 +53,7 @@ impl Header
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)) }
+ { Some(((self.pat_highlight & 0xff) as u32, (self.pat_highlight >> 8) as u32)) }
else { None }
}
}
diff --git a/src/main.rs b/src/main.rs
index 0b76033..728c3e2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,8 +9,10 @@ fn main() -> Result<(), itfile::Error> {
{
Ok(f) =>
{
- let mut conv = convert::Converter::new(&f);
- conv.convert();
+ let mut conv = convert::Converter::new();
+ conv.convert(&f);
+ let mf = conv.result();
+ midifile::write_file("output.mid", &mf)?;
Ok(())
}
Err(e) => Err(e)
diff --git a/src/midifile.rs b/src/midifile.rs
index 71a861c..219a931 100644
--- a/src/midifile.rs
+++ b/src/midifile.rs
@@ -10,6 +10,7 @@ pub struct RawMidiEvent
data: Vec<u8>
}
+#[derive(Clone)]
pub enum MidiEvent
{
NoteOn{ch: u8, key: u8, vel: u8},
@@ -26,6 +27,7 @@ pub enum MidiEvent
MetaEndOfTrack
}
+#[derive(Clone)]
pub struct TimedMidiEvent
{
pub t: u32,
@@ -39,7 +41,7 @@ impl From<&MidiEvent> for RawMidiEvent
match e
{
MidiEvent::NoteOff{ch, key, vel}
- if *vel == 0x40 => RawMidiEvent{ty: 0x90 | ch, p1: *key, p2: 0, data:vec![]},
+ if *vel == 0x40 => RawMidiEvent{ty: 0x90 | ch, p1: *key, p2: 0, data: vec![]},
MidiEvent::NoteOff{ch, key, vel} => RawMidiEvent{ty: 0x80 | ch, p1: *key, p2: *vel, data: vec![]},
MidiEvent::NoteOn {ch, key, vel} => RawMidiEvent{ty: 0x90 | ch, p1: *key, p2: *vel, data: vec![]},
MidiEvent::KeyAfterTouch{ch, key, val} => RawMidiEvent{ty: 0xa0 | ch, p1: *key, p2: *val, data: vec![]},
@@ -64,12 +66,12 @@ impl From<&MidiEvent> for RawMidiEvent
RawMidiEvent{ty: 0xff, p1: 0x03, p2: s.len() as u8, data: Vec::from(sb)}
}
MidiEvent::MetaEndOfTrack =>
- RawMidiEvent{ty: 0xff, p1: 0x2f, p2: 0xff, data: vec![]}
+ RawMidiEvent{ty: 0xff, p1: 0x2f, p2: 0, data: vec![]}
}
}
}
-type MidiTrack = Vec<TimedMidiEvent>;
+pub type MidiTrack = Vec<TimedMidiEvent>;
pub struct MidiFile
{
@@ -77,19 +79,19 @@ pub struct MidiFile
pub tracks: Vec<MidiTrack>
}
-fn write_u16be(f: &mut File, v: u16) -> io::Result<()>
+fn write_u16be<W>(f: &mut W, v: u16) -> io::Result<()> where W: Write
{
let bytes = v.to_be_bytes();
f.write_all(&bytes)
}
-fn write_u32be(f: &mut File, v: u32) -> io::Result<()>
+fn write_u32be<W>(f: &mut W, v: u32) -> io::Result<()> where W: Write
{
let bytes = v.to_be_bytes();
f.write_all(&bytes)
}
-fn write_varlen(f: &mut File, v: u32) -> io::Result<()>
+fn write_varlen<W>(f: &mut W, v: u32) -> io::Result<()> where W: Write
{
if v > 0x0fffffff
{ return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid variable length value")); }
@@ -103,7 +105,7 @@ fn write_varlen(f: &mut File, v: u32) -> io::Result<()>
f.write_all(&buf[..])
}
-fn write_raw_event(f: &mut File, re: &RawMidiEvent) -> io::Result<()>
+fn write_raw_event<W>(f: &mut W, re: &RawMidiEvent) -> io::Result<()> where W: Write
{
let mut buf: Vec<u8> = Vec::new();
buf.push(re.ty);
@@ -113,23 +115,26 @@ fn write_raw_event(f: &mut File, re: &RawMidiEvent) -> io::Result<()>
f.write_all(&buf[..])
}
-fn write_track(f: &mut File, trk: &MidiTrack) -> io::Result<()>
+fn write_track<W>(f: &mut W, trk: &MidiTrack) -> io::Result<()> where W: Write
{
let header = "MTrk".as_bytes();
f.write_all(header)?;
+ let mut buf: Vec<u8> = Vec::new();
let mut curt = 0u32;
for te in trk
{
let TimedMidiEvent{t, e} = te;
- write_varlen(f, t - curt)?;
+ write_varlen(&mut buf, t - curt)?;
curt = *t;
let re = RawMidiEvent::from(e);
- write_raw_event(f, &re)?;
+ write_raw_event(&mut buf, &re)?;
}
+ write_u32be(f, buf.len() as u32)?;
+ f.write_all(&buf[..])?;
Ok(())
}
-fn write_file(filename: &str, mf: &MidiFile) -> io::Result<()>
+pub fn write_file(filename: &str, mf: &MidiFile) -> io::Result<()>
{
let mut f = File::create(filename)?;
let header = "MThd".as_bytes();
diff --git a/src/portmod.rs b/src/portmod.rs
index 9100570..a271ead 100644
--- a/src/portmod.rs
+++ b/src/portmod.rs
@@ -1,3 +1,4 @@
+#[derive(Debug)]
pub enum Slide
{
Up(u8),
@@ -6,6 +7,7 @@ pub enum Slide
FineDown(u8)
}
+#[derive(Debug)]
pub enum Effect
{
SetSpeed(u8),
@@ -68,7 +70,7 @@ impl Effect
pub fn from_it_efx(f: (u8, u8)) -> Effect
{
let (efx, fxp) = f;
- match efx as char
+ match (efx + 'A' as u8 - 1) as char
{
'A' => Effect::SetSpeed(fxp),
'B' => Effect::PosJump(fxp),
diff --git a/src/utils.rs b/src/utils.rs
index 0dc91f7..cbe9716 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1,5 +1,6 @@
use std::ops::*;
+#[derive(Copy, Clone)]
pub struct Rational
{
n: i64,
@@ -77,6 +78,6 @@ impl Rational
let c = gcd(self.n, self.d);
Rational{n: self.n / c, d: self.d / c}
}
- fn as_int_trunc(self) -> i64 {self.n / self.d}
- fn as_int_round(self) -> i64 {(self.n as f64 / self.d as f64).round() as i64}
+ pub fn as_int_trunc(self) -> i64 {self.n / self.d}
+ pub fn as_int_round(self) -> i64 {(self.n as f64 / self.d as f64).round() as i64}
}