picadae calibration programs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MidiFilePlayer.py 4.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. ##| Copyright: (C) 2019-2020 Kevin Larke <contact AT larke DOT org>
  2. ##| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
  3. import json
  4. class MidiFilePlayer:
  5. def __init__( self, cfg, api, midiDev, midiFn, velMapFn="velMapD.json" ):
  6. self.cfg = cfg
  7. self.api = api
  8. self.midiDev = midiDev
  9. self.midiL = [] # [ (us,status,d0,d1) ]
  10. self._parse_midi_file(midiFn)
  11. self.nextIdx = None
  12. self.startMs = 0
  13. self.curDutyPctD = {} # { pitch:duty } track the current hold duty cycle of each note
  14. self.velMapD = {}
  15. self.holdDutyPctD = cfg.calibrateArgs['holdDutyPctD']
  16. with open(velMapFn,'r') as f:
  17. velMapD = json.load(f)
  18. for pitch,usDbL in velMapD.items():
  19. self.velMapD[ int(pitch) ] = usDbL
  20. def start(self, ms):
  21. self.nextIdx = 0
  22. self.startMs = ms
  23. def stop( self, ms):
  24. self.nextIdx = None
  25. for pitch in self.velMapD.keys():
  26. self.api.note_off( int(pitch) )
  27. def tick( self, ms):
  28. if self.nextIdx is None:
  29. return
  30. curOffsMs = ms - self.startMs
  31. while self.nextIdx < len(self.midiL):
  32. if curOffsMs < self.midiL[ self.nextIdx ][0]:
  33. break
  34. cmd = self.midiL[ self.nextIdx ][1]
  35. if cmd == 'non':
  36. self._note_on(self.midiL[ self.nextIdx ][2],self.midiL[ self.nextIdx ][3])
  37. elif cmd == 'nof':
  38. self._note_off(self.midiL[ self.nextIdx ][2])
  39. elif cmd == 'ctl' and self.midiL[ self.nextIdx ][2] == 64:
  40. self.midiDev.send_controller(64,self.midiL[ self.nextIdx ][3])
  41. self.nextIdx += 1
  42. if self.nextIdx >= len(self.midiL):
  43. self.nextIdx = None
  44. def _get_duty_cycle( self, pitch, pulseUsec ):
  45. dutyPct = 50
  46. if pitch in self.holdDutyPctD:
  47. dutyPct = self.holdDutyPctD[pitch][0][1]
  48. for refUsec,refDuty in self.holdDutyPctD[pitch]:
  49. print(pitch,refUsec,refDuty)
  50. if pulseUsec < refUsec:
  51. break
  52. dutyPct = refDuty
  53. return dutyPct
  54. def _set_duty_cycle( self, pitch, pulseUsec ):
  55. dutyPct = self._get_duty_cycle( pitch, pulseUsec )
  56. if pitch not in self.curDutyPctD or self.curDutyPctD[pitch] != dutyPct:
  57. self.curDutyPctD[pitch] = dutyPct
  58. self.api.set_pwm_duty( pitch, dutyPct )
  59. print("Hold Duty Set:",dutyPct)
  60. return dutyPct
  61. def _get_pulse_us( self, pitch, vel ):
  62. usDbL = self.velMapD[pitch]
  63. idx = round(vel * len(usDbL) / 127)
  64. if idx > len(usDbL):
  65. idx = len(usDbL)-1
  66. us = usDbL[ idx ][0]
  67. print('non',pitch,vel,idx,us)
  68. return us
  69. def _note_on( self, pitch, vel ):
  70. if pitch not in self.velMapD:
  71. print("Missing pitch:",pitch)
  72. else:
  73. pulseUs = self._get_pulse_us(pitch,vel)
  74. self._set_duty_cycle( pitch, pulseUs )
  75. self.api.note_on_us( pitch, pulseUs )
  76. def _note_off( self, pitch ):
  77. self.api.note_off( pitch )
  78. def _parse_midi_file( self,fn ):
  79. with open(fn,"r") as f:
  80. for lineNumb,line in enumerate(f):
  81. if lineNumb >= 3:
  82. tokenL = line.split()
  83. if len(tokenL) > 5:
  84. usec = int(tokenL[3])
  85. status = None
  86. d0 = None
  87. d1 = None
  88. if tokenL[5] == 'non' or tokenL[5]=='nof' or tokenL[5]=='ctl':
  89. status = tokenL[5]
  90. d0 = int(tokenL[7])
  91. d1 = int(tokenL[8])
  92. self.midiL.append( (usec/1000,status,d0,d1))
  93. self.midiL = sorted( self.midiL, key=lambda x: x[0] )
  94. if __name__ == "__main__":
  95. midiFn = "/home/kevin/media/audio/midi/txt/988-v25.txt"
  96. mfp = MidiFilePlayer(None,None,midiFn)
  97. print(mfp.midiL[0:10])