#!/usr/bin/python # $Id: boaconductor.py,v 1.8 2005/06/19 17:57:52 brinkman Exp $ # Peter Brinkmann (brinkman@math.tu-berlin.de) from Tkinter import * from math import * import time from pyseq import * from midiplayer import * class SimpleKalman: def __init__(self, p, q, r): self.p=p self.q=q self.r=r self.x=None def next(self, x): if self.x is None: self.x=x return x pm=self.p+self.q k=pm/(pm+self.r) self.x+=k*(x-self.x) self.p=(1-k)*pm return self.x class BoaConductor(Frame): def __init__(self, tune, parent): self.seq=PlaySeq(tune) self.ev=snd_seq_event() Frame.__init__(self, parent) self.pack(fill=X, expand=YES) self.master.title('BoaConductor') self.height=self.width=500 self.xc, self.yc=self.width/2, self.height/2 self.x=self.y=None self.gain=1 self.phi0=4*pi self.callhandle=None self.paused=0 self.start() self.cv=Canvas(self, width=self.width, height=self.height, relief=SUNKEN, borderwidth=2) self.cv.pack() Button(self, text='Restart', command=self.start).pack() Button(self, text='Quit', command=self.cleanup).pack() self.cv.bind('', self.handleEntrance) self.cv.bind('', self.handleMotion) self.cv.create_line(self.xc, 0, self.xc, self.height) self.cv.create_line(0, self.yc, self.width, self.yc) def cleanup(self): self.seq.handleSignal() self.quit() def handleEntrance(self, e): self.x, self.y=e.x, e.y def setTempoMTV(self, omega): self.seq.q.setMidiTempoValue(int(2e6*pi/omega/self.gain)) def setVolume(self, d): vol=min(16384, int(d/(.7*self.width)*32768)) for p in self.seq.oports: self.ev.setMasterVolume(vol) self.seq.q.postEvent(self.ev, p, 0) def start(self): if not self.callhandle is None: self.after_cancel(self.callhandle) self.callhandle=None self.seq.q.stop() self.seq.panic() self.t=None self.phi=0 self.paused=0 self.tempo=SimpleKalman(1, 1e-5, .03) # the smaller the last value, # the more responsive the behavior (beware of wobble) self.volume=SimpleKalman(1, 1e-5, .01) def startQueue(self): self.seq.play() def angle(self, x, y): return atan2(y-self.yc, x-self.xc) def distance(self, x, y): return sqrt((y-self.yc)**2+(x-self.xc)**2) def angleDiff(self, x0, y0, x1, y1): phi0, phi1=self.angle(x0, y0), self.angle(x1, y1) return min(abs(phi1-phi0), abs(phi1-phi0+2*pi), abs(phi1-phi0-2*pi)) def handleMotion(self, e): if not self.callhandle is None: self.after_cancel(self.callhandle) t=time.time() if self.t is None: self.t=t self.x, self.y=e.x, e.y return dphi=self.angleDiff(e.x, e.y, self.x, self.y) if self.phiself.phi0: self.startQueue() dt=t-self.t self.setTempoMTV(self.tempo.next(dphi/dt)) self.setVolume(self.volume.next(self.distance(e.x, e.y))) if self.paused: self.paused=0 self.seq.q.cont() self.x, self.y=e.x, e.y self.t=t self.phi+=dphi self.after(1000, self.cv.delete, self.cv.create_oval(e.x-2, e.y-2, e.x+2, e.y+2, fill='red')) self.callhandle=self.after(250, self.pause) def pause(self): self.callhandle=None self.seq.q.stop() self.paused=1 if __name__=='__main__': if len(sys.argv)<2: fn='trio_es_2_nagorni.mid' else: fn=sys.argv[1] print 'hook up midi ports, draw circles' tune=MidiToAlsa(fn) BoaConductor(tune, Tk()).mainloop()