use std::collections::{BTreeMap, BTreeSet}; use std::cell::RefCell; use std::rc::Rc; use crate::itfile::{ITFile, Cell}; use crate::utils::Rational; use crate::midifile::{MidiEvent, TimedMidiEvent, MidiTrack, MidiFile, lint_track}; use crate::portmod::Effect; #[derive(Debug)] #[derive(Clone)] 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 } type CellHandlerFn<'a> = dyn 'a + FnMut(u8, Cell, PlayerState) -> PlayerState; type EfxHandlerFn<'a> = dyn 'a + FnMut(u8, Cell, Rc>, PlayerState, Rational) -> Vec<(TimedMidiEvent, (u8, u8))>; struct Player<'a, 'b> { h: Box>>, it: &'a ITFile } struct ChannelMemory { note: u8, postnote: u8, inst: u8, postinst: u8, vol: u8, efxmem: [u8; 32], pitch: Rational } impl Default for ChannelMemory { fn default() -> ChannelMemory { ChannelMemory { note: 0xff, postnote: 0xff, inst: 0xff, postinst: 0xff, vol: 0, efxmem: Default::default(), pitch: 0u32.into() } } } pub struct Converter<'a> { miditick: Rational, chmem: Vec>>, trks: Vec, fx_handlers: Vec>>> } impl<'a, 'b> Player<'a, 'b> { fn new(handler: Box>>, it: &'a ITFile) -> Player<'a, 'b> { Player { h: handler, it } } fn process_timingfx(&self, cell: 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 = PlayerState{skip_row: !0, ..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())(c as u8, cell, ret); ret = self.process_timingfx(cell, ret); } ret.current_tick += 1; } ret = (self.h.borrow_mut())(!0, 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 { 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 { 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<'a> Converter<'a> { pub fn new() -> Converter<'a> { 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 setup_fx_handlers<'x, 'y>(&mut self) { 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, mut inst, .. } = cell; if mask & 0x11 != 0 && ps.current_tick == 0 { if mask & 0x22 == 0 { inst = chmem.borrow().postinst; } 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}}, (ch, inst))); 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}}, (ch, chmem.borrow().postinst))); } ret.push((TimedMidiEvent{t: t.as_int_trunc() as u32, e: MidiEvent::NoteOn{ch: 0, key: note, vel: 0x40}}, (ch, inst))); chmem.borrow_mut().postnote = note; chmem.borrow_mut().postinst = inst; } } } ret }; self.fx_handlers.push(Box::new(RefCell::new(nonfx))); } fn pre_pass(it: &ITFile) -> BTreeMap<(u8, u8), usize> { let mut chinst = [0u8; 64]; let mut instchmap: BTreeSet<(u8, u8)> = BTreeSet::new(); { 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(); } let mut instch: BTreeMap<(u8, u8), usize> = BTreeMap::new(); instchmap.iter().enumerate().for_each( |(i, p)| { instch.insert(*p, i + 1); } ); instch } pub fn convert(&mut self, it: &ITFile) { 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{}", it.insts[(inst - 1) as usize].inst_name() , *inst, *ch); self.trks[*trkn].push(TimedMidiEvent{ t:0, e: MidiEvent::MetaTrackName(tn) }); } { 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, (ch, inst))| { let target_track = instch.get(&(*ch, *inst)).unwrap_or(&0); self.trks[*target_track].push(e.clone()); } ); }); ps }; let p = Player::new(Box::new(RefCell::new(h)), it); p.process_orders(); } 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})); self.trks.iter().for_each(|t| { lint_track(t); }); } pub fn result(self) -> MidiFile { MidiFile{div: 960, tracks: self.trks} } }