From f8cf52fa4b2bbaff5627fa397a26589070084d3e Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Fri, 25 Nov 2022 23:34:51 -0500 Subject: 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. --- src/convert.rs | 219 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 130 insertions(+), 89 deletions(-) (limited to 'src/convert.rs') 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>, - it: &'a itfile::ITFile -} - -pub struct Converter<'a> -{ - instch: Option>, - it: &'a itfile::ITFile -} +type CellHandlerFn<'a> = dyn 'a + FnMut(u8, Cell, PlayerState) -> PlayerState; +type EfxHandlerFn<'a> = dyn 'a + FnMut(u8, Cell, Rc>, 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; -} - -trait EffectHandler -{ - fn process(&mut self, ch: u8, cell: itfile::Cell, chmem: &mut ChannelMemory, ps: PlayerState, mt: Rational) -> Vec; - fn handled_effects(&self) -> Vec; -} - -struct PrePassCellHandler -{ - instchmap: Option>, - chinst: [u8; 64] + h: Box>>, + 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>> + chmem: Vec>>, + trks: Vec, + fx_handlers: Vec>>> } -impl<'a> Player<'a> +impl<'a, 'b> Player<'a, 'b> { - fn new(handler: Box>, it: &'a itfile::ITFile) -> Player<'a> + fn new(handler: Box>>, 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 - { Box::new(self.instchmap.take().unwrap()) } -} - -impl<'a> ConvertCellHandler<'a> -{ - fn register_fx_handler(&mut self, hndlr: Box>) + fn setup_fx_handlers<'x, 'y>(&mut self) { - self.fx_handlers.push(hndlr); + let nonfx = |ch: u8, cell, chmem: Rc>, 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 += >::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 - { 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::>() + 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 += >::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} } } -- cgit v1.2.3