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


                                           
                  
                           
                    
                           


                  
                 

                    



                     
            

                   
                      
                



                 
                                     










                                              
                                                                                      




                                              
                                                                                                                                                 














                                          
                   



                             
                       
                               
                                                          



                   
                                                                                        






                       









                                                                                       
                                                                                                  




































                                                                                                                               
                                                                         

                                                                       
                         

                                                      
                                
                                 
                                                                  
             






                                                                                 
             
                                                                                   
                                  
                                                                           





                                               
                                                                       
     

                                                                


                                                                                                           

                
     
                            
     




                                  


                                        
                                                                     

                          
                             
                         

          





                                                        


                                                                      
             
                                                                   
                                      

                                                    







                                       
                                                                                     
     
                                                                






                                                                
          






                                                
                                                                                  
     
                                     




                                               
                                                                                     

                                                                 

                   
                                                                                                           
         
          













                                                                                      
                                                                
                           
                              













                                                                                    
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<RefCell<dyn CellHandler>>,
    it: &'a itfile::ITFile
}

pub struct Converter<'a>
{
    instch: Option<BTreeMap<(u8, u8), usize>>,
    it: &'a itfile::ITFile
}

trait CellHandler
{
    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]
}

#[derive(Default)]
struct ChannelMemory
{
    note: u8,
    vol: u8,
    efxmem: [u8; 32],
    pitch: Rational
}

struct ConvertCellHandler<'a>
{
    miditick: Rational,
    chmem: [ChannelMemory; 64],
    fx_handlers: Vec<Box<RefCell<dyn EffectHandler + 'a>>>
}

impl<'a> Player<'a>
{
    fn new(handler: Box<RefCell<dyn CellHandler>>, 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<dyn Any>
    { Box::new(self.instchmap.take().unwrap()) }
}

impl<'a> ConvertCellHandler<'a>
{
    fn register_fx_handler(&mut self, hndlr: Box<RefCell<dyn EffectHandler + 'a>>)
    {
        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 += <Rational as From<u32>>::from(960) / (ps.speed as u16 * ps.rpb as u16).into();
        }
        ps
    }
    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)
    {
        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 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();
    }
}