i'm attaching a demo of what it takes to write your own merging buffer in python. it took me 4 hours (moderately experienced programmer new to python). there are some tricky things to look out for, and i had the advantage of a built-in threadsafe PriorityQueue. i have to set my own internal latency to around 10ms in order to keep the missed timestamps around 1-3%, and i lose whatever fancy driver/hardware optimizations apple might have in place (did they get "active midi timing" from emagic?). if portmidi took care of this, large classes of applications might be able to avoid concurrency altogether, and timing-sensitive applications would be feasible in slower dynamic/interpreted environments (and those that don't expose threading). these issues definitely seem like perfect things for portmidi to save users from. -e
import pypm import threading import time import bisect import Queue
def allNotesOff(m): for i in range(16): for j in range(2**7-1): m.Write([[[0x90 + i,j,0],0]]) def mean(x): return sum(x)/len(x) def std(x): m=mean(x) return mean(map(lambda y:abs(y-m),x)) class midiBuffer(threading.Thread): def __init__(self): self.stop=False self.incoming=Queue.PriorityQueue() #threadsafe self.outgoing=[] threading.Thread.__init__(self) def run(self): latency=1 #latency is in ms, 0 means ignore timestamps dev = 0 MidiOut=pypm.Output(dev, latency) myLatency=10 t=0 class report():pass rpt=report() rpt.sleeps=0 rpt.latencies=[] try: while not self.stop: try: while True: s,e=self.incoming.get_nowait() if s<=t: MidiOut.Write(e) else: #since PriorityQueue has no peek method, transfer events to a peekable format self.outgoing.insert(bisect.bisect(map(lambda x:x[0],self.outgoing),s),[s,e]) except Queue.Empty: pass while pypm.Time()<=t: rpt.sleeps+=1 time.sleep(.0003) #prevent proc hog t=pypm.Time() while len(self.outgoing)>0 and t>=self.outgoing[0][0]-myLatency: rpt.latencies.append(self.outgoing[0][0]-t) MidiOut.Write(self.outgoing.pop(0)[1]) finally: print "sleeps: " + str(rpt.sleeps) misses=len(filter(lambda x:x<0,rpt.latencies)) print "latencies (ms, negative=late): mean=" + str(mean(rpt.latencies)) + " std=" + str(std(rpt.latencies)) + " misses=" + str(misses) + " of " + str(len(rpt.latencies)) + " (" + str(100*misses/len(rpt.latencies)) +"%)" allNotesOff(MidiOut) del MidiOut def playChord(buff,notes,chan,vel,dur,onset): ChordList = [] for i in range(len(notes)): ChordList.append([[0x90 + chan,notes[i],vel], onset]) buff.incoming.put((onset,ChordList)) ChordList = [] offset=onset+dur*1000 for i in range(len(notes)): ChordList.append([[0x90 + chan,notes[i],0],offset]) buff.incoming.put((offset,ChordList)) def test(): pypm.Initialize() b=midiBuffer() b.start() tempo=120 dur=60.0/tempo/2 notes=[60, 63, 66, 70] n=0 while n<100: t=pypm.Time() playChord(b,[notes[0]-12],1,127,dur,t) playChord(b,notes,0,60,dur,t) n+=1 while pypm.Time()<t+2*dur*1000: pass b.stop=True b.join() pypm.Terminate() test()
_______________________________________________ media_api mailing list media_api@create.ucsb.edu http://lists.create.ucsb.edu/mailman/listinfo/media_api