Mercurial > code > home > repos > light9
view flax/curvecalc @ 197:ba2677823b35
zoom control and other cleanups. also reads song length now
author | drewp |
---|---|
date | Wed, 16 Jun 2004 13:00:11 +0000 |
parents | 07bac5061d69 |
children | f5d3492981ab |
line wrap: on
line source
#!/usr/bin/python """ todo: curveview should preserve more objects, for speed maybe """ from __future__ import division import xmlrpclib,time,socket,sys,textwrap,math from bisect import bisect_left,bisect,bisect_right import Tkinter as tk from dispatch import dispatcher from twisted.internet import reactor,tksupport from twisted.web.xmlrpc import Proxy sys.path.append("../light8") import dmxclient import Submaster from TLUtility import make_attributes_from_args class Curve: """curve does not know its name. see Curveset""" points = None # x-sorted list of (x,y) def __init__(self): self.points = [] self.points = [(0,0),(1,1),(9,1),(10,0)] for x in range(11,500): self.points.append((x,.5)) def eval(self,t): i = bisect_left(self.points,(t,None))-1 if self.points[i][0]>t: return self.points[i][1] if i>=len(self.points)-1: return self.points[i][1] p1,p2 = self.points[i],self.points[i+1] frac = (t-p1[0])/(p2[0]-p1[0]) y = p1[1]+(p2[1]-p1[1])*frac return y __call__=eval class Curveview(tk.Canvas): def __init__(self,master,curve,**kw): self.curve=curve tk.Canvas.__init__(self,master,width=10,height=10, relief='sunken',bd=1, closeenough=5,**kw) self.selected_points=[] # idx of points being dragged self.update() self.bind("<Enter>",self.focus) dispatcher.connect(self.input_time,"input time") dispatcher.connect(self.update,"zoom changed") self.bind("<Configure>",self.update) def screen_from_world(self,p): start,end = self.zoom ht = self.winfo_height() return (p[0]-start)/(end-start)*self.winfo_width(), (ht-5)-p[1]*(ht-10) def world_from_screen(self,x,y): start,end = self.zoom ht = self.winfo_height() return x/self.winfo_width()*(end-start)+start, ((ht-5)-y)/(ht-10) def input_time(self,val): t=val pts = self.screen_from_world((val,0))+self.screen_from_world((val,1)) self.delete('timecursor') self.create_line(*pts,**dict(width=2,fill='red',tags=('timecursor',))) def update(self,*args): self.zoom = dispatcher.send("zoom area")[0][1] cp = self.curve.points visible_x = (self.world_from_screen(0,0)[0], self.world_from_screen(self.winfo_width(),0)[0]) visleftidx = max(0,bisect_left(cp,(visible_x[0],None))-1) visrightidx = min(len(cp)-1,bisect_left(cp,(visible_x[1],None))+1) visible_points = cp[visleftidx:visrightidx] self.delete('curve') linepts=[] for p in visible_points: linepts.extend(self.screen_from_world(p)) line = self.create_line(*linepts,**{'tags':'curve'}) # canvas doesnt have keyboard focus, so i can't easily change the # cursor when ctrl is pressed # def curs(ev): # print ev.state # self.bind("<KeyPress>",curs) # self.bind("<KeyRelease-Control_L>",lambda ev: curs(0)) self.tag_bind(line,"<Control-ButtonPress-1>",self.newpoint) self.dots = {} # idx : canvas rectangle if len(visible_points)<50: ###self.zoom[1]-self.zoom[0]<30 or len(visible_points)<: for i,p in enumerate(visible_points): rad=3 p = self.screen_from_world(p) # if p[0]-prevx<10: # # too close- skip the dots # continue dot = self.create_rectangle(p[0]-rad,p[1]-rad,p[0]+rad,p[1]+rad, outline='black',fill='blue', tags=('curve','point')) self.tag_bind(dot,"<ButtonPress-1>", lambda ev,i=i: self.dotpress(ev,i)) self.bind("<Motion>", lambda ev,i=i: self.dotmotion(ev,i)) self.bind("<ButtonRelease-1>", lambda ev,i=i: self.dotrelease(ev,i)) self.dots[i]=dot self.highlight_selected_dots() def newpoint(self,ev): cp = self.curve.points p = self.world_from_screen(ev.x,ev.y) i = bisect(cp,(p[0],None)) self.unselect() cp.insert(i,p) self.update() def highlight_selected_dots(self): for i,d in self.dots.items(): if i in self.selected_points: self.itemconfigure(d,fill='red') else: self.itemconfigure(d,fill='blue') def dotpress(self,ev,dotidx): self.selected_points=[dotidx] self.highlight_selected_dots() def dotmotion(self,ev,dotidx): cp = self.curve.points moved=0 for idx in self.selected_points: newp = self.world_from_screen(ev.x,ev.y) if idx>0 and newp[0]<=cp[idx-1][0]: continue if idx<len(cp)-1 and newp[0]>=cp[idx+1][0]: continue moved=1 cp[idx] = newp if moved: self.update() def unselect(self): self.selected_points=[] self.highlight_selected_dots() def dotrelease(self,ev,dotidx): self.unselect() class Curveset: curves = None # curvename : curve def __init__(self): self.curves = {} def add_curve(self,name,curve): self.curves[name] = curve dispatcher.send("add_curve",sender=self,name=name) def globalsdict(self): return self.curves.copy() class Curvesetview(tk.Frame): curves = None # curvename : Curveview def __init__(self,master,curveset,**kw): self.curves = {} self.curveset = curveset tk.Frame.__init__(self,master,**kw) dispatcher.connect(self.add_curve,"add_curve",sender=self.curveset) def add_curve(self,name): f = tk.Frame(self,relief='raised',bd=1) f.pack(side='top',fill='both',exp=1) tk.Label(f,text="curve %r"%name).pack(side='left') cv = Curveview(f,self.curveset.curves[name]) cv.pack(side='right',fill='both',exp=1) self.curves[name] = cv class Music: def __init__(self): self.player=None # xmlrpc Proxy to player self.recenttime=0 def current_time(self): """return deferred which gets called with the current time""" if self.player is None: self.player = Proxy("http://spot:8040") d = self.player.callRemote("songlength") def sendmax(l): dispatcher.send("max time",maxtime=l) d.addCallback(sendmax) d = self.player.callRemote('gettime') def sendtime(t): dispatcher.send("input time",val=t) return t # pass along to the real receiver def error(e): pass#self.player=None d.addCallback(sendtime) return d class Subexpr: curveset = None def __init__(self,curveset): self.curveset = curveset self.lasteval = None self.expr="" def eval(self,t): if self.expr=="": dispatcher.send("expr_error",sender=self,exc="no expr, using 0") return 0 glo = self.curveset.globalsdict() glo['t'] = t try: self.lasteval = eval(self.expr,glo) except Exception,e: dispatcher.send("expr_error",sender=self,exc=e) else: dispatcher.send("expr_error",sender=self,exc="no errors") return self.lasteval def expr(): doc = "python expression for level as a function of t, using curves" def fget(self): return self._expr def fset(self, value): self._expr = value dispatcher("expr_changed",sender=self) return locals() expr = property(**expr()) class Subexprview(tk.Frame): def __init__(self,master,se,**kw): self.subexpr=se tk.Frame.__init__(self,master,**kw) self.evar = tk.StringVar() e = self.ent = tk.Entry(master,textvariable=self.evar) e.pack(side='left',fill='both',exp=1) self.expr_changed() self.evar.trace_variable('w',self.evar_changed) dispatcher.connect(self.expr_changed,"expr_changed", sender=self.subexpr) self.error = tk.Label(master) self.error.pack(side='left') dispatcher.connect(lambda exc: self.error.config(text=str(exc)), "expr_error",sender=self.subexpr,weak=0) def expr_changed(self): if self.subexpr.expr!=self.evar.get(): self.evar.set(self.subexpr.expr) def evar_changed(self,*args): self.subexpr.expr = self.evar.get() class Subterm: """one Submaster and its Subexpr""" def scaled(self,t): return self.sub * self.subexpr.eval(t) class Subtermview(tk.Frame): def __init__(self,master,st,**kw): self.subterm = st tk.Frame.__init__(self,master,bd=1,relief='raised',**kw) tk.Label(self,text="sub %r" % self.subterm.sub.name).pack(side='left') sev=Subexprview(self,self.subterm.subexpr) sev.pack(side='left',fill='both',exp=1) class Output: def __init__(self,subterms): make_attributes_from_args('subterms') def send_dmx(self,t): scaledsubs=[] for st in self.subterms: scl = st.scaled(t) scaledsubs.append(scl) out = Submaster.sub_maxes(*scaledsubs) dispatcher.send("output levels",val=out.get_levels()) dmxclient.outputlevels(out.get_dmx_list(),twisted=1) def statuslines(master): for signame,textfilter in [ ('input time',lambda t: "%.2fs"%t), ('output levels', lambda levels: textwrap.fill("; ".join(["%s:%.2f"%(n,v) for n,v in levels.items()]),70)), ('update period',lambda t: "%.1fms"%(t*1000)), ]: l = tk.Label(master,anchor='w',justify='left') l.pack(side='top',fill='x') dispatcher.connect(lambda val,l=l,sn=signame,tf=textfilter: l.config(text=sn+": "+tf(val)), signame,weak=0) class Zoomcontrol(object,tk.Canvas): def maxtime(): doc = "seconds at the right edge of the bar" def fget(self): return self._maxtime def fset(self, value): self._maxtime = value self.updatewidget() return locals() maxtime = property(**maxtime()) def start(): def fget(self): return self._start def fset(self,v): self._start = max(0,v) return locals() start = property(**start()) def end(): def fget(self): return self._end def fset(self,v): self._end = min(self.maxtime,v) return locals() end = property(**end()) def __init__(self,master,**kw): self.maxtime=370 self.start=0 self.end=20 tk.Canvas.__init__(self,master,width=250,height=30, relief='raised',bd=1,bg='gray60',**kw) self.leftbrack = self.create_line(0,0,0,0,0,0,0,0,width=5) self.rightbrack = self.create_line(0,0,0,0,0,0,0,0,width=5) self.shade = self.create_rectangle(0,0,0,0,fill='gray70',outline=None) self.time = self.create_line(0,0,0,0,fill='red',width=2) self.updatewidget() self.bind("<Configure>",self.updatewidget) self.bind("<ButtonPress-1>",lambda ev: setattr(self,'lastx',ev.x)) self.tag_bind(self.leftbrack,"<B1-Motion>", lambda ev: self.adjust('start',ev)) self.tag_bind(self.rightbrack,"<B1-Motion>", lambda ev: self.adjust('end',ev)) self.tag_bind(self.shade,"<B1-Motion>", lambda ev: self.adjust('offset',ev)) dispatcher.connect(lambda: (self.start,self.end),"zoom area",weak=0) dispatcher.connect(self.input_time,"input time") dispatcher.connect(lambda maxtime: (setattr(self,'maxtime',maxtime), self.updatewidget()),"max time",weak=0) self.created=1 def input_time(self,val): t=val x=self.can_for_t(t) self.coords(self.time,x,0,x,self.winfo_height()) def adjust(self,attr,ev): if not hasattr(self,'lastx'): return new = self.can_for_t(getattr(self,attr)) + (ev.x - self.lastx) self.lastx = ev.x setattr(self,attr,self.t_for_can(new)) self.updatewidget() dispatcher.send("zoom changed") def offset(): doc = "virtual attr that adjusts start and end together" def fget(self): return self.start def fset(self, value): d = self.end-self.start self.start = value self.end = self.start+d return locals() offset = property(**offset()) def can_for_t(self,t): return t/self.maxtime*(self.winfo_width()-30)+20 def t_for_can(self,x): return (x-20)/(self.winfo_width()-30)*self.maxtime def updatewidget(self,*args): """redraw pieces based on start/end""" if not hasattr(self,'created'): return y1,y2=3,self.winfo_height()-3 lip = 6 scan = self.can_for_t(self.start) ecan = self.can_for_t(self.end) self.coords(self.leftbrack,scan+lip,y1,scan,y1,scan,y2,scan+lip,y2) self.coords(self.rightbrack,ecan-lip,y1,ecan,y1,ecan,y2,ecan-lip,y2) self.coords(self.shade,scan+3,y1+lip,ecan-3,y2-lip) self.delete("tics") lastx=-1000 for t in range(0,int(self.maxtime)): x = self.can_for_t(t) if 0<x<self.winfo_width() and x-lastx>30: txt=str(t) if lastx==-1000: txt=txt+"sec" self.create_line(x,0,x,15, tags=('tics',)) self.create_text(x,self.winfo_height()-1,anchor='s', text=txt,tags=('tics',),font='6x13') lastx = x ####################################################################### root=tk.Tk() root.wm_geometry("790x930") #root.tk_focusFollowsMouse() m=Music() zc = Zoomcontrol(root) zc.pack(side='top',fill='x') cs = Curveset() csv = Curvesetview(root,cs) csv.pack(side='top',fill='both',exp=1) for loop in range(6): cs.add_curve('c'+str(loop+1),Curve()) subterms = [] for subname in "zip_orange","zip_red": se = Subexpr(cs) if subname=='zip_orange': se.expr="c1(t)+c2(t)+c3(t)+c4(t)+c5(t)" st=Subterm() st.sub=Submaster.Submaster(subname) st.subexpr=se stv=Subtermview(root,st) stv.pack(side='top',fill='x') subterms.append(st) out = Output(subterms) statuslines(root) recent_t=[] def update(): d = m.current_time() d.addCallback(update2) d.addErrback(updateerr) def updateerr(e): reactor.callLater(1,update) def update2(t): global recent_t reactor.callLater(.001,update) recent_t = recent_t[-50:]+[t] period = (recent_t[-1]-recent_t[0])/len(recent_t) dispatcher.send("update period",val=period) out.send_dmx(t) update() tksupport.install(root,ms=10) if 0: sys.path.append("/home/drewp/projects/editor/pour") from utils import runstats runstats("reactor.run()") else: reactor.run()