Mercurial > code > home > repos > light9
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) |