Changeset - 415c206f7534
[Not reviewed]
default
0 2 0
Drew Perttula - 18 years ago 2007-06-13 07:23:54
drewp@bigasterisk.com
fix sub loading and reloading in CC
2 files changed with 35 insertions and 16 deletions:
0 comments (0 inline, 0 general)
bin/curvecalc
Show inline comments
 
#!/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, RDF
 
from rdflib.Graph import Graph
 
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, prof
 
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):
 
    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 %r" % self.subterm.submaster.name)
 
        l = tk.Label(self,
 
                     text="sub %s" % graph.label(self.subterm.submaster.uri))
 
        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)
 

	
 
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)
 
def add_one_subterm(graph, subUri, curveset, subterms, root, ssv, expr=None):
 
    subname = graph.label(subUri)
 
    if expr is None:
 
        expr = '%s(t)' % subname
 

	
 
    term = Subterm(Submaster.Submaster(graph=graph, sub=sub), Subexpr(curveset,expr))
 
    print "req to add %r" % subUri
 
    for s in graph.triples((subUri, None, None)):
 
        print s
 
    term = Subterm(Submaster.Submaster(graph=graph, sub=subUri),
 
                   Subexpr(curveset,expr))
 
    subterms.append(term)
 

	
 
    stv=Subtermview(ssv,term)
 
    stv=Subtermview(ssv, graph, 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, '')
 
        add_one_subterm(graph, L9['sub/%s' % 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 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:
 
        uri = URIRef(song + "/subterm/" + subterm.submaster.name)
 
        graph.add((song, L9['subterm'], uri))
 
        graph.add((uri, RDF.type, L9['Subterm']))
 
        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, 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 graphPathForSubterms(song):
 
    return showconfig.subtermsForSong(showconfig.songFilenameFromURI(song)) + ".n3"
 

	
 
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):
 
        graph.parse(os.path.join(subsDir, filename), format="n3")
 

	
 
#######################################################################
 
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()
 
graphOrig = showconfig.getGraph()
 
graph = Graph() # a copy, since we're going to add subs into it
 
for s in graphOrig:
 
    graph.add(s)
 
read_all_subs(graph)
 
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(), showconfig.songFilenameFromURI(song)))
 
curveset.load(basename=os.path.join(showconfig.curvesDir(),
 
                                    showconfig.songFilenameFromURI(song)))
 

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

	
 
try:
 
    g = Graph()
 
    g.parse(graphPathForSubterms(song), format='n3')
 
    add_subterms_for_song(g, song, curveset, subterms, root, ssv)
 
    graph.parse(graphPathForSubterms(song), format='n3')
 
    add_subterms_for_song(graph, song, curveset, subterms, root, ssv)
 
except OSError, e:
 
    print e
 

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

	
 
def savekey(*args):
 
    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"
 
    
 
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")
 
prof.run(reactor.run, profile=False)
 

	
light9/Submaster.py
Show inline comments
 
from __future__ import division
 
import os
 
from rdflib.Graph import Graph
 
from rdflib import RDFS, Literal, BNode
 
from light9.namespaces import L9, XSD
 
from light9.TLUtility import dict_scale, dict_max
 
from light9 import Patch, showconfig
 
try:
 
    import dispatch.dispatcher as dispatcher
 
except ImportError:
 
    from louie import dispatcher
 

	
 
