Files @ 40e7d08e0123
Branch filter:

Location: light9/bin/curvecalc - annotation

David McClosky
combine_subdict optionally lets you set the name, permanent-ness
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
63601fe0c3b0
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
2072a0dd7b19
f41004d5a507
f41004d5a507
9b360ee8636e
4e7d40c6aa42
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
9b360ee8636e
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
a65a1634fce4
a65a1634fce4
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
a65a1634fce4
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
63601fe0c3b0
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
c5a79314afdf
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
2072a0dd7b19
f41004d5a507
f41004d5a507
f41004d5a507
2072a0dd7b19
f41004d5a507
f41004d5a507
f41004d5a507
233ee9b1e561
b7095e4a6c43
b7095e4a6c43
b7095e4a6c43
b7095e4a6c43
b7095e4a6c43
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
2072a0dd7b19
b7095e4a6c43
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
9827df597f86
9827df597f86
9827df597f86
9827df597f86
9827df597f86
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
63601fe0c3b0
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
63601fe0c3b0
63601fe0c3b0
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
#!/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,optparse
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
from light9.wavelength import wavelength 

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)),
        ('update status',lambda t: str(t)),
        ]:
        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()

parser = optparse.OptionParser()
options,args = parser.parse_args()

try:
    song = args[0]
except IndexError:
    raise SystemExit("song name is required, e.g. '05-mix'")

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

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(song)):
    try:
        subname,expr = line.strip().split(" ",1)
    except ValueError:
        subname = line.strip()
        expr = ""

    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=os.path.join(showconfig.curvesDir(),song))
    print "saved"

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

create_status_lines(root)
for helpline in ["Bindings: C-s save subterms; B1 drag point; C-B1 curve add point; 1..5 add point at time; Esc see current time; Mousewheel zoom",
                 "Available in functions: nsin/ncos period=amp=1; within(a,b) bef(x) aft(x) compare to time; smoove(x) cubic smoothstep; curvename(t) eval curve"]:
    tk.Label(root,text=helpline, font="Helvetica -12 italic",
             anchor='w').pack(side='top',fill='x')

recent_t=[]
later = None
def update():
    global later
    d = music.current_time()
    d.addCallback(update2)
    d.addErrback(updateerr)
def updateerr(e):
    global later
    dispatcher.send("update status",val=e.getErrorMessage())
    if later and not later.cancelled and not later.called: later.cancel()
    later = reactor.callLater(1,update)
def update2(t):
    global recent_t,later
    dispatcher.send("update status",
                    val="ok: receiving times from music player")
    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()