changeset 205:3905d3c92aaa

twisted mainloop, more row-change keys, xmlrpc fadesub command on port 8050
author drewp
date Sun, 10 Apr 2005 15:03:24 +0000
parents 186a66095036
children 851cf44cea40
files flax/KeyboardComposer.py flax/MusicTime.py flax/Submaster.py flax/TimelineDMX.py flax/add_sub flax/curvecalc flax/dmxchanedit.py flax/kcclient flax/zoomcontrol.py
diffstat 9 files changed, 404 insertions(+), 100 deletions(-) [+]
line wrap: on
line diff
--- a/flax/KeyboardComposer.py	Sun Apr 10 15:00:36 2005 +0000
+++ b/flax/KeyboardComposer.py	Sun Apr 10 15:03:24 2005 +0000
@@ -1,8 +1,10 @@
-from __future__ import nested_scopes
+from __future__ import division,nested_scopes
 import sys, time
 sys.path.append('..')
 from Widgets.Fadable import Fadable
 
+from twisted.internet import reactor,tksupport
+from twisted.web import xmlrpc, server
 from Tix import *
 import math, atexit, pickle
 from Submaster import Submasters, sub_maxes
@@ -40,10 +42,10 @@
         self.slider_var = DoubleVar()
         self.slider_var.set(current_level)
         self.scale = SubScale(self, variable=self.slider_var, width=20)