class Submaster:
 
    "Contain a dictionary of levels, but you didn't need to know that"
 
    def __init__(self,
 
                 name=None,
 
                 graph=None, sub=None,
 
                 leveldict=None, temporary=False):
 
        """sub is the URI for this submaster, graph is a graph where
 
        we can learn about the sub. If graph is not provided, we look
 
        in a file named name.
 

	
 
        name is the filename where we can load a graph about this URI
 
        (see showconfig.subFile)
 

	
 
        passing name alone makes a new empty sub
 

	
 
        temporary means the sub won't get saved or loaded
 

	
 

	
 
        pass:
 
          name, temporary=True  -  no rdf involved
 
          sub, filename         -  read sub URI from graph at filename
 
          
 
          name - new sub
 
          sub - n
 
          name, sub - new 
 
        
 
        """
 
        if name is sub is leveldict is None:
 
            raise TypeError("more args are needed")
 
        if sub is not None:
 
            name = graph.label(sub)
 
        if graph is not None:
 
            # old code was passing leveldict as second positional arg
 
            assert isinstance(graph, Graph)
 
        self.name = name
 
        self.uri = sub
 
        self.temporary = temporary
 
        if leveldict:
 
            self.levels = leveldict
 
        else:
 
            self.levels = {}
 
            self.reload(quiet=True)
 
            self.reload(quiet=True, graph=graph)
 
        if not self.temporary:
 
            dispatcher.connect(self.reload, 'reload all subs')
 
            
 
    def reload(self, quiet=False):
 
    def reload(self, quiet=False, graph=None):
 
        if self.temporary:
 
            return
 
        try:
 
            oldlevels = self.levels.copy()
 
            self.levels.clear()
 
            patchGraph = showconfig.getGraph()
 
            if graph is None:
 
            graph = Graph()
 
            graph.parse(showconfig.subFile(self.name), format="nt")
 
            subUri = L9['sub/%s' % self.name]
 
            for lev in graph.objects(subUri, L9['lightLevel']):
 
                chan = graph.value(lev, L9['channel'])
 
                val = graph.value(lev, L9['level'])
 
                name = patchGraph.label(chan)
 
                self.levels[name] = float(val)
 

	
 
            if (not quiet) and (oldlevels != self.levels):
 
                print "sub %s changed" % self.name
 
        except IOError:
 
            print "Can't read file for sub: %s" % self.name
 
        except IOError, e:
 
            print "Can't read file for sub: %r (%s)" % (self.name, e)
 
    def save(self):
 
        if self.temporary:
 
            print "not saving temporary sub named",self.name
 
            return
 

	
 
        graph = Graph()
 
        subUri = L9['sub/%s' % self.name]
 
        graph.add((subUri, RDFS.label, Literal(self.name)))
 
        for chan in self.levels.keys():
 
            try:
 
                chanUri = Patch.get_channel_uri(chan)
 
            except KeyError:
 
                print "saving dmx channels with no :Channel node is not supported yet. Give channel %s a URI for it to be saved. Omitting this channel from the sub." % chan
 
                continue
 
            lev = BNode()
 
            graph.add((subUri, L9['lightLevel'], lev))
 
            graph.add((lev, L9['channel'], chanUri))
 
            graph.add((lev, L9['level'],
 
                       Literal(self.levels[chan], datatype=XSD['decimal'])))
 

	
 
        graph.serialize(showconfig.subFile(self.name), format="nt")
 

	
 
    def set_level(self, channelname, level, save=True):
 
        self.levels[Patch.resolve_name(channelname)] = level
 
        if save:
 
            self.save()
 
    def set_all_levels(self, leveldict):
 
        self.levels.clear()
 
        for k, v in leveldict.items():
 
            self.set_level(k, v, save=0)
 
        self.save()
 
    def get_levels(self):
 
        return self.levels
 
    def no_nonzero(self):
 
        return (not self.levels.values()) or not (max(self.levels.values()) > 0)
 
    def __mul__(self, scalar):
 
        return Submaster("%s*%s" % (self.name, scalar), 
 
                         leveldict=dict_scale(self.levels, scalar),
 
                         temporary=True)
 
    __rmul__ = __mul__
 
    def max(self, *othersubs):
 
        return sub_maxes(self, *othersubs)
 
    def __repr__(self):
 
        items = self.levels.items()
 
        items.sort()
 
        levels = ' '.join(["%s:%.2f" % item for item in items])
 
        return "<'%s': [%s]>" % (self.name, levels)
 
    def get_dmx_list(self):
 
        leveldict = self.get_levels() # gets levels of sub contents
 

	
 
        levels = []
 
        for k, v in leveldict.items():
 
            if v == 0:
 
                continue
 
            try:
 
                dmxchan = Patch.get_dmx_channel(k) - 1
 
            except ValueError:
 
                print "error trying to compute dmx levels for submaster %s" % self.name
 
                raise
 
            if dmxchan >= len(levels):
 
                levels.extend([0] * (dmxchan - len(levels) + 1))
 
            levels[dmxchan] = max(v, levels[dmxchan])
 

	
 
        return levels
 
    def normalize_patch_names(self):
 
        """Use only the primary patch names."""
 
        # possibly busted -- don't use unless you know what you're doing
 
        self.set_all_levels(self.levels.copy())
 
    def get_normalized_copy(self):
 
        """Get a copy of this sumbaster that only uses the primary patch 
 
        names.  The levels will be the same."""
 
        newsub = Submaster("%s (normalized)" % self.name, temporary=1)
 
        newsub.set_all_levels(self.levels)
 
        return newsub
 
    def crossfade(self, othersub, amount):
 
        """Returns a new sub that is a crossfade between this sub and
 
        another submaster.  
 
        
 
        NOTE: You should only crossfade between normalized submasters."""
 
        otherlevels = othersub.get_levels()
 
        keys_set = {}
 
        for k in self.levels.keys() + otherlevels.keys():
 
            keys_set[k] = 1
 
        all_keys = keys_set.keys()
 

	
 
        xfaded_sub = Submaster("xfade", temporary=1)
 
        for k in all_keys:
 
            xfaded_sub.set_level(k, 
 
                                 linear_fade(self.levels.get(k, 0),
 
                                             otherlevels.get(k, 0),
 
                                             amount))
 

	
 
        return xfaded_sub
 
    def __cmp__(self, other):
 
        raise NotImplementedError
 
        return cmp(repr(self), repr(other))
 
    def __hash__(self):
 
        raise NotImplementedError
 
        return hash(repr(self))
 
                                            
 
