aboutsummaryrefslogtreecommitdiff
path: root/src/convert.rs
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/convert.rs
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/convert.rs')
-rw-r--r--src/convert.rs219
1 files changed, 130 insertions, 89 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}
}
}