Files @ 5322639d61e9
Branch filter:

Location: light9/bin/curvecalc - annotation

Drew Perttula
refactoring and little fixes in curvecalc and keyboardcomposer
font change in KC, cleanup of CC's main section, maybe some little
fixes that i can't remember because darcs doesn't show the changes
at the same time i'm writing this message
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
63601fe0c3b0
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
2072a0dd7b19
f41004d5a507
f41004d5a507
9b360ee8636e
0f112a7dd6b3
0f112a7dd6b3
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
9b360ee8636e
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
e7630a2072bd
e7630a2072bd
e7630a2072bd
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
9bf9685f5aae
9bf9685f5aae
9bf9685f5aae
9bf9685f5aae
e76b112c837e
e76b112c837e
e76b112c837e
e76b112c837e
e76b112c837e
9bf9685f5aae
9bf9685f5aae
9bf9685f5aae
9bf9685f5aae
9bf9685f5aae
9bf9685f5aae
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
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
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
5bfcf309e1ad
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
e3362ad9a123
f41004d5a507
5bfcf309e1ad
5bfcf309e1ad
5bfcf309e1ad
e3362ad9a123
e3362ad9a123
304a918e7488
d482699decbd
d482699decbd
d482699decbd
d482699decbd
f41004d5a507
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
5322639d61e9
f41004d5a507
f41004d5a507
f41004d5a507
0f112a7dd6b3
f41004d5a507
f41004d5a507
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
5bfcf309e1ad
f41004d5a507
2072a0dd7b19
f41004d5a507
f41004d5a507
f41004d5a507
2072a0dd7b19
f41004d5a507
f41004d5a507
5bfcf309e1ad
5bfcf309e1ad
5322639d61e9
5322639d61e9
f41004d5a507
5322639d61e9
f41004d5a507
f41004d5a507
f41004d5a507
2072a0dd7b19
b7095e4a6c43
f41004d5a507
f41004d5a507
f41004d5a507
e3362ad9a123
f41004d5a507
f41004d5a507
e7630a2072bd
9827df597f86
9827df597f86
9827df597f86
9827df597f86
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
9de7bbf50267
f41004d5a507
9de7bbf50267
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
from light9.uihelpers import toplevelat

class Music:
    def __init__(self):
        self.player=None # xmlrpc Proxy to player
        self.recenttime=0

        dispatcher.connect(self.seekplay_or_pause,"music seek")

    def seekplay_or_pause(self,t):
        self.music.seekplay_or_pause(t)
        
    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
    
    def seekplay_or_pause(self,t):
        self.player.callRemote('seekplay_or_pause',t)
        
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):
        subexpr_eval = self.subexpr.eval(t)
        # this stops an error that didn't used to happen. i think when
        # the expr is empty, subexpr_eval becomes None, so this zero
        # is ok
        if subexpr_eval is None:
            subexpr_eval = 0
        if isinstance(subexpr_eval, Submaster.Submaster):
            # if the expression returns a submaster, just return it
            return subexpr_eval
        else:
            # otherwise, return our submaster multiplied by the value returned
            return self.submaster * subexpr_eval

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,music):
        make_attributes_from_args('subterms','music')

        self.recent_t=[]
        self.later = None

        self.update()
        
    def update(self):
        d = music.current_time()
        d.addCallback(self.update2)
        d.addErrback(self.updateerr)
        
    def updateerr(self,e):

        print e.getTraceback()
        dispatcher.send("update status",val=e.getErrorMessage())
        if self.later and not self.later.cancelled and not self.later.called:
            self.later.cancel()
        self.later = reactor.callLater(1,self.update)
        
    def update2(self,t):
        dispatcher.send("update status",
                        val="ok: receiving time from music player")
        if self.later and not self.later.cancelled and not self.later.called:
            self.later.cancel()

        self.later = reactor.callLater(.05, self.update)

        self.recent_t = self.recent_t[-50:]+[t]
        period = (self.recent_t[-1] - self.recent_t[0]) / len(self.recent_t)
        dispatcher.send("update period", val=period)
        self.send_dmx(t)
        
    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=None):
    if expr is None:
        expr = '%s(t)' % subname

    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 sub_commands_tk(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, '')
        newname.set('')

    def reload_subs():
        dispatcher.send('reload all subs')

    tk.Button(f, text="reload subs (C-r)", 
        command=reload_subs).pack(side='left')
    tk.Label(f, text="new subterm named:").pack(side='left')
    entry = tk.Entry(f, textvariable=newname)
    entry.pack(side='left', fill='x', exp=1)
    entry.bind("<Key-Return>", lambda evt: add_cmd())

    return f

def add_subterms_from_file(filename, curveset, subterms, root, ssv):
    for line in file(filename):
        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')

#######################################################################
root=tk.Tk()
root.tk_setPalette("gray50")
toplevelat("curvecalc",root)
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("Curvemaster 3000MX - %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 = []
sub_commands_tk(root, curveset, subterms, root, ssv).pack(side='top',fill='x')

add_subterms_from_file(showconfig.subtermsForSong(song),
                       curveset, subterms, root, ssv)

out = Output(subterms, music)

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)
root.bind("<Control-Key-r>", lambda evt: dispatcher.send('reload all subs'))

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; S-Esc see curtime to end; Mousewheel zoom; C-p play/pause music at mouse",
                 "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')

#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=20)
if 0:
    sys.path.append("/home/drewp/projects/cuisine/pour")
    from utils import runstats
    runstats("reactor.run()")
else:
    reactor.run()