Changeset - 2848cf5e14c5
[Not reviewed]
default
0 2 0
David McClosky - 20 years ago 2005-06-18 01:37:23
dmcc@bigasterisk.com
keyboardcomposer: skip subs at 0 when combining, temporary subs don't listen for reloads
2 files changed with 3 insertions and 2 deletions:
0 comments (0 inline, 0 general)
bin/keyboardcomposer
Show inline comments
 
@@ -20,263 +20,263 @@ nudge_keys = {
 
    'down' : list('asdfghjkl')
 
}
 
nudge_keys['down'].append('semicolon')
 

	
 
class SubScale(Scale, Fadable):
 
    def __init__(self, master, *args, **kw):
 
        self.scale_var = kw.get('variable') or DoubleVar()
 
        kw.update({'variable' : self.scale_var,
 
                   'from' : 1, 'to' : 0, 'showvalue' : 0,
 
                   'sliderlength' : 15, 'res' : 0.01,
 
                   'width' : 40, 'troughcolor' : 'black', 'bg' : 'grey40',
 
                   'highlightthickness' : 1, 'bd' : 1,
 
                   'highlightcolor' : 'red', 'highlightbackground' : 'black',
 
                   'activebackground' : 'red'})
 
        Scale.__init__(self, master, *args, **kw)
 
        Fadable.__init__(self, var=self.scale_var, wheel_step=0.05)
 
        self.draw_indicator_colors()
 
    def draw_indicator_colors(self):
 
        if self.scale_var.get() == 0:
 
            self['troughcolor'] = 'black'
 
        else:
 
            self['troughcolor'] = 'blue'
 

	
 
class SubmasterTk(Frame):
 
    def __init__(self, master, name, current_level):
 
        Frame.__init__(self, master, bd=1, relief='raised', bg='black')
 
        self.slider_var = DoubleVar()
 
        self.slider_var.set(current_level)
 
        self.scale = SubScale(self, variable=self.slider_var, width=20)
 
        namelabel = Label(self, text=name, font="Arial 11", bg='black',
 
            fg='white')
 
        namelabel.pack(side=TOP)
 
        levellabel = Label(self, textvariable=self.slider_var, font="Arial 11",
 
            bg='black', fg='white')
 
        levellabel.pack(side=TOP)
 
        self.scale.pack(side=BOTTOM, expand=1, fill=BOTH)
 

	
 
