Files @ e4a711d9a338
Branch filter:

Location: light9/flax/curvecalc - annotation

drewp
new dispatcher import
45b12307c695
45b12307c695
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
45b12307c695
ba2677823b35
ba2677823b35
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
f5d3492981ab
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
07bac5061d69
ba2677823b35
ba2677823b35
f5d3492981ab
45b12307c695
ba2677823b35
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
ba2677823b35
ba2677823b35
ba2677823b35
45b12307c695
45b12307c695
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
45b12307c695
ba2677823b35
ba2677823b35
ba2677823b35
45b12307c695
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
45b12307c695
45b12307c695
ba2677823b35
45b12307c695
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
45b12307c695
ba2677823b35
45b12307c695
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
45b12307c695
45b12307c695
45b12307c695
ba2677823b35
45b12307c695
45b12307c695
45b12307c695
ba2677823b35
ba2677823b35
45b12307c695
07bac5061d69
07bac5061d69
45b12307c695
07bac5061d69
45b12307c695
ba2677823b35
07bac5061d69
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
07bac5061d69
07bac5061d69
ba2677823b35
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
ba2677823b35
45b12307c695
45b12307c695
ba2677823b35
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
ba2677823b35
45b12307c695
45b12307c695
45b12307c695
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
ba2677823b35
45b12307c695
45b12307c695
45b12307c695
ba2677823b35
ba2677823b35
45b12307c695
07bac5061d69
45b12307c695
ba2677823b35
ba2677823b35
ba2677823b35
07bac5061d69
07bac5061d69
ba2677823b35
45b12307c695
07bac5061d69
07bac5061d69
45b12307c695
45b12307c695
07bac5061d69
45b12307c695
07bac5061d69
07bac5061d69
07bac5061d69
45b12307c695
07bac5061d69
07bac5061d69
07bac5061d69
45b12307c695
07bac5061d69
ba2677823b35
07bac5061d69
45b12307c695
45b12307c695
45b12307c695
ba2677823b35
07bac5061d69
45b12307c695
45b12307c695
07bac5061d69
45b12307c695
45b12307c695
45b12307c695
07bac5061d69
45b12307c695
07bac5061d69
07bac5061d69
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
45b12307c695
#!/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

from zoomcontrol import Zoomcontrol

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)


#######################################################################
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()