123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- ##| Copyright: (C) 2019-2020 Kevin Larke <contact AT larke DOT org>
- ##| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
-
- 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])
-
-
-
-
|