-        namelabel = Label(self, text=name, font="Arial 8", bg='black',
+        namelabel = Label(self, text=name, font="Arial 11", bg='black',
             fg='white')
         namelabel.pack(side=TOP)
-        levellabel = Label(self, textvariable=self.slider_var, font="Arial 8",
+        levellabel = Label(self, textvariable=self.slider_var, font="Arial 11",
             bg='black', fg='white')
         levellabel.pack(side=TOP)
         self.scale.pack(side=BOTTOM, expand=1, fill=BOTH)
@@ -70,6 +72,7 @@
         self.rows = [] # this holds Tk Frames for each row
         self.slider_vars = {} # this holds subname:sub Tk vars
         self.slider_table = {} # this holds coords:sub Tk vars
+        self.name_to_subtk = {} # subname : SubmasterTk instance
         self.current_row = 0
         
         self.make_key_hints()
@@ -123,13 +126,16 @@
                     lambda evt, num=keys.index(key), d=d: \
                         self.got_nudger(num, d, full=1))
 
-        # page up and page down change the row
-        for key in '<Prior> <Next> <Control-n> <Control-p>'.split():
+        # Row changing:
+        # Page dn, C-n, and ] do down
+        # Page up, C-p, and ' do up
+        for key in '<Prior> <Next> <Control-n> <Control-p> ' \
+                   '<Key-bracketright> <Key-apostrophe>'.split():
             tkobject.bind(key, self.change_row)
 
     def change_row(self, event):
         diff = 1
-        if event.keysym in ('Prior', 'p'):
+        if event.keysym in ('Prior', 'p', 'bracketright'):
             diff = -1
         old_row = self.current_row
         self.current_row += diff
@@ -163,6 +169,7 @@
             current_level = self.current_sub_levels.get(sub.name, 0)
             subtk = self.draw_sub_slider(row, col, sub.name, current_level)
             self.slider_table[(rowcount, col)] = subtk
+            self.name_to_subtk[sub.name] = subtk
             col += 1
             col %= 10
 
@@ -203,6 +210,7 @@
         print "saving current levels as", subname
         sub = self.get_levels_as_sub()
         sub.name = subname
+        sub.temporary = 0
         sub.save()
 
     def save(self):
@@ -236,6 +244,20 @@
         self.buttonframe.destroy()
         self.draw_ui()
 
+class LevelServer(xmlrpc.XMLRPC):
+    def __init__(self,name_to_subtk):
+        self.name_to_subtk = name_to_subtk
+        
+    def xmlrpc_fadesub(self,subname,level,secs):
+        """submaster will fade to level in secs"""
+        try:
+            self.name_to_subtk[subname].scale.fade(level,secs)
+            ret='ok'
+        except Exception,e:
+            ret=str(e)
+        return ret
+
+
 if __name__ == "__main__":
     s = Submasters()
 
@@ -243,9 +265,12 @@
     tl = toplevelat("Keyboard Composer", existingtoplevel=root)
     kc = KeyboardComposer(tl, s, dmxdummy=0)
     kc.pack(fill=BOTH, expand=1)
-    atexit.register(kc.save)
-    try:
-        mainloop()
-    except KeyboardInterrupt:
-        tl.destroy()
-        sys.exit()
+
+    ls = LevelServer(kc.name_to_subtk)
+    reactor.listenTCP(8050, server.Site(ls))
+
+    root.bind("<Destroy>",reactor.stop)
+    root.protocol('WM_DELETE_WINDOW', reactor.stop)
+    reactor.addSystemEventTrigger('after','shutdown',kc.save)
+    tksupport.install(root,ms=10)
+    reactor.run()
--- a/flax/MusicTime.py	Sun Apr 10 15:00:36 2005 +0000
+++ b/flax/MusicTime.py	Sun Apr 10 15:03:24 2005 +0000
@@ -34,7 +34,7 @@
         self.after(100, self.update_time)
 
 if __name__ == "__main__":
-    from optik import OptionParser
+    from optparse import OptionParser
     parser = OptionParser()
     parser.add_option("-s", "--server", default='dash')
     parser.add_option("-p", "--port", default=8040, type='int')
--- a/flax/Submaster.py	Sun Apr 10 15:00:36 2005 +0000
+++ b/flax/Submaster.py	Sun Apr 10 15:03:24 2005 +0000
@@ -37,6 +37,7 @@
             print "Can't read file for sub: %s" % self.name
     def save(self):
         if self.temporary:
+            print "not saving temporary sub named",self.name
             return
 
         subfile = file("subs/%s" % self.name, 'w')
--- a/flax/TimelineDMX.py	Sun Apr 10 15:00:36 2005 +0000
+++ b/flax/TimelineDMX.py	Sun Apr 10 15:03:24 2005 +0000
@@ -27,7 +27,7 @@
         print "TimelineDMX: set timeline to", tlname
         self.show.set_timeline(tlname)
     def find_player(self):
-        self.player = xmlrpclib.Server("http://localhost:8040")
+        self.player = xmlrpclib.Server("http://spot:8040")
     def send_levels(self):
         levels = self.show.calc_active_submaster().get_dmx_list()
         
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flax/add_sub	Sun Apr 10 15:03:24 2005 +0000
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+# usage: add_sub [-l sublevel] subname subterms_to_add_to
+
+from optparse import OptionParser
+
+parser = OptionParser()
+parser.add_option('-l', '--level', default='0')
+opts, args = parser.parse_args()
+print 'debug', opts, args
+
+sub = args.pop(0)
+print "adding '%s' at %s" % (sub, opts.level)
+
+for subterm in args:
+    print "subterm", subterm
+    print
+    filename = 'subterms/%s' % subterm
+    subs = [line.split(None, 1)[0] for line in file(filename).readlines()]
+    if sub not in subs:
+        f = file(filename, 'a')
+        print 'appended!'
+        print >>f, "%s %s" % (sub, opts.level)
--- a/flax/curvecalc	Sun Apr 10 15:00:36 2005 +0000
+++ b/flax/curvecalc	Sun Apr 10 15:03:24 2005 +0000
@@ -5,15 +5,18 @@
 
 """
 from __future__ import division
-import xmlrpclib,time,socket,sys,textwrap,math,glob
+import xmlrpclib,time,socket,sys,textwrap,math,glob,random
 from bisect import bisect_left,bisect,bisect_right
 import Tkinter as tk
 from dispatch import dispatcher
 from twisted.internet import reactor,tksupport
+import twisted
 from twisted.web.xmlrpc import Proxy
 
 sys.path.append("../light8")
 import dmxclient
+sys.path.append("../../semprini")
+from lengther import wavelength
 import Submaster
 from TLUtility import make_attributes_from_args
 
@@ -23,15 +26,19 @@
     """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),(10,0)]
 
     def load(self,filename):