def linear_fade(start, end, amount):
 
    """Fades between two floats by an amount.  amount is a float between
 
    0 and 1.  If amount is 0, it will return the start value.  If it is 1,
 
    the end value will be returned."""
 
    level = start + (amount * (end - start))
 
    return level
 

	
 
def sub_maxes(*subs):
 
    nonzero_subs = [s for s in subs if not s.no_nonzero()]
 
    name = "max(%s)" % ", ".join([repr(s) for s in nonzero_subs])
 
    return Submaster(name,
 
                     leveldict=dict_max(*[sub.levels for sub in nonzero_subs]),
 
                     temporary=1)
 

	
 
def combine_subdict(subdict, name=None, permanent=False):
 
    """A subdict is { Submaster objects : levels }.  We combine all
 
    submasters first by multiplying the submasters by their corresponding
 
    levels and then max()ing them together.  Returns a new Submaster
 
    object.  You can give it a better name than the computed one that it
 
    will get or make it permanent if you'd like it to be saved to disk.
 
    Serves 8."""
 
    scaledsubs = [sub * level for sub, level in subdict.items()]
 
    maxes = sub_maxes(*scaledsubs)
 
    if name:
 
        maxes.name = name
 
    if permanent:
 
        maxes.temporary = False
 

	
 
    return maxes
 

	
 
class Submasters:
 
    "Collection o' Submaster objects"
 
    def __init__(self):
 
        self.submasters = {}
 

	
 
        files = os.listdir(showconfig.subsDir())
 

	
 
        for filename in files:
 
            # we don't want these files
 
            if filename.startswith('.') or filename.endswith('~') or \
 
               filename.startswith('CVS'):
 
                continue
 
            self.submasters[filename] = Submaster(filename)
 
        print "loaded subs", self.submasters
 
    def get_all_subs(self):
 
        "All Submaster objects"
 
        l = self.submasters.items()
 
        l.sort()
 
        l = [x[1] for x in l]
 
        songs = []
 
        notsongs = []
 
        for s in l:
 
            if s.name.startswith('song'):
 
                songs.append(s)
 
            else:
 
                notsongs.append(s)
 
        combined = notsongs + songs
 
        return combined
 
    def get_all_sub_names(self):
 
        return [s.name for s in self.get_all_subs()]
 
    def get_sub_by_name(self, name):
 
        "Makes a new sub if there isn't one."
 
        if name in self.submasters:
 
            return self.submasters[name]
 
        return Submaster(name)
 
    __getitem__ = get_sub_by_name
 

	
 
def fullsub(*chans):
 
    """Make a submaster with chans at full."""
 
    return Submaster('%r' % chans,
 
        dict([(c, 1.0) for c in chans]), temporary=True)
 

	
 
# a global instance of Submasters, created on demand
 
_submasters = None
 

	
 
def get_global_submasters():
 
    """Get (and make on demand) the global instance of Submasters"""
 
    global _submasters
 
    if _submasters is None:
 
        _submasters = Submasters()
 
    return _submasters
 

	
 
def get_sub_by_name(name, submasters=None):
 
    """name is a channel or sub nama, submasters is a Submasters object.
 
    If you leave submasters empty, it will use the global instance of
 
    Submasters."""
 
    if not submasters:
 
        submasters = get_global_submasters()
 

	
 
    if name in submasters.get_all_sub_names():
 
        return submasters.get_sub_by_name(name)
 

	
 
    try:
 
        val = int(name)
 
        s = Submaster("#%d" % val, {val : 1.0}, temporary=True)
 
        return s
 
    except ValueError:
 
        pass
 

	
 
    try:
 
        subnum = Patch.get_dmx_channel(name)
 
        s = Submaster("'%s'" % name, {subnum : 1.0}, temporary=True)
 
        return s
 
    except ValueError:
 
        pass
 

	
 
    # make an error sub
 
    return Submaster('%s' % name)
 

	
 
if __name__ == "__main__":
 
    Patch.reload_data()
 
    s = Submasters()
 
    print s.get_all_subs()
 
    if 0: # turn this on to normalize all subs
 
        for sub in s.get_all_subs():
 
            print "before", sub
 
            sub.normalize_patch_names()
 
            sub.save()
 
            print "after", sub
0 comments (0 inline, 0 general)