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.

PolyNoteTester.py 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import types
  2. import time
  3. from random import randrange
  4. class PolyNoteTester:
  5. def __init__( self, cfg, api ):
  6. self.api = api
  7. r = types.SimpleNamespace(**cfg.PolyNoteTester)
  8. self.cfg = r
  9. if r.mode == "simple":
  10. print("mode:simple")
  11. self.schedL = self._gen_simple_sched(r)
  12. else:
  13. print("mode:poly")
  14. self.schedL = self._gen_sched(r)
  15. self.schedL = sorted( self.schedL, key=lambda x: x[0] )
  16. self.nextMs = 0 # next transition time
  17. self.schedIdx = 0 # next event to play
  18. self.isStartedFl = False
  19. #self._report()
  20. def _report( self ):
  21. for t,cmd,pitch,atkUs in self.schedL:
  22. print("%s %6i %3i %5i" % (cmd,t,pitch,atkUs))
  23. def _gen_simple_sched( self, r ):
  24. """ Play each note sequentially from lowest to highest. """
  25. durMs = int(r.minNoteDurMs + (r.maxNoteDurMs - r.minNoteDurMs)/2)
  26. ioMs = int(r.minInterOnsetMs + (r.maxInterOnsetMs - r.minInterOnsetMs)/2)
  27. atkUs = int(r.minAttackUsec + (r.maxAttackUsec - r.minAttackUsec)/2)
  28. schedL = []
  29. t0 = 0
  30. for pitch in range(r.minPitch,r.maxPitch+1):
  31. schedL.append((t0,'on',pitch,atkUs))
  32. schedL.append((t0+durMs,'off',pitch,0))
  33. t0 += durMs + ioMs
  34. return schedL
  35. def _does_note_overlap( self, beg0Ms, end0Ms, beg1Ms, end1Ms ):
  36. """ if note 0 is entirely before or after note 1 """
  37. return not (beg0Ms > end1Ms or end0Ms < beg1Ms)
  38. def _do_any_notes_overlap( self, begMs, endMs, begEndL ):
  39. for beg1,end1 in begEndL:
  40. if self._does_note_overlap(begMs,endMs,beg1,end1):
  41. return True
  42. return False
  43. def _get_last_end_time( self, begEndL ):
  44. end0 = 0
  45. for beg,end in begEndL:
  46. if end > end0:
  47. end0 = end
  48. return end0
  49. def _gen_sched( self, r ):
  50. pitchL = [ randrange(r.minPitch,r.maxPitch) for _ in range(r.noteCount) ]
  51. durMsL = [ randrange(r.minNoteDurMs, r.maxNoteDurMs) for _ in range(r.noteCount) ]
  52. ioMsL = [ randrange(r.minInterOnsetMs, r.maxInterOnsetMs) for _ in range(r.noteCount) ]
  53. atkUsL = [ randrange(r.minAttackUsec, r.maxAttackUsec) for _ in range(r.noteCount) ]
  54. schedL = []
  55. pitchD = {} # pitch: [ (begMs,endMs) ]
  56. t0 = 0
  57. # for each pitch,dur,ioi,atkUs tuple
  58. for pitch,durMs,interOnsetMs,atkUs in zip(pitchL,durMsL,ioMsL,atkUsL):
  59. # calc note begin and end time
  60. begMs = t0
  61. endMs = t0 + durMs
  62. # if this pitch hasn't yet been added to pitchD
  63. if pitch not in pitchD:
  64. pitchD[ pitch ] = [ (begMs,endMs) ]
  65. else:
  66. # if the proposed note overlaps with other notes for this pitch
  67. if self._do_any_notes_overlap( begMs, endMs, pitchD[pitch] ):
  68. # move this pitch past the last note
  69. begMs = self._get_last_end_time( pitchD[pitch] ) + interOnsetMs
  70. endMs = begMs + durMs
  71. # add the new note to pitchD
  72. pitchD[ pitch ].append( (begMs,endMs) )
  73. # update the schedule
  74. schedL.append( (begMs, 'on', pitch, atkUs))
  75. schedL.append( (endMs, 'off', pitch, 0 ))
  76. t0 += interOnsetMs
  77. return schedL
  78. def start( self ):
  79. self.schedIdx = 0
  80. self.nextMs = 0
  81. self.api.set_hold_duty_all(self.cfg.holdDutyPct)
  82. self.isStartedFl = True
  83. def stop( self ):
  84. self.isStartedFl = False
  85. self.api.all_notes_off()
  86. def tick( self, ms ):
  87. while self.isStartedFl and ms >= self.nextMs and self.schedIdx < len(self.schedL):
  88. t0,cmd,pitch,usec = self.schedL[self.schedIdx]
  89. if cmd == 'on':
  90. if pitch not in self.cfg.skipPitchL:
  91. decay_level = self.api.calc_decay_level( usec )
  92. self.api.note_on_us( pitch, usec, decay_level )
  93. print("on %i %i %i" % (pitch,usec,decay_level))
  94. elif cmd == 'off':
  95. if pitch not in self.cfg.skipPitchL:
  96. self.api.note_off( pitch )
  97. print("off %i" % pitch)
  98. self.schedIdx += 1
  99. if self.schedIdx < len(self.schedL):
  100. self.nextMs = ms + (self.schedL[self.schedIdx][0] - t0)
  101. else:
  102. self.isStartedFl = False
  103. print("Done.")