Files @ 2072a0dd7b19
Branch filter:

Location: light9/bin/curvecalc

drewp@bigasterisk.com
factor out LIGHT9_SHOW
#!/usr/bin/python

"""
todo: curveview should preserve more objects, for speed maybe

"""
from __future__ import division
import xmlrpclib,time,socket,sys,textwrap,math,glob,random,os
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

import run_local
from light9 import Submaster, dmxclient, networking, showconfig
from light9.TLUtility import make_attributes_from_args
from light9.zoomcontrol import Zoomcontrol
from light9.curve import Curve, Curveview, Curveset, Curvesetview

sys.path.append("../../semprini")
from lengther import wavelength # for measuring duration of .wav

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(networking.musicUrl())
#            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)
            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,expr=""):
        self.curveset = curveset
        self.lasteval = None
        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="ok")
        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(self,textvariable=self.evar)
        e.pack(side='left',fill='x',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(self)
        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 __init__(self,submaster,subexpr):
        make_attributes_from_args('submaster','subexpr')
    def scaled(self,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)
        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):

        scaledsubs=[]
        for st in self.subterms:
            scl = st.scaled(t)
            scaledsubs.append(scl)
        out = Submaster.sub_maxes(*scaledsubs)
        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 [
        ('input time',lambda t: "%.2fs"%t),
        ('output levels',
         lambda levels: textwrap.fill("; ".join(["%s:%.2f"%(n,v)
                                                 for n,v in
                                                 levels.items()[:5]
                                                 if v>0]),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)

def savesubterms(filename,subterms):
    s=""
    for st in subterms:
        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 add_one_subterm(subname, curveset, subterms, root, ssv, expr=''):
    term = Subterm(Submaster.Submaster(subname), Subexpr(curveset,expr))
    subterms.append(term)

    stv=Subtermview(ssv,term)
    # stv.pack(side='top',fill='x')

    ssv.add_subtermview(stv)

    return term

def subterm_adder(master, curveset, subterms, root, ssv):
    f=tk.Frame(master,relief='raised',bd=1)
    newname = tk.StringVar()

    def add_cmd():
        add_one_subterm(newname.get(), curveset, subterms, root, ssv, '')

    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.tk_setPalette("gray50")
root.wm_geometry("1120x850")
root.tk_focusFollowsMouse()

music=Music()

zc = Zoomcontrol(root)
zc.pack(side='top',fill='x')

curveset = Curveset()
csv = Curvesetview(root,curveset)
csv.pack(side='top',fill='both',exp=1)

ssv = SubtermSetView(root)
ssv.pack(side='top', fill='x')

song = sys.argv[1]
root.title("Curemaster 2000MX - %s" % song)

musicfilename = showconfig.songFilename(song)
maxtime = wavelength(musicfilename)
dispatcher.send("max time",maxtime=maxtime)
dispatcher.connect(lambda: maxtime, "get max time",weak=0)
curveset.load(basename=os.path.join(showconfig.curvesDir(),song))

subterms = []
subterm_adder(root, curveset, subterms, root, ssv).pack(side='top',fill='x')
for line in file(showconfig.subtermsForSong()):
    subname,expr = line.strip().split(" ",1)

    term = add_one_subterm(subname, curveset, subterms, root, ssv, expr)
    
    # stv=Subtermview(root,term)
    # stv.pack(side='top',fill='x')

out = Output(subterms)

def savekey(*args):
    print "saving",song
    savesubterms(showconfig.subtermsForSong(song),subterms)
    curveset.save(basename="curves/"+song)
    print "saved"

    
root.bind("<Control-Key-s>",savekey)

create_status_lines(root)
    
recent_t=[]
later = None
def update():
    global later
    d = music.current_time()
    d.addCallback(update2)
    d.addErrback(updateerr)
def updateerr(e):
    global later
    print "err",e
    if later and not later.cancelled and not later.called: later.cancel()
    later = reactor.callLater(1,update)
def update2(t):
    global recent_t,later

    if later and not later.cancelled and not later.called: later.cancel()
    later = reactor.callLater(.01,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()

#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")
    from utils import runstats
    runstats("reactor.run()")
else:
    reactor.run()