Mercurial > code > home > repos > light9
changeset 214:38c1ccfb6820
keyboardcomposer works
author | drewp@bigasterisk.com |
---|---|
date | Mon, 11 Apr 2005 02:21:26 +0000 |
parents | 87caa8906582 |
children | 2072a0dd7b19 |
files | Widgets/Fadable.py Widgets/FlyingFader.py bin/keyboardcomposer flax/KeyboardComposer.py light9/Fadable.py light9/FlyingFader.py light9/Submaster.py |
diffstat | 7 files changed, 635 insertions(+), 622 deletions(-) [+] |
line wrap: on
line diff
--- a/Widgets/Fadable.py Mon Apr 11 01:55:37 2005 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,150 +0,0 @@ -# taken from SnackMix -- now that's reusable code -from Tix import * -import time - -class Fadable: - """Fading mixin: must mix in with a Tk widget (or something that has - 'after' at least) This is currently used by VolumeBox and MixerTk. - It's probably too specialized to be used elsewhere, but could possibly - work with an Entry or a Meter, I guess. (Actually, this is used by - KeyboardComposer and KeyboardRecorder now too.) - - var is a Tk variable that should be used to set and get the levels. - If use_fades is true, it will use fades to move between levels. - If key_bindings is true, it will install these keybindings: - - Press a number to fade to that amount (e.g. '5' = 50%). Also, - '`' (grave) will fade to 0 and '0' will fade to 100%. - - If mouse_bindings is true, the following mouse bindings will be - installed: Right clicking toggles muting. The mouse wheel will - raise or lower the volume. Shift-mouse wheeling will cause a more - precise volume adjustment. Control-mouse wheeling will cause a - longer fade.""" - def __init__(self, var, wheel_step=5, use_fades=1, key_bindings=1, - mouse_bindings=1): - self.use_fades = use_fades # whether increase and decrease should fade - self.wheel_step = wheel_step # amount that increase and descrease should - # change volume (by default) - - self.fade_start_level = 0 - self.fade_end_level = 0 - self.fade_start_time = 0 - self.fade_length = 1 - self.fade_step_time = 10 - self.fade_var = var - self.fading = 0 # whether a fade is in progress - - if key_bindings: - for k in range(1, 10): - self.bind("<Key-%d>" % k, - lambda evt, k=k: self.fade(k / 10.0)) - self.bind("<Key-0>", lambda evt: self.fade(1.0)) - self.bind("<grave>", lambda evt: self.fade(0)) - - # up / down arrows - self.bind("<Key-Up>", lambda evt: self.increase()) - self.bind("<Key-Down>", lambda evt: self.decrease()) - - if mouse_bindings: - # right mouse button toggles muting - # self.bind('<3>', lambda evt: self.toggle_mute()) - # "NOT ANY MORE!" - homer - - # mouse wheel - self.bind('<4>', lambda evt: self.increase()) - self.bind('<5>', lambda evt: self.decrease()) - - # modified mouse wheel - self.bind('<Shift-4>', lambda evt: self.increase(multiplier=0.2)) - self.bind('<Shift-5>', lambda evt: self.decrease(multiplier=0.2)) - self.bind('<Control-4>', lambda evt: self.increase(length=1)) - self.bind('<Control-5>', lambda evt: self.decrease(length=1)) - - self.last_level = 0 # used for muting - def fade(self, value, length=0.5, step_time=10): - """Fade to value in length seconds with steps every step_time - milliseconds""" - if length == 0: # 0 seconds fades happen right away and prevents - # and prevents us from entering the fade loop, - # which would cause a divide by zero - self.fade_var.set(value) - self.fading = 0 # we stop all fades - else: # the general case - self.fade_start_time = time.time() - self.fade_length = length - - self.fade_start_level = self.fade_var.get() - self.fade_end_level = value - - self.fade_step_time = step_time - if not self.fading: - self.fading = 1 - self.do_fade() - def do_fade(self): - """Actually performs the fade for Fadable.fade. Shouldn't be called - directly.""" - now = time.time() - elapsed = now - self.fade_start_time - complete = elapsed / self.fade_length - complete = min(1.0, complete) - diff = self.fade_end_level - self.fade_start_level - newlevel = (complete * diff) + self.fade_start_level - self.fade_var.set(newlevel) - if complete < 1: - self.after(self.fade_step_time, self.do_fade) - else: - self.fading = 0 - def increase(self, multiplier=1, length=0.3): - """Increases the volume by multiplier * wheel_step. If use_fades is - true, it do this as a fade over length time.""" - amount = self.wheel_step * multiplier - if self.fading: - newlevel = self.fade_end_level + amount - else: - newlevel = self.fade_var.get() + amount - newlevel = min(100, newlevel) - self.set_volume(newlevel, length) - def decrease(self, multiplier=1, length=0.3): - """Descreases the volume by multiplier * wheel_step. If use_fades - is true, it do this as a fade over length time.""" - amount = self.wheel_step * multiplier - if self.fading: - newlevel = self.fade_end_level - amount - else: - newlevel = self.fade_var.get() - amount - newlevel = max(0, newlevel) - self.set_volume(newlevel, length) - def set_volume(self, newlevel, length=0.3): - """Sets the volume to newlevel, performing a fade of length if - use_fades is true.""" - if self.use_fades: - self.fade(newlevel, length=length) - else: - self.fade_var.set(newlevel) - def toggle_mute(self): - """Toggles whether the volume is being muted.""" - curlevel = self.fade_var.get() - if curlevel: - newlevel = 0 - self.last_level = curlevel - self['bg'] = 'red' # TODO: let them choose these colors - else: - newlevel = self.last_level - self['bg'] = 'lightGray' - - self.fade_var.set(newlevel) - -if __name__ == "__main__": - class SubScale(Scale, Fadable): - def __init__(self, master, *args, **kw): - self.scale_var = DoubleVar() - kw['variable'] = self.scale_var - Scale.__init__(self, master, *args, **kw) - Fadable.__init__(self, var=self.scale_var) - - root = Tk() - root.tk_focusFollowsMouse() - ss = SubScale(root, from_=1, to_=0, res=0.01) - ss.pack() - mainloop()
--- a/Widgets/FlyingFader.py Mon Apr 11 01:55:37 2005 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,191 +0,0 @@ -from Tix import * -from time import time,sleep -from __future__ import division - -class Mass: - def __init__(self): - self.x=0 # position - self.xgoal=0 # position goal - - self.v=0 # velocity - self.maxspeed = .8 # maximum speed, in position/second - self.maxaccel = 3 # maximum acceleration, in position/second^2 - self.eps = .03 # epsilon - numbers within this much are considered the same - - self._lastupdate=time() - self._stopped=1 - - def equal(self,a,b): - return abs(a-b)<self.eps - - def stop(self): - self.v=0 - self.xgoal=self.x - self._stopped=1 - - def update(self): - t0 = self._lastupdate - tnow = time() - self._lastupdate = tnow - - dt = tnow-t0 - - self.x += self.v*dt - # hitting the ends stops the slider - if self.x>1: self.v=max(self.v,0); self.x=1 - if self.x<0: self.v=min(self.v,0); self.x=0 - - if self.equal(self.x,self.xgoal): - self.x=self.xgoal # clean up value - self.stop() - return - - self._stopped=0 - dir = (-1.0,1,0)[self.xgoal>self.x] - - if abs(self.xgoal-self.x) < abs(self.v*5*dt): - # apply the brakes on the last 5 steps - dir *= -.5 - - self.v += dir*self.maxaccel*dt # velocity changes with acceleration in the right direction - self.v = min(max(self.v,-self.maxspeed),self.maxspeed) # clamp velocity - - #print "x=%+.03f v=%+.03f a=%+.03f %f" % (self.x,self.v,self.maxaccel,self.xgoal) - - def goto(self,newx): - self.xgoal=newx - - def ismoving(self): - return not self._stopped - -class FlyingFader(Frame): - def __init__(self, master, variable, label, fadedur=1.5, font=('Arial', 8), labelwidth=12, - **kw): - Frame.__init__(self, master) - self.name = label - self.variable = variable - - self.mass = Mass() - - self.config({'bd':1, 'relief':'raised'}) - scaleopts = {'variable' : variable, 'showvalue' : 0, 'from' : 1.0, - 'to' : 0, 'res' : 0.001, 'width' : 20, 'length' : 200, 'orient':'vert'} - scaleopts.update(kw) - if scaleopts['orient']=='vert': - side1=TOP - side2=BOTTOM - else: - side1=RIGHT - side2=LEFT - - self.scale = Scale(self, **scaleopts) - self.vlabel = Label(self, text="0.0", width=6, font=font) - self.label = Label(self, text=label, font=font, anchor='w',width=labelwidth) #wraplength=40, ) - - self.oldtrough = self.scale['troughcolor'] - - self.scale.pack(side=side2, expand=1, fill=BOTH, anchor='c') - self.vlabel.pack(side=side2, expand=0, fill=X) - self.label.pack(side=side2, expand=0, fill=X) - - for k in range(1, 10): - self.scale.bind("<Key-%d>" % k, - lambda evt, k=k: self.newfade(k / 10.0, evt)) - - self.scale.bind("<Key-0>", lambda evt: self.newfade(1.0, evt)) - self.scale.bind("<grave>", lambda evt: self.newfade(0, evt)) - - self.scale.bind("<1>", self.cancelfade) - self.scale.bind("<2>", self.cancelfade) - self.scale.bind("<3>", self.mousefade) - - self.trace_ret = self.variable.trace('w', self.updatelabel) - self.bind("<Destroy>",self.ondestroy) - - def ondestroy(self,*ev): - self.variable.trace_vdelete('w',self.trace_ret) - - def cancelfade(self, evt): - self.fadegoal = self.variable.get() - self.fadevel = self.fadeacc = 0 - - self.scale['troughcolor'] = self.oldtrough - - def mousefade(self, evt): - target = float(self.tk.call(self.scale, 'get', evt.x, evt.y)) - self.newfade(target, evt) - - def ismoving(self): - return self.fadevel!=0 or self.fadeacc!=0 - - def newfade(self, newlevel, evt=None, length=None): - - # these are currently unused-- Mass needs to accept a speed input - mult = 1 - if evt.state & 8 and evt.state & 4: mult = 0.25 # both - elif evt.state & 8: mult = 0.5 # alt - elif evt.state & 4: mult = 2 # control - - - self.mass.x = self.variable.get() - self.mass.goto(newlevel) - - self.gofade() - - def gofade(self): - self.mass.update() - self.variable.set(self.mass.x) - - if not self.mass.ismoving(): - self.scale['troughcolor'] = self.oldtrough - return - - # blink the trough while the thing's moving - if time()%.4>.2: - # self.scale.config(troughcolor=self.oldtrough) - self.scale.config(troughcolor='orange') - else: - # self.scale.config(troughcolor='white') - self.scale.config(troughcolor='yellow') - -# colorfade(self.scale, percent) - self.after(30, self.gofade) - - def updatelabel(self, *args): - if self.variable: - self.vlabel['text'] = "%.3f" % self.variable.get() -# if self.fadetimes[1] == 0: # no fade -# self.vlabel['fg'] = 'black' -# elif self.curfade[1] > self.curfade[0]: -# self.vlabel['fg'] = 'red' -# else: -# self.vlabel['fg'] = 'blue' - - def get(self): - return self.scale.get() - - def set(self, val): - self.scale.set(val) - -def colorfade(scale, lev): - low = (255, 255, 255) - high = (0, 0, 0) - out = [int(l+lev*(h-l)) for h, l in zip(high,low)] - col="#%02X%02X%02X" % tuple(out) - scale.config(troughcolor=col) - -if __name__ == '__main__': - root = Tk() - root.tk_focusFollowsMouse() - - FlyingFader(root, variable=DoubleVar(), label="suck").pack(side=LEFT, - expand=1, fill=BOTH) - FlyingFader(root, variable=DoubleVar(), label="moof").pack(side=LEFT, - expand=1, fill=BOTH) - FlyingFader(root, variable=DoubleVar(), label="zarf").pack(side=LEFT, - expand=1, fill=BOTH) - FlyingFader(root, variable=DoubleVar(), - label="long name goes here. got it?").pack(side=LEFT, expand=1, - fill=BOTH) - - root.mainloop()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/keyboardcomposer Mon Apr 11 02:21:26 2005 +0000 @@ -0,0 +1,279 @@ +#!/usr/bin/python + +from __future__ import division,nested_scopes +import sys, time + +from twisted.internet import reactor,tksupport +from twisted.web import xmlrpc, server +from Tix import * +import math, atexit, pickle + +import run_local +from light9.Fadable import Fadable +from light9.Submaster import Submasters, sub_maxes +from light9 import dmxclient +from light9.uihelpers import toplevelat + +nudge_keys = { + 'up' : list('qwertyuiop'), + '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): + def __init__(self, root, submasters, current_sub_levels=None, dmxdummy=0): + Frame.__init__(self, root, bg='black') + self.submasters = submasters + self.dmxdummy = dmxdummy + + 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 = {} # 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.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()] + + 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 get_dmx_list(self): + maxes = self.get_levels_as_sub() + return maxes.get_dmx_list() + def send_levels(self): + if not self.dmxdummy: + levels = self.get_dmx_list() + dmxclient.outputlevels(levels) + # print "sending levels", levels + def send_levels_loop(self): + self.send_levels() + self.after(1000, self.send_levels_loop) + 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() + +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, dmxdummy=0) + kc.pack(fill=BOTH, expand=1) + + ls = LevelServer(kc.name_to_subtk) + reactor.listenTCP(8050, server.Site(ls)) + + root.bind("<Destroy>",reactor.stop) + root.protocol('WM_DELETE_WINDOW', reactor.stop) + reactor.addSystemEventTrigger('after','shutdown',kc.save) + tksupport.install(root,ms=10) + reactor.run()
--- a/flax/KeyboardComposer.py Mon Apr 11 01:55:37 2005 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,276 +0,0 @@ -from __future__ import division,nested_scopes -import sys, time -sys.path.append('..') -from Widgets.Fadable import Fadable - -from twisted.internet import reactor,tksupport -from twisted.web import xmlrpc, server -from Tix import * -import math, atexit, pickle -from Submaster import Submasters, sub_maxes -import dmxclient -from uihelpers import toplevelat - -nudge_keys = { - 'up' : list('qwertyuiop'), - '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): - def __init__(self, root, submasters, current_sub_levels=None, dmxdummy=0): - Frame.__init__(self, root, bg='black') - self.submasters = submasters - self.dmxdummy = dmxdummy - - 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 = {} # 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.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()] - - 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 get_dmx_list(self): - maxes = self.get_levels_as_sub() - return maxes.get_dmx_list() - def send_levels(self): - if not self.dmxdummy: - levels = self.get_dmx_list() - dmxclient.outputlevels(levels) - # print "sending levels", levels - def send_levels_loop(self): - self.send_levels() - self.after(1000, self.send_levels_loop) - 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() - -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, dmxdummy=0) - kc.pack(fill=BOTH, expand=1) - - ls = LevelServer(kc.name_to_subtk) - reactor.listenTCP(8050, server.Site(ls)) - - root.bind("<Destroy>",reactor.stop) - root.protocol('WM_DELETE_WINDOW', reactor.stop) - reactor.addSystemEventTrigger('after','shutdown',kc.save) - tksupport.install(root,ms=10) - reactor.run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/light9/Fadable.py Mon Apr 11 02:21:26 2005 +0000 @@ -0,0 +1,150 @@ +# taken from SnackMix -- now that's reusable code +from Tix import * +import time + +class Fadable: + """Fading mixin: must mix in with a Tk widget (or something that has + 'after' at least) This is currently used by VolumeBox and MixerTk. + It's probably too specialized to be used elsewhere, but could possibly + work with an Entry or a Meter, I guess. (Actually, this is used by + KeyboardComposer and KeyboardRecorder now too.) + + var is a Tk variable that should be used to set and get the levels. + If use_fades is true, it will use fades to move between levels. + If key_bindings is true, it will install these keybindings: + + Press a number to fade to that amount (e.g. '5' = 50%). Also, + '`' (grave) will fade to 0 and '0' will fade to 100%. + + If mouse_bindings is true, the following mouse bindings will be + installed: Right clicking toggles muting. The mouse wheel will + raise or lower the volume. Shift-mouse wheeling will cause a more + precise volume adjustment. Control-mouse wheeling will cause a + longer fade.""" + def __init__(self, var, wheel_step=5, use_fades=1, key_bindings=1, + mouse_bindings=1): + self.use_fades = use_fades # whether increase and decrease should fade + self.wheel_step = wheel_step # amount that increase and descrease should + # change volume (by default) + + self.fade_start_level = 0 + self.fade_end_level = 0 + self.fade_start_time = 0 + self.fade_length = 1 + self.fade_step_time = 10 + self.fade_var = var + self.fading = 0 # whether a fade is in progress + + if key_bindings: + for k in range(1, 10): + self.bind("<Key-%d>" % k, + lambda evt, k=k: self.fade(k / 10.0)) + self.bind("<Key-0>", lambda evt: self.fade(1.0)) + self.bind("<grave>", lambda evt: self.fade(0)) + + # up / down arrows + self.bind("<Key-Up>", lambda evt: self.increase()) + self.bind("<Key-Down>", lambda evt: self.decrease()) + + if mouse_bindings: + # right mouse button toggles muting + # self.bind('<3>', lambda evt: self.toggle_mute()) + # "NOT ANY MORE!" - homer + + # mouse wheel + self.bind('<4>', lambda evt: self.increase()) + self.bind('<5>', lambda evt: self.decrease()) + + # modified mouse wheel + self.bind('<Shift-4>', lambda evt: self.increase(multiplier=0.2)) + self.bind('<Shift-5>', lambda evt: self.decrease(multiplier=0.2)) + self.bind('<Control-4>', lambda evt: self.increase(length=1)) + self.bind('<Control-5>', lambda evt: self.decrease(length=1)) + + self.last_level = 0 # used for muting + def fade(self, value, length=0.5, step_time=10): + """Fade to value in length seconds with steps every step_time + milliseconds""" + if length == 0: # 0 seconds fades happen right away and prevents + # and prevents us from entering the fade loop, + # which would cause a divide by zero + self.fade_var.set(value) + self.fading = 0 # we stop all fades + else: # the general case + self.fade_start_time = time.time() + self.fade_length = length + + self.fade_start_level = self.fade_var.get() + self.fade_end_level = value + + self.fade_step_time = step_time + if not self.fading: + self.fading = 1 + self.do_fade() + def do_fade(self): + """Actually performs the fade for Fadable.fade. Shouldn't be called + directly.""" + now = time.time() + elapsed = now - self.fade_start_time + complete = elapsed / self.fade_length + complete = min(1.0, complete) + diff = self.fade_end_level - self.fade_start_level + newlevel = (complete * diff) + self.fade_start_level + self.fade_var.set(newlevel) + if complete < 1: + self.after(self.fade_step_time, self.do_fade) + else: + self.fading = 0 + def increase(self, multiplier=1, length=0.3): + """Increases the volume by multiplier * wheel_step. If use_fades is + true, it do this as a fade over length time.""" + amount = self.wheel_step * multiplier + if self.fading: + newlevel = self.fade_end_level + amount + else: + newlevel = self.fade_var.get() + amount + newlevel = min(100, newlevel) + self.set_volume(newlevel, length) + def decrease(self, multiplier=1, length=0.3): + """Descreases the volume by multiplier * wheel_step. If use_fades + is true, it do this as a fade over length time.""" + amount = self.wheel_step * multiplier + if self.fading: + newlevel = self.fade_end_level - amount + else: + newlevel = self.fade_var.get() - amount + newlevel = max(0, newlevel) + self.set_volume(newlevel, length) + def set_volume(self, newlevel, length=0.3): + """Sets the volume to newlevel, performing a fade of length if + use_fades is true.""" + if self.use_fades: + self.fade(newlevel, length=length) + else: + self.fade_var.set(newlevel) + def toggle_mute(self): + """Toggles whether the volume is being muted.""" + curlevel = self.fade_var.get() + if curlevel: + newlevel = 0 + self.last_level = curlevel + self['bg'] = 'red' # TODO: let them choose these colors + else: + newlevel = self.last_level + self['bg'] = 'lightGray' + + self.fade_var.set(newlevel) + +if __name__ == "__main__": + class SubScale(Scale, Fadable): + def __init__(self, master, *args, **kw): + self.scale_var = DoubleVar() + kw['variable'] = self.scale_var + Scale.__init__(self, master, *args, **kw) + Fadable.__init__(self, var=self.scale_var) + + root = Tk() + root.tk_focusFollowsMouse() + ss = SubScale(root, from_=1, to_=0, res=0.01) + ss.pack() + mainloop()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/light9/FlyingFader.py Mon Apr 11 02:21:26 2005 +0000 @@ -0,0 +1,191 @@ +from Tix import * +from time import time,sleep +from __future__ import division + +class Mass: + def __init__(self): + self.x=0 # position + self.xgoal=0 # position goal + + self.v=0 # velocity + self.maxspeed = .8 # maximum speed, in position/second + self.maxaccel = 3 # maximum acceleration, in position/second^2 + self.eps = .03 # epsilon - numbers within this much are considered the same + + self._lastupdate=time() + self._stopped=1 + + def equal(self,a,b): + return abs(a-b)<self.eps + + def stop(self): + self.v=0 + self.xgoal=self.x + self._stopped=1 + + def update(self): + t0 = self._lastupdate + tnow = time() + self._lastupdate = tnow + + dt = tnow-t0 + + self.x += self.v*dt + # hitting the ends stops the slider + if self.x>1: self.v=max(self.v,0); self.x=1 + if self.x<0: self.v=min(self.v,0); self.x=0 + + if self.equal(self.x,self.xgoal): + self.x=self.xgoal # clean up value + self.stop() + return + + self._stopped=0 + dir = (-1.0,1,0)[self.xgoal>self.x] + + if abs(self.xgoal-self.x) < abs(self.v*5*dt): + # apply the brakes on the last 5 steps + dir *= -.5 + + self.v += dir*self.maxaccel*dt # velocity changes with acceleration in the right direction + self.v = min(max(self.v,-self.maxspeed),self.maxspeed) # clamp velocity + + #print "x=%+.03f v=%+.03f a=%+.03f %f" % (self.x,self.v,self.maxaccel,self.xgoal) + + def goto(self,newx): + self.xgoal=newx + + def ismoving(self): + return not self._stopped + +class FlyingFader(Frame): + def __init__(self, master, variable, label, fadedur=1.5, font=('Arial', 8), labelwidth=12, + **kw): + Frame.__init__(self, master) + self.name = label + self.variable = variable + + self.mass = Mass() + + self.config({'bd':1, 'relief':'raised'}) + scaleopts = {'variable' : variable, 'showvalue' : 0, 'from' : 1.0, + 'to' : 0, 'res' : 0.001, 'width' : 20, 'length' : 200, 'orient':'vert'} + scaleopts.update(kw) + if scaleopts['orient']=='vert': + side1=TOP + side2=BOTTOM + else: + side1=RIGHT + side2=LEFT + + self.scale = Scale(self, **scaleopts) + self.vlabel = Label(self, text="0.0", width=6, font=font) + self.label = Label(self, text=label, font=font, anchor='w',width=labelwidth) #wraplength=40, ) + + self.oldtrough = self.scale['troughcolor'] + + self.scale.pack(side=side2, expand=1, fill=BOTH, anchor='c') + self.vlabel.pack(side=side2, expand=0, fill=X) + self.label.pack(side=side2, expand=0, fill=X) + + for k in range(1, 10): + self.scale.bind("<Key-%d>" % k, + lambda evt, k=k: self.newfade(k / 10.0, evt)) + + self.scale.bind("<Key-0>", lambda evt: self.newfade(1.0, evt)) + self.scale.bind("<grave>", lambda evt: self.newfade(0, evt)) + + self.scale.bind("<1>", self.cancelfade) + self.scale.bind("<2>", self.cancelfade) + self.scale.bind("<3>", self.mousefade) + + self.trace_ret = self.variable.trace('w', self.updatelabel) + self.bind("<Destroy>",self.ondestroy) + + def ondestroy(self,*ev): + self.variable.trace_vdelete('w',self.trace_ret) + + def cancelfade(self, evt): + self.fadegoal = self.variable.get() + self.fadevel = self.fadeacc = 0 + + self.scale['troughcolor'] = self.oldtrough + + def mousefade(self, evt): + target = float(self.tk.call(self.scale, 'get', evt.x, evt.y)) + self.newfade(target, evt) + + def ismoving(self): + return self.fadevel!=0 or self.fadeacc!=0 + + def newfade(self, newlevel, evt=None, length=None): + + # these are currently unused-- Mass needs to accept a speed input + mult = 1 + if evt.state & 8 and evt.state & 4: mult = 0.25 # both + elif evt.state & 8: mult = 0.5 # alt + elif evt.state & 4: mult = 2 # control + + + self.mass.x = self.variable.get() + self.mass.goto(newlevel) + + self.gofade() + + def gofade(self): + self.mass.update() + self.variable.set(self.mass.x) + + if not self.mass.ismoving(): + self.scale['troughcolor'] = self.oldtrough + return + + # blink the trough while the thing's moving + if time()%.4>.2: + # self.scale.config(troughcolor=self.oldtrough) + self.scale.config(troughcolor='orange') + else: + # self.scale.config(troughcolor='white') + self.scale.config(troughcolor='yellow') + +# colorfade(self.scale, percent) + self.after(30, self.gofade) + + def updatelabel(self, *args): + if self.variable: + self.vlabel['text'] = "%.3f" % self.variable.get() +# if self.fadetimes[1] == 0: # no fade +# self.vlabel['fg'] = 'black' +# elif self.curfade[1] > self.curfade[0]: +# self.vlabel['fg'] = 'red' +# else: +# self.vlabel['fg'] = 'blue' + + def get(self): + return self.scale.get() + + def set(self, val): + self.scale.set(val) + +def colorfade(scale, lev): + low = (255, 255, 255) + high = (0, 0, 0) + out = [int(l+lev*(h-l)) for h, l in zip(high,low)] + col="#%02X%02X%02X" % tuple(out) + scale.config(troughcolor=col) + +if __name__ == '__main__': + root = Tk() + root.tk_focusFollowsMouse() + + FlyingFader(root, variable=DoubleVar(), label="suck").pack(side=LEFT, + expand=1, fill=BOTH) + FlyingFader(root, variable=DoubleVar(), label="moof").pack(side=LEFT, + expand=1, fill=BOTH) + FlyingFader(root, variable=DoubleVar(), label="zarf").pack(side=LEFT, + expand=1, fill=BOTH) + FlyingFader(root, variable=DoubleVar(), + label="long name goes here. got it?").pack(side=LEFT, expand=1, + fill=BOTH) + + root.mainloop()
--- a/light9/Submaster.py Mon Apr 11 01:55:37 2005 +0000 +++ b/light9/Submaster.py Mon Apr 11 02:21:26 2005 +0000 @@ -56,6 +56,8 @@ self.save() def get_levels(self): return self.levels + def all_zeros(self): + return not (max(self.levels.values()) > 0) def __mul__(self, scalar): return Submaster("%s*%s" % (self.name, scalar), dict_scale(self.levels, scalar), temporary=1) @@ -70,7 +72,13 @@ levels = [0] * 68 for k, v in leveldict.items(): - dmxchan = Patch.get_dmx_channel(k) - 1 + 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 @@ -112,16 +120,18 @@ return level def sub_maxes(*subs): - return Submaster("max(%r)" % (subs,), - dict_max(*[sub.levels for sub in subs]), temporary=1) + nonzero_subs = [s for s in subs if not s.all_zeros()] + 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) class Submasters: "Collection o' Submaster objects" def __init__(self): self.submasters = {} - import os - files = os.listdir('subs') + files = os.listdir(os.path.join(os.getenv("LIGHT9_SHOW"),'subs')) for filename in files: # we don't want these files