class KeyboardComposer(Frame, SubClient):
 
    def __init__(self, root, submasters, current_sub_levels=None):
 
        Frame.__init__(self, root, bg='black')
 
        SubClient.__init__(self)
 
        self.submasters = submasters
 
        self.name_to_subtk = {}
 
        self.current_sub_levels = {}
 
        if current_sub_levels:
 
            self.current_sub_levels = current_sub_levels
 
        else:
 
            try:
 
                self.current_sub_levels = \
 
                    pickle.load(file('.keyboardcomposer.savedlevels'))
 
            except IOError:
 
                pass
 

	
 
        self.draw_ui()
 
        self.send_levels_loop()
 
    def draw_ui(self):
 
        self.rows = [] # this holds Tk Frames for each row
 
        self.slider_vars = {} # this holds subname:sub Tk vars
 
        self.slider_table = {} # this holds coords:sub Tk vars
 
        self.name_to_subtk.clear() # subname : SubmasterTk instance
 
        self.current_row = 0
 
        
 
        self.make_key_hints()
 
        self.draw_sliders()
 
        self.highlight_row(self.current_row)
 
        self.rows[self.current_row].focus()
 

	
 
        self.buttonframe = Frame(self, bg='black')
 
        self.buttonframe.pack(side=BOTTOM)
 
        self.alltozerobutton = Button(self.buttonframe, text="All to Zero", 
 
            command=self.alltozero, bg='black', fg='white')
 
        self.alltozerobutton.pack(side='left')
 
        self.refreshbutton = Button(self.buttonframe, text="Refresh", 
 
            command=self.refresh, bg='black', fg='white')
 
        self.refreshbutton.pack(side=LEFT)
 
        self.save_stage_button = Button(self.buttonframe, text="Save", 
 
            command=lambda: self.save_current_stage(self.sub_name.get()), 
 
            bg='black', fg='white')
 
        self.save_stage_button.pack(side=LEFT)
 
        self.sub_name = Entry(self.buttonframe, bg='black', fg='white')
 
        self.sub_name.pack(side=LEFT)
 
        self.stop_frequent_update_time = 0
 
    def make_key_hints(self):
 
        keyhintrow = Frame(self)
 

	
 
        col = 0
 
        for upkey, downkey in zip(nudge_keys['up'],
 
                                  nudge_keys['down']):
 
            # what a hack!
 
            downkey = downkey.replace('semicolon', ';')
 
            upkey, downkey = (upkey.upper(), downkey.upper())
 

	
 
            # another what a hack!
 
            keylabel = Label(keyhintrow, text='%s\n%s' % (upkey, downkey), 
 
                width=1, font=('Arial', 10), bg='red', fg='white', anchor='c')
 
            keylabel.pack(side=LEFT, expand=1, fill=X)
 
            col += 1
 

	
 
        keyhintrow.pack(fill=X, expand=0)
 
        self.keyhints = keyhintrow
 
    def setup_key_nudgers(self, tkobject):
 
        for d, keys in nudge_keys.items():
 
            for key in keys:
 
                # lowercase makes full=0
 
                keysym = "<KeyPress-%s>" % key
 
                tkobject.bind(keysym, \
 
                    lambda evt, num=keys.index(key), d=d: \
 
                        self.got_nudger(num, d))
 

	
 
                # uppercase makes full=1
 
                keysym = "<KeyPress-%s>" % key.upper()
 
                keysym = keysym.replace('SEMICOLON', 'colon')
 
                tkobject.bind(keysym, \
 
                    lambda evt, num=keys.index(key), d=d: \
 
                        self.got_nudger(num, d, full=1))
 

	
 
        # Row changing:
 
        # Page dn, C-n, and ] do down
 
        # Page up, C-p, and ' do up
 
        for key in '<Prior> <Next> <Control-n> <Control-p> ' \
 
                   '<Key-bracketright> <Key-apostrophe>'.split():
 
            tkobject.bind(key, self.change_row)
 

	
 
    def change_row(self, event):
 
        diff = 1
 
        if event.keysym in ('Prior', 'p', 'bracketright'):
 
            diff = -1
 
        old_row = self.current_row
 
        self.current_row += diff
 
        self.current_row = max(0, self.current_row)
 
        self.current_row = min(len(self.rows) - 1, self.current_row)
 
        self.unhighlight_row(old_row)
 
        self.highlight_row(self.current_row)
 
        row = self.rows[self.current_row]
 
        self.keyhints.pack_configure(before=row)
 
    def got_nudger(self, number, direction, full=0):
 
        subtk = self.slider_table[(self.current_row, number)]
 
        if direction == 'up':
 
            if full:
 
                subtk.scale.fade(1)
 
            else:
 
                subtk.scale.increase()
 
        else:
 
            if full:
 
                subtk.scale.fade(0)
 
            else:
 
                subtk.scale.decrease()
 
    def draw_sliders(self):
 
        self.tk_focusFollowsMouse()
 

	
 
        rowcount = -1
 
        col = 0
 
        for sub in self.submasters.get_all_subs():
 
            if col == 0: # make new row
 
                row = self.make_row()
 
                rowcount += 1
 
            current_level = self.current_sub_levels.get(sub.name, 0)
 
            subtk = self.draw_sub_slider(row, col, sub.name, current_level)
 
            self.slider_table[(rowcount, col)] = subtk
 
            self.name_to_subtk[sub.name] = subtk
 
            col += 1
 
            col %= 10
 

	
 
            def slider_changed(x, y, z, subtk=subtk):
 
                subtk.scale.draw_indicator_colors()
 
                self.send_levels()
 

	
 
            subtk.slider_var.trace('w', slider_changed)
 
    def make_row(self):
 
        row = Frame(self, bd=2, bg='black')
 
        row.pack(expand=1, fill=BOTH)
 
        self.setup_key_nudgers(row)
 
        self.rows.append(row)
 
        return row
 
    def draw_sub_slider(self, row, col, name, current_level):
 
        subtk = SubmasterTk(row, name, current_level)
 
        subtk.place(relx=col * 0.1, rely=0, relwidth=0.1, relheight=1)
 
        self.setup_key_nudgers(subtk.scale)
 

	
 
        self.slider_vars[name] = subtk.slider_var
 
        return subtk
 
    def highlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'red'
 
    def unhighlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'black'
 
    def get_levels(self):
 
        return dict([(name, slidervar.get()) 
 
            for name, slidervar in self.slider_vars.items()])
 
    def get_levels_as_sub(self):
 
        scaledsubs = [self.submasters.get_sub_by_name(sub) * level \
 
            for sub, level in self.get_levels().items()]
 
            for sub, level in self.get_levels().items() if level > 0.0]
 

	
 
        maxes = sub_maxes(*scaledsubs)
 
        return maxes
 
    def save_current_stage(self, subname):
 
        print "saving current levels as", subname
 
        sub = self.get_levels_as_sub()
 
        sub.name = subname
 
        sub.temporary = 0
 
        sub.save()
 

	
 
    def save(self):
 
        pickle.dump(self.get_levels(), 
 
                    file('.keyboardcomposer.savedlevels', 'w'))
 
    def send_frequent_updates(self):
 
        """called when we get a fade -- send events as quickly as possible"""
 
        if time.time() <= self.stop_frequent_update_time:
 
            self.send_levels()
 
            self.after(10, self.send_frequent_updates)
 

	
 
    def refresh(self):
 
        self.save()
 
        self.submasters = Submasters()
 
        self.current_sub_levels = \
 
            pickle.load(file('.keyboardcomposer.savedlevels'))
 
        for r in self.rows:
 
            r.destroy()
 
        self.keyhints.destroy()
 
        self.buttonframe.destroy()
 
        self.draw_ui()
 

	
 
    def alltozero(self):
 
        for name, subtk in self.name_to_subtk.items():
 
            if subtk.scale.scale_var.get() != 0:
 
                subtk.scale.fade(value=0.0, length=0)
 

	
 
