Mercurial > code > home > repos > light9
comparison light9/curve.py @ 362:fc87327e29c4
CC now attaches to hardware sliders and knobs. tres cool. KC gets a --sliders option to enable the sliders
author | Drew Perttula <drewp@bigasterisk.com> |
---|---|
date | Fri, 15 Jun 2007 06:04:55 +0000 |
parents | ff914126f3ea |
children | 058f2f508504 |
comparison
equal
deleted
inserted
replaced
361:ff914126f3ea | 362:fc87327e29c4 |
---|---|
1 from __future__ import division | 1 from __future__ import division |
2 import sys,math,glob,random,os | 2 import sys,math,glob,random,os, time |
3 from bisect import bisect_left,bisect,bisect_right | 3 from bisect import bisect_left,bisect,bisect_right |
4 import Tkinter as tk | 4 import Tkinter as tk |
5 try: | 5 try: |
6 from dispatch import dispatcher | 6 from dispatch import dispatcher |
7 except ImportError: | 7 except ImportError: |
10 import run_local | 10 import run_local |
11 from light9 import Submaster, dmxclient, networking, cursors | 11 from light9 import Submaster, dmxclient, networking, cursors |
12 from light9.TLUtility import make_attributes_from_args | 12 from light9.TLUtility import make_attributes_from_args |
13 from light9.dmxchanedit import gradient | 13 from light9.dmxchanedit import gradient |
14 from light9.zoomcontrol import RegionZoom | 14 from light9.zoomcontrol import RegionZoom |
15 from bcf2000 import BCF2000 | |
15 | 16 |
16 class Curve: | 17 class Curve: |
17 """curve does not know its name. see Curveset""" | 18 """curve does not know its name. see Curveset""" |
18 points = None # x-sorted list of (x,y) | 19 points = None # x-sorted list of (x,y) |
19 def __init__(self): | 20 def __init__(self): |
59 bisect(self.points, (x2,None)) + beyond) | 60 bisect(self.points, (x2,None)) + beyond) |
60 return range(leftidx, rightidx) | 61 return range(leftidx, rightidx) |
61 | 62 |
62 def points_between(self, x1, x2): | 63 def points_between(self, x1, x2): |
63 return [self.points[i] for i in self.indices_between(x1,x2)] | 64 return [self.points[i] for i in self.indices_between(x1,x2)] |
65 | |
66 def point_before(self, x): | |
67 """(x,y) of the point left of x, or None""" | |
68 leftidx = self.index_before(x) | |
69 if leftidx is None: | |
70 return None | |
71 return self.points[leftidx] | |
72 | |
73 def index_before(self, x): | |
74 leftidx = bisect(self.points, (x,None)) - 1 | |
75 if leftidx < 0: | |
76 return None | |
77 return leftidx | |
78 | |
64 | 79 |
65 def vlen(v): | 80 def vlen(v): |
66 return math.sqrt(v[0]*v[0] + v[1]*v[1]) | 81 return math.sqrt(v[0]*v[0] + v[1]*v[1]) |
67 | 82 |
68 def angle_between(base, p0, p1): | 83 def angle_between(base, p0, p1): |
125 | 140 |
126 self.curveview.update_curve() | 141 self.curveview.update_curve() |
127 | 142 |
128 | 143 |
129 class Curveview(tk.Canvas): | 144 class Curveview(tk.Canvas): |
130 def __init__(self,master,curve,**kw): | 145 def __init__(self, master, curve, knobEnabled=False, **kw): |
146 """knobEnabled=True highlights the previous key and ties it to a | |
147 hardware knob""" | |
131 self.curve=curve | 148 self.curve=curve |
149 self.knobEnabled = knobEnabled | |
132 self._time = 0 | 150 self._time = 0 |
133 self.last_mouse_world = None | 151 self.last_mouse_world = None |
134 tk.Canvas.__init__(self,master,width=10,height=10, | 152 tk.Canvas.__init__(self,master,width=10,height=10, |
135 relief='sunken',bd=1, | 153 relief='sunken',bd=1, |
136 closeenough=5,takefocus=1, **kw) | 154 closeenough=5,takefocus=1, **kw) |
137 self.selected_points=[] # idx of points being dragged | 155 self.selected_points=[] # idx of points being dragged |
138 self.update_curve() | 156 self.update_curve() |
139 # self.bind("<Enter>",self.focus) | 157 # self.bind("<Enter>",self.focus) |
140 dispatcher.connect(self.input_time,"input time") | 158 dispatcher.connect(self.input_time,"input time") |
141 dispatcher.connect(self.update_curve,"zoom changed") | 159 dispatcher.connect(self.update_curve,"zoom changed") |
142 dispatcher.connect(self.update_curve,"points changed",sender=self.curve) | 160 dispatcher.connect(self.update_curve,"points changed", |
161 sender=self.curve) | |
143 dispatcher.connect(self.select_between,"select between") | 162 dispatcher.connect(self.select_between,"select between") |
163 if self.knobEnabled: | |
164 dispatcher.connect(self.knob_in, "knob in") | |
165 dispatcher.connect(self.slider_in, "slider in") | |
144 self.bind("<Configure>",self.update_curve) | 166 self.bind("<Configure>",self.update_curve) |
145 for x in range(1, 6): | 167 for x in range(1, 6): |
146 def add_kb_marker_point(evt, x=x): | 168 def add_kb_marker_point(evt, x=x): |
147 self.add_point((self.current_time(), (x - 1) / 4.0)) | 169 self.add_point((self.current_time(), (x - 1) / 4.0)) |
148 | 170 |
189 self.bind("<ButtonRelease-1>", #"<Alt-KeyRelease>", | 211 self.bind("<ButtonRelease-1>", #"<Alt-KeyRelease>", |
190 self.select_release) | 212 self.select_release) |
191 | 213 |
192 self.bind("<ButtonPress-1>", self.check_deselect, add=True) | 214 self.bind("<ButtonPress-1>", self.check_deselect, add=True) |
193 | 215 |
216 def knob_in(self, curve, value): | |
217 """user turned a hardware knob, which edits the point to the | |
218 left of the current time""" | |
219 if curve != self.curve: | |
220 return | |
221 idx = self.curve.index_before(self.current_time()) | |
222 if idx is not None: | |
223 pos = self.curve.points[idx] | |
224 self.curve.points[idx] = (pos[0], value) | |
225 self.update_curve() | |
226 | |
227 def slider_in(self, curve, value): | |
228 """user pushed on a slider. make a new key""" | |
229 if curve != self.curve: | |
230 return | |
231 | |
232 self.curve.insert_pt((self.current_time(), value)) | |
233 self.update_curve() | |
234 | |
194 def print_state(self, msg=""): | 235 def print_state(self, msg=""): |
195 if 0: | 236 if 0: |
196 print "%s: dragging_dots=%s selecting=%s" % ( | 237 print "%s: dragging_dots=%s selecting=%s" % ( |
197 msg, self.dragging_dots, self.selecting) | 238 msg, self.dragging_dots, self.selecting) |
198 | 239 |
255 def world_from_screen(self,x,y): | 296 def world_from_screen(self,x,y): |
256 start,end = self.zoom | 297 start,end = self.zoom |
257 ht = self.winfo_height() | 298 ht = self.winfo_height() |
258 return x/self.winfo_width()*(end-start)+start, ((ht-5)-y)/(ht-10) | 299 return x/self.winfo_width()*(end-start)+start, ((ht-5)-y)/(ht-10) |
259 | 300 |
260 def input_time(self,val): | 301 def input_time(self, val, forceUpdate=False): |
302 # i tried various things to make this not update like crazy, | |
303 # but the timeline was always missing at startup, so i got | |
304 # scared that things were getting built in a funny order. | |
305 #if self._time == val: | |
306 # return | |
307 | |
261 t=val | 308 t=val |
262 pts = self.screen_from_world((val,0))+self.screen_from_world((val,1)) | 309 pts = self.screen_from_world((val,0))+self.screen_from_world((val,1)) |
263 self.delete('timecursor') | 310 self.delete('timecursor') |
264 self.create_line(*pts,**dict(width=2,fill='red',tags=('timecursor',))) | 311 self.create_line(*pts,**dict(width=2,fill='red',tags=('timecursor',))) |
312 self.have_time_line = True | |
265 self._time = t | 313 self._time = t |
314 if self.knobEnabled: | |
315 self.delete('knob') | |
316 prevKey = self.curve.point_before(t) | |
317 if prevKey is not None: | |
318 pos = self.screen_from_world(prevKey) | |
319 self.create_oval(pos[0] - 8, pos[1] - 8, | |
320 pos[0] + 8, pos[1] + 8, | |
321 outline='#800000', | |
322 tags=('knob',)) | |
323 dispatcher.send("knob out", value=prevKey[1], curve=self.curve) | |
266 | 324 |
267 def update_curve(self,*args): | 325 def update_curve(self,*args): |
268 | 326 |
269 self.zoom = dispatcher.send("zoom area")[0][1] | 327 self.zoom = dispatcher.send("zoom area")[0][1] |
270 cp = self.curve.points | 328 cp = self.curve.points |
483 def dotrelease(self,ev): | 541 def dotrelease(self,ev): |
484 self.print_state("dotrelease") | 542 self.print_state("dotrelease") |
485 if not self.dragging_dots: | 543 if not self.dragging_dots: |
486 return | 544 return |
487 self.last_mouse_world = None | 545 self.last_mouse_world = None |
546 self.dragging_dots = False | |
547 | |
548 class Sliders(BCF2000): | |
549 def __init__(self, cb, knobCallback): | |
550 BCF2000.__init__(self) | |
551 self.cb = cb | |
552 self.knobCallback = knobCallback | |
553 def valueIn(self, name, value): | |
554 if name.startswith("slider"): | |
555 self.cb(int(name[6:]), value / 127) | |
556 if name.startswith("knob"): | |
557 self.knobCallback(int(name[4:]), value / 127) | |
558 | |
488 | 559 |
489 class Curveset: | 560 class Curveset: |
490 curves = None # curvename : curve | 561 curves = None # curvename : curve |
491 def __init__(self): | 562 def __init__(self, sliders=False): |
492 self.curves = {} | 563 """sliders=True means support the hardware sliders""" |
493 def load(self,basename): | 564 self.curves = {} # name : Curve |
565 self.curveName = {} # reverse | |
566 self.sliderCurve = {} # slider number (1 based) : curve name | |
567 self.sliderNum = {} # reverse | |
568 if sliders: | |
569 self.sliders = Sliders(self.hw_slider_in, self.hw_knob_in) | |
570 dispatcher.connect(self.curvesToSliders, "curves to sliders") | |
571 dispatcher.connect(self.knobOut, "knob out") | |
572 self.lastSliderTime = {} # num : time | |
573 self.sliderSuppressOutputUntil = {} # num : time | |
574 self.sliderIgnoreInputUntil = {} | |
575 else: | |
576 self.sliders = None | |
577 | |
578 def load(self,basename, skipMusic=False): | |
494 """find all files that look like basename-curvename and add | 579 """find all files that look like basename-curvename and add |
495 curves with their contents""" | 580 curves with their contents""" |
496 for filename in glob.glob("%s-*"%basename): | 581 for filename in glob.glob("%s-*"%basename): |
497 curvename = filename[filename.rfind('-')+1:] | 582 curvename = filename[filename.rfind('-')+1:] |
583 if skipMusic and curvename in ['music', 'smooth_music']: | |
584 continue | |
498 c=Curve() | 585 c=Curve() |
499 c.load(filename) | 586 c.load(filename) |
500 curvename = curvename.replace('-','_') | 587 curvename = curvename.replace('-','_') |
501 self.add_curve(curvename,c) | 588 self.add_curve(curvename,c) |
502 def save(self,basename): | 589 def save(self,basename): |
503 """writes a file for each curve with a name | 590 """writes a file for each curve with a name |
504 like basename-curvename""" | 591 like basename-curvename""" |
505 for name,cur in self.curves.items(): | 592 for name,cur in self.curves.items(): |
506 cur.save("%s-%s" % (basename,name)) | 593 cur.save("%s-%s" % (basename,name)) |
594 | |
507 def add_curve(self,name,curve): | 595 def add_curve(self,name,curve): |
508 self.curves[name] = curve | 596 self.curves[name] = curve |
509 dispatcher.send("add_curve",sender=self,name=name) | 597 self.curveName[curve] = name |
598 | |
599 if self.sliders and name not in ['smooth_music', 'music']: | |
600 num = len(self.sliderCurve) + 1 | |
601 self.sliderCurve[num] = name | |
602 self.sliderNum[name] = num | |
603 else: | |
604 num = None | |
605 | |
606 dispatcher.send("add_curve", slider=num, knobEnabled=num is not None, | |
607 sender=self,name=name) | |
510 | 608 |
511 def globalsdict(self): | 609 def globalsdict(self): |
512 return self.curves.copy() | 610 return self.curves.copy() |
513 | 611 |
514 def get_time_range(self): | 612 def get_time_range(self): |
524 c = Curve() | 622 c = Curve() |
525 s,e = self.get_time_range() | 623 s,e = self.get_time_range() |
526 c.points.extend([(s,0), (e,0)]) | 624 c.points.extend([(s,0), (e,0)]) |
527 self.add_curve(name,c) | 625 self.add_curve(name,c) |
528 | 626 |
627 def hw_slider_in(self, num, value): | |
628 try: | |
629 curve = self.curves[self.sliderCurve[num]] | |
630 except KeyError: | |
631 return | |
632 | |
633 now = time.time() | |
634 if now < self.sliderIgnoreInputUntil.get(num): | |
635 return | |
636 # don't make points too fast. This is the minimum spacing | |
637 # between slider-generated points. | |
638 self.sliderIgnoreInputUntil[num] = now + .1 | |
639 | |
640 # don't push back on the slider for a little while, since the | |
641 # user might be trying to slowly move it. This should be | |
642 # bigger than the ignore time above. | |
643 self.sliderSuppressOutputUntil[num] = now + .2 | |
644 | |
645 dispatcher.send("slider in", curve=curve, value=value) | |
646 | |
647 def hw_knob_in(self, num, value): | |
648 try: | |
649 curve = self.curves[self.sliderCurve[num]] | |
650 except KeyError: | |
651 return | |
652 dispatcher.send("knob in", curve=curve, value=value) | |
653 | |
654 def curvesToSliders(self, t): | |
655 now = time.time() | |
656 for num, name in self.sliderCurve.items(): | |
657 if now < self.sliderSuppressOutputUntil.get(num): | |
658 continue | |
659 # self.lastSliderTime[num] = now | |
660 | |
661 value = self.curves[name].eval(t) | |
662 self.sliders.valueOut("slider%s" % num, value * 127) | |
663 | |
664 def knobOut(self, curve, value): | |
665 try: | |
666 num = self.sliderNum[self.curveName[curve]] | |
667 except KeyError: | |
668 return | |
669 self.sliders.valueOut("knob%s" % num, value * 127) | |
529 | 670 |
530 class Curvesetview(tk.Frame): | 671 class Curvesetview(tk.Frame): |
531 curves = None # curvename : Curveview | 672 curves = None # curvename : Curveview |
532 def __init__(self,master,curveset,**kw): | 673 def __init__(self, master, curveset, **kw): |
533 self.curves = {} | 674 self.curves = {} |
534 self.curveset = curveset | 675 self.curveset = curveset |
535 tk.Frame.__init__(self,master,**kw) | 676 tk.Frame.__init__(self,master,**kw) |
536 | 677 |
537 f = tk.Frame(self,relief='raised',bd=1) | 678 f = tk.Frame(self,relief='raised',bd=1) |
548 entry.pack(side='left', fill='x',exp=1) | 689 entry.pack(side='left', fill='x',exp=1) |
549 entry.bind("<Key-Return>", new_curve) | 690 entry.bind("<Key-Return>", new_curve) |
550 | 691 |
551 dispatcher.connect(self.add_curve,"add_curve",sender=self.curveset) | 692 dispatcher.connect(self.add_curve,"add_curve",sender=self.curveset) |
552 | 693 |
553 def add_curve(self,name): | 694 def add_curve(self,name, slider=None, knobEnabled=False): |
554 f = tk.Frame(self,relief='raised',bd=1) | 695 f = tk.Frame(self,relief='raised',bd=1) |
555 f.pack(side='top',fill='both',exp=1) | 696 f.pack(side='top',fill='both',exp=1) |
556 | 697 |
557 | 698 |
558 leftside = tk.Frame(f) | 699 leftside = tk.Frame(f) |
559 leftside.pack(side='left') | 700 leftside.pack(side='left') |
560 | 701 |
561 collapsed = tk.IntVar() | 702 collapsed = tk.IntVar() |
562 txt = "curve %r" % name | 703 txt = "curve '%s'" % name |
563 if len(name) > 7: | 704 if len(name) > 7: |
564 txt = repr(name) | 705 txt = name |
565 tk.Label(leftside,text=txt,font="6x10", | 706 tk.Label(leftside,text=txt,font="6x10", |
566 width=15).pack(side='top') | 707 width=15).pack(side='top') |
567 | 708 |
709 sliderLabel = None | |
568 def cmd(): | 710 def cmd(): |
569 if collapsed.get(): | 711 if collapsed.get(): |
712 if sliderLabel: | |
713 sliderLabel.pack_forget() | |
570 f.pack(exp=0) | 714 f.pack(exp=0) |
571 else: | 715 else: |
716 if sliderLabel: | |
717 sliderLabel.pack(side='top') | |
572 f.pack(exp=1) | 718 f.pack(exp=1) |
573 tk.Checkbutton(leftside, text="collapsed", font="6x10", | 719 tk.Checkbutton(leftside, text="collapsed", font="6x10", |
574 variable=collapsed, command=cmd).pack(side='top') | 720 variable=collapsed, command=cmd).pack(side='top') |
575 | 721 |
576 cv = Curveview(f,self.curveset.curves[name]) | 722 if slider is not None: |
723 # slider should have a checkbutton, defaults to off for | |
724 # music tracks | |
725 sliderLabel = tk.Label(leftside, text="Slider %s" % slider, | |
726 fg='#800000', font='arial 12 bold') | |
727 sliderLabel.pack(side='top') | |
728 | |
729 cv = Curveview(f, self.curveset.curves[name], | |
730 knobEnabled=knobEnabled) | |
577 cv.pack(side='left',fill='both',exp=1) | 731 cv.pack(side='left',fill='both',exp=1) |
578 self.curves[name] = cv | 732 self.curves[name] = cv |