diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/convert.rs | 219 | ||||
-rw-r--r-- | src/itfile.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 6 | ||||
-rw-r--r-- | src/midifile.rs | 27 | ||||
-rw-r--r-- | src/portmod.rs | 4 | ||||
-rw-r--r-- | src/utils.rs | 5 |
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} } |