comparison 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
comparison
equal deleted inserted replaced
196:07bac5061d69 197:ba2677823b35
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 """
4 todo: curveview should preserve more objects, for speed maybe
5
6 """
3 from __future__ import division 7 from __future__ import division
4 import xmlrpclib,time,bisect,socket,sys,textwrap 8 import xmlrpclib,time,socket,sys,textwrap,math
9 from bisect import bisect_left,bisect,bisect_right
5 import Tkinter as tk 10 import Tkinter as tk
6 from dispatch import dispatcher 11 from dispatch import dispatcher
7 from twisted.internet import reactor,tksupport 12 from twisted.internet import reactor,tksupport
8 from twisted.web.xmlrpc import Proxy 13 from twisted.web.xmlrpc import Proxy
9 14
18 points = None # x-sorted list of (x,y) 23 points = None # x-sorted list of (x,y)
19 def __init__(self): 24 def __init__(self):
20 self.points = [] 25 self.points = []
21 26
22 self.points = [(0,0),(1,1),(9,1),(10,0)] 27 self.points = [(0,0),(1,1),(9,1),(10,0)]
28 for x in range(11,500):
29 self.points.append((x,.5))
23 30
24 def eval(self,t): 31 def eval(self,t):
25 i = bisect.bisect_left(self.points,(t,None))-1 32 i = bisect_left(self.points,(t,None))-1
26 33
27 if self.points[i][0]>t: 34 if self.points[i][0]>t:
28 return self.points[i][1] 35 return self.points[i][1]
29 if i>=len(self.points)-1: 36 if i>=len(self.points)-1:
30 return self.points[i][1] 37 return self.points[i][1]
37 __call__=eval 44 __call__=eval
38 45
39 class Curveview(tk.Canvas): 46 class Curveview(tk.Canvas):
40 def __init__(self,master,curve,**kw): 47 def __init__(self,master,curve,**kw):
41 self.curve=curve 48 self.curve=curve
42 tk.Canvas.__init__(self,master,height=130,closeenough=5,**kw) 49 tk.Canvas.__init__(self,master,width=10,height=10,
50 relief='sunken',bd=1,
51 closeenough=5,**kw)
43 self.selected_points=[] # idx of points being dragged 52 self.selected_points=[] # idx of points being dragged
44 self.update() 53 self.update()
54 self.bind("<Enter>",self.focus)
55 dispatcher.connect(self.input_time,"input time")
56 dispatcher.connect(self.update,"zoom changed")
57 self.bind("<Configure>",self.update)
45 def screen_from_world(self,p): 58 def screen_from_world(self,p):
46 return p[0]*30+5,120-p[1]*100 59 start,end = self.zoom
60 ht = self.winfo_height()
61 return (p[0]-start)/(end-start)*self.winfo_width(), (ht-5)-p[1]*(ht-10)
47 def world_from_screen(self,x,y): 62 def world_from_screen(self,x,y):
48 return (x-5)/30,(120-y)/100 63 start,end = self.zoom
49 def update(self): 64 ht = self.winfo_height()
65 return x/self.winfo_width()*(end-start)+start, ((ht-5)-y)/(ht-10)
66
67 def input_time(self,val):
68 t=val
69 pts = self.screen_from_world((val,0))+self.screen_from_world((val,1))
70 self.delete('timecursor')
71 self.create_line(*pts,**dict(width=2,fill='red',tags=('timecursor',)))
72 def update(self,*args):
73
74 self.zoom = dispatcher.send("zoom area")[0][1]
75 cp = self.curve.points
76
77 visible_x = (self.world_from_screen(0,0)[0],
78 self.world_from_screen(self.winfo_width(),0)[0])
79
80 visleftidx = max(0,bisect_left(cp,(visible_x[0],None))-1)
81 visrightidx = min(len(cp)-1,bisect_left(cp,(visible_x[1],None))+1)
82
83 visible_points = cp[visleftidx:visrightidx]
84
50 self.delete('curve') 85 self.delete('curve')
51 linepts=[] 86 linepts=[]
52 for p in self.curve.points: 87 for p in visible_points:
53 linepts.extend(self.screen_from_world(p)) 88 linepts.extend(self.screen_from_world(p))
54 self.create_line(*linepts,**{'tags':'curve'}) 89 line = self.create_line(*linepts,**{'tags':'curve'})
55 for i,p in enumerate(self.curve.points): 90
56 rad=3 91 # canvas doesnt have keyboard focus, so i can't easily change the
57 p = self.screen_from_world(p) 92 # cursor when ctrl is pressed
58 dot = self.create_rectangle(p[0]-rad,p[1]-rad,p[0]+rad,p[1]+rad, 93 # def curs(ev):
59 outline='black',fill='blue', 94 # print ev.state
60 tags=('curve','point')) 95 # self.bind("<KeyPress>",curs)
61 self.tag_bind(dot,"<ButtonPress-1>", 96 # self.bind("<KeyRelease-Control_L>",lambda ev: curs(0))
62 lambda ev,i=i: self.dotpress(ev,i)) 97 self.tag_bind(line,"<Control-ButtonPress-1>",self.newpoint)
63 self.bind("<Motion>", 98
99 self.dots = {} # idx : canvas rectangle
100
101 if len(visible_points)<50: ###self.zoom[1]-self.zoom[0]<30 or len(visible_points)<:
102 for i,p in enumerate(visible_points):
103 rad=3
104 p = self.screen_from_world(p)
105 # if p[0]-prevx<10:
106 # # too close- skip the dots
107 # continue
108 dot = self.create_rectangle(p[0]-rad,p[1]-rad,p[0]+rad,p[1]+rad,
109 outline='black',fill='blue',
110 tags=('curve','point'))
111 self.tag_bind(dot,"<ButtonPress-1>",
112 lambda ev,i=i: self.dotpress(ev,i))
113 self.bind("<Motion>",
64 lambda ev,i=i: self.dotmotion(ev,i)) 114 lambda ev,i=i: self.dotmotion(ev,i))
65 self.bind("<ButtonRelease-1>", 115 self.bind("<ButtonRelease-1>",
66 lambda ev,i=i: self.dotrelease(ev,i)) 116 lambda ev,i=i: self.dotrelease(ev,i))
117 self.dots[i]=dot
118
119 self.highlight_selected_dots()
120
121 def newpoint(self,ev):
122 cp = self.curve.points
123
124 p = self.world_from_screen(ev.x,ev.y)
125 i = bisect(cp,(p[0],None))
126
127 self.unselect()
128 cp.insert(i,p)
129 self.update()
130
131 def highlight_selected_dots(self):
132 for i,d in self.dots.items():
133 if i in self.selected_points:
134 self.itemconfigure(d,fill='red')
135 else:
136 self.itemconfigure(d,fill='blue')
67 137
68 def dotpress(self,ev,dotidx): 138 def dotpress(self,ev,dotidx):
69 self.selected_points=[dotidx] 139 self.selected_points=[dotidx]
140 self.highlight_selected_dots()
70 141
71 def dotmotion(self,ev,dotidx): 142 def dotmotion(self,ev,dotidx):
72 cp = self.curve.points 143 cp = self.curve.points
73 144
145 moved=0
74 for idx in self.selected_points: 146 for idx in self.selected_points:
75 newp = self.world_from_screen(ev.x,ev.y) 147 newp = self.world_from_screen(ev.x,ev.y)
76 if idx>0 and newp[0]<=cp[idx-1][0]: 148 if idx>0 and newp[0]<=cp[idx-1][0]:
77 continue 149 continue
78 if idx<len(cp)-1 and newp[0]>=cp[idx+1][0]: 150 if idx<len(cp)-1 and newp[0]>=cp[idx+1][0]:
79 continue 151 continue
80 152 moved=1
81 cp[idx] = newp 153 cp[idx] = newp
82 154 if moved:
83 self.update() 155 self.update()
156 def unselect(self):
157 self.selected_points=[]
158 self.highlight_selected_dots()
159
84 def dotrelease(self,ev,dotidx): 160 def dotrelease(self,ev,dotidx):
85 self.selected_points=[] 161 self.unselect()
86 print "press",dotidx
87
88 162
89 class Curveset: 163 class Curveset:
90 curves = None # curvename : curve 164 curves = None # curvename : curve
91 def __init__(self): 165 def __init__(self):
92 self.curves = {} 166 self.curves = {}
103 self.curveset = curveset 177 self.curveset = curveset
104 tk.Frame.__init__(self,master,**kw) 178 tk.Frame.__init__(self,master,**kw)
105 dispatcher.connect(self.add_curve,"add_curve",sender=self.curveset) 179 dispatcher.connect(self.add_curve,"add_curve",sender=self.curveset)
106 def add_curve(self,name): 180 def add_curve(self,name):
107 f = tk.Frame(self,relief='raised',bd=1) 181 f = tk.Frame(self,relief='raised',bd=1)
108 f.pack(side='top') 182 f.pack(side='top',fill='both',exp=1)
109 tk.Label(f,text="curve %r"%name).pack(side='left') 183 tk.Label(f,text="curve %r"%name).pack(side='left')
110 cv = Curveview(f,self.curveset.curves[name]) 184 cv = Curveview(f,self.curveset.curves[name])
111 cv.pack(side='right') 185 cv.pack(side='right',fill='both',exp=1)
112 self.curves[name] = cv 186 self.curves[name] = cv
113 187
114 class Music: 188 class Music:
115 def __init__(self): 189 def __init__(self):
116 self.player=None # xmlrpc Proxy to player 190 self.player=None # xmlrpc Proxy to player
117 self.recenttime=0 191 self.recenttime=0
192
118 def current_time(self): 193 def current_time(self):
119 """return deferred which gets called with the current time""" 194 """return deferred which gets called with the current time"""
120 if self.player is None: 195 if self.player is None:
121 self.player = Proxy("http://spot:8040") 196 self.player = Proxy("http://spot:8040")
197 d = self.player.callRemote("songlength")
198 def sendmax(l):
199 dispatcher.send("max time",maxtime=l)
200 d.addCallback(sendmax)
122 d = self.player.callRemote('gettime') 201 d = self.player.callRemote('gettime')
123 def sendtime(t): 202 def sendtime(t):
124 dispatcher.send("input time",val=t) 203 dispatcher.send("input time",val=t)
125 return t # pass along to the real receiver 204 return t # pass along to the real receiver
126 def error(e): 205 def error(e):
203 scaledsubs.append(scl) 282 scaledsubs.append(scl)
204 out = Submaster.sub_maxes(*scaledsubs) 283 out = Submaster.sub_maxes(*scaledsubs)
205 dispatcher.send("output levels",val=out.get_levels()) 284 dispatcher.send("output levels",val=out.get_levels())
206 dmxclient.outputlevels(out.get_dmx_list(),twisted=1) 285 dmxclient.outputlevels(out.get_dmx_list(),twisted=1)
207 286
287 def statuslines(master):
288 for signame,textfilter in [
289 ('input time',lambda t: "%.2fs"%t),
290 ('output levels',
291 lambda levels: textwrap.fill("; ".join(["%s:%.2f"%(n,v)
292 for n,v in
293 levels.items()]),70)),
294 ('update period',lambda t: "%.1fms"%(t*1000)),
295 ]:
296 l = tk.Label(master,anchor='w',justify='left')
297 l.pack(side='top',fill='x')
298 dispatcher.connect(lambda val,l=l,sn=signame,tf=textfilter:
299 l.config(text=sn+": "+tf(val)),
300 signame,weak=0)
301
302 class Zoomcontrol(object,tk.Canvas):
303
304 def maxtime():
305 doc = "seconds at the right edge of the bar"
306 def fget(self): return self._maxtime
307 def fset(self, value):
308 self._maxtime = value
309 self.updatewidget()
310 return locals()
311 maxtime = property(**maxtime())
312
313 def start():
314 def fget(self): return self._start
315 def fset(self,v): self._start = max(0,v)
316 return locals()
317 start = property(**start())
318
319 def end():
320 def fget(self): return self._end
321 def fset(self,v): self._end = min(self.maxtime,v)
322 return locals()
323 end = property(**end())
324
325
326 def __init__(self,master,**kw):
327 self.maxtime=370
328 self.start=0
329 self.end=20
330 tk.Canvas.__init__(self,master,width=250,height=30,
331 relief='raised',bd=1,bg='gray60',**kw)
332 self.leftbrack = self.create_line(0,0,0,0,0,0,0,0,width=5)
333 self.rightbrack = self.create_line(0,0,0,0,0,0,0,0,width=5)
334 self.shade = self.create_rectangle(0,0,0,0,fill='gray70',outline=None)
335 self.time = self.create_line(0,0,0,0,fill='red',width=2)
336 self.updatewidget()
337 self.bind("<Configure>",self.updatewidget)
338
339 self.bind("<ButtonPress-1>",lambda ev: setattr(self,'lastx',ev.x))
340 self.tag_bind(self.leftbrack,"<B1-Motion>",
341 lambda ev: self.adjust('start',ev))
342 self.tag_bind(self.rightbrack,"<B1-Motion>",
343 lambda ev: self.adjust('end',ev))
344 self.tag_bind(self.shade,"<B1-Motion>",
345 lambda ev: self.adjust('offset',ev))
346 dispatcher.connect(lambda: (self.start,self.end),"zoom area",weak=0)
347 dispatcher.connect(self.input_time,"input time")
348 dispatcher.connect(lambda maxtime: (setattr(self,'maxtime',maxtime),
349 self.updatewidget()),"max time",weak=0)
350 self.created=1
351 def input_time(self,val):
352 t=val
353 x=self.can_for_t(t)
354 self.coords(self.time,x,0,x,self.winfo_height())
355
356 def adjust(self,attr,ev):
357 if not hasattr(self,'lastx'):
358 return
359 new = self.can_for_t(getattr(self,attr)) + (ev.x - self.lastx)
360 self.lastx = ev.x
361 setattr(self,attr,self.t_for_can(new))
362 self.updatewidget()
363 dispatcher.send("zoom changed")
364
365 def offset():
366 doc = "virtual attr that adjusts start and end together"
367 def fget(self):
368 return self.start
369 def fset(self, value):
370 d = self.end-self.start
371 self.start = value
372 self.end = self.start+d
373 return locals()
374 offset = property(**offset())
375
376 def can_for_t(self,t):
377 return t/self.maxtime*(self.winfo_width()-30)+20
378 def t_for_can(self,x):
379 return (x-20)/(self.winfo_width()-30)*self.maxtime
380
381 def updatewidget(self,*args):
382 """redraw pieces based on start/end"""
383 if not hasattr(self,'created'): return
384 y1,y2=3,self.winfo_height()-3
385 lip = 6
386 scan = self.can_for_t(self.start)
387 ecan = self.can_for_t(self.end)
388 self.coords(self.leftbrack,scan+lip,y1,scan,y1,scan,y2,scan+lip,y2)
389 self.coords(self.rightbrack,ecan-lip,y1,ecan,y1,ecan,y2,ecan-lip,y2)
390 self.coords(self.shade,scan+3,y1+lip,ecan-3,y2-lip)
391 self.delete("tics")
392 lastx=-1000
393 for t in range(0,int(self.maxtime)):
394 x = self.can_for_t(t)
395 if 0<x<self.winfo_width() and x-lastx>30:
396 txt=str(t)
397 if lastx==-1000:
398 txt=txt+"sec"
399 self.create_line(x,0,x,15,
400 tags=('tics',))
401 self.create_text(x,self.winfo_height()-1,anchor='s',
402 text=txt,tags=('tics',),font='6x13')
403 lastx = x
208 404
209 ####################################################################### 405 #######################################################################
210 root=tk.Tk() 406 root=tk.Tk()
407 root.wm_geometry("790x930")
408 #root.tk_focusFollowsMouse()
211 409
212 m=Music() 410 m=Music()
411
412 zc = Zoomcontrol(root)
413 zc.pack(side='top',fill='x')
213 414
214 cs = Curveset() 415 cs = Curveset()
215 csv = Curvesetview(root,cs) 416 csv = Curvesetview(root,cs)
216 csv.pack() 417 csv.pack(side='top',fill='both',exp=1)
217 418
218 for loop in range(6): 419 for loop in range(6):
219 cs.add_curve('c'+str(loop+1),Curve()) 420 cs.add_curve('c'+str(loop+1),Curve())
220
221 421
222 subterms = [] 422 subterms = []
223 for subname in "zip_orange","zip_red": 423 for subname in "zip_orange","zip_red":
224 424
225 se = Subexpr(cs) 425 se = Subexpr(cs)
229 st=Subterm() 429 st=Subterm()
230 st.sub=Submaster.Submaster(subname) 430 st.sub=Submaster.Submaster(subname)
231 st.subexpr=se 431 st.subexpr=se
232 432
233 stv=Subtermview(root,st) 433 stv=Subtermview(root,st)
234 stv.pack(side='top',fill='x',exp=1) 434 stv.pack(side='top',fill='x')
235 subterms.append(st) 435 subterms.append(st)
236 436
237 out = Output(subterms) 437 out = Output(subterms)
238 438
239 for signame,textfilter in [ 439 statuslines(root)
240 ('input time',lambda t: "%.2fs"%t),
241 ('output levels',
242 lambda levels: textwrap.fill("; ".join(["%s:%.2f"%(n,v)
243 for n,v in levels.items()]),100)),
244 ('update period',lambda t: "%.1fms"%(t*1000)),
245 ]:
246 l = tk.Label(root,anchor='w')
247 l.pack(side='top',fill='x')
248 dispatcher.connect(lambda val,l=l,sn=signame,tf=textfilter:
249 l.config(text=sn+": "+tf(val)),
250 signame,weak=0)
251 440
252
253 recent_t=[] 441 recent_t=[]
254 def update(): 442 def update():
255 d = m.current_time() 443 d = m.current_time()
256 d.addCallback(update2) 444 d.addCallback(update2)
257 d.addErrback(updateerr) 445 d.addErrback(updateerr)