#!/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.extend(["../semprini","../../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(song)): 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("",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("",lambda ev: reactor.stop) root.bind("",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()