
import json

class MidiFilePlayer:
    def __init__( self, cfg, api, midiDev, midiFn, velMapFn="velMapD.json" ):
        self.cfg      = cfg
        self.api      = api
        self.midiDev  = midiDev
        self.midiL    = []  # [ (us,status,d0,d1) ]

        self._parse_midi_file(midiFn)
        self.nextIdx = None
        self.startMs  = 0
        self.curDutyPctD = {}  # { pitch:duty } track the current hold duty cycle of each note
        self.velMapD = {}
        self.holdDutyPctD = cfg.calibrateArgs['holdDutyPctD']
        
        with open(velMapFn,'r') as f:
            velMapD = json.load(f)

            for pitch,usDbL in velMapD.items():
                self.velMapD[ int(pitch) ] = usDbL

    def start(self, ms):
        self.nextIdx = 0
        self.startMs = ms

    def stop( self, ms):
        self.nextIdx = None
        for pitch in self.velMapD.keys():
            self.api.note_off( int(pitch) )

    def tick( self, ms):

        if self.nextIdx is None:
            return
        
        curOffsMs = ms - self.startMs
        
        while self.nextIdx < len(self.midiL):
            
            if curOffsMs < self.midiL[ self.nextIdx ][0]:
                break
            
            cmd = self.midiL[ self.nextIdx ][1]
            
            if cmd == 'non':
                self._note_on(self.midiL[ self.nextIdx ][2],self.midiL[ self.nextIdx ][3])
            elif cmd == 'nof':
                self._note_off(self.midiL[ self.nextIdx ][2])
            elif cmd == 'ctl' and self.midiL[ self.nextIdx ][2] == 64:
                self.midiDev.send_controller(64,self.midiL[ self.nextIdx ][3])
                
            self.nextIdx += 1
            

        if self.nextIdx >= len(self.midiL):
            self.nextIdx = None
            
    def _get_duty_cycle( self, pitch, pulseUsec ):
        
        dutyPct = 50

        if pitch in self.holdDutyPctD:
            
            dutyPct = self.holdDutyPctD[pitch][0][1]
            for refUsec,refDuty in self.holdDutyPctD[pitch]:
                print(pitch,refUsec,refDuty)
                if pulseUsec < refUsec:
                    break
                dutyPct = refDuty

        return dutyPct
            
    def _set_duty_cycle( self, pitch, pulseUsec ):

        dutyPct = self._get_duty_cycle( pitch, pulseUsec )

        if pitch not in self.curDutyPctD or self.curDutyPctD[pitch] != dutyPct:
            self.curDutyPctD[pitch] = dutyPct
            self.api.set_pwm_duty( pitch, dutyPct )
            print("Hold Duty Set:",dutyPct)

        return dutyPct

    def _get_pulse_us( self, pitch, vel ):

        usDbL = self.velMapD[pitch]
        idx = round(vel * len(usDbL) / 127)
        
        if idx > len(usDbL):
            idx = len(usDbL)-1
            
        us = usDbL[ idx ][0]

        print('non',pitch,vel,idx,us)

        return us
    
    def _note_on( self, pitch, vel ):

        if pitch not in self.velMapD:
            print("Missing pitch:",pitch)
        else:
            pulseUs = self._get_pulse_us(pitch,vel)
            self._set_duty_cycle( pitch, pulseUs )
            self.api.note_on_us( pitch, pulseUs )


    def _note_off( self, pitch ):
        self.api.note_off( pitch )


    def _parse_midi_file( self,fn ):

        with open(fn,"r") as f:

            for lineNumb,line in enumerate(f):
                if lineNumb >= 3:
                    tokenL = line.split()

                    if len(tokenL) > 5:
                        usec   = int(tokenL[3])
                        status = None
                        d0     = None
                        d1     = None
                        if tokenL[5] == 'non' or tokenL[5]=='nof' or tokenL[5]=='ctl':
                            status = tokenL[5]
                            d0     = int(tokenL[7])
                            d1     = int(tokenL[8])
                            self.midiL.append( (usec/1000,status,d0,d1))

            self.midiL = sorted( self.midiL, key=lambda x: x[0] )
        


if __name__ == "__main__":

    midiFn = "/home/kevin/media/audio/midi/txt/988-v25.txt"

    mfp = MidiFilePlayer(None,None,midiFn)
                        
    print(mfp.midiL[0:10])
    
    

    
