# HG changeset patch # User drewp@bigasterisk.com # Date 2012-06-06 01:31:51 # Node ID 847edbfe65c87255daf129b272cfcc7eedeef4ed # Parent 03453848ed4c6be8a64332a3dc6e484aab99813e refactor subterms Ignore-this: 632a7fcc1917ceed60970e69e85d03f5 diff --git a/bin/curvecalc b/bin/curvecalc --- a/bin/curvecalc +++ b/bin/curvecalc @@ -10,20 +10,19 @@ todo: curveview should preserve more obj """ from __future__ import division -import time,textwrap,math,random,os,optparse, urllib2 +import time,textwrap,os,optparse, urllib2 import Tix as tk import louie as dispatcher from twisted.internet import reactor,tksupport -from rdflib import Literal, URIRef, RDF, RDFS +from rdflib import URIRef from rdflib import Graph import rdflib import logging log = logging.getLogger() import run_local -from light9 import Submaster, dmxclient, showconfig, prof, Patch -from light9.TLUtility import make_attributes_from_args +from light9 import showconfig, prof from light9.curvecalc.zoomcontrol import Zoomcontrol from light9.curvecalc.curve import Curveset from light9.curvecalc.curveview import Curvesetview @@ -31,214 +30,9 @@ from light9.curvecalc.musicaccess import from light9.wavelength import wavelength from light9.uihelpers import toplevelat from light9.namespaces import L9 -import light9.Effects - -class Expr(object): - """singleton, provides functions for use in subterm expressions, - e.g. chases""" - def __init__(self): - self.effectGlobals = light9.Effects.configExprGlobals() - - def exprGlobals(self, startDict, t): - """globals dict for use by expressions""" - - glo = startDict.copy() - - # add in functions from Effects - glo.update(self.effectGlobals) - - 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 - - - def smoove(x): - return -2 * (x ** 3) + 3 * (x ** 2) - glo['smoove'] = smoove - - def aft(t, x, smooth=0): - left = x - smooth / 2 - right = x + smooth / 2 - if left < t < right: - return smoove((t - left) / (right - left)) - return t > x - glo['aft'] = lambda x, smooth=0: aft(t, x, smooth) - - def chan(name): - return Submaster.Submaster( - leveldict={Patch.get_dmx_channel(name) : 1.0}, - temporary=True) - glo['chan'] = chan - - 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 - - - - return glo - -exprglo = Expr() - -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 = exprglo.exprGlobals(glo, 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="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) - - def __repr__(self): - return "" % (self.submaster, self.subexpr) - -class Subtermview(tk.Frame): - def __init__(self, master, graph, st, **kw): - self.subterm = st - tk.Frame.__init__(self,master,bd=1,relief='raised',**kw) - l = tk.Label(self, text="sub %s" % 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 = self.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(.02, 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): - dispatcher.send("curves to sliders", t=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 +from light9.curvecalc.subterm import read_all_subs, savekey, graphPathForSubterms +from light9.curvecalc.subtermview import makeSubtermCommandRow, add_one_subterm +from light9.curvecalc.output import Output def makeStatusLines(master): """various labels that listen for dispatcher signals""" @@ -258,81 +52,6 @@ def makeStatusLines(master): l.config(text=sn+": "+tf(val)), signame, weak=False) -def add_one_subterm(graph, subUri, curveset, subterms, master, expr=None): - subname = graph.label(subUri) - print "%s's label is %s" % (subUri, subname) - if not subname: # fake sub, like for a chase - st = graph.subjects(L9['sub'], subUri).next() - subname = graph.label(st) - print "using parent subterm's name instead. parent %r, name %r" % (st, subname) - assert subname, "%s has no name" % subUri - if expr is None: - expr = '%s(t)' % subname - - term = Subterm(Submaster.Submaster(graph=graph, name=subname, sub=subUri), - Subexpr(curveset,expr)) - subterms.append(term) - - stv=Subtermview(master, graph, term) - stv.pack(side='top',fill='x') - - return term - -def makeSubtermCommandRow(master, curveset, subterms, root, ssv, graph): - """ - the row that starts with 'reload subs' button - """ - f=tk.Frame(master,relief='raised',bd=1) - newname = tk.StringVar() - - def add_cmd(evt): - uri = L9['sub/%s' % newname.get()] - graph.add((uri, RDF.type, L9.Subterm)) - graph.add((uri, RDFS.label, Literal(newname.get()))) - add_one_subterm(graph, uri, - curveset, subterms, ssv, None) - if evt.state & 4: # control key modifier - curveset.new_curve(newname.get()) - 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 (C-Enter for curve too, C-n for focus):").pack(side='left') - entry = tk.Entry(f, textvariable=newname) - entry.pack(side='left', fill='x', exp=1) - entry.bind("", add_cmd) - - def focus_entry(): - entry.focus() - - dispatcher.connect(focus_entry, "focus new subterm", weak=False) - - return f - -def savesubterms(filename,subterms): - raise NotImplementedError - s="" - for st in subterms: - s=s+"%s %s\n" % (st.submaster.name, st.subexpr.expr) - - file(filename,'w').write(s) - -def createSubtermGraph(song, subterms): - """rdf graph describing the subterms, readable by add_subterms_for_song""" - graph = Graph() - for subterm in subterms: - assert subterm.submaster.name, "submaster has no name" - uri = URIRef(song + "/subterm/" + subterm.submaster.name) - graph.add((song, L9['subterm'], uri)) - graph.add((uri, RDF.type, L9['Subterm'])) - graph.add((uri, RDFS.label, Literal(subterm.submaster.name))) - graph.add((uri, L9['sub'], L9['sub/%s' % subterm.submaster.name])) - graph.add((uri, L9['expression'], Literal(subterm.subexpr.expr))) - return graph - def add_subterms_for_song(graph, song, curveset, subterms, master): for st in graph.objects(song, L9['subterm']): log.info("song %s has subterm %s", song, st) @@ -347,18 +66,6 @@ def add_subterms_for_song(graph, song, c curveset, subterms, master, expr) -def graphPathForSubterms(song): - return showconfig.subtermsForSong(showconfig.songFilenameFromURI(song)) + ".n3" - -@prof.logTime -def read_all_subs(graph): - """read all sub files into this graph so when add_one_subterm tries - to add, the sub will be available""" - subsDir = showconfig.subsDir() - for filename in os.listdir(subsDir): - # parsing nt is faster, but it should try n3 format if the parsing fails - graph.parse(os.path.join(subsDir, filename), format="n3") - def makeGraph(): graphOrig = showconfig.getGraph() graph = Graph() # a copy, since we're going to add subs into it @@ -367,15 +74,6 @@ def makeGraph(): read_all_subs(graph) return graph -def savekey(song, subterms, curveset): - print "saving", song - g = createSubtermGraph(song, subterms) - g.serialize(graphPathForSubterms(song), format="nt") - - curveset.save(basename=os.path.join(showconfig.curvesDir(), - showconfig.songFilenameFromURI(song))) - print "saved" - def setupKeyBindings(root, song, subterms, curveset): root.bind("", lambda *args: savekey(song, subterms, curveset)) diff --git a/light9/curvecalc/output.py b/light9/curvecalc/output.py new file mode 100644 --- /dev/null +++ b/light9/curvecalc/output.py @@ -0,0 +1,61 @@ +import time +from twisted.internet import reactor +from light9 import Submaster, dmxclient +from louie import dispatcher + +class Output: + lastsendtime=0 + lastsendlevs=None + def __init__(self, subterms, music): + self.subterms, self.music = subterms, music + + self.recent_t=[] + self.later = None + + self.update() + + def update(self): + d = self.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(.02, 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): + dispatcher.send("curves to sliders", t=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 diff --git a/light9/curvecalc/subterm.py b/light9/curvecalc/subterm.py new file mode 100644 --- /dev/null +++ b/light9/curvecalc/subterm.py @@ -0,0 +1,163 @@ +import math, os, random, logging +from rdflib import Graph, URIRef, RDF, RDFS, Literal +from louie import dispatcher +import light9.Effects +from light9 import Submaster, showconfig, Patch, prof +from light9.namespaces import L9 +log = logging.getLogger() + +class Expr(object): + """singleton, provides functions for use in subterm expressions, + e.g. chases""" + def __init__(self): + self.effectGlobals = light9.Effects.configExprGlobals() + + def exprGlobals(self, startDict, t): + """globals dict for use by expressions""" + + glo = startDict.copy() + + # add in functions from Effects + glo.update(self.effectGlobals) + + 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 + + + def smoove(x): + return -2 * (x ** 3) + 3 * (x ** 2) + glo['smoove'] = smoove + + def aft(t, x, smooth=0): + left = x - smooth / 2 + right = x + smooth / 2 + if left < t < right: + return smoove((t - left) / (right - left)) + return t > x + glo['aft'] = lambda x, smooth=0: aft(t, x, smooth) + + def chan(name): + return Submaster.Submaster( + leveldict={Patch.get_dmx_channel(name) : 1.0}, + temporary=True) + glo['chan'] = chan + + 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 + + + + return glo + +exprglo = Expr() + +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 = exprglo.exprGlobals(glo, 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="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 Subterm: + """one Submaster and its Subexpr""" + def __init__(self, submaster, subexpr): + self.submaster, self.subexpr = 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) + + def __repr__(self): + return "" % (self.submaster, self.subexpr) + + +def graphPathForSubterms(song): + return showconfig.subtermsForSong(showconfig.songFilenameFromURI(song)) + ".n3" + +@prof.logTime +def read_all_subs(graph): + """read all sub files into this graph so when add_one_subterm tries + to add, the sub will be available""" + subsDir = showconfig.subsDir() + for filename in os.listdir(subsDir): + # parsing nt is faster, but it should try n3 format if the parsing fails + graph.parse(os.path.join(subsDir, filename), format="n3") + +def createSubtermGraph(song, subterms): + """rdf graph describing the subterms, readable by add_subterms_for_song""" + graph = Graph() + for subterm in subterms: + assert subterm.submaster.name, "submaster has no name" + uri = URIRef(song + "/subterm/" + subterm.submaster.name) + graph.add((song, L9['subterm'], uri)) + graph.add((uri, RDF.type, L9['Subterm'])) + graph.add((uri, RDFS.label, Literal(subterm.submaster.name))) + graph.add((uri, L9['sub'], L9['sub/%s' % subterm.submaster.name])) + graph.add((uri, L9['expression'], Literal(subterm.subexpr.expr))) + return graph + +def savekey(song, subterms, curveset): + print "saving", song + g = createSubtermGraph(song, subterms) + g.serialize(graphPathForSubterms(song), format="nt") + + curveset.save(basename=os.path.join(showconfig.curvesDir(), + showconfig.songFilenameFromURI(song))) + print "saved" diff --git a/light9/curvecalc/subtermview.py b/light9/curvecalc/subtermview.py new file mode 100644 --- /dev/null +++ b/light9/curvecalc/subtermview.py @@ -0,0 +1,91 @@ +import Tix as tk +from louie import dispatcher +from rdflib import RDF, RDFS, Literal +from light9 import Submaster +from light9.namespaces import L9 +from light9.curvecalc.subterm import Subterm, Subexpr + + +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 Subtermview(tk.Frame): + def __init__(self, master, graph, st, **kw): + self.subterm = st + tk.Frame.__init__(self,master,bd=1,relief='raised',**kw) + l = tk.Label(self, text="sub %s" % self.subterm.submaster.name) + l.pack(side='left') + sev=Subexprview(self,self.subterm.subexpr) + sev.pack(side='left',fill='both',exp=1) + +def makeSubtermCommandRow(master, curveset, subterms, root, ssv, graph): + """ + the row that starts with 'reload subs' button + """ + f=tk.Frame(master,relief='raised',bd=1) + newname = tk.StringVar() + + def add_cmd(evt): + uri = L9['sub/%s' % newname.get()] + graph.add((uri, RDF.type, L9.Subterm)) + graph.add((uri, RDFS.label, Literal(newname.get()))) + add_one_subterm(graph, uri, + curveset, subterms, ssv, None) + if evt.state & 4: # control key modifier + curveset.new_curve(newname.get()) + 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 (C-Enter for curve too, C-n for focus):").pack(side='left') + entry = tk.Entry(f, textvariable=newname) + entry.pack(side='left', fill='x', exp=1) + entry.bind("", add_cmd) + + def focus_entry(): + entry.focus() + + dispatcher.connect(focus_entry, "focus new subterm", weak=False) + + return f + +def add_one_subterm(graph, subUri, curveset, subterms, master, expr=None): + subname = graph.label(subUri) + print "%s's label is %s" % (subUri, subname) + if not subname: # fake sub, like for a chase + st = graph.subjects(L9['sub'], subUri).next() + subname = graph.label(st) + print "using parent subterm's name instead. parent %r, name %r" % (st, subname) + assert subname, "%s has no name" % subUri + if expr is None: + expr = '%s(t)' % subname + + term = Subterm(Submaster.Submaster(graph=graph, name=subname, sub=subUri), + Subexpr(curveset,expr)) + subterms.append(term) + + stv=Subtermview(master, graph, term) + stv.pack(side='top',fill='x') + + return term