Files @ a6662d61ebcd
Branch filter:

Location: light9/bin/curvecalc - annotation

Drew Perttula
SC, KC, CC now run and seem to load and save ok. CC does not have any rdf for its data files
f41004d5a507
f41004d5a507
f41004d5a507
a6662d61ebcd
a6662d61ebcd
a6662d61ebcd
a6662d61ebcd
a6662d61ebcd
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
63601fe0c3b0
f41004d5a507
f41004d5a507
d4aff817a304
d4aff817a304
d4aff817a304
d4aff817a304
f41004d5a507
f41004d5a507
f41004d5a507
8a1ec8aca432
2193eab0650b
2193eab0650b
2193eab0650b
2193eab0650b
f41004d5a507
f41004d5a507
2072a0dd7b19
1c590824dd14
f41004d5a507
9b360ee8636e
0f112a7dd6b3
0f112a7dd6b3
8a1ec8aca432
f41004d5a507
1c590824dd14
1c590824dd14
1c590824dd14
1c590824dd14
1c590824dd14
1c590824dd14
1c590824dd14
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
1c590824dd14
1c590824dd14
1c590824dd14
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
e99bd20dad85
e99bd20dad85
e99bd20dad85
e99bd20dad85
e99bd20dad85
e99bd20dad85
e99bd20dad85
e99bd20dad85
e99bd20dad85
e99bd20dad85
e99bd20dad85
e99bd20dad85
e99bd20dad85
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
a3267d8c498e
a3267d8c498e
a3267d8c498e
a3267d8c498e
a3267d8c498e
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
8a1ec8aca432
8a1ec8aca432
5322639d61e9
5322639d61e9
5322639d61e9
8a1ec8aca432
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
a6662d61ebcd
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
a6662d61ebcd
e3362ad9a123
f41004d5a507
5bfcf309e1ad
5bfcf309e1ad
5bfcf309e1ad
e3362ad9a123
e3362ad9a123
304a918e7488
d482699decbd
d482699decbd
d482699decbd
d482699decbd
f41004d5a507
5322639d61e9
8a1ec8aca432
8a1ec8aca432
8a1ec8aca432
8a1ec8aca432
5322639d61e9
a6662d61ebcd
a6662d61ebcd
a6662d61ebcd
f41004d5a507
f41004d5a507
f41004d5a507
0f112a7dd6b3
f41004d5a507
f41004d5a507
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
8a1ec8aca432
63601fe0c3b0
63601fe0c3b0
63601fe0c3b0
2193eab0650b
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
8a1ec8aca432
8a1ec8aca432
f41004d5a507
8a1ec8aca432
f41004d5a507
f41004d5a507
f41004d5a507
a6662d61ebcd
f41004d5a507
f41004d5a507
a6662d61ebcd
5bfcf309e1ad
8a1ec8aca432
f41004d5a507
2193eab0650b
5322639d61e9
f41004d5a507
f41004d5a507
f41004d5a507
a6662d61ebcd
a6662d61ebcd
f41004d5a507
f41004d5a507
f41004d5a507
e3362ad9a123
f41004d5a507
f41004d5a507
c9dcc57116c2
c9dcc57116c2
9827df597f86
9827df597f86
9827df597f86
9827df597f86
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
9de7bbf50267
2193eab0650b
f41004d5a507
9de7bbf50267
f41004d5a507
f41004d5a507
f41004d5a507
f41004d5a507
#!/usr/bin/python

"""
now launches like this:
% bin/curvecalc http://light9.bigasterisk.com/show/dance2007/song1



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
try:
    from dispatch import dispatcher
except ImportError:
    import louie as dispatcher 
from twisted.internet import reactor,tksupport
import twisted
from twisted.web.xmlrpc import Proxy
from rdflib import Literal, URIRef
import logging
log = logging.getLogger()
logging.basicConfig(format="%(asctime)s %(levelname)-5s %(name)s %(filename)s:%(lineno)d: %(message)s")
log.setLevel(logging.DEBUG)

import run_local
from light9 import Submaster, dmxclient, networking, showconfig
from light9.TLUtility import make_attributes_from_args, dict_subset
from light9.zoomcontrol import Zoomcontrol
from light9.curve import Curve, Curveview, Curveset, Curvesetview
from light9.wavelength import wavelength
from light9.uihelpers import toplevelat
from light9.namespaces import L9

import light9.Effects
fx_dict = light9.Effects.__dict__
all_fx = fx_dict['__all__']
expr_helpers = dict_subset(fx_dict, all_fx)
del fx_dict
del all_fx

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

        # add in functions from Effects
        glo.update(expr_helpers)

        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)
        # we prevent any exceptions from escaping, since they cause us to
        # stop sending levels
        try:
            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
        except Exception, e:
            dispatcher.send("expr_error", sender=self.subexpr, exc=str(e))
            return Submaster.Submaster('Error: %s' % str(e), temporary=True)

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

        # spot alsa soundcard offset is always 0, we get times about a
        # second ahead of what's really getting played
        #t = t - .7
        
        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(graph, sub, curveset, subterms, root, ssv, expr=None):
    subname = graph.label(sub)
    if expr is None:
        expr = '%s(t)' % subname

    term = Subterm(Submaster.Submaster(sub), 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, graph):
    f=tk.Frame(master,relief='raised',bd=1)
    newname = tk.StringVar()

    def add_cmd():
        add_one_subterm(graph, 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_for_song(graph, song, curveset, subterms, root, ssv):
    for st in graph.objects(song, L9['subterm']):
        add_one_subterm(graph, graph.value(st, L9['sub']), curveset, subterms,
                        root, ssv, graph.value(st, L9['expression']))

def songFilename(uri):
    return uri.split('/')[-1]

#######################################################################
root=tk.Tk()
root.tk_setPalette("gray50")
toplevelat("curvecalc",root)
root.tk_focusFollowsMouse()

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

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

log.debug("music")
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')

graph = showconfig.getGraph()
root.title("Curvemaster 3000MX - %s" % graph.label(song))

musicfilename = showconfig.songOnDisk(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(), songFilename(song)))

subterms = []
sub_commands_tk(root, curveset, subterms, root, ssv, graph).pack(side='top',fill='x')

add_subterms_for_song(graph, song, curveset, subterms, root, ssv)

log.debug("output")
out = Output(subterms, music)

def savekey(*args):
    print "saving",song
    savesubterms(showconfig.subtermsForSong(songFilename(song)), subterms)
    curveset.save(basename=os.path.join(showconfig.curvesDir(), songFilename(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;  Esc see current time; S-Esc see curtime to end; Mousewheel zoom; C-p play/pause music at mouse",
                 "Curve point bindings: B1 drag point; C-B1 curve add point; S-B1 sketch points; Del selected points; 1..5 add point at time; Alt-Shift-B1 drag select points",
                 "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)
log.debug("run")
if 0:
    sys.path.append("/home/drewp/projects/cuisine/pour")
    from utils import runstats
    runstats("reactor.run()")
else:
    reactor.run()