+        self.points[:]=[]
         for line in file(filename):
             self.points.append(tuple([float(a) for a in line.split()]))
         self.points.sort()
         dispatcher.send("points changed",sender=self)
 
     def save(self,filename):
+        if filename.endswith('-music') or filename.endswith('_music'):
+            print "not saving music track"
+            return
         f = file(filename,'w')
         for p in self.points:
             f.write("%s %s\n" % p)
@@ -50,21 +57,45 @@
         y = p1[1]+(p2[1]-p1[1])*frac
         return y
 
+    def insert_pt(self,new_pt):
+        i = bisect(self.points,(new_pt[0],None))
+        self.points.insert(i,new_pt)
     __call__=eval
 
 class Curveview(tk.Canvas):
     def __init__(self,master,curve,**kw):
         self.curve=curve
+        self._time = 0
         tk.Canvas.__init__(self,master,width=10,height=10,
                            relief='sunken',bd=1,
-                           closeenough=5,**kw)
+                           closeenough=5,takefocus=1, **kw)
         self.selected_points=[] # idx of points being dragged
         self.update()
-        self.bind("<Enter>",self.focus)
+        # self.bind("<Enter>",self.focus)
         dispatcher.connect(self.input_time,"input time")
         dispatcher.connect(self.update,"zoom changed")
         dispatcher.connect(self.update,"points changed",sender=self.curve)
         self.bind("<Configure>",self.update)
+        for x in range(1, 6):
+            def add_kb_marker_point(evt, x=x):
+                print "add_kb_marker_point", evt
+                self.add_point((self.current_time(), (x - 1) / 4.0))
+
+            self.bind("<Key-%s>" % x, add_kb_marker_point)
+
+
+        for butnum,factor in (5, 1.5),(4, 1/1.5):
+            self.bind("<ButtonPress-%s>"%butnum,
+                      lambda ev,factor=factor:
+                      dispatcher.send("zoom about mouse",
+                                      t=self.world_from_screen(ev.x,0)[0],
+                                      factor=factor))
+        self.bind("<Key-Escape>",lambda ev:
+                  dispatcher.send("see time",
+                                  t=self.current_time()))
+    def current_time(self):
+        return self._time
+
     def screen_from_world(self,p):
         start,end = self.zoom
         ht = self.winfo_height()
@@ -79,6 +110,7 @@
         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',)))
+        self._time = t
     def update(self,*args):
 
         self.zoom = dispatcher.send("zoom area")[0][1]
@@ -91,14 +123,55 @@
         visrightidx = min(len(cp)-1,bisect_left(cp,(visible_x[1],None))+1)
                              
         visible_points = cp[visleftidx:visrightidx+1]
+        visible_idxs = range(visleftidx,visrightidx+1)
         
         self.delete('curve')
+
+        self._draw_markers(visible_x)
+        
+        self._draw_line(visible_idxs,visible_points)
+        
+        self.dots = {} # idx : canvas rectangle
+
+        if len(visible_points)<50:
+            self._draw_handle_points(visible_idxs,visible_points)
+
+    def _draw_markers(self,visible_x):
+        mark = self._draw_one_marker
+
+        mark(0,"0")
+        t1,t2=visible_x
+        if t2-t1<30:
+            for t in range(int(t1),int(t2)+1):
+                mark(t,str(t))
+        mark(-4,"-4")
+
+        endtimes = dispatcher.send("get max time")
+        if endtimes:
+            endtime = endtimes[0][1]
+            mark(endtime,"end %.1f"%endtime)
+            mark(endtime+10,"post %.1f"%(endtime+10))
+        
+    def _draw_one_marker(self,t,label):
+        x = self.screen_from_world((t,0))[0]
+        self.create_line(x,self.winfo_height(),x,self.winfo_height()-20,
+                         tags=('curve',))
+        self.create_text(x,self.winfo_height()-20,text=label,anchor='s',
+                         tags=('curve',))
+
+
+    def _draw_line(self,visible_idxs,visible_points):
         linepts=[]
