use std::collections::{BTreeMap, BTreeSet}; use std::any::Any; use std::cell::RefCell; use crate::itfile; use crate::utils::Rational; use crate::midifile; use crate::portmod::Effect; struct PlayerState { skip_row: u8, skip_ord: u8, current_ord: u8, current_row: u8, current_tick: u8, speed: u8, tempo: u8, rpb: u8, loop_start: u8, loop_ctr: u8, row_extension: u8, in_rep: bool } struct Player<'a> { h: Box>, it: &'a itfile::ITFile } pub struct Converter<'a> { instch: Option>, it: &'a itfile::ITFile } trait CellHandler { 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] } #[derive(Default)] struct ChannelMemory { note: u8, vol: u8, efxmem: [u8; 32], pitch: Rational } struct ConvertCellHandler<'a> { miditick: Rational, chmem: [ChannelMemory; 64], fx_handlers: Vec>> } impl<'a> Player<'a> { fn new(handler: Box>, it: &'a itfile::ITFile) -> Player<'a> { Player { h: handler, it } } fn process_timingfx(&self, cell: itfile::Cell, ps: PlayerState) -> PlayerState { let e = Effect::from_it_efx((cell.efx, cell.fxp)); if ps.current_tick == 0 { match e { Effect::SetSpeed(s) => PlayerState{speed: s, ..ps}, Effect::PattLoopStart => PlayerState{loop_start: ps.current_row, ..ps}, Effect::SetTempo(t) => PlayerState{tempo: t, ..ps}, Effect::RowExtention(t) => PlayerState{row_extension: ps.row_extension + t, ..ps}, _ => ps } } else if ps.current_tick == ps.speed - 1 { match e { Effect::PosJump(p) => self.skip_to(!0, p, ps), Effect::PattBreak(r) => self.skip_to(r, !0, ps), Effect::PattLoop(c) => match ps.loop_ctr { u8::MAX => PlayerState{loop_ctr: 1, .. self.skip_to(ps.loop_start, ps.current_ord, ps)}, _ if ps.loop_ctr >= c => PlayerState{loop_ctr: !0, ..ps}, _ => PlayerState{loop_ctr: ps.loop_start + 1, .. self.skip_to(ps.loop_start, ps.current_ord, ps)} }, Effect::PattDelay(c) => match ps.loop_ctr { u8::MAX => PlayerState{loop_ctr: 1, in_rep: true, .. self.skip_to(ps.current_row, ps.current_ord, ps)}, _ if ps.loop_ctr >= c => PlayerState{loop_ctr: !0, in_rep: false, ..ps}, _ => PlayerState{loop_ctr: ps.loop_start + 1, .. self.skip_to(ps.current_row, ps.current_ord, ps)} }, Effect::TempoSlideDown(v) => PlayerState{tempo: ps.tempo - v, ..ps}, Effect::TempoSlideUp(v) => PlayerState{tempo: ps.tempo + v, ..ps}, _ => ps } } else { match e { Effect::TempoSlideDown(v) => PlayerState{tempo: ps.tempo - v, ..ps}, Effect::TempoSlideUp(v) => PlayerState{tempo: ps.tempo + v, ..ps}, _ => ps } } } 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; for r in skip_row..self.it.patterns[pat].nrows { ret.current_row = r; ret.current_tick = 0; while ret.current_tick < ret.speed + ret.row_extension { 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.process_timingfx(cell, ret); } ret.current_tick += 1; } ret = (&self.h).borrow_mut().process(!0, itfile::Cell::default(), ret); ret.row_extension = 0; if (!ret.skip_row) != 0 || (!ret.skip_ord) != 0 { return ret; } } ret } /// Used for effects Bxx, Cxx and SBx /// /// passing !0 to row or ord if it's unused fn skip_to(&self, row: u8, ord: u8, ps: PlayerState) -> PlayerState { PlayerState { skip_row: if !row != 0 { row } else { ps.skip_row }, skip_ord: if !ord != 0 { ord } else { if (!row != 0) && (!ps.skip_ord == 0) { ps.current_ord + 1 } else { ps.skip_ord } }, ..ps } } fn process_orders(&self) { let mut ps = PlayerState { skip_row: !0, skip_ord: !0, current_ord: 0, current_row: 0, current_tick: 0, speed: self.it.header.speed, tempo: self.it.header.tempo, rpb: self.it.time_signature().unwrap_or((4, 16)).0 as u8, loop_start: 0, loop_ctr: !0, row_extension: 0, in_rep: false }; let mut oid = 0; loop { if oid >= self.it.orders.len() { break; } if self.it.orders[oid] == 0xff { break; } if self.it.orders[oid] == 0xfe { continue; } ps.current_ord = oid as u8; ps = self.process_pattern(self.it.orders[oid].into(), ps); if !ps.skip_ord != 0 { if ps.skip_ord as usize <= oid && !ps.loop_ctr == 0 { println!("loop?"); } else { oid = ps.skip_ord as usize; } ps.skip_ord = !0; } else { oid += 1; } } } } impl CellHandler for PrePassCellHandler { fn process(&mut self, ch: u8, cell: itfile::Cell, ps: PlayerState) -> PlayerState { 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 } fn result_data(&mut self) -> Box { Box::new(self.instchmap.take().unwrap()) } } impl<'a> ConvertCellHandler<'a> { fn register_fx_handler(&mut self, hndlr: Box>) { self.fx_handlers.push(hndlr); } } impl<'a> CellHandler for ConvertCellHandler<'a> { fn process(&mut self, ch: u8, cell: itfile::Cell, ps: PlayerState) -> PlayerState { let itfile::Cell{mask, note, inst, vol, efx, fxp} = cell; if !ch == 0 { self.miditick += >::from(960) / (ps.speed as u16 * ps.rpb as u16).into(); } ps } 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) { 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 mut instch = BTreeMap::new(); m.iter().enumerate().for_each( |(i, p)| { instch.insert(*p, i + 1); } ); self.instch = Some(instch); } println!("{:?}", self.instch); } pub fn convert(&'a mut self) { self.pre_pass(); } }