Files @ 07bac5061d69
Branch filter:

Location: light9/flax/curvecalc

drewp
evaluates curves based on input time from ascoltami; outputs dmx
#!/usr/bin/python

from __future__ import division
import xmlrpclib,time,bisect,socket,sys,textwrap
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


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)]
        
    def eval(self,t):
        i = bisect.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,height=130,closeenough=5,**kw)
        self.selected_points=[] # idx of points being dragged
        self.update()
    def screen_from_world(self,p):
        return p[0]*30+5,120-p[1]*100
    def world_from_screen(self,x,y):
        return (x-5)/30,(120-y)/100
    def update(self):
        self.delete('curve')
        linepts=[]
        for p in self.curve.points:
            linepts.extend(self.screen_from_world(p))
        self.create_line(*linepts,**{'tags':'curve'})
        for i,p in enumerate(self.curve.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))
        
    def dotpress(self,ev,dotidx):
        self.selected_points=[dotidx]

    def dotmotion(self,ev,dotidx):
        cp = self.curve.points
        
        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
            
            cp[idx] = newp
        
        self.update()
    def dotrelease(self,ev,dotidx):
        self.selected_points=[]
        print "press",dotidx

        
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')
        tk.Label(f,text="curve %r"%name).pack(side='left')
        cv = Curveview(f,self.curveset.curves[name])
        cv.pack(side='right')
        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('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)


#######################################################################
root=tk.Tk()

m=Music()

cs = Curveset()
csv = Curvesetview(root,cs)
csv.pack()

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',exp=1)
    subterms.append(st)

out = Output(subterms)

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()]),100)),
    ('update period',lambda t: "%.1fms"%(t*1000)),
    ]:
    l = tk.Label(root,anchor='w')
    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)
    

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