aboutsummaryrefslogblamecommitdiff
path: root/src/convert.rs
blob: d3e1a4b802a5ef23ee7d6c759f0413fd204a2f52 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                                           
                       

                                  
                           
                                                                                  
                           
 

                

                  
                 

                    



                     
            

                   
                      
                

 
                                                                              
                                                                                                                                       
 
                     
 

                                       

 


                    

                 
                 
                

                     
                   

 








                                 
                       






                                       
                                         
 
                   
                       

                                           
                                                    

 
                           
 
                                                                                      






                       
                                                                          








                                                                                       
                                                                                                  




































                                                                                                                               
                                                                         

                                                                       
                                                      

                                                      
                                
                                 
                                                                  
             


                                                                        
                                                                        


                                                               
             
                                                                  
                                  
                                                                           





                                               
                                                                       
     
                                                        

                                                                


                                                                                                           

                
     
                            
     




                                  


                                        
                                                                     

                          
                             
                         

          





                                                        


                                                                      
             
                                                                   
                                      

                                                    





                              
                              
 
                                                   
     

                               
               








                                                                      
     
                                           
     
                                                                                                     
                                                           
                                     
                                                                  

                                                       



                                                   





                                                                                                                              




                                                          
                                                                                                                                                                      

                                                               



                                                          
                                                                                                                                                                                         
                         

                                                                                                                                             
                                                           
                                                           

                                                         





                                                             
     
                                                         
     

                                                                
         












                                                                        
         



                                                                    
     
                             
     
                         









                                                                                                         
                                                                                                              


                                                                                          
         












                                                                                                                        


                                                                                           






                                                               
         



                                                                                         

                                                          
     
                                   
     
                                             

     
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<RefCell<ChannelMemory>>, PlayerState, Rational) -> Vec<(TimedMidiEvent, (u8, u8))>;

struct Player<'a, 'b>
{
    h: Box<RefCell<CellHandlerFn<'b>>>,
    it: &'a ITFile
}

struct ChannelMemory
{
    note: u8,
    postnote: u8,
    inst: u8,
    postinst: u8,
    initvol: u8,
    vol: u8,
    efxmem: [u8; 32],
    pitch: Rational
}

impl Default for ChannelMemory
{
    fn default() -> ChannelMemory
    {
        ChannelMemory {
            note: 0xff,
            postnote: 0xff,
            inst: 0xff,
            postinst: 0xff,
            initvol: 0,
            vol: 0,
            efxmem: Default::default(),
            pitch: 0u32.into()
        }
    }
}

pub struct Converter<'a, 'b> where 'a: 'b
{
    it: &'a ITFile,
    miditick: Rational,
    chmem: Vec<Rc<RefCell<ChannelMemory>>>,
    trks: Vec<MidiTrack>,
    fx_handlers: Vec<Box<RefCell<EfxHandlerFn<'b>>>>
}

impl<'a, 'b> Player<'a, 'b>
{
    fn new(handler: Box<RefCell<CellHandlerFn<'b>>>, 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, 'b> Converter<'a, 'b>
{
    pub fn new(it: &'a ITFile) -> Converter<'a, 'b>
    {
        let mut ret = Converter
        {
            it,
            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<RefCell<ChannelMemory>>, ps: PlayerState, t: Rational| {
            if !ch == 0 || ps.in_rep { return Vec::new(); }
            let mut ret = Vec::new();
            let Cell { mask, note, mut inst, mut vol, .. } = cell;
            if mask & 0x11 != 0 && ps.current_tick == 0
            {
                if mask & 0x22 == 0
                {
                    inst = chmem.borrow().postinst;
                }
                if mask & 0x44 == 0 && (0u8..0x78).contains(&note)
                {
                    let samp = if self.it.inst_mode() { self.it.insts[(inst - 1) as usize].samp_for_key(note) } else { inst };
                    if samp != 0
                    { vol = self.it.samps[(samp - 1) as usize].default_vol(); }
                }
                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;
                        },
                    _ =>
                    {
                        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)));
                        }
                        if vol == 0 { vol = 1; } //TODO: investigate
                        ret.push((TimedMidiEvent{t: t.as_int_trunc() as u32, e: MidiEvent::NoteOn{ch: 0, key: note, vel: vol}}, (ch, inst)));
                        chmem.borrow_mut().postnote = note;
                        chmem.borrow_mut().postinst = inst;
                        chmem.borrow_mut().initvol = vol;
                        chmem.borrow_mut().vol = vol;
                    }
                }
            }
            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)
    {
        let it = self.it;
        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 += <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, (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}
    }
}