149 lines
4.7 KiB
Python
149 lines
4.7 KiB
Python
|
import types
|
||
|
import time
|
||
|
from random import randrange
|
||
|
|
||
|
class PolyNoteTester:
|
||
|
def __init__( self, cfg, api ):
|
||
|
self.api = api
|
||
|
|
||
|
r = types.SimpleNamespace(**cfg.PolyNoteTester)
|
||
|
self.cfg = r
|
||
|
|
||
|
if r.mode == "simple":
|
||
|
print("mode:simple")
|
||
|
self.schedL = self._gen_simple_sched(r)
|
||
|
else:
|
||
|
print("mode:poly")
|
||
|
self.schedL = self._gen_sched(r)
|
||
|
|
||
|
|
||
|
self.schedL = sorted( self.schedL, key=lambda x: x[0] )
|
||
|
self.nextMs = 0 # next transition time
|
||
|
self.schedIdx = 0 # next event to play
|
||
|
self.isStartedFl = False
|
||
|
|
||
|
#self._report()
|
||
|
|
||
|
def _report( self ):
|
||
|
for t,cmd,pitch,atkUs in self.schedL:
|
||
|
print("%s %6i %3i %5i" % (cmd,t,pitch,atkUs))
|
||
|
|
||
|
def _gen_simple_sched( self, r ):
|
||
|
""" Play each note sequentially from lowest to highest. """
|
||
|
durMs = int(r.minNoteDurMs + (r.maxNoteDurMs - r.minNoteDurMs)/2)
|
||
|
ioMs = int(r.minInterOnsetMs + (r.maxInterOnsetMs - r.minInterOnsetMs)/2)
|
||
|
atkUs = int(r.minAttackUsec + (r.maxAttackUsec - r.minAttackUsec)/2)
|
||
|
schedL = []
|
||
|
|
||
|
t0 = 0
|
||
|
for pitch in range(r.minPitch,r.maxPitch+1):
|
||
|
schedL.append((t0,'on',pitch,atkUs))
|
||
|
schedL.append((t0+durMs,'off',pitch,0))
|
||
|
t0 += durMs + ioMs
|
||
|
|
||
|
return schedL
|
||
|
|
||
|
|
||
|
def _does_note_overlap( self, beg0Ms, end0Ms, beg1Ms, end1Ms ):
|
||
|
""" if note 0 is entirely before or after note 1 """
|
||
|
return not (beg0Ms > end1Ms or end0Ms < beg1Ms)
|
||
|
|
||
|
def _do_any_notes_overlap( self, begMs, endMs, begEndL ):
|
||
|
for beg1,end1 in begEndL:
|
||
|
if self._does_note_overlap(begMs,endMs,beg1,end1):
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
def _get_last_end_time( self, begEndL ):
|
||
|
end0 = 0
|
||
|
for beg,end in begEndL:
|
||
|
if end > end0:
|
||
|
end0 = end
|
||
|
|
||
|
return end0
|
||
|
|
||
|
|
||
|
def _gen_sched( self, r ):
|
||
|
|
||
|
pitchL = [ randrange(r.minPitch,r.maxPitch) for _ in range(r.noteCount) ]
|
||
|
durMsL = [ randrange(r.minNoteDurMs, r.maxNoteDurMs) for _ in range(r.noteCount) ]
|
||
|
ioMsL = [ randrange(r.minInterOnsetMs, r.maxInterOnsetMs) for _ in range(r.noteCount) ]
|
||
|
atkUsL = [ randrange(r.minAttackUsec, r.maxAttackUsec) for _ in range(r.noteCount) ]
|
||
|
schedL = []
|
||
|
pitchD = {} # pitch: [ (begMs,endMs) ]
|
||
|
|
||
|
t0 = 0
|
||
|
# for each pitch,dur,ioi,atkUs tuple
|
||
|
for pitch,durMs,interOnsetMs,atkUs in zip(pitchL,durMsL,ioMsL,atkUsL):
|
||
|
|
||
|
# calc note begin and end time
|
||
|
begMs = t0
|
||
|
endMs = t0 + durMs
|
||
|
|
||
|
# if this pitch hasn't yet been added to pitchD
|
||
|
if pitch not in pitchD:
|
||
|
pitchD[ pitch ] = [ (begMs,endMs) ]
|
||
|
else:
|
||
|
|
||
|
# if the proposed note overlaps with other notes for this pitch
|
||
|
if self._do_any_notes_overlap( begMs, endMs, pitchD[pitch] ):
|
||
|
# move this pitch past the last note
|
||
|
begMs = self._get_last_end_time( pitchD[pitch] ) + interOnsetMs
|
||
|
endMs = begMs + durMs
|
||
|
|
||
|
# add the new note to pitchD
|
||
|
pitchD[ pitch ].append( (begMs,endMs) )
|
||
|
|
||
|
|
||
|
# update the schedule
|
||
|
schedL.append( (begMs, 'on', pitch, atkUs))
|
||
|
schedL.append( (endMs, 'off', pitch, 0 ))
|
||
|
|
||
|
t0 += interOnsetMs
|
||
|
|
||
|
|
||
|
return schedL
|
||
|
|
||
|
|
||
|
def start( self ):
|
||
|
self.schedIdx = 0
|
||
|
self.nextMs = 0
|
||
|
self.api.set_hold_duty_all(self.cfg.holdDutyPct)
|
||
|
self.isStartedFl = True
|
||
|
|
||
|
|
||
|
def stop( self ):
|
||
|
self.isStartedFl = False
|
||
|
self.api.all_notes_off()
|
||
|
|
||
|
|
||
|
def tick( self, ms ):
|
||
|
|
||
|
while self.isStartedFl and ms >= self.nextMs and self.schedIdx < len(self.schedL):
|
||
|
|
||
|
t0,cmd,pitch,usec = self.schedL[self.schedIdx]
|
||
|
|
||
|
if cmd == 'on':
|
||
|
if pitch not in self.cfg.skipPitchL:
|
||
|
decay_level = self.api.calc_decay_level( usec )
|
||
|
self.api.note_on_us( pitch, usec, decay_level )
|
||
|
print("on %i %i %i" % (pitch,usec,decay_level))
|
||
|
|
||
|
elif cmd == 'off':
|
||
|
if pitch not in self.cfg.skipPitchL:
|
||
|
self.api.note_off( pitch )
|
||
|
print("off %i" % pitch)
|
||
|
|
||
|
self.schedIdx += 1
|
||
|
if self.schedIdx < len(self.schedL):
|
||
|
self.nextMs = ms + (self.schedL[self.schedIdx][0] - t0)
|
||
|
else:
|
||
|
self.isStartedFl = False
|
||
|
print("Done.")
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|