class LevelServer(xmlrpc.XMLRPC):
 
    def __init__(self,name_to_subtk):
 
        self.name_to_subtk = name_to_subtk
 
        
 
    def xmlrpc_fadesub(self,subname,level,secs):
 
        """submaster will fade to level in secs"""
 
        try:
 
            self.name_to_subtk[subname].scale.fade(level,secs)
 
            ret='ok'
 
        except Exception,e:
 
            ret=str(e)
 
        return ret
 

	
 
if __name__ == "__main__":
 
    s = Submasters()
 

	
 
    root = Tk()
 
    tl = toplevelat("Keyboard Composer", existingtoplevel=root)
 

	
 
    kc = KeyboardComposer(tl, s)
 
    kc.pack(fill=BOTH, expand=1)
 

	
 
    import twisted.internet
 
    try:
 
        ls = LevelServer(kc.name_to_subtk)
 
        reactor.listenTCP(networking.kcPort(), server.Site(ls))
 
    except twisted.internet.error.CannotListenError, e:
 
        print "Can't (and won't!) start level server:"
 
        print e
 

	
 
    root.protocol('WM_DELETE_WINDOW', reactor.stop)
 
    reactor.addSystemEventTrigger('after', 'shutdown', kc.save)
 
    
 
    tksupport.install(root,ms=10)
 
    reactor.run()
light9/Submaster.py
Show inline comments
 
from __future__ import division
 
import os
 
from light9.TLUtility import dict_scale, dict_max
 
from light9 import Patch, showconfig
 
import dispatch.dispatcher as dispatcher
 

	
 
class Submaster:
 
    "Contain a dictionary of levels, but you didn't need to know that"
 
    def __init__(self, name, leveldict=None, temporary=0):
 
        self.name = name
 
        self.temporary = temporary
 
        if leveldict:
 
            self.levels = leveldict
 
        else:
 
            self.levels = {}
 
            self.reload(quiet=True)
 
        dispatcher.connect(self.reload, 'reload all subs')
 
        if not self.temporary:
 
            dispatcher.connect(self.reload, 'reload all subs')
 
    def reload(self, quiet=False):
 
        if self.temporary:
 
            return
 
        try:
 
            oldlevels = self.levels.copy()
 
            self.levels.clear()
 
            subfile = file(showconfig.subFile(self.name))
 
            for line in subfile.readlines():
 
                if not line.strip(): # if line is only whitespace
 
                    continue # "did i say newspace?"
 

	
 
                try:
 
                    name, val = line.split(':')
 
                    name = name.strip()
 
                    self.levels[name] = float(val)
 
                except ValueError:
 
                    print "(%s) Error with this line: %s" % (self.name, 
 
                        line[:-1])
 

	
 
                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
 
    def save(self):
 
        if self.temporary:
 
            print "not saving temporary sub named",self.name
 
            return
 

	
 
        subfile = file(showconfig.subFile(self.name), 'w')
 
        names = self.levels.keys()
 
        names.sort()
 
        for name in names:
 
            val = self.levels[name]
 
            subfile.write("%s : %s\n" % (name, val))
 
    def set_level(self, channelname, level, save=1):
 
        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), 
 
            dict_scale(self.levels, scalar), temporary=1)
 
    __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 = [0] * 68
 
        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
 
            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):
 
        return cmp(repr(self), repr(other))
 
    def __hash__(self):
 
        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,
 
                     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)
 
    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."
 
        return self.submasters.get(name, Submaster(name))
 
    __getitem__ = get_sub_by_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)