-        for p in visible_points:
+        step=1
+        linewidth=2
+        if len(visible_points)>800:
+            step = int(len(visible_points)/800)
+            linewidth=1
+        for p in visible_points[::step]:
             linepts.extend(self.screen_from_world(p))
-        if not linepts:
+        if len(linepts)<4:
             return
-        line = self.create_line(*linepts,**{'tags':'curve'})
+        line = self.create_line(*linepts,**dict(width=linewidth,tags='curve'))
 
         # canvas doesnt have keyboard focus, so i can't easily change the
         # cursor when ctrl is pressed
@@ -106,35 +179,45 @@
         #            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.tag_bind(line,"<Control-ButtonPress-1>",self.newpointatmouse)
 
-        self.dots = {} # idx : canvas rectangle
 
-        if len(visible_points)<50: 
-            for i,p in enumerate(visible_points):
-                rad=3
-                p = self.screen_from_world(p)
-                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
+    def _draw_handle_points(self,visible_idxs,visible_points):
+        for i,p in zip(visible_idxs,visible_points):
+            rad=3
+            worldp = p
+            p = self.screen_from_world(p)
+            dot = self.create_rectangle(p[0]-rad,p[1]-rad,p[0]+rad,p[1]+rad,
+                                        outline='black',fill='blue',
+                                        tags=('curve','point', 'handle%d' % i))
+            if worldp[1] == 0:
+                rad += 3
+                dot2 = self.create_oval(p[0]-rad,p[1]-rad,
+                                             p[0]+rad,p[1]+rad,
+                                             outline='darkgreen',
+                                             tags=('curve','point', 'handle%d' % i))
+            self.tag_bind('handle%d' % i,"<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
+        self.highlight_selected_dots()
         
-        p = self.world_from_screen(ev.x,ev.y)
-        i = bisect(cp,(p[0],None))
 
+    def newpointatmouse(self, ev):
+        p = self.world_from_screen(ev.x,ev.y)
+        x, y = p
+        y = max(0, y)
+        y = min(1, y)
+        p = x, y
+        self.add_point(p)
+
+    def add_point(self, p):
         self.unselect()
-        cp.insert(i,p)
+        self.curve.insert_pt(p)
         self.update()
 
     def highlight_selected_dots(self):
@@ -150,7 +233,6 @@
 
     def dotmotion(self,ev,dotidx):
         cp = self.curve.points
-
         moved=0
         for idx in self.selected_points:
             x,y = self.world_from_screen(ev.x,ev.y)
@@ -178,9 +260,10 @@
         """find all files that look like basename-curvename and add
         curves with their contents"""
         for filename in glob.glob("%s-*"%basename):
-            curvename = filename[filename.rfind('-')+1:]
+            curvename = filename[filename.find('-')+1:]
             c=Curve()
             c.load(filename)
+            curvename = curvename.replace('-','_')
             self.add_curve(curvename,c)            
     def save(self,basename):
         """writes a file for each curve with a name
@@ -192,6 +275,15 @@
         dispatcher.send("add_curve",sender=self,name=name)
     def globalsdict(self):
         return self.curves.copy()
+    def new_curve(self,name):
+        if name=="":
+            print "no name given"
+            return
+        while name in self.curves:
+           name=name+"-1"
+
+        self.add_curve(name,Curve())
+
 
 class Curvesetview(tk.Frame):
     curves = None # curvename : Curveview
@@ -199,11 +291,21 @@
         self.curves = {}
         self.curveset = curveset
         tk.Frame.__init__(self,master,**kw)
+        
+        f = tk.Frame(self,relief='raised',bd=1)
+        f.pack(side='top',fill='x')
+        tk.Button(f,text="new curve named:",
+                  command=lambda: self.curveset.new_curve(self.newcurvename.get())).pack(side='left')
+        self.newcurvename = tk.StringVar()
+        tk.Entry(f,textvariable=self.newcurvename).pack(side='left',
+                                                        fill='x',exp=1)
+        
+        
         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')
+        tk.Label(f,text="curve %r"%name,width=15).pack(side='left')
         cv = Curveview(f,self.curveset.curves[name])
         cv.pack(side='right',fill='both',exp=1)
         self.curves[name] = cv
@@ -216,11 +318,11 @@
     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")
-            d.addCallback(lambda l: dispatcher.send("max time",maxtime=l))
-            d = self.player.callRemote("songname")
-            d.addCallback(lambda n: dispatcher.send("songname",name=n))
+            self.player = Proxy("http://miles:8040")
+#            d = self.player.callRemote("songlength")
+#            d.addCallback(lambda l: dispatcher.send("max time",maxtime=l))
+#            d = self.player.callRemote("songname")
+#            d.addCallback(lambda n: dispatcher.send("songname",name=n))
         d = self.player.callRemote('gettime')
         def sendtime(t):
             dispatcher.send("input time",val=t)
@@ -232,22 +334,50 @@
         
 class Subexpr:
     curveset = None
-    def __init__(self,curveset):
+    def __init__(self,curveset,expr=""):
         self.curveset = curveset
         self.lasteval = None
-        self.expr=""
+        self.expr=expr
+        self._smooth_random_items = [random.random() for x in range(100)]
     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
+
+        glo['nsin'] = lambda x: (math.sin(x * (2 * math.pi)) + 1) / 2
+        glo['ncos'] = lambda x: (math.cos(x * (2 * math.pi)) + 1) / 2
+        glo['within'] = lambda a, b: a < t < b
+        glo['bef'] = lambda x: t < x
+        glo['aft'] = lambda x: x < t
+        glo['smoove'] = lambda x: -2 * (x ** 3) + 3 * (x ** 2)
+
+        def smooth_random(speed=1):
+            """1 = new stuff each second, <1 is slower, fade-ier"""
+            x = (t * speed) % len(self._smooth_random_items)
+            x1 = int(x)
+            x2 = (int(x) + 1) % len(self._smooth_random_items)
+            y1 = self._smooth_random_items[x1]
+            y2 = self._smooth_random_items[x2]
+            return y1 + (y2 - y1) * ((x - x1))
+
+        def notch_random(speed=1):
+            """1 = new stuff each second, <1 is slower, notch-ier"""
+            x = (t * speed) % len(self._smooth_random_items)
+            x1 = int(x)
+            y1 = self._smooth_random_items[x1]
+            return y1
+            
+        glo['noise'] = smooth_random
+        glo['notch'] = notch_random
+
         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")
+            dispatcher.send("expr_error",sender=self,exc="ok")
         return self.lasteval
 
     def expr():
@@ -283,18 +413,23 @@
 
 class Subterm:
     """one Submaster and its Subexpr"""
+    def __init__(self,submaster,subexpr):
+        make_attributes_from_args('submaster','subexpr')
     def scaled(self,t):
-        return self.sub * self.subexpr.eval(t)
+        return self.submaster * 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')
+        l = tk.Label(self,text="sub %r" % self.subterm.submaster.name)
+        l.pack(side='left')
         sev=Subexprview(self,self.subterm.subexpr)
         sev.pack(side='left',fill='both',exp=1)
 
 class Output:
+    lastsendtime=0
+    lastsendlevs=None
     def __init__(self,subterms):
         make_attributes_from_args('subterms')
     def send_dmx(self,t):
@@ -304,8 +439,14 @@
             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)
+        levs = out.get_levels()
+        now=time.time()
+        if now-self.lastsendtime>5 or levs!=self.lastsendlevs:
+            dispatcher.send("output levels",val=levs)
+            dmxclient.outputlevels(out.get_dmx_list(),
+                                   twisted=1,clientid='curvecalc')
+            self.lastsendtime = now
+            self.lastsendlevs = levs
 
 def create_status_lines(master):
     for signame,textfilter in [
@@ -313,7 +454,8 @@
         ('output levels',
          lambda levels: textwrap.fill("; ".join(["%s:%.2f"%(n,v)
                                                  for n,v in
-                                                 levels.items()]),70)),
+                                                 levels.items()[:5]
+                                                 if v>0]),70)),
         ('update period',lambda t: "%.1fms"%(t*1000)),
         ]:
         l = tk.Label(master,anchor='w',justify='left')
@@ -323,19 +465,54 @@
                            signame,weak=0)
 
 def savesubterms(filename,subterms):
-    f = file(filename,'w')
+    s=""
     for st in subterms:
-        f.write("%s %s\n" % (st.sub.name,st.subexpr.expr))
-    f.close()
+        s=s+"%s %s\n" % (st.submaster.name,st.subexpr.expr)
+    
+    file(filename,'w').write(s)
+
+class SubtermSetView(tk.Frame):
+    def __init__(self, master, *args, **kw):
+        tk.Frame.__init__(self, master, *args, **kw)
+        self.cur_row = 0
+        self.cur_col = 0
+        self.ncols = 2
+    def add_subtermview(self, stv):
+        stv.grid(row=self.cur_row, column=self.cur_col, sticky='news')
+        self.columnconfigure(self.cur_col, weight=1)
+
+        self.cur_col += 1
+        self.cur_col %= self.ncols
+        if self.cur_col == 0:
+            self.cur_row += 1
 
-def save(song,subterms,curveset):
-    savesubterms("subterms/"+song,subterms)
-    curveset.save(basename="curves/"+song)
+def add_one_subterm(subname, curveset, subterms, root, expr=''):
+    term = Subterm(Submaster.Submaster(subname), Subexpr(curveset,expr))
+    subterms.append(term)
+
+    stv=Subtermview(ssv,term)
+    # stv.pack(side='top',fill='x')
+    global ssv
+    ssv.add_subtermview(stv)
+
+    return term
 
+def subterm_adder(master, curveset, subterms, root):
+    f=tk.Frame(master,relief='raised',bd=1)
+    newname = tk.StringVar()
+
+    def add_cmd():
+        add_one_subterm(newname.get(), curveset, subterms, root, '')
+
+    tk.Button(f,text="new subterm named:", command=add_cmd).pack(side='left')
+    tk.Entry(f,textvariable=newname).pack(side='left',fill='x',exp=1)
+    return f
+    
 #######################################################################
 root=tk.Tk()
-root.wm_geometry("790x930")
-#root.tk_focusFollowsMouse()
+root.tk_setPalette("gray50")
+root.wm_geometry("1120x850")
+root.tk_focusFollowsMouse()
 
 music=Music()
 
@@ -346,29 +523,38 @@
 csv = Curvesetview(root,curveset)
 csv.pack(side='top',fill='both',exp=1)
 
-song = "16mix.wav"
+ssv = SubtermSetView(root)
+ssv.pack(side='top', fill='x')
 
+song = sys.argv[1]
+root.title("Curemaster 2000MX - %s" % song)
+
+musicfilename = "/my/music/projects/sharlyn2004/%s.wav" % song
+maxtime = wavelength(musicfilename)
+dispatcher.send("max time",maxtime=maxtime)
+dispatcher.connect(lambda: maxtime, "get max time",weak=0)
 curveset.load(basename="curves/"+song)
 
 subterms = []
+subterm_adder(root, curveset, subterms, root).pack(side='top',fill='x')
 for line in file("subterms/"+song):
     subname,expr = line.strip().split(" ",1)
 
-    term = Subterm()
-
-    sexpr = Subexpr(curveset)
-    sexpr.expr = expr
+    term = add_one_subterm(subname, curveset, subterms, root, expr)
     
-    term.sub = Submaster.Submaster(subname)
-    term.subexpr = sexpr
-    subterms.append(term)
-    
-    stv=Subtermview(root,term)
-    stv.pack(side='top',fill='x')
+    # stv=Subtermview(root,term)
+    # stv.pack(side='top',fill='x')
 
 out = Output(subterms)
 
-#save(song,subterms,curveset)
+def savekey(*args):
+    print "saving",song
+    savesubterms("subterms/"+song,subterms)
+    curveset.save(basename="curves/"+song)
+    print "saved"
+
+    
+root.bind("<Control-Key-s>",savekey)
 
 create_status_lines(root)
     
@@ -396,6 +582,13 @@
     out.send_dmx(t)
 update()
 
+#def logprint(msg):
+#    print "log",msg
+#twisted.python.log.addObserver(logprint)
+
+root.bind("<Control-Key-q>",lambda ev: reactor.stop)
+root.bind("<Destroy>",lambda ev: reactor.stop)
+root.protocol('WM_DELETE_WINDOW', reactor.stop)
 tksupport.install(root,ms=10)
 if 0:
     sys.path.append("/home/drewp/projects/editor/pour")
--- a/flax/dmxchanedit.py	Sun Apr 10 15:00:36 2005 +0000
+++ b/flax/dmxchanedit.py	Sun Apr 10 15:03:24 2005 +0000
@@ -12,7 +12,7 @@
 from uihelpers import make_frame, colorlabel, eventtoparent
 from dispatch import dispatcher
 
-stdfont = ('Arial', 10)
+stdfont = ('Arial', 12)
 
 class Onelevel(tk.Frame):
     """a name/level pair"""
@@ -53,20 +53,23 @@
             self.desc_lab.config(bg='cyan')
             self._start_y=ev.y
             self._start_lev=self.currentlevel
-#        self.bind("<ButtonPress-1>",b1down)
         def b1motion(ev):
             delta=self._start_y-ev.y
             self.changelevel(self._start_lev+delta*.005)
-#        self.bind("<B1-Motion>",b1motion)
         def b1up(ev):
             self.desc_lab.config(bg='black')
-#        self.bind("<B1-ButtonRelease>",b1up)
+        def b3up(ev):
+            self.changelevel(0.0)
+        def b3down(ev):
+            self.changelevel(1.0)
 
         # make the buttons work in the child windows
         for w in self.winfo_children():
             for e,func in (('<ButtonPress-1>',b1down),
                            ('<B1-Motion>',b1motion),
-                           ('<ButtonRelease-1>',b1up)):
+                           ('<ButtonRelease-1>',b1up),
+                           ('<ButtonRelease-3>', b3up),
+                           ('<ButtonPress-3>', b3down)):
                 w.bind(e,func)
 #                w.bind(e,lambda ev,e=e: eventtoparent(ev,e))
         
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flax/kcclient	Sun Apr 10 15:03:24 2005 +0000
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+"""send KeyboardComposer a fade request, for use from the shell"""
+
+import sys,xmlrpclib
+
+subname = sys.argv[1]
+level = float(sys.argv[2])
+fadesecs = 0
+if len(sys.argv)>3:
+    fadesecs = float(sys.argv[3])
+
+levelserver = xmlrpclib.ServerProxy("http://localhost:8050")
+
+levelserver.fadesub(subname,level,fadesecs)
+
+
--- a/flax/zoomcontrol.py	Sun Apr 10 15:00:36 2005 +0000
+++ b/flax/zoomcontrol.py	Sun Apr 10 15:03:24 2005 +0000
@@ -4,6 +4,8 @@
 
 class Zoomcontrol(object,tk.Canvas):
 
+    mintime=-5
+
     def maxtime():
         doc = "seconds at the right edge of the bar"
         def fget(self): return self._maxtime
@@ -15,7 +17,7 @@
     
     def start():
         def fget(self): return self._start
-        def fset(self,v): self._start = max(0,v)
+        def fset(self,v): self._start = max(self.mintime,v)
         return locals()
     start = property(**start())
 
@@ -39,26 +41,67 @@
         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))
+        if 0:
+            # works, but you have to stay in the widget while you drag
+            self.bind("<ButtonPress-1>",self.press)
+            self.tag_bind(self.leftbrack,"<B1-Motion>",
+                          lambda ev: self.adjust(ev,'start'))
+            self.tag_bind(self.rightbrack,"<B1-Motion>",
+                          lambda ev: self.adjust(ev,'end'))
+            self.tag_bind(self.shade,"<B1-Motion>",
+                          lambda ev: self.adjust(ev,'offset'))
+        else:
+            # works better
+            # bind to buttonpress wasnt working, but Enter is good enough
+            self.tag_bind(self.leftbrack,"<Enter>",
+                          lambda ev: self.press(ev,'start'))
+            self.tag_bind(self.shade,"<Enter>",
+                          lambda ev: self.press(ev,'offset'))
+            self.tag_bind(self.rightbrack,"<Enter>",
+                          lambda ev: self.press(ev,'end'))
+            self.bind("<B1-Motion>",self.adjust)
+            self.bind("<ButtonRelease-1>",self.release)
+        
         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)
+        dispatcher.connect(lambda maxtime: (setattr(self,'maxtime',maxtime+15),
+                                            self.updatewidget()),
+                           "max time",weak=0)
+        dispatcher.connect(self.zoom_about_mouse,"zoom about mouse")
+        dispatcher.connect(self.see_time,"see time")
         self.created=1
+    def zoom_about_mouse(self,t,factor):
+        self.start = t - factor*(t-self.start)
+        self.end = t + factor*(self.end-t)
+        self.updatewidget()
+        dispatcher.send("zoom changed")
+    def see_time(self,t):
+        margin = (self.end-self.start)*.5 # centering is nicest
+        if t<self.start:
+            self.offset-=(self.start-t)+margin
+        if t>self.end:
+            self.offset+=(t-self.end)+margin
+        self.updatewidget()
+        dispatcher.send("zoom changed")
+            
     def input_time(self,val):
         t=val
         x=self.can_for_t(t)
         self.coords(self.time,x,0,x,self.winfo_height())
+    def press(self,ev,attr):
+        self.adjustingattr = attr
+        
+    def release(self,ev):
+        if hasattr(self,'adjustingattr'): del self.adjustingattr
+        if hasattr(self,'lastx'): del self.lastx
+    def adjust(self,ev,attr=None):
 
-    def adjust(self,attr,ev):
+        if not hasattr(self,'adjustingattr'):
+            return
+        attr = self.adjustingattr
+        
         if not hasattr(self,'lastx'):
-            return
+            self.lastx = ev.x
         new = self.can_for_t(getattr(self,attr)) + (ev.x - self.lastx)
         self.lastx = ev.x
         setattr(self,attr,self.t_for_can(new))
@@ -77,9 +120,9 @@
     offset = property(**offset())
 
     def can_for_t(self,t):
-        return t/self.maxtime*(self.winfo_width()-30)+20
+        return (t-self.mintime)/(self.maxtime-self.mintime)*(self.winfo_width()-30)+20
     def t_for_can(self,x):
-        return (x-20)/(self.winfo_width()-30)*self.maxtime
+        return (x-20)/(self.winfo_width()-30)*(self.maxtime-self.mintime)+self.mintime
 
     def updatewidget(self,*args):
         """redraw pieces based on start/end"""
@@ -90,7 +133,7 @@
         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.coords(self.shade,scan+5,y1+lip,ecan-5,y2-lip)
         self.delete("tics")
         lastx=-1000
         for t in range(0,int(self.maxtime)):