Changeset - 45b12307c695
[Not reviewed]
default
! ! !
drewp - 23 years ago 2002-07-03 09:37:57

Initial revision
112 files changed:
Op.py
32
Port.py
77
Changeset was too big and was cut off... Show full diff anyway
0 comments (0 inline, 0 general)
DataTypes/NextTime.py
Show inline comments
 
new file 100644
 
'''Baby, there won't be no next time'''
 

	
 
class NextTime:
 
    def __init__(self, nextclock, interval=None)
 
        self.nextclock = nextclock
 
        self.interval = interval
 
    def cylic(self):
 
        return interval is not None
DataTypes/dmx.py
Show inline comments
 
new file 100644
 

	
 
class DMX(list):
 
    """the signal that goes on a real-life dmx wire. it's up to 512
 
    un-named channels each with a 8-bit value on each channel. this type
 
    is useful for a DMXOut node or DMXLevelDisplay node. the channels are
 
    stored in a python list where the first channel is at index 0. the
 
    first channel in dmx terminology would be called channel 1."""
 
    
 
    def __init__(self,dmxlevels):
 
        if len(dmxlevels)>512:
 
            raise TypeError("DMX objects can't have more than 512 channels")
 
        list.extend(dmxlevels) # list.__init__ did't work right
 

	
 
    def append(self,level):
 
        if len(self)==512:
 
            raise TypeError("DMX objects can't have more than 512 channels")
 
        list.append(self,level)
 

	
 
    def extend(self,levels):
 
        if len(self)+len(levels)>512:
 
            raise TypeError("DMX objects can't have more than 512 channels")
 
        list.extend(self,levels)
 

	
 
    def __setslice__(self,i,j,seq):
 
        newlength = len(self)-(max(0,j)-max(0,i))+len(seq)
 
        # we could check if newlength>512, but any length-changing slice is
 
        # probably wrong for this datatype
 
        if newlength!=len(self):
 
            raise NotImplementedError("Different-length setslice would disturb DMX channels")
 
        list.__setslice__(self,i,j,seq)
 
        
DataTypes/dmxlevel.py
Show inline comments
 
new file 100644
 
###
 

	
 
"""
 
Snippet 0x93.2b: example of outputting a special type
 

	
 
class DMXLevel(float):
 
  def __init__(self,f):
 
    self.value = min(max(0,f),255)
 
  ...
 
  def __get__(...) # maybe
 

	
 
output.dmxlevel = DMXLevel(300)
 
    
 
>>> print output.dmxlevel
 
    255
 

	
 
dmxlevel = DMXLevel(3)
 
dmxlevel += 800
 
d = d + 800
 

	
 
There's yer problem:
 
http://python.org/doc/current/ref/numeric-types.html#l2h-152
 

	
 
"""
NodeInstance.py
Show inline comments
 
new file 100644
 
'''Internal class for StateManager'''
 

	
 
# warning: pseduocode
 
class NodeInstance:
 
    ip_addr?
 
    get_this_nodes_url()
 

	
 
    type = the node type (a subclass of Node)
 
        ops (get from type)
 
    input,output = the ports that are created for the node instance
 
    state = associated state
NodeType.py
Show inline comments
 
new file 100644
 
"""each node descends from this base class"""
 

	
 
class NodeType:
 
    def __init__(self):
 
        """TBD"""
 
        self.ops = Ops()
 

	
 
        ''' maybe
 
        self.iports = []
 
        self.oports = []
 
        self.params = []
 
        '''
 
    def get_state(self, stateaccess):
 
        """This is similar to the pickle.__getstate__ method, except
 
        we need this alternate version in order to give the node
 
        (which holds no state) access to its instance's state.
 

	
 
        If your node keeps some transient items in its state dict
 
        (such as a buffer of recently received inputs), it may want to
 
        return a copy of the state dict without those items. set_state
 
        should restore them properly (if they're missing from the
 
        current state, which they might not be).
 

	
 
        get_state might be called at any time, and it's certainly not
 
        guaranteed that the node instance is going out of service.
 
        get_state might get called to checkpoint the nodes for a
 
        backup, for example. set_state might also get called anytime.
 
        """
 
        return stateaccess
 
    def set_state(self, stateaccess, dict):
 
        """dict (named after the pickle.__setstate__ argument) is
 
        always a value that was previously returned from
 
        get_state. Don't adjust the current nodetype's state, of course;
 
        use dict to update stateaccess.  If there were elements
 
        missing from dict (see the note in get_state for why this
 
        might be the case), you should restore them here as
 
        appropriate.
 
        """        
 
        stateaccess.update(dict)
 
    def get_default_params(self):
 
        '''Returns dictionary of param names and DataType instances.  DataTypes 
 
        can be given values'''
 
        return {}
 
    def get_default_ports(self):
 
        '''Returns pinless port objects'''
 
        return {}
Nodes/delay.py
Show inline comments
 
new file 100644
 
"""delay node outputs the input a fixed time later"""
 

	
 

	
 
class DelayOps(Ops):
 
    def clocked(self, input, output, stateaccess):
 
        stateaccess.buffer
 

	
 

	
 
class Delay(Node):
 
    def __init__(self):
 
        Node.__init__(self)
 
        
 
    def getnodeparams(self):
 
        return {'delay':State.Time}
Nodes/dmxout.py
Show inline comments
 
new file 100644
 

	
 

	
 
class ops(Ops):
 
    def changed(self, input, output, stateaccess):
 
        input.dmx
 
    
 
class DMXOut(Node):
 
    def get_default_ports(self):
 
        return {'dmx':InputPort(DMX,required=1,maxpins=1)}
 
    def get_default_params(self):
 
        return {'outputrefresh':NextTime,'outputdevice':DMXDevice}
Nodes/gamma.py
Show inline comments
 
new file 100644
 
"""node that performs a simple gamma (exp) function on its input"""
 

	
 
class GammaOps(Ops):
 
    def started(self, input, output, stateaccess):
 
        self.startmeup(stateaccess)
 
    def changed(self, input, output, stateaccess):
 
        port.output = port.input ** stateaccess.gamma + stateaccess.offset
 
        stateaccess.lastvalue = State.FloatingPoint(port.input)
 

	
 
        output = gamma(input)
 
    # no timed function
 
    def startmeup(self, stateaccess):
 
        # whatever
 
        pass
 

	
 
class Gamma(Node):
 
    def __init__(self):
 
        Node.__init__(self)
 
        self.node_params = {'gamma':State.FloatingPoint,'offset':State.FloatingPoint}
 
        self.ops = GammaOps()
 

	
 
    def getnodeparams(self):
 
        return self.node_params
 
        
 
    def getports(self):
 
        return (Port('a', optional=1),
 
                Port('b'))
 

	
 
    def __str__(self):
 
        return "3"
 

	
 
world.register_node(Gamma)
Nodes/sine.py
Show inline comments
 
new file 100644
 
"""node that generates a sine wave"""
 

	
 

	
 
class SineGenerator(Node):
 
    def op(self, input, output, stateaccess):
 

	
 
        # input and output have names
 
        output.sin = stateaccess.magnitude * math.sin(stateaccess.phase+input.time)
 

	
 
        """
 
        # dict-style access for names with spaces
 
        output['sin'] = input['ti me']
 
        # underscore magic for accessing names with spaces-- the port object makes
 
        # this work
 
        output.sin=input.ti_me
 

	
 
        input.time = input.money
 
        """
 

	
 
    def getports(self):
 
        return OutputPort('sin'), InputPort('time'), InputPort('money')
Op.py
Show inline comments
 
new file 100644
 
"""each node type has an Op within it"""
 

	
 
class Op:
 
    """nodes can have several versions of their operation function.
 

	
 
    ops don't return anything! 
 
    """
 
    def __init__(self):
 
        """This should not be overridden without being called."""
 
        pass
 

	
 
    def inputschanged(self, input, output, stateaccess):
 
        """If you only define one op function body, make it this one. """
 
        pass
 
    
 
    def created(self, input, output, stateaccess):
 
        """This is called one time when the node is newly created. It's
 
        not called when the node instance is pickled/unpickled. Use this
 
        version to initialize state."""
 
        # an extra call to changed() should help the outputs get set
 
        # correctly before any real inputs-changed events come around
 
        # (assuming this method doesn't get overridden with a
 
        # specialized version)
 
        self.inputschanged(input, output, stateaccess)        
 

	
 
    def statechanged(self, input, output, stateaccess):
 
        '''State might have been changed by a user dragging a parameter or by
 
        a state being hcanged otherwise.'''
 
        self.inputschanged(input, output, stateaccess)        
 

	
 
    def clocked(self, input, output, stateaccess):
 
        self.inputschanged(input, output, stateaccess)        
Port.py
Show inline comments
 
new file 100644
 
from nodetypes import DiscoType
 

	
 
ANY = -1
 

	
 
class Port:
 
    def __setattr__(self, key, value):
 
        '''Alias for __setitem___'''
 
        self[key] = value
 
    def __setitem__(self, key, value):
 
        pass
 
    def __getattr__(self, key):
 
        '''Alias for __getitem___'''
 
        return self[key]
 
    def __getitem__(self, key):
 
        pass
 

	
 
class InputPort(Port):
 
    def __init__(self, allowedtype, required=1, maxpins=ANY):
 
        self.pins = []
 

	
 
class OutputPort(Port):
 
    def __init__(self):
 
        self.pins = []
 

	
 
class Pin:
 
    def __init__(self, connection, value=DiscoType):
 
        pass
 

	
 
'''
 
Snippet Pi=3: RFC 2: New port semantics
 

	
 
# an example of the max node's op
 
def changed(self, inputs):
 
    # note how this function does not use stateaccess, as it doesn't use state
 
    return max(inputs.values())
 

	
 
# so, how the heck does this work?
 
# we check the function to get the names of kw args in the function.
 
# we always pass self, but everything else is optional
 
# the node asked for inputs, which looks like this:
 
# inputs = {'portname' : PortObj, 'portname2', PortObj}
 
# somehow, the PortObjs are max'ible.
 
# the node has only one output so it can just return the value to set the 
 
# output.  (maybe)
 
# alteratively, if we decide that you always return a new dict of outputs:
 
# return {'outputportname' : max(inputs.values())}
 
# which isn't horrible, but not great
 

	
 
# another example: an adder.  the node has ports A and B, and an output C:
 
# C also gets capped at stateaccess[min].
 
def changed(self, a, b, c, stateaccess):
 
    c.set(max(stateaccess['min'], a + b))
 
    return {}
 

	
 
# or:
 
def changed(self, a, b, stateaccess):
 
    c = max(stateaccess['min'], a + b)
 
    return {'c' : c}
 

	
 
# which i think is clearer.  doing all port changes at the end has some
 
# book-keeping advantages (we can detect easily which ports are changed)
 
# the counter node could work this way:
 

	
 
def changed(self, someoutput):
 
    return {'someoutput' : someoutput + 1}
 
'''
 

	
 
'''
 
type 1: a, b, d, e
 
type 2: b, c, d, f
 

	
 
conversion maps:
 
a -> [ ]
 
b -> b
 
d -> d
 
e -> f
 
'''
StateManager.py
Show inline comments
 
new file 100644
 
''' Database of NodeInstances, part of the Core '''
 

	
 
# this will be hard to write until NodeInstances are written, but I'll try
 
# anyway
 

	
 
__version__ = "$Id: StateManager.py,v 1.1 2002/07/04 00:21:35 drewp Exp $"
 

	
 
class StateManager:
 
    '''StateManager is the second of the core to be built.  It should be 
 
       after the network, then the scheduler.
 

	
 
       After StateManager is constructed, you probably want to do load_state().
 
       All of the above is taken care of by the Core module.
 
       
 
       Also, in general, 'name' indicates the name of a node, in NRL
 
       (like URL) syntax:
 
       node:group/innergroup/node
 

	
 
       or
 

	
 
       node:node
 

	
 
       if node is in the top level group (the root, or universe, or whatever
 
        you want to call it
 
    '''
 
    def __init__(self, network):
 
        '''Sets up some dicts, maybe'''
 
        # need some storage locations, etc.
 
        self.network = network
 
    def save_state(self):
 
        '''Save state to disk'''
 
        pass
 
    def load_state(self):
 
        '''Load state from disk'''
 
        pass
 
    def get_input_names(self, name):
 
        '''Get the names of the nodes which are inputs to a node'''
 
        pass
 
    def get_output_names(self, name):
 
        '''Get the names of the nodes which are outputs to a node'''
 
        pass
Widgets/Fadable.py
Show inline comments
 
new file 100644
 
# 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.
 

	
 
    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(100))
 
            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
 
        seconds"""
 
        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_=100, to_=0,)
 
    ss.pack()
 
    mainloop()
Widgets/FlyingFader.py
Show inline comments
 
new file 100644
 
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()
Widgets/__init__.py
Show inline comments
 
new file 100644
doc/extra
Show inline comments
 
new file 100644
 

	
 

	
 
'''
 

	
 
        up
 
        |\________
 
       / \_      /\
 
      /    \   /   \
 
   d1        d2    d3
 

	
 

	
 
   up has a single output that outputs {d1 : .8, d2:0.1}
 
   
 

	
 
'''
 

	
doc/naming
Show inline comments
 
new file 100644
 

	
 

	
 
DM DP Name
 
-1 -1 Nodiverse
 
-0 -1 Nodity or "Hardcore Nodity"
 
+0 -1 Noditivity
 
-1 +0 Lightland
 
+0 +1 everything's under control (EVAC would be a funny abbrev, but it doesn't
 
            work - maybe EVerything's Unda Control)
 
+0 -1 Nodeworks
 
-1 +0 OpenNode, FreeNode, etc.
 
      OpenDMX (for DMXd maybe)
 

	
flax/CueFaders.py
Show inline comments
 
new file 100644
 
from __future__ import division, nested_scopes
 
import Tix as Tk
 
import time
 
from TreeDict import TreeDict, allow_class_to_be_pickled
 
from TLUtility import enumerate
 
import Submaster, dmxclient
 

	
 
cue_state_indicator_colors = {
 
             # bg       fg
 
    'prev' : ('blue',   'white'),
 
    'cur' :  ('yellow', 'black'),
 
    'next' : ('red',    'white'),
 
}
 

	
 
# TODO 
 
# FIXE pause fades, set new time to be remaining about of time in the fade so 
 
#           fade can continue properly
 
# FIXE make fades work properly: the set_next / prev bug
 
# WONT find cue by page ("not necessawy!")
 
# WONT CueFader controls KeyboardController?  unlikely
 
# FIXE AutoSave loop
 

	
 
class LabelledScale(Tk.Frame):
 
    """Scale with two labels: a name and current value"""
 
    def __init__(self, master, label, **opts):
 
        Tk.Frame.__init__(self, master, bd=2, relief='raised', bg='black')
 
        self.labelformatter = opts.get('labelformatter')
 
        try:
 
            del opts['labelformatter']
 
        except KeyError:
 
            pass
 

	
 
        opts.setdefault('variable', Tk.DoubleVar())
 
        opts.setdefault('showvalue', 0)
 

	
 
        self.normaltrough = opts.get('troughcolor', 'black')
 
        self.flashtrough = opts.get('flashtroughcolor', 'red')
 
        try:
 
            del opts['flashtroughcolor']
 
        except KeyError:
 
            pass
 

	
 
        self.scale_var = opts['variable']
 
        self.scale = Tk.Scale(self, **opts)
 
        self.scale.pack(side='top', expand=1, fill='both')
 
        self.name = Tk.Label(self, text=label, bg='black', fg='white')
 
        self.name.pack(side='bottom')
 
        self.scale_value = Tk.Label(self, bg='black', fg='white')
 
        self.scale_value.pack(side='bottom')
 
        self.scale_var.trace('w', self.update_value_label)
 
        self.update_value_label()
 
        self.disabled = (self.scale['state'] == 'disabled')
 
    def set_label(self, label):
 
        self.name['text'] = label
 
    def update_value_label(self, *args):
 
        val = self.scale_var.get() * 100
 
        if self.labelformatter:
 
            format = self.labelformatter(val)
 
        else:
 
            format = "%0.2f" % val
 
        self.scale_value['text'] = format
 
        if val != 0:
 
            self.scale['troughcolor'] = self.flashtrough
 
        else:
 
            self.scale['troughcolor'] = self.normaltrough
 
    def disable(self):
 
        if not self.disabled:
 
            self.scale['state'] = 'disabled'
 
            self.scale_var.set(0)
 
            self.disabled = 1
 
    def enable(self):
 
        if self.disabled:
 
            self.scale['state'] = 'normal'
 
            self.disabled = 0
 

	
 
class TimedGoButton(Tk.Frame):
 
    """Go button, fade time entry, and time fader"""
 
    def __init__(self, master, name, scale_to_fade, **kw):
 
        Tk.Frame.__init__(self, master, bg='black')
 
        self.name = name
 
        self.scale_to_fade = scale_to_fade
 
        self.button = Tk.Button(self, text=name, command=self.start_fade, **kw)
 
        self.button.pack(fill='both', expand=1, side='left')
 

	
 
        self.timer_var = Tk.DoubleVar()
 
        self.timer_entry = Tk.Control(self, step=0.5, min=0, integer=0, 
 
            variable=self.timer_var, selectmode='immediate')
 
        for widget in (self.timer_entry, self.timer_entry.entry, 
 
            self.timer_entry.incr, self.timer_entry.decr, self.button, self):
 
            widget.bind("<4>", self.wheelscroll)
 
            widget.bind("<5>", self.wheelscroll)
 
        self.timer_entry.entry.configure(width=5, bg='black', fg='white')
 
        self.timer_entry.pack(fill='y', side='left')
 
        self.timer_var.set(2)
 
        self.disabled = (self.button['state'] == 'disabled')
 
        self.start_time = 0
 
        self.fading = 0
 
        self.last_after_key = 0
 
    def manual_override(self, *args):
 
        self.end_fade()
 
    def wheelscroll(self, event):
 
        """Mouse wheel increments or decrements timer."""
 
        if event.num == 4: # scroll up
 
            self.timer_entry.increment()
 
        else:            # scroll down
 
            self.timer_entry.decrement()
 
    def start_fade(self, end_level=1):
 
        self.last_start_time = self.start_time
 
        self.start_time = time.time()
 
        self.start_level = self.scale_to_fade.scale_var.get()
 
        self.end_level = end_level
 

	
 
        if self.fading == 1: # if we're already fading
 
            self.fading = 'paused'
 
            # new fade should be as long as however much was left
 
            self.fade_length = self.fade_length - \
 
                (time.time() - self.last_start_time)
 
            self.button['text'] = 'Unpause'
 
            self.after_cancel(self.last_after_key)
 
        else:
 
            try:
 
                fade_time = float(self.timer_var.get())
 
            except ValueError:
 
                # since we use a TixControl now, i don't think we need to worry 
 
                # about validation any more.
 
                print ">>> Can't fade -- bad time", self.timer_var.get()
 
                return
 

	
 
            # if we're not already fading, we get our time from the entry
 
            if self.fading != 'paused':
 
                self.fade_length = fade_time
 

	
 
            self.button['text'] = 'Pause'
 
            self.fading = 1
 
            self.do_fade()
 
    def do_fade(self):
 
        diff = time.time() - self.start_time
 
        if diff < self.fade_length:
 
            percent = diff / self.fade_length
 
            newlevel = self.start_level + \
 
                (percent * (self.end_level - self.start_level))
 
            self.scale_to_fade.scale_var.set(newlevel)
 

	
 
            if newlevel != self.end_level:
 
                self.last_after_key = self.after(10, self.do_fade)
 
            else:
 
                self.end_fade()
 
        else:
 
            self.scale_to_fade.scale_var.set(self.end_level)
 
            self.end_fade()
 
    def end_fade(self):
 
        self.button['text'] = self.name
 
        self.fading = 0
 
        self.after_cancel(self.last_after_key)
 
    def disable(self):
 
        if not self.disabled:
 
            self.button['state'] = 'disabled'
 
            self.disabled = 1
 
    def enable(self):
 
        if self.disabled:
 
            self.button['state'] = 'normal'
 
            self.disabled = 0
 
    def set_time(self, time):
 
        self.timer_var.set(time)
 
    def get_time(self):
 
        return self.timer_var.get()
 
    def is_fading(self):
 
        return self.fading
 

	
 
class CueFader(Tk.Frame):
 
    def __init__(self, master, cuelist):
 
        Tk.Frame.__init__(self, master, bg='black')
 
        self.cuelist = cuelist
 
        self.cuelist.set_fader(self)
 

	
 
        self.last_levels_sent = 0
 
        self.current_dmx_levels = [0] * 68
 
        self.after(0, self.send_dmx_levels_loop) # start DMX sending loop
 

	
 
        # this is a mechanism to stop Tk from autoshifting too much.
 
        # if this variable is true, the mouse button is down.  we don't want
 
        # to shift until they release it.  when it is released, we will
 
        # set it to false and then call autoshift.
 
        self.no_shifts_until_release = 0
 

	
 
        self.scales = {}
 
        self.shift_buttons = {}
 
        self.go_buttons = {}
 
        
 
        topframe = Tk.Frame(self, bg='black')
 

	
 
        self.set_prev_button = Tk.Button(topframe, text='Set Prev',
 
            command=lambda: cuelist.set_selection_as_prev(),
 
            fg='white', bg='blue')
 
        self.set_prev_button.pack(side='left')
 

	
 
        self.auto_shift = Tk.IntVar()
 
        self.auto_shift.set(1)
 

	
 
        self.auto_shift_checkbutton = Tk.Checkbutton(topframe, 
 
            variable=self.auto_shift, text='Autoshift', 
 
            command=self.toggle_autoshift, bg='black', fg='white',
 
            highlightbackground='black')
 
        self.auto_shift_checkbutton.pack(fill='both', side='left')
 

	
 
        self.auto_load_times = Tk.IntVar()
 
        self.auto_load_times.set(1)
 

	
 
        self.auto_load_times_checkbutton = Tk.Checkbutton(topframe, 
 
            variable=self.auto_load_times, text='Autoload Times', 
 
            bg='black', fg='white', 
 
            highlightbackground='black')
 
        self.auto_load_times_checkbutton.pack(fill='both', side='left')
 

	
 
        self.mute = Tk.IntVar()
 
        self.mute.set(0)
 

	
 
        self.mutebutton = Tk.Checkbutton(topframe, 
 
            variable=self.mute, text='Mute', 
 
            bg='black', fg='white', 
 
            highlightbackground='black',
 
            command=self.send_dmx_levels)
 
        self.mutebutton.pack(fill='both', side='left')
 

	
 
        self.set_next_button = Tk.Button(topframe, text='Set Next',
 
            command=lambda: cuelist.set_selection_as_next(),
 
            fg='white', bg='red')
 
        self.set_next_button.pack(side='left')
 

	
 
        topframe.pack(side='top')
 

	
 
        faderframe = Tk.Frame(self, bg='black')
 
        self.direction_info = (('Prev', 1, 0, 'left', 'blue'),
 
                               ('Next', 0, 1, 'right', 'red'))
 
        for name, start, end, side, color in self.direction_info:
 
            frame = Tk.Frame(self, bg='black')
 
            scale = LabelledScale(frame, name, from_=start, to_=end, 
 
                res=0.0001, orient='horiz', flashtroughcolor=color,
 
                labelformatter=lambda val, name=name: self.get_scale_desc(val, 
 
                                                                          name))
 
            scale.pack(fill='x', expand=0)
 
            go = TimedGoButton(frame, 'Go %s' % name, scale, bg=color, 
 
                fg='white', width=10)
 
            go.pack(fill='both', expand=1)
 
            frame.pack(side=side, fill='both', expand=1)
 
        
 
            shift = Tk.Button(frame, text="Shift %s" % name, state='disabled',
 
                command=lambda name=name: self.shift(name), fg=color, 
 
                bg='black')
 

	
 
            self.scales[name] = scale
 
            self.shift_buttons[name] = shift
 
            self.go_buttons[name] = go
 

	
 
            scale.scale_var.trace('w', \
 
                lambda x, y, z, name=name, scale=scale: self.xfade(name, scale))
 
            go.timer_var.trace('w',
 
                lambda x, y, z, scale=scale: scale.update_value_label())
 

	
 
            def button_press(event, name=name, scale=scale):
 
                self.no_shifts_until_release = 1 # prevent shifts until release
 
            def button_release(event, name=name, scale=scale):
 
                self.no_shifts_until_release = 0
 
                self.autoshift(name, scale)
 

	
 
            scale.scale.bind("<ButtonPress>", button_press)
 
            scale.scale.bind("<ButtonRelease>", button_release)
 
        faderframe.pack(side='bottom', fill='both', expand=1)
 

	
 
        self.current_dir = 'Next'
 
        self.cues_as_subs = {}
 
        self.update_cue_cache()
 
    def reload_cue_times(self):
 
        prev, cur, next = self.cuelist.get_current_cues()
 
        self.go_buttons['Next'].set_time(next.time)
 
    def update_cue_cache(self, compute_dmx_levels=1):
 
        """Rebuilds subs from the current cues.  As this is expensive, we don't
 
        do it unless necessary (i.e. whenever we shift or a cue is edited)"""
 
        # print "update_cue_cache"
 
        # load the subs to fade between
 
        for cue, name in zip(self.cuelist.get_current_cues(), 
 
                             ('Prev', 'Cur', 'Next')):
 
            self.cues_as_subs[name] = cue.get_levels_as_sub()
 
        if compute_dmx_levels:
 
            self.compute_dmx_levels()
 
    def compute_dmx_levels(self):
 
        """Compute the DMX levels to send.  This should get called whenever the
 
        DMX levels could change: either during a crossfade or when a cue is
 
        edited.  Since this is called when we know that a change might occur,
 
        we will send the new levels too."""
 
        cur_sub = self.cues_as_subs.get('Cur')
 
        if cur_sub:
 
            scale = self.scales[self.current_dir]
 
            scale_val = scale.scale_var.get() 
 

	
 
            other_sub = self.cues_as_subs[self.current_dir]
 
            current_levels_as_sub = cur_sub.crossfade(other_sub, scale_val)
 
            self.current_dmx_levels = current_levels_as_sub.get_dmx_list()
 
            self.send_dmx_levels()
 

	
 
            # print "compute_dmx_levels: fade at", scale_val
 
            # print "between", cur_sub.name, 
 
            # print "and", other_sub.name
 
            # print 
 
    def send_dmx_levels(self, *args):
 
        # print "send_dmx_levels", self.current_dmx_levels
 
        if self.mute.get():
 
            dmxclient.outputlevels([0] * 68)
 
        else:
 
            dmxclient.outputlevels(self.current_dmx_levels)
 
        self.last_levels_sent = time.time()
 
    def send_dmx_levels_loop(self):
 
        diff = time.time() - self.last_levels_sent
 
        if diff >= 2: # too long since last send
 
            self.send_dmx_levels()
 
            self.after(200, self.send_dmx_levels_loop)
 
        else:
 
            self.after(int((2 - diff) * 100), self.send_dmx_levels_loop)
 
    def get_scale_desc(self, val, name):
 
        """Returns a description to the TimedGoButton"""
 
        go_button = self.go_buttons.get(name)
 
        if go_button:
 
            time = go_button.get_time()
 
            return "%0.2f%%, %0.1fs left" % (val, time - ((val / 100.0) * time))
 
        else:
 
            return "%0.2f%%" % val
 
    def toggle_autoshift(self):
 
        for name, button in self.shift_buttons.items():
 
            if not self.auto_shift.get():
 
                button.pack(side='bottom', fill='both', expand=1)
 
            else:
 
                button.pack_forget()
 
    def shift(self, name):
 
        # to prevent overshifting
 
        if self.no_shifts_until_release:
 
            return
 
        # print "shift", name
 

	
 
        self.cuelist.shift((-1, 1)[name == 'Next'])
 
        self.update_cue_cache(compute_dmx_levels=0)
 
        for scale_name, scale in self.scales.items():
 
            # print "shift: setting scale to 0", scale_name
 
            scale.scale.set(0)
 
            self.go_buttons[scale_name].manual_override()
 
        self.update_cue_cache(compute_dmx_levels=1)
 

	
 
        if self.auto_load_times.get():
 
            self.reload_cue_times()
 
    def autoshift(self, name, scale):
 
        scale_val = scale.scale_var.get() 
 

	
 
        if scale_val == 1:
 
            if self.auto_shift.get():
 
                self.shift(name)
 
    def xfade(self, name, scale):
 
        if self.auto_shift.get():
 
            self.autoshift(name, scale)
 
            scale_val = scale.scale_var.get() 
 
        else:
 
            scale_val = scale.scale_var.get() 
 
            if scale_val == 1:
 
                self.shift_buttons[name]['state'] = 'normal'
 
            else:
 
                # disable any dangerous shifting
 
                self.shift_buttons[name]['state'] = 'disabled'
 

	
 
        d = self.opposite_direction(name)
 
        if scale_val != 0:
 
            # disable illegal three part crossfades
 
            self.scales[d].disable()
 
            self.go_buttons[d].disable()
 
        else:
 
            # undo above work
 
            self.scales[d].enable()
 
            self.go_buttons[d].enable()
 

	
 
        self.current_dir = name
 
        self.compute_dmx_levels()
 
    def opposite_direction(self, d):
 
        if d == 'Next':
 
            return 'Prev'
 
        else:
 
            return 'Next'
 

	
 
class Cue:
 
    """A Cue has a name, a time, and any number of other attributes."""
 
    def __init__(self, name, time=3, sub_levels='', **attrs):
 
        self.name = name
 
        self.time = time
 
        self.sub_levels = sub_levels
 
        self.__dict__.update(attrs)
 
    def __repr__(self):
 
        return "<Cue %s, length %s>" % (self.name, self.time)
 
    def get_levels_as_sub(self):
 
        """Get this Cue as a combined Submaster, normalized.  This method
 
        should not be called constantly, since it is somewhat expensive.  It
 
        will reload the submasters from disk, combine all subs together, and
 
        then compute the normalized form."""
 
        subdict = {}
 
        for line in self.sub_levels.split(','):
 
            try:
 
                line = line.strip()
 
                if not line: 
 
                    continue
 
                sub, scale = line.split(':')
 
                sub = sub.strip()
 
                scale = float(scale)
 
                subdict[sub] = scale
 
            except ValueError:
 
                print "Parsing error for '%s' in %s" % (self.sub_levels, self)
 

	
 
        s = Submaster.Submasters()
 
        newsub = Submaster.sub_maxes(*[s[sub] * scale 
 
            for sub, scale in subdict.items()])
 
        return newsub.get_normalized_copy()
 

	
 
empty_cue = Cue('empty')
 

	
 
allow_class_to_be_pickled(Cue)
 

	
 
class CueList:
 
    """Persistent list of Cues"""
 
    def __init__(self, filename):
 
        self.filename = filename
 
        self.treedict = TreeDict()
 
        try:
 
            self.treedict.load(filename)
 
        except IOError:
 
            self.treedict.cues = []
 
        self.cues = self.treedict.cues
 
        self.current_cue_index = -1
 
        self.next_pointer = 0
 
        self.prev_pointer = None
 

	
 
        import atexit
 
        atexit.register(self.save)
 
    def add_cue(self, cue, index=None):
 
        """Adds a Cue object to the list.  If no index is specified,
 
        the cue will be added to the end."""
 
        index = index or len(self.cues)
 
        self.cues.insert(index, cue)
 
    def shift(self, diff):
 
        """Shift through cue history"""
 
        old_index = self.current_cue_index
 
        self.current_cue_index = None
 
        if diff < 0: # if going backwards
 
            if self.prev_pointer: # use a prev pointer if we have one
 
                self.current_cue_index = self.prev_pointer
 
            self.next_pointer = old_index
 
            self.prev_pointer = None
 
        else:
 
            if self.next_pointer: # use a next pointer if we have one
 
                self.current_cue_index = self.next_pointer
 
            self.next_pointer = None
 
            self.prev_pointer = old_index
 
        if not self.current_cue_index:
 
            self.current_cue_index = old_index + diff
 
    def set_next(self, index):
 
        self.next_pointer = index
 
    def set_prev(self, index):
 
        self.prev_pointer = index
 
    def bound_index(self, index):
 
        if not self.cues or index < 0:
 
            return None
 
        else:
 
            return min(index, len(self.cues) - 1)
 
    def get_current_cue_indices(self):
 
        """Returns a list of the indices of three cues: the previous cue,
 
        the current cue, and the next cue."""
 
        cur = self.current_cue_index
 
        return [self.bound_index(index) for index in
 
                    (self.prev_pointer or cur - 1, 
 
                     cur, 
 
                     self.next_pointer or cur + 1)]
 
    def get_current_cues(self):
 
        """Returns a list of three cues: the previous cue, the current cue,
 
        and the next cue."""
 
        return [self.get_cue_by_index(index) 
 
            for index in self.get_current_cue_indices()]
 
    def get_cue_by_index(self, index):
 
        try:
 
            return self.cues[self.bound_index(index)]
 
        except TypeError:
 
            return empty_cue
 
    def __del__(self):
 
        self.save()
 
    def save(self, backup=0):
 
        if backup:
 

	
 
            backupfilename = "%s-backup" % self.filename
 
            print time.asctime(), "Saving backup version of cues to", \
 
                backupfilename
 
            self.treedict.save(backupfilename)
 
        else:
 
            print time.asctime(), "Saving cues to", self.filename
 
            self.treedict.save(self.filename)
 
    def reload(self):
 
        # TODO: we probably will need to make sure that indices still make
 
        # sense, etc.
 
        self.treedict.load(self.filename)
 

	
 
class TkCueList(CueList, Tk.Frame):
 
    def __init__(self, master, filename):
 
        CueList.__init__(self, filename)
 
        Tk.Frame.__init__(self, master, bg='black')
 
        self.fader = None
 
        
 
        self.edit_tl = Tk.Toplevel()
 
        self.editor = CueEditron(self.edit_tl, 
 
            changed_callback=self.cue_changed)
 
        self.editor.pack(fill='both', expand=1)
 

	
 
        def edit_cue(index):
 
            index = int(index)
 
            self.editor.set_cue_to_edit(self.cues[index])
 
            
 
        self.columns = ('name', 'time', 'page', 'cuenum', 'desc', 'sub_levels')
 
        self.scrolled_hlist = Tk.ScrolledHList(self,
 
            options='hlist.columns %d hlist.header 1' % len(self.columns))
 
        self.hlist = self.scrolled_hlist.hlist
 
        self.hlist.configure(fg='white', bg='black', 
 
            command=self.select_callback, browsecmd=edit_cue)
 
        self.hlist.bind("<4>", self.wheelscroll)
 
        self.hlist.bind("<5>", self.wheelscroll)
 
        self.scrolled_hlist.pack(fill='both', expand=1)
 

	
 
        boldfont = self.tk.call('tix', 'option', 'get', 
 
            'bold_font')
 
        header_style = Tk.DisplayStyle('text', refwindow=self,
 
            anchor='center', padx=8, pady=2, font=boldfont)
 

	
 
        for count, header in enumerate(self.columns):
 
            self.hlist.header_create(count, itemtype='text',
 
                text=header, style=header_style)
 

	
 
        self.cue_label_windows = {}
 
        for count, cue in enumerate(self.cues):
 
            self.display_cue(count, cue)
 
        self.update_cue_indicators()
 
        self.save_loop()
 
    def set_fader(self, fader):
 
        self.fader = fader
 
    def wheelscroll(self, evt):
 
        """Perform mouse wheel scrolling"""
 
        if evt.num == 4: # scroll down
 
            amount = -2
 
        else:            # scroll up
 
            amount = 2
 
        self.hlist.yview('scroll', amount, 'units')
 
    def cue_changed(self, cue):
 
        path = self.cues.index(cue)
 
        for col, header in enumerate(self.columns):
 
            try:
 
                text = getattr(cue, header)
 
            except AttributeError:
 
                text = ''
 

	
 
            if col == 0:
 
                self.cue_label_windows[path]['text'] = text
 
            else:
 
                self.hlist.item_configure(path, col, text=text)
 

	
 
        if cue in self.get_current_cues() and self.fader:
 
            self.fader.update_cue_cache()
 
            self.fader.reload_cue_times()
 
    def display_cue(self, path, cue):
 
        for col, header in enumerate(self.columns):
 
            try:
 
                text = getattr(cue, header)
 
            except AttributeError:
 
                text = ''
 

	
 
            if col == 0:
 
                lab = Tk.Label(self.hlist, text=text, fg='white', bg='black')
 
                def select_and_highlight(event):
 
                    self.select_callback(path)
 
                    self.hlist.selection_clear()
 
                    self.hlist.selection_set(path)
 

	
 
                lab.bind("<Double-1>", select_and_highlight)
 
                self.hlist.add(path, itemtype='window', window=lab)
 
                self.cue_label_windows[path] = lab
 
            else:
 
                self.hlist.item_create(path, col, text=text)
 
    def reset_cue_indicators(self, cue_indices=None):
 
        """If cue_indices is None, we'll reset all of them."""
 
        cue_indices = cue_indices or self.cue_label_windows.keys()
 
        for key in cue_indices:
 
            if key is None:
 
                continue
 
            window = self.cue_label_windows[key]
 
            window.configure(fg='white', bg='black')
 
    def update_cue_indicators(self):
 
        states = dict(zip(self.get_current_cue_indices(), 
 
                     ('prev', 'cur', 'next')))
 

	
 
        for count, state in states.items():
 
            if count is None:
 
                continue
 
            window = self.cue_label_windows[count]
 
            bg, fg = cue_state_indicator_colors[state]
 
            window.configure(bg=bg, fg=fg)
 
    def shift(self, diff):
 
        self.reset_cue_indicators(self.get_current_cue_indices())
 
        CueList.shift(self, diff)
 
        self.update_cue_indicators()
 
        # try to see all indices, but next takes priority over all, and cur
 
        # over prev
 
        for index in self.get_current_cue_indices():
 
            if index is not None:
 
                self.hlist.see(index)
 
    def select_callback(self, index):
 
        new_next = int(index)
 
        self.set_next(new_next)
 
    def set_next(self, index):
 
        prev, cur, next = self.get_current_cue_indices()
 
        self.reset_cue_indicators((next,))
 
        CueList.set_next(self, index)
 
        self.update_cue_indicators()
 

	
 
        if self.fader: # XXX this is untested
 
            self.fader.update_cue_cache()
 
    def set_prev(self, index):
 
        prev, cur, next = self.get_current_cue_indices()
 
        self.reset_cue_indicators((prev,))
 
        CueList.set_prev(self, index)
 
        self.update_cue_indicators()
 

	
 
        if self.fader: # XXX this is untested
 
            self.fader.update_cue_cache()
 
    def set_selection_as_prev(self):
 
        sel = self.hlist.info_selection()
 
        if sel:
 
            self.set_prev(int(sel[0]))
 
    def set_selection_as_next(self):
 
        sel = self.hlist.info_selection()
 
        if sel:
 
            self.set_next(int(sel[0]))
 
    def save_loop(self):
 
        """This saves the CueList every minute."""
 
        self.save(backup=1)
 
        self.after(60000, self.save_loop)
 

	
 
class CueEditron(Tk.Frame):
 
    def __init__(self, master, changed_callback=None, cue=None):
 
        Tk.Frame.__init__(self, master, bg='black')
 
        self.master = master
 
        self.cue = cue
 
        self.changed_callback = changed_callback
 
        self.enable_callbacks = 1
 

	
 
        self.setup_editing_forms()
 
        self.set_cue_to_edit(cue)
 
    def set_cue_to_edit(self, cue):
 
        if cue != self.cue:
 
            self.cue = cue
 
            self.fill_in_cue_info()
 
            self.set_title()
 
    def set_title(self):
 
            try:
 
                self.master.title("Editing '%s'" % self.cue.name)
 
            except AttributeError:
 
                pass
 
    def setup_editing_forms(self):
 
        self.variables = {}
 
        for row, field in enumerate(('name', 'time', 'page', 'cuenum', 'desc', 
 
            'sub_levels')):
 
            lab = Tk.Label(self, text=field, fg='white', bg='black')
 
            lab.grid(row=row, column=0, sticky='nsew')
 

	
 
            entryvar = Tk.StringVar()
 
            entry = Tk.Entry(self, fg='white', bg='black', 
 
                textvariable=entryvar, insertbackground='white',
 
                highlightcolor='red') # TODO this red/black is backwards
 
            entry.grid(row=row, column=1, sticky='nsew')
 

	
 
            self.variables[field] = entryvar
 

	
 
            def field_changed(x, y, z, field=field, entryvar=entryvar):
 
                if self.cue:
 
                    setattr(self.cue, field, entryvar.get())
 
                    if self.enable_callbacks and self.changed_callback:
 
                        self.changed_callback(self.cue)
 
                if field == 'name':
 
                    self.set_title()
 

	
 
            entryvar.trace('w', field_changed)
 
        self.columnconfigure(1, weight=1)
 
    def fill_in_cue_info(self):
 
        self.enable_callbacks = 0
 
        for row, field in enumerate(('name', 'time', 'page', 'cuenum', 'desc', 
 
            'sub_levels')):
 
            text = ''
 
            if self.cue:
 
                try:
 
                    text = getattr(self.cue, field)
 
                except AttributeError:
 
                    pass
 
            self.variables[field].set(text)
 
        self.enable_callbacks = 1
 

	
 
if __name__ == "__main__":
 
    root = Tk.Tk()
 
    root.title("ShowMaster 9000")
 
    root.geometry("600x670")
 
    cl = TkCueList(root, 'cues/dolly')
 
    cl.pack(fill='both', expand=1)
 

	
 
    fader = CueFader(root, cl)
 
    fader.pack(fill='both', expand=1)
 
    try:
 
        Tk.mainloop()
 
    except KeyboardInterrupt:
 
        root.destroy()
flax/KeyboardComposer.py
Show inline comments
 
new file 100644
 
from __future__ import nested_scopes
 
import sys, time
 
sys.path.append('..')
 
from Widgets.Fadable import Fadable
 

	
 
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 8", bg='black',
 
            fg='white')
 
        namelabel.pack(side=TOP)
 
        levellabel = Label(self, textvariable=self.slider_var, font="Arial 8",
 
            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.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))
 

	
 
        # page up and page down change the row
 
        for key in '<Prior> <Next> <Control-n> <Control-p>'.split():
 
            tkobject.bind(key, self.change_row)
 

	
 
    def change_row(self, event):
 
        diff = 1
 
        if event.keysym in ('Prior', 'p'):
 
            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
 
            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.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()
 

	
 
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)
 
    atexit.register(kc.save)
 
    try:
 
        mainloop()
 
    except KeyboardInterrupt:
 
        tl.destroy()
 
        sys.exit()
flax/KeyboardRecorder.py
Show inline comments
 
new file 100644
 
from __future__ import nested_scopes
 
import sys, time
 
sys.path.append('..')
 
from Widgets.Fadable import Fadable
 

	
 
from Tix import *
 
import math, atexit, pickle
 
from sets import Set
 
from Submaster import Submasters, sub_maxes
 
import dmxclient
 
from uihelpers import toplevelat
 

	
 
# idea is that one system handles all the level logging persistence
 
# (it also needs to know which song we're currently working on)
 
# SubLevelLogger is not yet written
 
from SubLevelLogger import SubLevelLogger
 

	
 
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, kbrecorder=None):
 
        """kbrecorder is a KeyboardRecorder instance -- take that, Java"""
 
        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)
 
        self.namelabel = Label(self, text=name, font="Arial 8", bg='black',
 
            fg='white')
 
        self.namelabel.pack(side=TOP)
 
        self.levellabel = Label(self, 
 
            font="Arial 8", bg='black', fg='white')
 
        self.levellabel.pack(side=TOP)
 
        self.scale.pack(side=BOTTOM, expand=1, fill=BOTH)
 

	
 
        # recording bits
 
        self.kbrecorder = kbrecorder
 
        self.recording = 0
 
        self.name = name
 
        self._last_recorded_value = None
 
        for obj in (self, self.levellabel, self.namelabel, self.scale):
 
            obj.bind('<3>', self.toggle_record)
 

	
 
        self.slider_var.trace('w', self.draw_level)
 
        self.draw_level()
 
    def draw_level(self, *args):
 
        self.levellabel['text'] = "%d" % (self.slider_var.get() * 100)
 
    def toggle_record(self, evt):
 
        self.kbrecorder.toggle_sub_recording(self.name)
 
        self.recording = not self.recording
 
        if self.recording:
 
            conf = {'bg' : 'red'}
 
        else:
 
            conf = {'bg' : 'black'}
 

	
 
        for obj in (self, self.levellabel, self.namelabel):
 
            obj.config(**conf)
 
    def record(self, timestamp):
 
        """This is called whenever we get a timestamp and we're recording."""
 
        new_value = self.scale.scale_var.get()
 
        if new_value != self._last_recorded_value:
 
            s = SubLevelLogger()
 
            s.save_level(name, timestamp, new_value)
 

	
 
        self._last_recorded_value = new_value
 
    def get_recorded_level(self, timestamp):
 
        """This is called whenever we get a timestamp and we're not 
 
        recording.
 
        
 
        TODO: independent subs don't playback from recorded levels"""
 
        s = SubLevelLogger()
 
        new_value = s.load_level(name, timestamp)
 
        self.scale.scale_var.set(new_value)
 

	
 
class KeyboardRecorder(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.subs_being_recorded = Set() # yay, this is the first time I've
 
                                         # used Set!
 

	
 
        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, self)
 
        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.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()
 
    def toggle_sub_recording(self, subname):
 
        # xor the set with the subname
 
        self.subs_being_recorded = self.subs_being_recorded ^ Set([subname])
 
    def got_timestamp(self, timestamp):
 
        """Music player should ultimately call this (over XML-RPC).  
 
        
 
        For subs not being recorded, we bring up their values (unless
 
        they're independent maybe? -- independence not implemented yet).
 

	
 
        For subs being recorded, we record their values to disk.
 
        
 
        Each SubmasterTk talks to the SubLevelLogger to record and playback
 
        levels."""
 
        for sub in self.submasters.get_all_subs():
 
            name = sub.name
 
            subtk = self.name_to_subtk[name]
 
            if name in self.subs_being_recorded:
 
                subtk.record(timestamp)
 
            else:
 
                subtk.get_recorded_level(timestamp)
 

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

	
 
    root = Tk()
 
    tl = toplevelat("Keyboard Recorder XP MX 2004 Gold", existingtoplevel=root)
 
    kc = KeyboardRecorder(tl, s, dmxdummy=0)
 
    kc.pack(fill=BOTH, expand=1)
 
    atexit.register(kc.save)
 
    try:
 
        mainloop()
 
    except KeyboardInterrupt:
 
        tl.destroy()
 
        sys.exit()
flax/MusicTime.py
Show inline comments
 
new file 100644
 
import Tkinter as tk
 
import xmlrpclib, socket, time
 

	
 
class MusicTime:
 
    def __init__(self, server, port):
 
        self.player = xmlrpclib.Server("http://%s:%d" % (server, port))
 
    def get_music_time(self):
 
        playtime = None
 
        while not playtime:
 
            try:
 
                playtime = self.player.gettime()
 
            except socket.error, e:
 
                print "Server error %s, waiting" % e
 
                time.sleep(2)
 
        return playtime
 

	
 
class MusicTimeTk(tk.Frame, MusicTime):
 
    def __init__(self, master, server, port):
 
        tk.Frame.__init__(self)
 
        MusicTime.__init__(self, server, port)
 
        self.timevar = tk.DoubleVar()
 
        self.timelabel = tk.Label(self, textvariable=self.timevar, bd=2,
 
            relief='raised', width=10, padx=2, pady=2, anchor='w')
 
        self.timelabel.pack(expand=1, fill='both')
 
        def print_time(evt, *args):
 
            self.update_time()
 
            print self.timevar.get(), evt.keysym
 
        self.timelabel.bind('<KeyPress>', print_time)
 
        self.timelabel.bind('<1>', print_time)
 
        self.timelabel.focus()
 
        self.update_time()
 
    def update_time(self):
 
        self.timevar.set(self.get_music_time())
 
        self.after(100, self.update_time)
 

	
 
if __name__ == "__main__":
 
    from optik import OptionParser
 
    parser = OptionParser()
 
    parser.add_option("-s", "--server", default='dash')
 
    parser.add_option("-p", "--port", default=8040, type='int')
 
    options, args = parser.parse_args()
 
    
 
    root = tk.Tk()
 
    root.title("Time")
 
    MusicTimeTk(root, options.server, options.port).pack(expand=1, fill='both')
 
    try:
 
        tk.mainloop()
 
    except KeyboardInterrupt:
 
        root.destroy()
flax/Node.py
Show inline comments
 
new file 100644
 
# super rough code
 

	
 
# The magic value
 
NoChange = "NoChange"
 

	
 
class NodeType:
 
    def __init__(self, iports=None, oports=None):
 
        make_attributes_from_args('iports', 'oports')
 
    def process(self,iports,oports):
 
        pass
 
        # TODO: handle NoChange stuff
 

	
 
class AddNode(NodeType):
 
    """Adds two nodes together"""
 
    def __init__(self):
 
        NodeType.__init__(self, iports={'in1' : Port, 'in2' : Port},
 
                                oports={'out1' : Port})
 
    def process(self, ports):
 
        ports.out1 = ports.in1 + ports.in2
 

	
 
class SumNode(NodeType):
 
    """Adds any number of nodes together"""
 
    def __init__(self, empty_val=0):
 
        NodeType.__init__(self, iports={'in1' : MultiPort},
 
                                oports={'out1' : Port})
 
        self.empty_val = 0
 
    def process(self, ports):
 
        val = self.empty_val
 
        for p in ports.in1:
 
            val += p
 

	
 
        ports.out1 = val
 

	
 
class FadeNode(NodeType):
 
    """Provides a UI scaler to let you fade a value"""
 
    def __init__(self):
 
        NodeType.__init__(self, iports={'in1' : Port(), 
 
                                        'scale1' : Port()},
 
                                oports={'out1' : Port()},
 
    def process(self, iports, oports):
 
        ports.out1 = ports.in1 * ports.scale1 
 

	
 
class FadeConstellation(Constellation):
 
    """This somehow describes the following:
 

	
 
    [      ]    [ UI.Scale ]
 
      |            |
 
      | in         | scale
 
      |      ____ /
 
      |      |
 
    [ FadeNode ]
 
      | 
 
      | out
 
      |
 
    [      ]
 

	
 
    Maybe this is a group (I like this more):
 

	
 
      |
 
      | in
 
      |                FadeGroup
 
   - - - - - - - - - - - - -- - -
 
  |   |                          |
 
      |          [ UI.Scale ]    
 
  |   |            |             |
 
      | in         | scale  
 
  |   |      ____ /              |
 
      |      |
 
  | [ FadeNode ]                 |
 
      | 
 
  |   | out                      |
 
      |                          
 
  \ - - - - - - - - - - - -- - - /
 
      |
 
      | out
 
      | 
 
    """
 

	
 
Persistence
 
node instance saves:
 
    node name, id, and such
 
    input ports:
 
        any port details
 
        what the port connects to
 
    values:
 
        maybe UI.Scale level
 
        maybe group contents
 

	
 

	
 
p=InputPort(node=self,minconns=1,maxconns=2) # an input port to this node
 
p.connect(othernode)
 
print p.connections()
 
p.connect(yetanother)
 

	
 
op=OutputPort(node=self) # an output port
 
print op.connections() # lists all the nodes that call us an input node
 
op.connect(n) # calls n.connect(self)
 

	
 

	
 

	
 
        
 
Ports
 
    Port: "scalar"
 
    MultiPort: "array of Port"
 
    ^ all wrong
 

	
 
    Ports II:
 
        min/max number of connections
 
           (failure to fit these numbers means port is "deactivated")
 
        "Normal" ports are min=1, max=1
 
        "Multiports" are min=0, max=None
 
        "Binary" ports are min=2, max=2
 
        oh yeah, there are two totally different types of ports
 

	
 
        Input ports: min/max numbers of connections
 
           store current connections
 
        Output ports: nothing
 
           store nothing!
 

	
 
fake node persistence for subtract node
 

	
 
<!-- "my subtract" is a unique id -->
 
<!-- drew: there is no such thing as a subtract group -->
 
<node name="my subtract" type="math.Add">
 
  <inputs>
 
    <port name="in1">
 
       <noderef name="node I"/>
 
       <noderef name="node II"/>
 
    </port>
 
  </inputs>
 
  <state>
 
  </state>
 
    
 
</node>
 

	
 

	
 
<node name="the group" type="group">
 

	
 
  <!-- all of the port names of the group are being made up right
 
  here- a group has no preset inputs or outputs-->
 

	
 
  <inputs>
 
    <port name="group-in 1">
 
      <noderef name="node5"/>
 
      <noderef name="node6"/>
 
    </port>
 
  </inputs>
 
  
 
  <state>  
 
    <children>
 
      <noderef name="node3">
 
        <connect localPort="in1" groupPort="group-in1"/>
 
      </noderef>
 
      <noderef name="node4">
 
        <connect localPort="in1" groupPort="group-in1"/>
 
        <connect localPort="out1" groupPort="theoutput"/>
 
      </noderef>
 
    </children>
 

	
 
  </state>  
 
</node>
 

	
 
<node name="preset value" type="source.Scalar">
 
  <!-- no inputs, node has output only -->
 
  <state>
 
    <value>8.3</value>
 

	
 
    <minvalue>0</minvalue>
 
    <maxvalue>100</maxvalue>
 
    
 
    <gui>
 
      <slider res=".1" height="200" bgcolor="red"/>
 
      <priority>very high</priority>
 
      <xpos>395</xpos>
 
      <ypos>21</ypos>
 
    </gui>
 
    
 
  </state>
 

	
 
</node>
flax/Ports.py
Show inline comments
 
new file 100644
 
# super rough code
 

	
 
class AbstractPort:
 
    def __init__(self):
 
        pass
 
    def put_data(self, value):
 
        pass
 
    def get_data(self):
 
        pass
 

	
 
class Port(AbstractPort):
 
    "Connects from a node to exactly one node."
 
    def __init__(self, value=None):
 
        AbstractPort.__init__(self)
 
        self.value = value
 
    def put_data(self, value):
 
        self.value = value
 
    def get_data(self):
 
        return self.value
 

	
 
class MultiPort(AbstractPort):
 
    "Connects from a node to any number of nodes."
 
    def __init__(self, values=None):
 
        AbstractPort.__init__(self)
 
        self.values = values
 
    def put_data(self, values):
 
        self.values = values
 
    def get_data(self):
 
        return self.values
flax/Show.py
Show inline comments
 
new file 100644
 
from Timeline import *
 
from Submaster import Submasters, sub_maxes
 

	
 
class Show:
 
    def __init__(self, timelines, submasters):
 
        self.timelines = dict([(timeline.name, timeline)
 
            for timeline in timelines])
 
        self.submasters = submasters
 
        self.current_timeline = None
 
        self.current_time = 0
 
    def calc_active_submaster(self):
 
        "get levels from the current timeline at the current time"
 
        if not (self.current_timeline or self.current_time):
 
            return {}
 
        tl = self.current_timeline
 
        tl.set_time(self.current_time)
 
        levels = tl.get_levels()
 
        scaledsubs = [self.submasters.get_sub_by_name(sub) * level \
 
            for sub, level in levels.items()]
 
        maxes = sub_maxes(*scaledsubs)
 

	
 
        return maxes
 
    def set_timeline(self, name):
 
        "select a timeline"
 
        self.current_timeline = self.timelines.get(name)
 
        if not self.current_timeline:
 
            print "Show: '%s' is not the name of a timeline." % name
 
        else:
 
            self.set_time(0)
 
    def set_time(self, time):
 
        "set time of current timeline"
 
        self.current_time = time
 
        if not self.current_timeline:
 
            return
 
        self.current_timeline.set_time(time)
 
    def get_timelines(self):
 
        "Get names of all timelines"
 
        return self.timelines.keys()
 

	
 
# this is the default blender
 
linear = LinearBlender()
 
def T(time, level, **kw):
 
    """This used to be a synonym for TimedEvent:
 

	
 
    T = TimedEvent
 

	
 
    It now acts in a similar way, except that it will fill in a default 
 
    blender if you don't.  The default blender is a LinearBlender.  It also
 
    sets frame to MISSING so the track can fill it in."""
 
    if 'blender' not in kw:
 
        global linear
 
        kw['blender'] = linear
 

	
 
    return TimedEvent(time, level=level, frame=MISSING, **kw)
 

	
 
def translate_tracks_from_file(timelinename):
 
    try:
 
        f = open('timelines/' + timelinename)
 
    except IOError:
 
        return []
 

	
 
    events = {}
 
    current_sub = None
 
    current_time = None
 
    lines = f.readlines()
 
    alltext = ' '.join(lines)
 

	
 
    for token in alltext.split():
 
        # print 't', token
 
        if token.endswith(':'):
 
            # print 'sub'
 
            current_sub = token[:-1]
 
            current_time = None
 
        else:
 
            if not current_sub:
 
                raise "invalid file format", line
 
            if current_time is not None: # we now have a level
 
                # print 'level'
 
                try:
 
                    level = float(token)
 
                except:
 
                    print "bad level token", token
 
                    level = 0
 
                # time to write
 
                events.setdefault(current_sub, [])
 
                events[current_sub].append((current_time, level))
 
                current_time = None
 
            else:
 
                # print 'time'
 
                try:
 
                    current_time = float(token)
 
                except ValueError:
 
                    print "bad time token", token
 
                    current_time = 0
 

	
 
    tracks = []
 
    for sub, timelevels in events.items():
 
        tracks.append(TimelineTrack(sub, default_frame=sub,
 
            *[T(time, level) for time, level in timelevels]))
 

	
 
    return tracks
 

	
flax/Subcomposer.py
Show inline comments
 
new file 100644
 
#!/usr/bin/python
 

	
 
from __future__ import division, nested_scopes
 
import Tkinter as tk
 
from dmxchanedit import Levelbox
 
import sys,os,time,atexit
 
sys.path.append("../light8")
 
import dmxclient
 
import Patch
 
import Submaster
 

	
 
from dispatch import dispatcher
 

	
 
class Subcomposer(tk.Frame):
 
    def __init__(self, master, levelboxopts=None, dmxdummy=0, numchannels=68,
 
        use_persistentlevels=0):
 
        tk.Frame.__init__(self, master, bg='black')
 
        self.dmxdummy = dmxdummy
 
        self.numchannels = numchannels
 

	
 
        self.levels = [0]*68 # levels should never get overwritten, just edited
 

	
 
        self.levelbox = Levelbox(self)
 
        self.levelbox.pack(side='top')
 
        # the dmx levels we edit and output, range is 0..1 (dmx chan 1 is
 
        # the 0 element)
 
        self.levelbox.setlevels(self.levels)
 

	
 
        self.savebox = Savebox(self, self.levels, cmd=self.savenewsub)
 
        self.savebox.pack(side='top')
 

	
 
        self.loadbox = Savebox(self, self.levels, verb="Load", cmd=self.loadsub)
 
        self.loadbox.pack(side='top')
 

	
 
        def alltozero():
 
            self.set_levels([0] * self.numchannels)
 
            dispatcher.send("levelchanged")
 

	
 
        tk.Button(self, text="all to zero", command=alltozero).pack(side='top')
 

	
 
        dispatcher.connect(self.levelchanged,"levelchanged")
 
        dispatcher.connect(self.sendupdate,"levelchanged")
 

	
 
        if use_persistentlevels:
 
            self.persistentlevels()
 

	
 
        self.lastupdate=0 # time we last sent to dmx
 

	
 
        self.lastsent=[] # copy of levels
 

	
 
    def persistentlevels(self):
 
        """adjusts levels from subcomposer.savedlevels, if possible; and
 
        arranges to save the levels in that file upon exit"""    
 
        self.load_levels()
 
        atexit.register(self.save_levels)
 
    def save_levels(self, *args):
 
        levelfile = file("subcomposer.savedlevels","w")
 
        levelfile.write(" ".join(map(str, self.levels)))
 
    def load_levels(self):
 
        try:
 
            levelfile = file("subcomposer.savedlevels","r")
 
            levels = map(float, levelfile.read().split())
 
            self.set_levels(levels)
 
        except IOError:
 
            pass
 
    def levelchanged(self, channel=None, newlevel=None):
 
        if channel is not None and newlevel is not None:
 
            if channel>len(self.levels):
 
                return
 
            self.levels[channel-1]=max(0,min(1,float(newlevel)))
 
        self.levelbox.setlevels(self.levels)
 
    def savenewsub(self, levels, subname):
 
        leveldict={}
 
        for i,lev in zip(range(len(self.levels)),self.levels):
 
            if lev!=0:
 
                leveldict[Patch.get_channel_name(i+1)]=lev
 
        
 
        s=Submaster.Submaster(subname,leveldict)
 
        s.save()
 
    def loadsub(self, levels, subname):
 
        """puts a sub into the levels, replacing old level values"""
 
        s=Submaster.Submasters().get_sub_by_name(subname)
 
        self.levels[:]=[0]*68
 
        self.levels[:]=s.get_dmx_list()
 
        dispatcher.send("levelchanged")
 
    def sendupdate(self):
 
        if not self.dmxdummy:
 
            dmxclient.outputlevels(self.levels)
 
            self.lastupdate = time.time()
 
            self.lastsent = self.levels[:]
 
    def considersendupdate(self, use_after_loop=0):
 
        """If use_after_loop is true, it is the period of the after loop."""
 
        if self.lastsent != self.levels or time.time() > self.lastupdate + 1:
 
            self.sendupdate()
 
        if use_after_loop:
 
            self.after(use_after_loop, self.considersendupdate, use_after_loop)
 
    def set_levels(self, levels):
 
        self.levels[:] = levels
 
        dispatcher.send("levelchanged")
 

	
 
def Savebox(master, levels, verb="Save", cmd=None):
 
    f=tk.Frame(master,bd=2,relief='raised')
 
    tk.Label(f,text="Sub name:").pack(side='left')
 
    e=tk.Entry(f)
 
    e.pack(side='left',exp=1,fill='x')
 
    def cb(*args):
 
        subname=e.get()
 
        cmd(levels,subname)
 
        print "sub",verb,subname
 
    e.bind("<Return>",cb)
 
    tk.Button(f,text=verb,command=cb).pack(side='left')
 
    return f
 

	
 
def open_sub_editing_window(subname, use_mainloop=1, dmxdummy=0):
 
    if use_mainloop:
 
        toplevel = tk.Tk()
 
    else:
 
        toplevel = tk.Toplevel()
 
    if dmxdummy: 
 
        dummy_str = ' (dummy)'
 
    else:
 
        dummy_str = ''
 
    toplevel.title("Subcomposer: %s%s" % (subname, dummy_str))
 
    sc = Subcomposer(toplevel, use_persistentlevels=0, dmxdummy=dmxdummy)
 
    sc.pack(fill='both', expand=1)
 
    sc.loadsub(None, subname) # don't ask
 
    sc.considersendupdate(use_after_loop=10)
 
    if use_mainloop:
 
        tk.mainloop()
 
    
 
#############################
 

	
 
if __name__ == "__main__":
 
    root=tk.Tk()
 
    root.config(bg='black')
 
    root.tk_setPalette("#004633")
 

	
 
    sc = Subcomposer(root, dmxdummy=0)
 
    sc.pack()
 

	
 
    while 1:
 
        if 0:
 
            for i in range(20): # don't let Tk take all the time
 
                tk._tkinter.dooneevent()
 
            print "loop"
 
        else:
 
            root.update()
 
        
 
        sc.considersendupdate()
 
        time.sleep(.01)
flax/Submaster.py
Show inline comments
 
new file 100644
 
from __future__ import division
 
from TLUtility import dict_scale, dict_max
 

	
 
import sys
 
sys.path.append('../light8')
 

	
 
import Patch
 

	
 
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()
 
    def reload(self):
 
        if self.temporary:
 
            return
 
        try:
 
            self.levels.clear()
 
            subfile = file("subs/%s" % 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])
 
        except IOError:
 
            print "Can't read file for sub: %s" % self.name
 
    def save(self):
 
        if self.temporary:
 
            return
 

	
 
        subfile = file("subs/%s" % 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 __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):
 
        levels = ' '.join(["%s:%.2f" % item for item in self.levels.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():
 
            dmxchan = Patch.get_dmx_channel(k) - 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 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):
 
    return Submaster("max(%r)" % (subs,),
 
        dict_max(*[sub.levels for sub in subs]), temporary=1)
 

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

	
 
        import os
 
        files = os.listdir('subs')
 

	
 
        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_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
flax/TLUtility.py
Show inline comments
 
new file 100644
 
"""Collected utility functions, many are taken from Drew's utils.py in
 
Cuisine CVS and Hiss's Utility.py."""
 

	
 
from __future__ import generators
 
import sys
 

	
 
__author__ = "David McClosky <dmcc@bigasterisk.com>, " + \
 
             "Drew Perttula <drewp@bigasterisk.com>"
 
__cvsid__ = "$Id: TLUtility.py,v 1.1 2003/05/25 08:25:35 dmcc Exp $"
 
__version__ = "$Revision: 1.1 $"[11:-2]
 

	
 
def make_attributes_from_args(*argnames):
 
    """
 
    This function simulates the effect of running
 
      self.foo=foo
 
    for each of the given argument names ('foo' in the example just
 
    now). Now you can write:
 
        def __init__(self,foo,bar,baz):
 
            copy_to_attributes('foo','bar','baz')
 
            ...
 
    instead of:
 
        def __init__(self,foo,bar,baz):
 
            self.foo=foo
 
            self.bar=bar
 
            self.baz=baz
 
            ... 
 
    """
 
    
 
    callerlocals=sys._getframe(1).f_locals
 
    callerself=callerlocals['self']
 
    for a in argnames:
 
        try:
 
            setattr(callerself,a,callerlocals[a])
 
        except KeyError:
 
            raise KeyError, "Function has no argument '%s'" % a
 

	
 
def enumerate(*collections):
 
    """Generates an indexed series:  (0,coll[0]), (1,coll[1]) ...
 
    
 
    this is a multi-list version of the code from the PEP:
 
    enumerate(a,b) gives (0,a[0],b[0]), (1,a[1],b[1]) ...
 
    """
 
    i = 0
 
    iters = [iter(collection) for collection in collections]
 
    while 1:
 
        yield [i,] + [iterator.next() for iterator in iters]
 
        i += 1
 

	
 
def dumpobj(o):
 
    """Prints all the object's non-callable attributes"""
 
    print repr(o)
 
    for a in [x for x in dir(o) if not callable(getattr(o, x))]:
 
        try:
 
            print "  %20s: %s " % (a, getattr(o, a))
 
        except:
 
            pass
 
    print ""
 

	
 
def dict_filter_update(d, **newitems):
 
    """Adds a set of new keys and values to dictionary 'd' if the values are
 
    true:
 

	
 
    >>> some_dict = {}
 
    >>> dict_filter_update(some_dict, a=None, b=0, c=1, e={}, s='hello')
 
    >>> some_dict
 
    {'c': 1, 's': 'hello'}
 
    """
 
    for k, v in newitems.items():
 
        if v: d[k] = v
 

	
 
def try_get_logger(channel):
 
    """Tries to get a logger with the channel 'channel'.  Will return a
 
    silent DummyClass if logging is not available."""
 
    try:
 
        import logging
 
        log = logging.getLogger(channel)
 
    except ImportError:
 
        log = DummyClass()
 
    return log
 

	
 
class DummyClass:
 
    """A class that can be instantiated but never used.  It is intended to
 
    be replaced when information is available.
 
    
 
    Usage:
 
    >>> d = DummyClass(1, 2, x="xyzzy")
 
    >>> d.someattr
 
    Traceback (most recent call last):
 
      File "<stdin>", line 1, in ?
 
      File "Utility.py", line 33, in __getattr__
 
        raise AttributeError, "Attempted usage of a DummyClass: %s" % key
 
    AttributeError: Attempted usage of a DummyClass: someattr
 
    >>> d.somefunction()
 
    Traceback (most recent call last):
 
      File "<stdin>", line 1, in ?
 
      File "Utility.py", line 33, in __getattr__
 
        raise AttributeError, "Attempted usage of a DummyClass: %s" % key
 
    AttributeError: Attempted usage of a DummyClass: somefunction"""
 
    def __init__(self, use_warnings=1, raise_exceptions=0, **kw):
 
        """Constructs a DummyClass"""
 
        make_attributes_from_args('use_warnings', 'raise_exceptions')
 
    def __getattr__(self, key):
 
        """Raises an exception to warn the user that a Dummy is not being
 
        replaced in time."""
 
        if key == "__del__":
 
            return
 
        msg = "Attempted usage of '%s' on a DummyClass" % key
 
        if self.use_warnings:
 
            import warnings
 
            warnings.warn(msg)
 
        if self.raise_exceptions:
 
            raise AttributeError, msg
 
        return lambda *args, **kw: self.bogus_function()
 
    def bogus_function(self):
 
        pass
 

	
 
class ClassyDict(dict):
 
    """A dict that accepts attribute-style access as well (for keys
 
    that are legal names, obviously). I used to call this Struct, but
 
    chose the more colorful name to avoid confusion with the struct
 
    module."""
 
    def __getattr__(self, a):
 
        return self[a]
 
    def __setattr__(self, a, v):
 
        self[a] = v
 
    def __delattr__(self, a):
 
        del self[a]
 

	
 
def trace(func):
 
    """Good old fashioned Lisp-style tracing.  Example usage:
 
    
 
    >>> def f(a, b, c=3):
 
    >>>     print a, b, c
 
    >>>     return a + b
 
    >>>
 
    >>>
 
    >>> f = trace(f)
 
    >>> f(1, 2)
 
    |>> f called args: [1, 2]
 
    1 2 3
 
    <<| f returned 3
 
    3
 

	
 
    TODO: print out default keywords (maybe)
 
          indent for recursive call like the lisp version (possible use of 
 
              generators?)"""
 
    name = func.func_name
 
    def tracer(*args, **kw):
 
        s = '|>> %s called' % name
 
        if args:
 
            s += ' args: %r' % list(args)
 
        if kw:
 
            s += ' kw: %r' % kw
 
        print s
 
        ret = func(*args, **kw)
 
        print '<<| %s returned %s' % (name, ret)
 
        return ret
 
    return tracer
 

	
 
# these functions taken from old light8 code
 
def dict_max(*dicts):
 
    """
 
    ({'a' : 5, 'b' : 9}, {'a' : 10, 'b' : 4})
 
      returns ==> {'a' : 10, 'b' : 9}
 
    """
 
    newdict = {}
 
    for d in dicts:
 
        for k,v in d.items():
 
            newdict[k] = max(v, newdict.get(k, 0))
 
    return newdict
 

	
 
def dict_scale(d,scl):
 
    """scales all values in dict and returns a new dict"""
 
    return dict([(k,v*scl) for k,v in d.items()])
 
    
 
def dict_subset(d, dkeys, default=0):
 
    """Subset of dictionary d: only the keys in dkeys.  If you plan on omitting
 
    keys, make sure you like the default."""
 
    newd = {} # dirty variables!
 
    for k in dkeys:
 
        newd[k] = d.get(k, default)
 
    return newd
 

	
 
# functions specific to Timeline
 
# TBD
 
def last_less_than(array, x):
 
    """array must be sorted"""
 
    best = None
 
    for elt in array:
 
        if elt <= x:
 
            best = elt
 
        elif best is not None:
 
            return best
 
    return best
 

	
 
# TBD
 
def first_greater_than(array, x):
 
    """array must be sorted"""
 
    array_rev = array[:]
 
    array_rev.reverse()
 
    best = None
 
    for elt in array_rev:
 
        if elt >= x:
 
            best = elt
 
        elif best is not None:
 
            return best
 
    return best
 

	
 

	
flax/TheShow.py
Show inline comments
 
new file 100644
 
from Timeline import *
 
from Submaster import Submasters, sub_maxes
 

	
 
class Show:
 
    def __init__(self, timelines, submasters):
 
        self.timelines = dict([(timeline.name, timeline)
 
            for timeline in timelines])
 
        self.submasters = submasters
 
        self.current_timeline = None
 
        self.current_time = 0
 
        try:
 
            self.current_timeline = timelines[0]
 
        except ValueError:
 
            pass
 
    def calc_active_submaster(self):
 
        "get levels from the current timeline at the current time"
 
        if not (self.current_timeline or self.current_time):
 
            return {}
 
        tl = self.current_timeline
 
        tl.set_time(self.current_time)
 
        levels = tl.get_levels()
 
        scaledsubs = [self.submasters.get_sub_by_name(sub) * level \
 
            for sub, level in levels.items()]
 
        maxes = sub_maxes(*scaledsubs)
 

	
 
        return maxes
 
    def set_timeline(self, name):
 
        "select a timeline"
 
        self.current_timeline = self.timelines.get(name)
 
        self.set_time(0)
 
        if not self.current_timeline:
 
            print "Show: '%s' is not the same of a timeline."
 
    def set_time(self, time):
 
        "set time of current timeline"
 
        self.current_time = time
 
        self.current_timeline.set_time(time)
 
    def get_timelines(self):
 
        "Get names of all timelines"
 
        return self.timelines.keys()
 

	
 
# this is the default blender
 
linear = LinearBlender()
 
def T(time, level, **kw):
 
    """This used to be a synonym for TimedEvent:
 

	
 
    T = TimedEvent
 

	
 
    It now acts in a similar way, except that it will fill in a default 
 
    blender if you don't.  The default blender is a LinearBlender.  It also
 
    sets frame to MISSING so the track can fill it in."""
 
    if 'blender' not in kw:
 
        global linear
 
        kw['blender'] = linear
 

	
 
    return TimedEvent(time, level=level, frame=MISSING, **kw)
 

	
 
quad = ExponentialBlender(2)
 
invquad = ExponentialBlender(0.5)
 
smoove = SmoothBlender()
 

	
 
track1 = TimelineTrack('red track',
 
    T(0, 0),
 
    T(4, 0.5, blender=quad),
 
    T(12, 0.7, blender=smoove),
 
    T(15, level=0.0), default_frame='red')
 
track2 = TimelineTrack('green track',
 
    T(0, 0.2, blender=invquad),
 
    T(5, 1.0, blender=smoove),
 
    T(10, 0.8),
 
    T(15, 0.6),
 
    T(20, 0.0), default_frame='green')
 
track3 = TimelineTrack('tableau demo',
 
    T(0, 0.0),
 
    T(2, 1.0, blender=InstantEnd()),
 
    T(18, 1.0),
 
    T(20, 0.0), default_frame='blue')
 
track4 = TimelineTrack('MJ fader',
 
    T(0, 0.0),
 
    T(5, 1.0),
 
    T(10, 0.0), default_frame='red')
 
bump21 = TimelineTrack('bump at 21',
 
    T(0,    0),
 
    T(20.4, 0.05),
 
    T(20.7,   1),
 
    T(25,   0.4),
 
    T(31,   0),
 
    T(31.1, 1),
 
    
 
    default_frame='sill')
 

	
 
# tl = Timeline('test', [track1, track2, track3, track4])
 
tl = Timeline('test', [track4, bump21])
 

	
 
strobe1 = TimelineTrack('strobify', 
 
    T(0, 0, blender=Strobe(ontime=0.25, offtime=0.25)),
 
    T(200, 1), default_frame='sill')
 

	
 
strobe_tl = Timeline('strobe test', [strobe1])
 

	
 
show = Show([tl, strobe_tl], Submasters())
 

	
 
if __name__ == "__main__":
 
    show.set_timeline('test')
 
    show.set_time(4)
 
    print show.get_levels().get_levels()
 
    print
 
    print show.get_levels()
flax/Timeline.py
Show inline comments
 
new file 100644
 
from TLUtility import make_attributes_from_args, dict_scale, dict_max, \
 
    DummyClass, last_less_than, first_greater_than
 
from time import time
 
from __future__ import division # "I'm sending you back to future!"
 

	
 
"""
 
Quote of the Build (from Ghostbusters II)
 
Dana:  Okay, but after dinner, I don't want you putting any of your old cheap 
 
       moves on me. 
 
Peter: Ohhhh no! I've got all NEW cheap moves.
 
"""
 

	
 
class MissingBlender(Exception):
 
    """Raised when a TimedEvent is missing a blender."""
 
    def __init__(self, timedevent):
 
        make_attributes_from_args('timedevent')
 
        Exception.__init__(self, "%r is missing a blender." % \
 
            self.timedevent)
 

	
 
# these are chosen so we can multiply by -1 to reverse the direction,
 
# and multiply direction by the time difference to determine new times.
 
FORWARD = 1
 
BACKWARD = -1
 

	
 
MISSING = 'missing'
 

	
 
class TimedEvent:
 
    """Container for a Frame which includes a time that it occurs at,
 
    and which blender occurs after it."""
 
    def __init__(self, time, frame=MISSING, blender=None, level=1.0):
 
        make_attributes_from_args('time', 'frame')
 
        self.next_blender = blender
 
        self.level = level
 
    def __float__(self):
 
        return self.time
 
    def __cmp__(self, other):
 
        if other is None:
 
            raise "I can't compare with a None.  I am '%s'" % str(self)
 
        if type(other) in (float, int):
 
            return cmp(self.time, other)
 
        else:
 
            return cmp(self.time, other.time)
 
    def __repr__(self):
 
        return "<TimedEvent %s at %.2f, time=%.2f, next blender=%s>" % \
 
            (self.frame, self.level, self.time, self.next_blender)
 
    def get_level(self):
 
        return self.level
 
    def __hash__(self):
 
        return id(self.time) ^ id(self.frame) ^ id(self.next_blender)
 

	
 
class Blender:
 
    """Blenders are functions that merge the effects of two LevelFrames."""
 
    def __init__(self):
 
        pass
 
    def __call__(self, startframe, endframe, blendtime,  time_since_startframe):
 
        """Return a LevelFrame combining two LevelFrames (startframe and 
 
        endframe).  blendtime is how much of the blend should be performed
 
        and will be expressed as a percentage divided by 100, i.e. a float
 
        between 0.0 and 1.0.  time_since_startframe is the time since the
 
        startframe was on screen in seconds (float).
 
        
 
        Very important note: Blenders will *not* be asked for values
 
        at end points (i.e. blendtime=0.0 and blendtime=1.0).
 
        The LevelFrames will be allowed to specify the values at
 
        those times.  This is unfortunately for implemementation and
 
        simplicity purposes.  In other words, if we didn't do this,
 
        we could have two blenders covering the same point in time and
 
        not know which one to ask for the value.  Thus, this saves us
 
        a lot of messiness with minimal or no sacrifice."""
 
        pass
 
    def __str__(self):
 
        """As a default, we'll just return the name of the class.  Subclasses
 
        can add parameters on if they want."""
 
        return str(self.__class__)
 
    def linear_blend(self, startframe, endframe, blendtime):
 
        """Utility function to help you produce linear combinations of two
 
        blends.  blendtime is the percent/100 that the blend should 
 
        completed.  In other words, 0.25 means it should be 0.75 * startframe +
 
        0.25 * endframe.  This function is included since many blenders are
 
        just functions on the percentage and still combine start and end frames
 
        in this way."""
 
        if startframe.frame == endframe.frame:
 
            level = startframe.level + (blendtime * \
 
                (endframe.level - startframe.level))
 
            levels = {startframe.frame : level}
 
        else:
 
            levels = {startframe.frame : (1.0 - blendtime) * startframe.level,
 
                endframe.frame : blendtime * endframe.level}
 
        return levels
 

	
 
class InstantEnd(Blender):
 
    """Instant change from startframe to endframe at the end.  In other words,
 
    the value returned will be the startframe all the way until the very end
 
    of the blend."""
 
    def __call__(self, startframe, endframe, blendtime, time_since_startframe):
 
        # "What!?" you say, "Why don't you care about blendtime?"
 
        # This is because Blenders never be asked for blenders at the endpoints
 
        # (after all, they wouldn't be blenders if they were). Please see
 
        # 'Very important note' in Blender.__doc__
 
        return {startframe.frame : startframe.level}
 

	
 
class InstantStart(Blender):
 
    """Instant change from startframe to endframe at the beginning.  In other
 
    words, the value returned will be the startframe at the very beginning
 
    and then be endframe at all times afterwards."""
 
    def __call__(self, startframe, endframe, blendtime, time_since_startframe):
 
        # "What!?" you say, "Why don't you care about blendtime?"
 
        # This is because Blenders never be asked for blenders at the endpoints
 
        # (after all, they wouldn't be blenders if they were). Please see
 
        # 'Very important note' in Blender.__doc__
 
        return {endframe.frame : endframe.level}
 

	
 
class LinearBlender(Blender):
 
    """Linear fade from one frame to another"""
 
    def __call__(self, startframe, endframe, blendtime, time_since_startframe):
 
        return self.linear_blend(startframe, endframe, blendtime)
 

	
 
class ExponentialBlender(Blender):
 
    """Exponential fade fron one frame to another.  You get to specify
 
    the exponent.  If my math is correct, exponent=1 means the same thing
 
    as LinearBlender."""
 
    def __init__(self, exponent):
 
        self.exponent = exponent
 
    def __call__(self, startframe, endframe, blendtime, time_since_startframe):
 
        blendtime = blendtime ** self.exponent
 
        return self.linear_blend(startframe, endframe, blendtime)
 

	
 
# 17:02:53 drewp: this makes a big difference for the SmoothBlender 
 
#                 (-x*x*(x-1.5)*2) function
 
class SmoothBlender(Blender):
 
    """Drew's "Smoove" Blender function.  Hopefully he'll document and
 
    parametrize it."""
 
    def __call__(self, startframe, endframe, blendtime, time_since_startframe):
 
        blendtime = (-1 * blendtime) * blendtime * (blendtime - 1.5) * 2
 
        return self.linear_blend(startframe, endframe, blendtime)
 

	
 
class Strobe(Blender):
 
    "Strobes the frame on the right side between offlevel and onlevel."
 
    def __init__(self, ontime, offtime, onlevel=1, offlevel=0):
 
        "times are in seconds (floats)"
 
        make_attributes_from_args('ontime', 'offtime', 'onlevel', 'offlevel')
 
        self.cycletime = ontime + offtime
 
    def __call__(self, startframe, endframe, blendtime, time_since_startframe):
 
        # time into the current period
 
        period_time = time_since_startframe % self.cycletime
 
        if period_time <= self.ontime:
 
            return {endframe.frame : self.onlevel}
 
        else:
 
            return {endframe.frame : self.offlevel}
 

	
 
class TimelineTrack:
 
    """TimelineTrack is a single track in a Timeline.  It consists of a
 
    list of TimedEvents and a name.  Length is automatically the location
 
    of the last TimedEvent.  To extend the Timeline past that, add an
 
    EmptyTimedEvent (which doesn't exist :-/)."""
 
    def __init__(self, name, *timedevents, **kw):
 
        if kw.get('default_frame'):
 
            self.default_frame = kw['default_frame']
 
        else:
 
            self.default_frame = None
 
        self.name = name
 
        self.set_events(list(timedevents))
 
    def set_events(self, events):
 
        """This is given a list of TimedEvents.  They need not be sorted."""
 
        self.events = events
 
        self._cleaup_events()
 
    def _cleaup_events(self):
 
        """This makes sure all events are in the right order and have defaults
 
        filled in if they have missing frames."""
 
        self.events.sort()
 
        self.fill_in_missing_frames()
 
    def add_event(self, event):
 
        """Add a TimedEvent object to this TimelineTrack"""
 
        self.events.append(event)
 
        self._cleaup_events(self.events)
 
    def delete_event(self, event):
 
        """Delete event by TimedEvent object"""
 
        self.events.remove(event)
 
        self._cleaup_events(self.events)
 
    def delete_event_by_name(self, name):
 
        """Deletes all events matching a certain name"""
 
        self.events = [e for e in self.events if e.name is not name]
 
        self._cleaup_events(self.events)
 
    def delete_event_by_time(self, starttime, endtime=None):
 
        """Deletes all events within a certain time range, inclusive.  endtime
 
        is optional."""
 
        endtime = endtime or starttime
 
        self.events = [e for e in self.events
 
            if e.time >= starttime and e.time <= endtime]
 
        self._cleaup_events(self.events)
 
    def fill_in_missing_frames(self):
 
        """Runs through all events and sets TimedEvent with missing frames to
 
        the default frame."""
 
        for event in self.events:
 
            if event.frame == MISSING:
 
                event.frame = self.default_frame
 
    def __str__(self):
 
        return "<TimelineTrack with events: %r>" % self.events
 
    def has_events(self):
 
        """Whether the TimelineTrack has anything in it.  In general,
 
        empty level Tracks should be avoided.  However, empty function tracks
 
        might be common."""
 
        return len(self.events)
 
    def length(self):
 
        """Returns the length of this track in pseudosecond time units.
 
        This is done by finding the position of the last TimedEvent."""
 
        return float(self.events[-1])
 
    def get(self, key, direction=FORWARD):
 
        """Returns the event at a specific time key.  If there is no event
 
        at that time, a search will be performed in direction.  Also note
 
        that if there are multiple events at one time, only the first will
 
        be returned.  (Probably first in order of adding.)  This is not
 
        a problem at the present since this method is intended for LevelFrames,
 
        which must exist at unique times."""
 
        if direction == BACKWARD:
 
            func = last_less_than
 
        else:
 
            func = first_greater_than
 

	
 
        return func(self.events, key)
 
    def get_range(self, i, j, direction=FORWARD):
 
        """Returns all events between i and j, exclusively.  If direction
 
        is FORWARD, j will be included.  If direction is BACKWARD, i will
 
        be included.  This is because this is used to find FunctionFrames
 
        and we assume that any function frames at the start point (which
 
        could be i or j) have been processed."""
 
        return [e for e in self.events if e >= i and e <= j]
 

	
 
        if direction == FORWARD:
 
            return [e for e in self.events if e > i and e <= j]
 
        else:
 
            return [e for e in self.events if e >= i and e < j]
 
    def __getitem__(self, key):
 
        """Returns the event at or after a specific time key.
 
        For example: timeline[3] will get the first event at time 3.
 

	
 
        If you want to get all events at time 3, you are in trouble, but
 
        you could achieve it with something like: 
 
        timeline.get_range(2.99, 3.01, FORWARD) 
 
        This is hopefully a bogus problem, since you can't have multiple
 
        LevelFrames at the same time."""
 
        return self.get(key, direction=FORWARD)
 
    def get_surrounding_frames(self, time):
 
        """Returns frames before and after a specific time.  This returns
 
        a 2-tuple: (previousframe, nextframe).  If you have chosen the exact 
 
        time of a frame, it will be both previousframe and nextframe."""
 
        return self.get(time, direction=BACKWARD), \
 
               self.get(time, direction=FORWARD)
 
    def get_levels_at_time(self, time):
 
        """Returns a LevelFrame with the levels of this track at that time."""
 
        before, after = self.get_surrounding_frames(time)
 
        
 
        if not after or before == after:
 
            return {before.frame : before.level}
 
        else: # we have a blended value
 
            diff = after.time - before.time
 
            elapsed = time - before.time
 
            percent = elapsed / diff
 
            if not before.next_blender:
 
                raise MissingBlender, before
 
            return before.next_blender(before, after, percent, elapsed)
 

	
 
class Timeline:
 
    def __init__(self, name, tracks, rate=1, direction=FORWARD):
 
        """
 
        Most/all of this is old:
 

	
 
        You can have multiple FunctionFrames at the same time.  Their
 
        order is important though, since FunctionFrames will be applied
 
        in the order seen in this list.  blenders is a list of Blenders.
 
        rate is the rate of playback.  If set to 1, 1 unit inside the
 
        Timeline will be 1 second.  direction is the initial direction.
 
        If you want to do have looping, place a LoopFunction at the end of
 
        the Timeline.  Timelines don't have a set length.  Their length
 
        is bounded by their last frame.  You can put an EmptyFrame at
 
        some time if you want to extend a Timeline."""
 
        
 
        make_attributes_from_args('name', 'tracks', 'rate', 'direction')
 
        self.current_time = 0
 
        self.last_clock_time = None
 
        self.stopped = 1
 
    def length(self):
 
        """Length of the timeline in pseudoseconds.  This is determined by
 
        finding the length of the longest track."""
 
        track_lengths = [track.length() for track in self.tracks]
 
        return max(track_lengths)
 
    def play(self):
 
        """Activates the timeline.  Future calls to tick() will advance the 
 
        timeline in the appropriate direction."""
 
        self.stopped = 0
 
    def stop(self):
 
        """The timeline will no longer continue in either direction, no
 
        FunctionFrames will be activated."""
 
        self.stopped = 1
 
        self.last_clock_time = None
 
    def reset(self):
 
        """Resets the timeline to 0.  Does not change the stoppedness of the 
 
        timeline."""
 
        self.current_time = 0
 
    def tick(self):
 
        """Updates the current_time and runs any FunctionFrames that the cursor
 
        passed over.  This call is ignored if the timeline is stopped."""
 
        if self.stopped:
 
            return
 
        
 
        last_time = self.current_time
 
        last_clock = self.last_clock_time
 
        
 
        # first, determine new time
 
        clock_time = time()
 
        if last_clock is None:
 
            last_clock = clock_time
 
        diff = clock_time - last_clock
 
        new_time = (self.direction * self.rate * diff) + last_time
 
        
 
        # update the time
 
        self.last_clock_time = clock_time
 
        self.current_time = new_time
 
        
 
        # now we make sure we're in bounds (we don't do this before, since it
 
        # can cause us to skip events that are at boundaries.
 
        self.current_time = max(self.current_time, 0)
 
        self.current_time = min(self.current_time, self.length())
 
    def reverse_direction(self):
 
        """Reverses the direction of play for this node"""
 
        self.direction = self.direction * -1
 
    def set_direction(self, direction):
 
        """Sets the direction of playback."""
 
        self.direction = direction
 
    def set_rate(self, new_rate):
 
        """Sets the rate of playback"""
 
        self.rate = new_rate
 
    def set_time(self, new_time):
 
        """Set the time to a new time."""
 
        self.current_time = new_time
 
    def get_levels(self):
 
        """Return the current levels from this timeline.  This is done by
 
        adding all the non-functional tracks together."""
 
        levels = [t.get_levels_at_time(self.current_time)
 
                    for t in self.tracks]
 
        return dict_max(*levels)
 

	
 
if __name__ == '__main__':
 
    def T(*args, **kw):
 
        """This used to be a synonym for TimedEvent:
 

	
 
        T = TimedEvent
 

	
 
        It now acts the same way, except that it will fill in a default 
 
        blender if you don't.  The default blender is a LinearBlender."""
 
        linear = LinearBlender()
 
        if 'blender' not in kw:
 
            kw['blender'] = linear
 

	
 
        return TimedEvent(*args, **kw)
 

	
 
    quad = ExponentialBlender(2)
 
    invquad = ExponentialBlender(0.5)
 
    smoove = SmoothBlender()
 

	
 
    track1 = TimelineTrack('red track',
 
        T(0, 'red', level=0),
 
        T(4, 'red', blender=quad, level=0.5),
 
        T(12, 'red', blender=smoove, level=0.7),
 
        T(15, 'red', level=0.0)) # last TimedEvent doesn't need a blender
 
    track2 = TimelineTrack('green track',
 
        T(0, 'green', blender=invquad, level=0.2),
 
        T(5, 'green', blender=smoove, level=1),
 
        T(10, 'green', level=0.8),
 
        T(15, 'green', level=0.6),
 
        T(20, 'green', level=0.0)) # last TimedEvent doesn't need a blender
 
    track3 = TimelineTrack('tableau demo',
 
        T(0, 'blue', level=0.0),
 
        T(2, 'blue', level=1.0, blender=InstantEnd()),
 
        T(18, 'blue', level=1.0),
 
        T(20, 'blue', level=0.0))
 

	
 
    tl = Timeline('test', [track1, track2, track3])
 

	
 
    tl.play()
 

	
 
    import Tix
 
    root = Tix.Tk()
 
    colorscalesframe = Tix.Frame(root)
 
    scalevars = {}
 
    # wow, this works out so well, it's almost like I planned it!
 
    # (actually, it's probably just Tk being as cool as it usually is)
 
    # ps. if this code ever turns into mainstream code for flax, I'll be
 
    # pissed (reason: we need to use classes, not this hacked crap!)
 
    colors = 'red', 'blue', 'green', 'yellow', 'purple'
 
    for color in colors:
 
        sv = Tix.DoubleVar()
 
        scalevars[color] = sv
 
        scale = Tix.Scale(colorscalesframe, from_=1, to_=0, res=0.01, bg=color,
 
            variable=sv)
 
        scale.pack(side=Tix.LEFT)
 

	
 
    def set_timeline_time(time):
 
        tl.set_time(float(time))
 
        # print 'set_timeline_time', time
 

	
 
    def update_scales():
 
        levels = tl.get_levels()
 
        for color in colors:
 
            scalevars[color].set(levels.get(color, 0))
 
    
 
    colorscalesframe.pack()
 
    time_scale = Tix.Scale(root, from_=0, to_=tl.length(), 
 
        orient=Tix.HORIZONTAL, res=0.01, command=set_timeline_time)
 
    time_scale.pack(side=Tix.BOTTOM, fill=Tix.X, expand=1)
 

	
 
    def play_tl():
 
        tl.tick()
 
        update_scales()
 
        time_scale.set(tl.current_time)
 
        # print 'time_scale.set', tl.current_time
 
        root.after(10, play_tl)
 
    
 
    controlwindow = Tix.Toplevel()
 
    Tix.Button(controlwindow, text='Stop', 
 
        command=lambda: tl.stop()).pack(side=Tix.LEFT)
 
    Tix.Button(controlwindow, text='Play', 
 
        command=lambda: tl.play()).pack(side=Tix.LEFT)
 
    Tix.Button(controlwindow, text='Reset', 
 
        command=lambda: time_scale.set(0)).pack(side=Tix.LEFT)
 
    Tix.Button(controlwindow, text='Flip directions', 
 
        command=lambda: tl.reverse_direction()).pack(side=Tix.LEFT)
 
    Tix.Button(controlwindow, text='1/2x', 
 
        command=lambda: tl.set_rate(0.5 * tl.rate)).pack(side=Tix.LEFT)
 
    Tix.Button(controlwindow, text='2x', 
 
        command=lambda: tl.set_rate(2 * tl.rate)).pack(side=Tix.LEFT)
 
    
 
    root.after(100, play_tl)
 

	
 
    # Timeline.set_time = trace(Timeline.set_time)
 
        
 
    Tix.mainloop()
flax/TimelineDMX.py
Show inline comments
 
new file 100644
 
import sys, time, socket
 
sys.path.append("../light8")
 
import Tix as tk
 

	
 
import Patch, Timeline, dmxclient, xmlrpclib
 
import TheShow
 

	
 
Patch.reload_data()
 

	
 
class ShowRunner(tk.Frame):
 
    def __init__(self, master, show):
 
        tk.Frame.__init__(self, master)
 
        self.master = master
 

	
 
        self.show = show
 
        self.find_player()
 
        self.build_timeline_list()
 
    def build_timeline_list(self):
 
        self.tl_list = tk.Frame(self)
 
        for tl in self.show.get_timelines():
 
            b=tk.Button(self.tl_list,text=tl,
 
                        anchor='w',pady=1)
 
            b.config(command=lambda tl=tl: self.set_timeline(tl))
 
            b.pack(side='top',fill='x')
 
        self.tl_list.pack()
 
    def set_timeline(self, tlname):
 
        print "TimelineDMX: set timeline to", tlname
 
        self.show.set_timeline(tlname)
 
    def find_player(self):
 
        self.player = xmlrpclib.Server("http://localhost:8040")
 
    def send_levels(self):
 
        levels = self.show.calc_active_submaster().get_dmx_list()
 
        
 
        dmxclient.outputlevels(levels)
 
    def sync_times(self):
 
        try:
 
            playtime = self.player.gettime()
 
            self.show.set_time(playtime)
 
        except socket.error, e:
 
            print "Server error %s, waiting"%e
 
            time.sleep(2)
 
    def mainloop(self):
 
        try:
 
            while 1:
 
                self.sync_times()
 
                self.send_levels()
 
                time.sleep(0.01)
 
                self.master.update()
 
        except KeyboardInterrupt:
 
            sys.exit(0)
 

	
 
if __name__ == "__main__":
 
    root = tk.Tk()
 
    s = ShowRunner(root, TheShow.show)
 
    s.show.set_timeline('strobe test')
 
    s.pack()
 
    s.mainloop()
flax/TreeDict.py
Show inline comments
 
new file 100644
 
"""Persistent Tree Dictionaries
 

	
 
Incidentally, this is also the Hiss Preferences System.  However, PTD is
 
expected to be usable outside of Hiss."""
 

	
 
__author__ = "David McClosky <dmcc@bigasterisk.com>, " + \
 
             "Drew Perttula <drewp@bigasterisk.com>"
 
__cvsid__ = "$Id: TreeDict.py,v 1.1 2003/07/06 08:33:06 dmcc Exp $"
 
__version__ = "$Revision: 1.1 $"[11:-2]
 

	
 
try:
 
    # use gnosis's XML pickler if available
 
    import gnosis.xml.pickle as pickle
 

	
 
    # this hack is needed or else xml.pickle will not be able to restore
 
    # methods (they will be restored as mysterious classes which contain
 
    # the same attributes but no methods, 
 
    # gnosis.xml.pickle.util._util.originalclassname, to be specific)
 
    # the important thing to get from this comment is that we cannot properly
 
    # pickle classes without them being "assigned" into gnosis.xml.pickle.
 
    # i hope this gets fixed in a later version of pickle.
 
except ImportError:
 
    # fallback to standard library pickle
 
    import pickle
 

	
 
def allow_class_to_be_pickled(class_obj):
 
    """to be documented"""
 
    name = class_obj.__name__
 
    setattr(pickle, name, class_obj)
 

	
 
class TreeDict(dict):
 
    """TreeDict is the workhorse for the preferences system.  It allows
 
    for simple creation and access of preference trees.  It could be
 
    used to store anything, though."""
 
    def __getattr__(self, attr):
 
        """Gets an attribute/item, but with a twist.  If not present, the
 
        attribute will be created, with the value set to a new TreeDict."""
 
        if attr.startswith('__'): # if it's special, we let dict handle it
 
            return self.dictgetattr(attr)
 
        if attr in self:
 
            return dict.__getitem__(self, attr)
 
        else:
 
            newtree = self.__class__()
 
            self.set_parent_attrs(newtree, attr) # record ourselves as the 
 
                                                 # parent
 
            dict.__setitem__(self, attr, newtree)
 
            return newtree
 
    def __setattr__(self, attr, newval):
 
        """Sets an attribute/item to a new value."""
 
        if attr.startswith('__'): # if it's special, we let dict handle it
 
            return dict.__setattr__(self, attr, newval)
 
        else:
 
            oldval = self[attr] or None
 
            dict.__setitem__(self, attr, newval)
 
            if isinstance(newval, self.__class__):
 
                self.set_parent_attrs(newval, attr)
 
            self.changed_callback(attr, oldval, newval)
 
    def __delattr__(self, attr):
 
        """Deletes an attribute/item"""
 
        if attr.startswith('__'): # if it's special, we let dict handle it
 
            return dict.__delattr__(self, attr)
 
        else: # otherwise, they meant a normal attribute/item
 
            dict.__delitem__(self, attr)
 

	
 
    # attr and item access are now the same
 
    __getitem__ = __getattr__
 
    __setitem__ = __setattr__
 
    __delitem__ = __delattr__
 

	
 
    # Original getattr/setattr for storing special attributes
 
    def dictgetattr(self, attr):
 
        """dict's original __getattribute__ method.  This is useful for
 
        storing bookkeeping information (like parents) which shouldn't
 
        be persistent."""
 
        return dict.__getattribute__(self, attr)
 
    def dictsetattr(self, attr, newval):
 
        """dict's original __setattr__ method.  This is useful for
 
        storing bookkeeping information (like parents) which shouldn't
 
        be persistent."""
 
        return dict.__setattr__(self, attr, newval)
 
    def set_parent_attrs(self, newtree, key):
 
        """Set ourselves as the parent to a new key."""
 
        # record that we are the parent of this new object
 
        newtree.dictsetattr('__parent', self)
 

	
 
        # we store the key that newtree is in
 
        newtree.dictsetattr('__parent_key', key)
 
    def get_path(self):
 
        """Returns the path to this TreeDict."""
 
        try:
 
            parent = self.dictgetattr('__parent')
 
            key = self.dictgetattr('__parent_key')
 
            return parent.get_path() + [key] 
 
        except AttributeError:
 
            return []
 

	
 
    def changed_callback(self, attr, oldval, newval):
 
        """Called whenever an attribute is changed.  It will be called with
 
        three arguments: the attribute that changed, the previous value of
 
        the attribute, and the newvalue of the attribute.
 
        If the attribute didn't exist before, the old value will be None.
 

	
 
        This should be overridden by subclasses of TreeDict."""
 

	
 
    # Tree operations
 
    def tree_update(self, othertree):
 
        """Recursive update().  All keys from the othertree are copied over.
 
        If both this tree and the other tree have a key which is a TreeDict,
 
        we will recurse into it."""
 
        for key in othertree: # if the key is in the other tree, merge it into
 
                              # ours
 
            if key in self and isinstance(self[key], self.__class__):
 
                self[key].tree_update(othertree[key])
 
            else: # copy othertree's branch to ours
 
                self[key] = othertree[key]
 

	
 
    # Loading and saving
 
    def load(self, filename, clobber=1):
 
        """Combine this TreeDict with the TreeDict previously save()d
 
        to a file.  If clobber=1, this tree will be clear()ed before
 
        combining (this is the default)."""
 
        newprefs = pickle.load(file(filename))
 
        if clobber: self.clear()
 
        self.tree_update(newprefs)
 
        return self
 
    def save(self, filename):
 
        """Save this TreeDict to a file.  You can later restore the TreeDict
 
        by creating a TreeDict and using the load() method:
 

	
 
        treedict.save("tree.xml")
 
        newtreedict = TreeDict()
 
        newtreedict.load("tree.xml")"""
 
        pickle.dump(self, file(filename, 'w'))
 

	
 
    def pprint(self, depth=0):
 
        "A simple pretty printing method, useful for debugging"
 
        for key in self:
 
            print "%s%s =" % ('\t' * depth, key),
 
            val = self[key]
 
            if isinstance(val, self.__class__):
 
                print
 
                val.pprint(depth + 1)
 
            else:
 
                print repr(self[key])
 

	
 
allow_class_to_be_pickled(TreeDict)
 

	
 
if __name__ == "__main__":
 
    # We subclass TreeDict here so we can demonstrate how to override
 
    # changed_callback.
 
    RealTreeDict = TreeDict
 
    class TreeDict(RealTreeDict):
 
        def changed_callback(self, attr, oldval, newval):
 
            fullpath = self.get_path() + [attr]
 
            
 
            if 0: # more information
 
                print 'changed_callback %s: %s -> %s' % ('.'.join(fullpath), 
 
                                                         oldval, newval)
 
            else: # output looks like the Python code that built the tree
 
                print 'changed_callback %s = %r' % ('.'.join(fullpath), 
 
                                                         newval)
 

	
 
    # store our new class so we can pickle :(
 
    # maybe we give someone a list of classes that we use, and it handles
 
    # the rest.  or, we just record a list of classes seen using 
 
    # setattr callbacks.
 
    allow_class_to_be_pickled(TreeDict)
 
    
 
    True = 1
 
    False = 0
 
    
 
    defaults_tree = TreeDict()
 
    defaults_tree.auto_scroll_to_bottom = True
 
    defaults_tree.aging.enabled = True
 
    defaults_tree.aging.fade_exponent = 3
 
    defaults_tree.aging.user_colors = TreeDict({'user1' : 'color1',
 
                                                'user2' : 'color2'})
 

	
 
    import time
 
    defaults_tree.current_time = time.asctime()
 

	
 
    # on disk
 
    new_tree = TreeDict()
 
    new_tree.some_extra_pref = "hi mom"
 
    new_tree.auto_scroll_to_bottom = False
 
    new_tree.aging.user_colors.user1 = 'green'
 

	
 
    defaults_tree.tree_update(new_tree)
 
    defaults_tree.pprint()
 

	
 
    # test load / save
 
    print "---"
 
    defaults_tree.save("persistence_test.xml")
 
    loaded_tree = TreeDict().load("persistence_test.xml")
 
    loaded_tree.pprint()
flax/UI.py
Show inline comments
 
new file 100644
 
# Abstract UI widgets
 

	
 
class UI(Something):
 
    pass
 

	
 
class Scale(UI):
 
    def __init__(self, start, stop, res=0.001):
 
        pass
 

	
 
""" unfinished
 
class Button(UI):
 
    def __init__(self, etc)
 
"""
flax/cues/cuelist1
Show inline comments
 
new file 100644
 
<?xml version="1.0"?>
 
<!DOCTYPE PyObject SYSTEM "PyObjects.dtd">
 
<PyObject family="obj" type="builtin_wrapper"  class="_EmptyClass">
 
<attr name="__toplevel__" family="map" type="__compound__" extra="None TreeDict" id="138506468" >
 
  <entry>
 
    <key type="string" value="cues" />
 
    <val type="list" id="139971548" >
 
      <item type="PyObject" id="140423764" class="Cue">
 
        <attr name="name" type="string" value="tevya special" />
 
        <attr name="desc" type="string" value="whoa - this works" />
 
        <attr name="page" type="string" value="3.2" />
 
        <attr name="sub_levels" type="string" value="green : 1.0" />
 
        <attr name="time" type="string" value="2" />
 
      </item>
 
      <item type="PyObject" id="139862804" class="Cue">
 
        <attr name="subdict" type="dict" id="139863044" >
 
        </attr>
 
        <attr name="name" type="string" value="lady luck" />
 
        <attr name="time" type="string" value="1" />
 
        <attr name="page" type="string" value="1.1.5" />
 
        <attr name="sub_levels" type="string" value="blue : 1.0, green : 0.5" />
 
        <attr name="desc" type="string" value="music flourish" />
 
      </item>
 
      <item type="PyObject" id="139862732" class="Cue">
 
        <attr name="subdict" type="dict" id="139852060" >
 
        </attr>
 
        <attr name="name" type="string" value="dolly solo" />
 
        <attr name="time" type="string" value="2" />
 
        <attr name="page" type="string" value="1.2.10" />
 
        <attr name="sub_levels" type="string" value="blue : 0.1, green : 0.1" />
 
        <attr name="desc" type="string" value="tevya: &quot;what&apos;s happening to the tradition?&quot;" />
 
      </item>
 
      <item type="PyObject" id="139915236" class="Cue">
 
        <attr name="subdict" type="dict" id="139852964" >
 
        </attr>
 
        <attr name="name" type="string" value="cue 3" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="1.2.14" />
 
        <attr name="sub_levels" type="string" value="red : 1" />
 
        <attr name="desc" type="string" value="the third cue" />
 
      </item>
 
      <item type="PyObject" id="139851228" class="Cue">
 
        <attr name="subdict" type="dict" id="139863188" >
 
        </attr>
 
        <attr name="name" type="string" value="heart" />
 
        <attr name="time" type="string" value="10" />
 
        <attr name="page" type="string" value="1.3.13" />
 
        <attr name="sub_levels" type="string" value="frontwhite : 0.2, red : 0.7" />
 
        <attr name="desc" type="string" value="&quot;you&apos;ve gotta have heart&quot;" />
 
      </item>
 
      <item type="PyObject" id="139863332" class="Cue">
 
        <attr name="subdict" type="dict" id="139841476" >
 
        </attr>
 
        <attr name="name" type="string" value="more musical refs" />
 
        <attr name="time" type="string" value="5" />
 
        <attr name="page" type="string" value="1.3.17" />
 
        <attr name="sub_levels" type="string" value="blue : 1.0" />
 
        <attr name="desc" type="string" value="etc." />
 
      </item>
 
      <item type="PyObject" id="139855188" class="Cue">
 
        <attr name="subdict" type="dict" id="139855428" >
 
        </attr>
 
        <attr name="name" type="string" value="rainbow shimmer" />
 
        <attr name="time" type="string" value="6" />
 
        <attr name="page" type="string" value="1.4.2" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="desc" type="string" value="curtain close" />
 
      </item>
 
      <item type="PyObject" id="139873788" class="Cue">
 
        <attr name="subdict" type="dict" id="139855956" >
 
        </attr>
 
        <attr name="name" type="string" value="fade up" />
 
        <attr name="time" type="string" value="7" />
 
        <attr name="page" type="string" value="2.1.1" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="desc" type="string" value="stage manager: &quot;worklights, please&quot;" />
 
      </item>
 
      <item type="PyObject" id="139927324" class="Cue">
 
        <attr name="subdict" type="dict" id="139854356" >
 
        </attr>
 
        <attr name="name" type="string" value="blackout" />
 
        <attr name="time" type="string" value="8" />
 
        <attr name="page" type="string" value="2.1.2" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="desc" type="string" value="&quot;lights out!&quot;" />
 
      </item>
 
      <item type="PyObject" id="139864660" class="Cue">
 
        <attr name="subdict" type="dict" id="139857340" >
 
        </attr>
 
        <attr name="name" type="string" value="sill" />
 
        <attr name="time" type="string" value="4.2" />
 
        <attr name="page" type="string" value="2.2.4" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="desc" type="string" value="another description" />
 
      </item>
 
      <item type="PyObject" id="139855916" class="Cue">
 
        <attr name="name" type="string" value="front only" />
 
        <attr name="desc" type="string" value="mr. cue 10" />
 
        <attr name="page" type="string" value="2.7.3" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="time" type="string" value="10" />
 
      </item>
 
      <item type="PyObject" id="139163156" class="Cue">
 
        <attr name="name" type="string" value="cue 11" />
 
        <attr name="desc" type="string" value="" />
 
        <attr name="page" type="string" value="" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="time" type="string" value="11" />
 
      </item>
 
      <item type="PyObject" id="139854140" class="Cue">
 
        <attr name="desc" type="string" value="" />
 
        <attr name="page" type="string" value="" />
 
        <attr name="name" type="string" value="cue 12" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="time" type="string" value="2.1" />
 
      </item>
 
      <item type="PyObject" id="139841764" class="Cue">
 
        <attr name="desc" type="string" value="" />
 
        <attr name="page" type="string" value="" />
 
        <attr name="name" type="string" value="cue 13" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="time" type="string" value="13" />
 
      </item>
 
      <item type="PyObject" id="139854308" class="Cue">
 
        <attr name="name" type="string" value="cue 14" />
 
        <attr name="desc" type="string" value="" />
 
        <attr name="page" type="string" value="" />
 
        <attr name="sub_levels" type="string" value="red : 1" />
 
        <attr name="time" type="string" value="14" />
 
      </item>
 
      <item type="PyObject" id="139858580" class="Cue">
 
        <attr name="name" type="string" value="cue 15" />
 
        <attr name="desc" type="string" value="" />
 
        <attr name="page" type="string" value="" />
 
        <attr name="sub_levels" type="string" value="red : 1" />
 
        <attr name="time" type="string" value="15" />
 
      </item>
 
      <item type="PyObject" id="139856100" class="Cue">
 
        <attr name="name" type="string" value="cue 16" />
 
        <attr name="desc" type="string" value="" />
 
        <attr name="page" type="string" value="" />
 
        <attr name="sub_levels" type="string" value="red : 1" />
 
        <attr name="time" type="string" value="16" />
 
      </item>
 
      <item type="PyObject" id="139856180" class="Cue">
 
        <attr name="name" type="string" value="cue 17" />
 
        <attr name="desc" type="string" value="" />
 
        <attr name="page" type="string" value="" />
 
        <attr name="sub_levels" type="string" value="red : 1" />
 
        <attr name="time" type="string" value="17" />
 
      </item>
 
      <item type="PyObject" id="139846156" class="Cue">
 
        <attr name="name" type="string" value="some name" />
 
        <attr name="desc" type="string" value="" />
 
        <attr name="page" type="string" value="" />
 
        <attr name="sub_levels" type="string" value="red : 1" />
 
        <attr name="time" type="string" value="18" />
 
      </item>
 
      <item type="PyObject" id="139841948" class="Cue">
 
        <attr name="name" type="string" value="cue 19" />
 
        <attr name="desc" type="string" value="" />
 
        <attr name="page" type="string" value="" />
 
        <attr name="sub_levels" type="string" value="red : 1" />
 
        <attr name="time" type="string" value="19" />
 
      </item>
 
    </val>
 
  </entry>
 
</attr>
 
</PyObject>
flax/cues/dolly
Show inline comments
 
new file 100644
 
<?xml version="1.0"?>
 
<!DOCTYPE PyObject SYSTEM "PyObjects.dtd">
 
<PyObject family="obj" type="builtin_wrapper"  class="_EmptyClass">
 
<attr name="__toplevel__" family="map" type="__compound__" extra="None TreeDict" id="137405252" >
 
  <entry>
 
    <key type="string" value="cues" />
 
    <val type="list" id="139876796" >
 
      <item type="PyObject" id="141731588" class="Cue">
 
        <attr name="name" type="string" value="bogus" />
 
        <attr name="time" type="string" value="1" />
 
        <attr name="page" type="string" value="--" />
 
        <attr name="cuenum" type="string" value="B" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="139954196" class="Cue">
 
        <attr name="name" type="string" value="emergency blackout" />
 
        <attr name="time" type="string" value="1" />
 
        <attr name="page" type="string" value="--" />
 
        <attr name="cuenum" type="string" value="Z" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="140850044" class="Cue">
 
        <attr name="name" type="string" value="curtain warmers, house" />
 
        <attr name="time" type="string" value="2" />
 
        <attr name="page" type="string" value="-7" />
 
        <attr name="cuenum" type="string" value="1" />
 
        <attr name="sub_levels" type="string" value="curtain_warmers : .32, house : 1" />
 
        <attr name="desc" type="string" value="preshow" />
 
      </item>
 
      <item type="PyObject" id="141733548" class="Cue">
 
        <attr name="name" type="string" value="house at half" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="-7" />
 
        <attr name="cuenum" type="string" value="1" />
 
        <attr name="sub_levels" type="string" value="curtain_warmers : .32, house : 0.5" />
 
        <attr name="desc" type="string" value="cue from liesel" />
 
      </item>
 
      <item type="PyObject" id="140862532" class="Cue">
 
        <attr name="name" type="string" value="house out" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="-7" />
 
        <attr name="cuenum" type="string" value="1" />
 
        <attr name="sub_levels" type="string" value="curtain_warmers : .32" />
 
        <attr name="desc" type="string" value="cue from liesel" />
 
      </item>
 
      <item type="PyObject" id="139906484" class="Cue">
 
        <attr name="name" type="string" value="curtain warmers out" />
 
        <attr name="time" type="string" value="2" />
 
        <attr name="page" type="string" value="-7" />
 
        <attr name="cuenum" type="string" value="1.5" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="desc" type="string" value="right before the curtains open" />
 
      </item>
 
      <item type="PyObject" id="135987020" class="Cue">
 
        <attr name="name" type="string" value="cast frozen" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-1-1" />
 
        <attr name="cuenum" type="string" value="2" />
 
        <attr name="sub_levels" type="string" value="1-1_sill : .8" />
 
        <attr name="desc" type="string" value="curtain opens" />
 
      </item>
 
      <item type="PyObject" id="140862084" class="Cue">
 
        <attr name="name" type="string" value="cast wakes up" />
 
        <attr name="time" type="string" value=".5" />
 
        <attr name="page" type="string" value="1-1-1" />
 
        <attr name="cuenum" type="string" value="3" />
 
        <attr name="sub_levels" type="string" value="1-1 : .8" />
 
        <attr name="desc" type="string" value="music cue" />
 
      </item>
 
      <item type="PyObject" id="139846484" class="Cue">
 
        <attr name="name" type="string" value="cast freezes, dolly walks RC to LC" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-1-1" />
 
        <attr name="cuenum" type="string" value="4" />
 
        <attr name="sub_levels" type="string" value="1-1_sill : .8" />
 
        <attr name="desc" type="string" value="dolly at right, SPOT on dolly entr" />
 
      </item>
 
      <item type="PyObject" id="140861980" class="Cue">
 
        <attr name="name" type="string" value="unfreeze" />
 
        <attr name="time" type="string" value="1.5" />
 
        <attr name="page" type="string" value="1-1-2" />
 
        <attr name="cuenum" type="string" value="6" />
 
        <attr name="sub_levels" type="string" value="1-1 : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="140862188" class="Cue">
 
        <attr name="name" type="string" value="freeze, scene is RC and C" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-1-2" />
 
        <attr name="cuenum" type="string" value="7" />
 
        <attr name="sub_levels" type="string" value="1-1_sill : .8, front_rc : .4, front_c : .3" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="139851716" class="Cue">
 
        <attr name="name" type="string" value="unfreeze" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-1-3" />
 
        <attr name="cuenum" type="string" value="8" />
 
        <attr name="sub_levels" type="string" value="1-1 : 1" />
 
        <attr name="desc" type="string" value="&quot;mrs levi!&quot;, SPOT OFF" />
 
      </item>
 
      <item type="PyObject" id="139853164" class="Cue">
 
        <attr name="name" type="string" value="freeze, scene RC" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-1-4" />
 
        <attr name="cuenum" type="string" value="9" />
 
        <attr name="sub_levels" type="string" value="1-1_sill : .8, front_rc : .7" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="139853604" class="Cue">
 
        <attr name="name" type="string" value="dolly at stairs" />
 
        <attr name="time" type="string" value="2" />
 
        <attr name="page" type="string" value="1-1-4" />
 
        <attr name="cuenum" type="string" value="9.5" />
 
        <attr name="sub_levels" type="string" value="1-1_sill : .2,  dolly_stairs_right : 1, dolly_runway_right : .7" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="139849116" class="Cue">
 
        <attr name="name" type="string" value="dolly walks to her R runway corner" />
 
        <attr name="time" type="string" value="2" />
 
        <attr name="page" type="string" value="1-1-4" />
 
        <attr name="cuenum" type="string" value="10" />
 
        <attr name="sub_levels" type="string" value="1-1_sill : .2, dolly_runway_right : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="140851756" class="Cue">
 
        <attr name="name" type="string" value="unfreeze" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-1-5" />
 
        <attr name="cuenum" type="string" value="11" />
 
        <attr name="sub_levels" type="string" value="1-1 : 1, dolly_runway_right : 1, dolly_stairs_right : 1" />
 
        <attr name="desc" type="string" value="&quot;i&apos;m waiting for that sign&quot;" />
 
      </item>
 
      <item type="PyObject" id="139849252" class="Cue">
 
        <attr name="name" type="string" value="stairs out" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-1-5" />
 
        <attr name="cuenum" type="string" value="11.5" />
 
        <attr name="sub_levels" type="string" value="1-1 : 1" />
 
        <attr name="desc" type="string" value="after dolly leaves stairs area" />
 
      </item>
 
      <item type="PyObject" id="139855116" class="Cue">
 
        <attr name="name" type="string" value="stage clears, band walks runway" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-1-5" />
 
        <attr name="cuenum" type="string" value="12" />
 
        <attr name="sub_levels" type="string" value="worklights : 1, runway_stairs : .45" />
 
        <attr name="desc" type="string" value="set change" />
 
      </item>
 
      <item type="PyObject" id="140863580" class="Cue">
 
        <attr name="name" type="string" value="scene opens outside store L" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-2-6" />
 
        <attr name="cuenum" type="string" value="13" />
 
        <attr name="sub_levels" type="string" value="1-2_outside : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="139854708" class="Cue">
 
        <attr name="name" type="string" value="enters store" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-2-6" />
 
        <attr name="cuenum" type="string" value="14" />
 
        <attr name="sub_levels" type="string" value="1-2 : 1, trap :0.2, upstairs : 0.2" />
 
        <attr name="desc" type="string" value="&quot;i can&apos;t help it...&quot;" />
 
      </item>
 
      <item type="PyObject" id="140852076" class="Cue">
 
        <attr name="name" type="string" value="trapdoor area" />
 
        <attr name="time" type="string" value="2" />
 
        <attr name="page" type="string" value="1-2-7" />
 
        <attr name="cuenum" type="string" value="15" />
 
        <attr name="sub_levels" type="string" value="1-2 : 0.5, trap : 1, upstairs : 0.2" />
 
        <attr name="desc" type="string" value="walking to trap, &quot;cornelius!&quot;" />
 
      </item>
 
      <item type="PyObject" id="140864308" class="Cue">
 
        <attr name="name" type="string" value="monologue and &quot;it takes a woman&apos;" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="cuenum" type="string" value="16" />
 
        <attr name="page" type="string" value="1-2-7" />
 
        <attr name="sub_levels" type="string" value="1-2_song_downstairs : 1, upstairs : 0.2, 1-2 : 0.6, trap :0.2" />
 
        <attr name="desc" type="string" value="sheep something" />
 
      </item>
 
      <item type="PyObject" id="140865828" class="Cue">
 
        <attr name="name" type="string" value="&quot;restore&quot; after song finishes" />
 
        <attr name="time" type="string" value="6" />
 
        <attr name="cuenum" type="string" value="17" />
 
        <attr name="page" type="string" value="1-2-9" />
 
        <attr name="sub_levels" type="string" value="1-2 : 1, upstairs : 0.2, trap :0.2" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="140861436" class="Cue">
 
        <attr name="name" type="string" value="dolly upstairs" />
 
        <attr name="time" type="string" value="5" />
 
        <attr name="cuenum" type="string" value="19" />
 
        <attr name="page" type="string" value="1-2-12" />
 
        <attr name="sub_levels" type="string" value="upstairs : .9,  1-2 : 0.5, trap : 0.2" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="139851956" class="Cue">
 
        <attr name="name" type="string" value="cross to trapdoor" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="cuenum" type="string" value="20" />
 
        <attr name="page" type="string" value="1-2-12" />
 
        <attr name="sub_levels" type="string" value="upstairs : 0.2,  1-2 : 0.5, trap : 1, 1-2_register : 0.7" />
 
        <attr name="desc" type="string" value="plans to make (?)" />
 
      </item>
 
      <item type="PyObject" id="140856468" class="Cue">
 
        <attr name="name" type="string" value="cross to upstairs" />
 
        <attr name="time" type="string" value="2" />
 
        <attr name="cuenum" type="string" value="21" />
 
        <attr name="page" type="string" value="1-2-14" />
 
        <attr name="sub_levels" type="string" value="upstairs : .9,  1-2 : 0.5, trap : 0.2" />
 
        <attr name="desc" type="string" value="&quot;yes, corn, yes!&quot;" />
 
      </item>
 
      <item type="PyObject" id="139847812" class="Cue">
 
        <attr name="name" type="string" value="cross to trapdoor and register area" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="cuenum" type="string" value="22" />
 
        <attr name="page" type="string" value="1-2-15" />
 
        <attr name="sub_levels" type="string" value="upstairs : 0.2,  1-2 : 0.5, trap : 1, 1-2_register : .8" />
 
        <attr name="desc" type="string" value="chicken at 8" />
 
      </item>
 
      <item type="PyObject" id="140856332" class="Cue">
 
        <attr name="name" type="string" value="wider area" />
 
        <attr name="time" type="string" value="7" />
 
        <attr name="cuenum" type="string" value="23" />
 
        <attr name="page" type="string" value="1-2-15" />
 
        <attr name="sub_levels" type="string" value="upstairs : 0.2,  1-2 : 0.7, trap : 1, 1-2_register : 1" />
 
        <attr name="desc" type="string" value="when needed" />
 
      </item>
 
      <item type="PyObject" id="139957404" class="Cue">
 
        <attr name="name" type="string" value="whole scene" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="cuenum" type="string" value="24" />
 
        <attr name="page" type="string" value="1-2-16" />
 
        <attr name="sub_levels" type="string" value="upstairs : 1.0,  1-2 : 1, trap : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="140865580" class="Cue">
 
        <attr name="name" type="string" value="follow the guys" />
 
        <attr name="time" type="string" value="2" />
 
        <attr name="cuenum" type="string" value="25" />
 
        <attr name="page" type="string" value="1-2-16" />
 
        <attr name="sub_levels" type="string" value="runway : .6, 1-2 : 0.5" />
 
        <attr name="desc" type="string" value="set breaks apart as they sing" />
 
      </item>
 
      <item type="PyObject" id="140859540" class="Cue">
 
        <attr name="name" type="string" value="chorus fills DL then whole stage" />
 
        <attr name="time" type="string" value="5" />
 
        <attr name="cuenum" type="string" value="26" />
 
        <attr name="page" type="string" value="1-2-17" />
 
        <attr name="sub_levels" type="string" value="allstage : .9, sky-outside_store : .75" />
 
        <attr name="desc" type="string" value="sunday clothes outside" />
 
      </item>
 
      <item type="PyObject" id="140859612" class="Cue">
 
        <attr name="name" type="string" value="blueout to worklights" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-2-18" />
 
        <attr name="cuenum" type="string" value="28" />
 
        <attr name="sub_levels" type="string" value="worklights : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="139960220" class="Cue">
 
        <attr name="name" type="string" value="outside hatshop, DL" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-3-19" />
 
        <attr name="cuenum" type="string" value="29" />
 
        <attr name="sub_levels" type="string" value="1-3_outside : 1, 1-3_inside : 0.1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="140859252" class="Cue">
 
        <attr name="name" type="string" value="inside and outside" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="1-3-19" />
 
        <attr name="cuenum" type="string" value="29+30" />
 
        <attr name="sub_levels" type="string" value="1-3_inside : 1, 1-3_outside : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="140855956" class="Cue">
 
        <attr name="name" type="string" value="enter hatshop" />
 
        <attr name="time" type="string" value="5" />
 
        <attr name="page" type="string" value="1-3-20" />
 
        <attr name="cuenum" type="string" value="30" />
 
        <attr name="sub_levels" type="string" value="1-3_inside : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="140858292" class="Cue">
 
        <attr name="name" type="string" value="SPOT alone for song, walks R then LC" />
 
        <attr name="time" type="string" value="7" />
 
        <attr name="page" type="string" value="1-3-21" />
 
        <attr name="cuenum" type="string" value="31" />
 
        <attr name="sub_levels" type="string" value="nsi_cue_1 : .5, sky : 0.5" />
 
        <attr name="desc" type="string" value="ribbons SPOT" />
 
      </item>
 
      <item type="PyObject" id="140858588" class="Cue">
 
        <attr name="name" type="string" value="SPOT OFF return to scene" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="1-3-22" />
 
        <attr name="cuenum" type="string" value="32" />
 
        <attr name="sub_levels" type="string" value="1-3_inside : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="140854604" class="Cue">
 
        <attr name="name" type="string" value="SPOT alone for reprise, walks R then LC" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="1-3-23" />
 
        <attr name="cuenum" type="string" value="33" />
 
        <attr name="sub_levels" type="string" value="nsi_cue_1 : .7, sky : 0.5" />
 
        <attr name="desc" type="string" value="ribbons SPOT" />
 
      </item>
 
      <item type="PyObject" id="139854852" class="Cue">
 
        <attr name="name" type="string" value="SPOT OFF return to scene" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="1-3-23" />
 
        <attr name="cuenum" type="string" value="34" />
 
        <attr name="sub_levels" type="string" value="1-3_inside : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="140859124" class="Cue">
 
        <attr name="name" type="string" value="actors are DL to runway SPOT" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="1-3-36" />
 
        <attr name="cuenum" type="string" value="35" />
 
        <attr name="sub_levels" type="string" value="runway_stairs : .4, 1-3_inside : .2, sky : 0.4, dolly_stairs_right : .6" />
 
        <attr name="desc" type="string" value="two people leave, set breaks apart" />
 
      </item>
 
      <item type="PyObject" id="142420220" class="Cue">
 
        <attr name="name" type="string" value="stage fills with dancers" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-3-36" />
 
        <attr name="cuenum" type="string" value="36" />
 
        <attr name="sub_levels" type="string" value="1-3-36_dance : 1" />
 
        <attr name="desc" type="string" value="SPOT OFF" />
 
      </item>
 
      <item type="PyObject" id="142420292" class="Cue">
 
        <attr name="name" type="string" value="front center mini" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-3-36" />
 
        <attr name="cuenum" type="string" value="36.5" />
 
        <attr name="sub_levels" type="string" value="1-3-36_dance : 0.7, front_lc : 0.5" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="139858052" class="Cue">
 
        <attr name="name" type="string" value="dolly at stairs" />
 
        <attr name="time" type="string" value="2" />
 
        <attr name="page" type="string" value="1-3-37" />
 
        <attr name="cuenum" type="string" value="36.6" />
 
        <attr name="sub_levels" type="string" value="runway_stairs_left: 1, dolly_runway_left : 0.35, front_lc : 0.5" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142421188" class="Cue">
 
        <attr name="name" type="string" value="dolly alone in L runway corner" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-3-37" />
 
        <attr name="cuenum" type="string" value="37" />
 
        <attr name="sub_levels" type="string" value="dolly_runway_left : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="140861188" class="Cue">
 
        <attr name="name" type="string" value="dolly at stairs" />
 
        <attr name="time" type="string" value="2" />
 
        <attr name="page" type="string" value="1-3-37" />
 
        <attr name="cuenum" type="string" value="37.5" />
 
        <attr name="sub_levels" type="string" value="runway_stairs_left: 1, dolly_runway_left : 0.5" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142421644" class="Cue">
 
        <attr name="name" type="string" value="dance?" />
 
        <attr name="time" type="string" value="6" />
 
        <attr name="page" type="string" value="1-3-38" />
 
        <attr name="cuenum" type="string" value="38" />
 
        <attr name="sub_levels" type="string" value="1-3-36_dance : 1, front_lc : .5" />
 
        <attr name="desc" type="string" value="guys enter from UL" />
 
      </item>
 
      <item type="PyObject" id="140864612" class="Cue">
 
        <attr name="name" type="string" value="SPOT" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="1-3-38" />
 
        <attr name="cuenum" type="string" value="39" />
 
        <attr name="sub_levels" type="string" value="1-3-36_dance : 0.4, sky : 0.4, runway : 0.16" />
 
        <attr name="desc" type="string" value="dolly alone" />
 
      </item>
 
      <item type="PyObject" id="142426412" class="Cue">
 
        <attr name="name" type="string" value="crowd arrives" />
 
        <attr name="time" type="string" value="10" />
 
        <attr name="page" type="string" value="1-3-38" />
 
        <attr name="cuenum" type="string" value="40" />
 
        <attr name="sub_levels" type="string" value="nsi_cue_2 : 1, patio : .5, runway_stairs: 0.6" />
 
        <attr name="desc" type="string" value="SPOT out when all sing" />
 
      </item>
 
      <item type="PyObject" id="140862844" class="Cue">
 
        <attr name="name" type="string" value="center scene" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="1-3-39" />
 
        <attr name="cuenum" type="string" value="42" />
 
        <attr name="sub_levels" type="string" value="nsi_cue_2 : 1" />
 
        <attr name="desc" type="string" value="during people ext" />
 
      </item>
 
      <item type="PyObject" id="142422884" class="Cue">
 
        <attr name="name" type="string" value="dolly DC alone SPOT" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="1-3-40" />
 
        <attr name="cuenum" type="string" value="43" />
 
        <attr name="sub_levels" type="string" value="1-3-36_dance : 0.4, sky : 0.4, runway : 0.16" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142422964" class="Cue">
 
        <attr name="name" type="string" value="fadeout" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="cuenum" type="string" value="44" />
 
        <attr name="page" type="string" value="1-3-40" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="140861036" class="Cue">
 
        <attr name="name" type="string" value="warmers, house" />
 
        <attr name="time" type="string" value="9" />
 
        <attr name="cuenum" type="string" value="45" />
 
        <attr name="page" type="string" value="1-3-40" />
 
        <attr name="sub_levels" type="string" value="curtain_warmers : .5, house : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142427772" class="Cue">
 
        <attr name="name" type="string" value="house at half" />
 
        <attr name="time" type="string" value="5" />
 
        <attr name="cuenum" type="string" value="45.2" />
 
        <attr name="page" type="string" value="" />
 
        <attr name="sub_levels" type="string" value="curtain_warmers : .5, house : 0.5" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142425820" class="Cue">
 
        <attr name="name" type="string" value="house out" />
 
        <attr name="time" type="string" value="5" />
 
        <attr name="cuenum" type="string" value="45.3" />
 
        <attr name="page" type="string" value="" />
 
        <attr name="sub_levels" type="string" value="curtain_warmers : .5" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="140864484" class="Cue">
 
        <attr name="name" type="string" value="warmers out" />
 
        <attr name="time" type="string" value="20" />
 
        <attr name="cuenum" type="string" value="45.5" />
 
        <attr name="page" type="string" value="" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="desc" type="string" value="do this manually" />
 
      </item>
 
      <item type="PyObject" id="142430708" class="Cue">
 
        <attr name="name" type="string" value="&quot;night&quot; scene" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="2-1-1" />
 
        <attr name="cuenum" type="string" value="46" />
 
        <attr name="sub_levels" type="string" value="2-1_outside : 1, upstairs : .25" />
 
        <attr name="desc" type="string" value="on curtain opening" />
 
      </item>
 
      <item type="PyObject" id="142430268" class="Cue">
 
        <attr name="name" type="string" value="lights change scene to later at night" />
 
        <attr name="time" type="string" value="5" />
 
        <attr name="page" type="string" value="2-1-3" />
 
        <attr name="cuenum" type="string" value="47" />
 
        <attr name="sub_levels" type="string" value="2-1_darker : .8" />
 
        <attr name="desc" type="string" value="after song" />
 
      </item>
 
      <item type="PyObject" id="142431412" class="Cue">
 
        <attr name="name" type="string" value="worklights" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="2-1-4" />
 
        <attr name="cuenum" type="string" value="51" />
 
        <attr name="sub_levels" type="string" value="worklights : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142433372" class="Cue">
 
        <attr name="name" type="string" value="lewis at top of stairs" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="2-2-5" />
 
        <attr name="cuenum" type="string" value="52" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 1, candles : 1" />
 
        <attr name="desc" type="string" value="harmonia gardens" />
 
      </item>
 
      <item type="PyObject" id="142433412" class="Cue">
 
        <attr name="name" type="string" value="left table" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="2-2-5" />
 
        <attr name="cuenum" type="string" value="53" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 0.5, 2-2_l_table : 1, candles : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142434548" class="Cue">
 
        <attr name="name" type="string" value="all" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="2-2-6" />
 
        <attr name="cuenum" type="string" value="54" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 1, candles : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142435980" class="Cue">
 
        <attr name="name" type="string" value="right table" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="2-2-6" />
 
        <attr name="cuenum" type="string" value="55" />
 
        <attr name="sub_levels" type="string" value="2-2_all : .5, 2-2_r_table : 1, candles : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142435140" class="Cue">
 
        <attr name="name" type="string" value="all" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="2-2-7" />
 
        <attr name="cuenum" type="string" value="56" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 1, candles : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142434292" class="Cue">
 
        <attr name="name" type="string" value="left table" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="cuenum" type="string" value="57" />
 
        <attr name="page" type="string" value="2-2-7" />
 
        <attr name="sub_levels" type="string" value="2-2_all : .5, 2-2_l_table : 1, candles : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142430396" class="Cue">
 
        <attr name="name" type="string" value="all, when curtain closes" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="cuenum" type="string" value="58" />
 
        <attr name="page" type="string" value="2-2-7" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 1, candles : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142434068" class="Cue">
 
        <attr name="name" type="string" value="right table, curtain opens" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="cuenum" type="string" value="59" />
 
        <attr name="page" type="string" value="2-2-7" />
 
        <attr name="sub_levels" type="string" value="2-2_all : .5, 2-2_r_table : 1, candles : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142423372" class="Cue">
 
        <attr name="name" type="string" value="all" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="cuenum" type="string" value="60" />
 
        <attr name="page" type="string" value="2-2-8" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 1, candles : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142420420" class="Cue">
 
        <attr name="name" type="string" value="left table (&quot;champagne&quot;)" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="cuenum" type="string" value="63" />
 
        <attr name="page" type="string" value="2-2-8" />
 
        <attr name="sub_levels" type="string" value="2-2_all : .5, 2-2_l_table : 1, candles : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142439444" class="Cue">
 
        <attr name="name" type="string" value="all" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="cuenum" type="string" value="64" />
 
        <attr name="page" type="string" value="2-2-8" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 1, candles : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142437660" class="Cue">
 
        <attr name="name" type="string" value="right table (&quot;let&apos;s dance&quot;)" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="cuenum" type="string" value="65" />
 
        <attr name="page" type="string" value="2-2-8" />
 
        <attr name="sub_levels" type="string" value="2-2_all : .5, 2-2_r_table : 1, candles : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142440044" class="Cue">
 
        <attr name="name" type="string" value="all" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="cuenum" type="string" value="66" />
 
        <attr name="page" type="string" value="2-2-9" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 1, candles : 1" />
 
        <attr name="desc" type="string" value="with music" />
 
      </item>
 
      <item type="PyObject" id="142428260" class="Cue">
 
        <attr name="name" type="string" value="waiters talk about dolly at the base of stairs" />
 
        <attr name="time" type="string" value="5" />
 
        <attr name="cuenum" type="string" value="67" />
 
        <attr name="page" type="string" value="2-2-10" />
 
        <attr name="sub_levels" type="string" value="2-2_all : .9, bottom_of_stairs : 0.6, candles : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142441660" class="Cue">
 
        <attr name="name" type="string" value="dimmer.." />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="cuenum" type="string" value="68" />
 
        <attr name="page" type="string" value="2-2-11" />
 
        <attr name="sub_levels" type="string" value="2-2_all : .8, bottom_of_stairs : 0.5, candles : 0.7" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="139856396" class="Cue">
 
        <attr name="name" type="string" value="dolly arrives and walks down stairs" />
 
        <attr name="time" type="string" value="8" />
 
        <attr name="cuenum" type="string" value="69" />
 
        <attr name="page" type="string" value="2-2-11" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 0.9, garden_stairs : 1, candles-bright : 1" />
 
        <attr name="desc" type="string" value="SPOT ON dolly when she entr" />
 
      </item>
 
      <item type="PyObject" id="142438596" class="Cue">
 
        <attr name="name" type="string" value="&quot;down center&quot; dolly&apos;s scene" />
 
        <attr name="time" type="string" value="10" />
 
        <attr name="cuenum" type="string" value="70" />
 
        <attr name="page" type="string" value="2-2-14" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 1, front_c : 0.6, candles-bright : 1" />
 
        <attr name="desc" type="string" value="SPOT IS OUT at end of singing" />
 
      </item>
 
      <item type="PyObject" id="142443164" class="Cue">
 
        <attr name="name" type="string" value="&quot;dim&quot; for polka dance" />
 
        <attr name="time" type="string" value="5" />
 
        <attr name="page" type="string" value="2-2-18" />
 
        <attr name="cuenum" type="string" value="71" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 0.7, front_rc : .7, candles : 1" />
 
        <attr name="desc" type="string" value="table move" />
 
      </item>
 
      <item type="PyObject" id="142442836" class="Cue">
 
        <attr name="name" type="string" value="&quot;light stairs&quot;" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="2-2-18" />
 
        <attr name="cuenum" type="string" value="72" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 0.8, garden_stairs : 0.5,bottom_of_stairs:1,candles:1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142441092" class="Cue">
 
        <attr name="name" type="string" value="&quot;bright&quot; during chase" />
 
        <attr name="time" type="string" value="1.5" />
 
        <attr name="page" type="string" value="2-2-19" />
 
        <attr name="cuenum" type="string" value="73" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 1, garden_stairs : 0.5, bottom_of_stairs : 1, candles:1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142439628" class="Cue">
 
        <attr name="name" type="string" value="blueout right after freeze" />
 
        <attr name="time" type="string" value="0" />
 
        <attr name="page" type="string" value="2-2-19" />
 
        <attr name="cuenum" type="string" value="74" />
 
        <attr name="sub_levels" type="string" value="worklights : 1.0" />
 
        <attr name="desc" type="string" value="&quot;gasp!&quot;" />
 
      </item>
 
      <item type="PyObject" id="142437020" class="Cue">
 
        <attr name="name" type="string" value="&quot;cold&quot; courtroom" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="2-3-20" />
 
        <attr name="cuenum" type="string" value="75" />
 
        <attr name="sub_levels" type="string" value="2-3_cold : 1, sky : 0.23" />
 
        <attr name="desc" type="string" value="when set change is complete" />
 
      </item>
 
      <item type="PyObject" id="142445916" class="Cue">
 
        <attr name="name" type="string" value="SPOT on cornelius song" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="2-3-22" />
 
        <attr name="cuenum" type="string" value="77" />
 
        <attr name="sub_levels" type="string" value="2-3_brighter : .5" />
 
        <attr name="desc" type="string" value="only takes a  moment" />
 
      </item>
 
      <item type="PyObject" id="142446004" class="Cue">
 
        <attr name="name" type="string" value="SPOT OUT room is cold again" />
 
        <attr name="time" type="string" value="9" />
 
        <attr name="page" type="string" value="2-3-23" />
 
        <attr name="cuenum" type="string" value="78" />
 
        <attr name="sub_levels" type="string" value="2-3_cold : 1, sky : 0.23" />
 
        <attr name="desc" type="string" value="end of song" />
 
      </item>
 
      <item type="PyObject" id="142446300" class="Cue">
 
        <attr name="name" type="string" value="blueout to worklights" />
 
        <attr name="time" type="string" value="2" />
 
        <attr name="page" type="string" value="2-3-25" />
 
        <attr name="cuenum" type="string" value="80" />
 
        <attr name="sub_levels" type="string" value="worklights : 1" />
 
        <attr name="desc" type="string" value="end of scene" />
 
      </item>
 
      <item type="PyObject" id="142446380" class="Cue">
 
        <attr name="name" type="string" value="store" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="2-4-26" />
 
        <attr name="cuenum" type="string" value="81" />
 
        <attr name="sub_levels" type="string" value="1-2 : 1, trap : 0.2, upstairs : 0.2" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142427140" class="Cue">
 
        <attr name="name" type="string" value="trapdoor up" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="2-4-26" />
 
        <attr name="cuenum" type="string" value="82" />
 
        <attr name="sub_levels" type="string" value="1-2 : 1, trap : 1.0, upstairs : 0.2" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142425276" class="Cue">
 
        <attr name="name" type="string" value="trapdoor down" />
 
        <attr name="time" type="string" value="5" />
 
        <attr name="page" type="string" value="2-4-26" />
 
        <attr name="cuenum" type="string" value="83" />
 
        <attr name="sub_levels" type="string" value="1-2 : 1, trap : 0.4, upstairs : 0.2" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142449700" class="Cue">
 
        <attr name="name" type="string" value="dolly alone at top of R stairs" />
 
        <attr name="time" type="string" value="7" />
 
        <attr name="page" type="string" value="2-4-28" />
 
        <attr name="cuenum" type="string" value="84" />
 
        <attr name="sub_levels" type="string" value="2-4_money : 1, 1-2 : 0.35, upstairs : 0.2, 1-2_register : .8" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142427260" class="Cue">
 
        <attr name="name" type="string" value="restore to store" />
 
        <attr name="time" type="string" value="5" />
 
        <attr name="page" type="string" value="2-4-28" />
 
        <attr name="cuenum" type="string" value="85" />
 
        <attr name="sub_levels" type="string" value="1-2 : 1, trap : 0.2, upstairs : 0.2" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142425036" class="Cue">
 
        <attr name="name" type="string" value="fade to front" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="2-4-30" />
 
        <attr name="cuenum" type="string" value="86" />
 
        <attr name="sub_levels" type="string" value="front_of_stage : .2, 1-2: .5" />
 
        <attr name="desc" type="string" value="before set breaks SPOT on them" />
 
      </item>
 
      <item type="PyObject" id="142432356" class="Cue">
 
        <attr name="name" type="string" value="crowd arrives" />
 
        <attr name="time" type="string" value="5" />
 
        <attr name="page" type="string" value="2-4-30" />
 
        <attr name="cuenum" type="string" value="87" />
 
        <attr name="sub_levels" type="string" value="2-2_all : 1, front_of_stage : 1" />
 
        <attr name="desc" type="string" value="SPOT OUT" />
 
      </item>
 
      <item type="PyObject" id="142440652" class="Cue">
 
        <attr name="name" type="string" value="flat splits" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="2-4-31" />
 
        <attr name="cuenum" type="string" value="88" />
 
        <attr name="sub_levels" type="string" value="finale : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142449396" class="Cue">
 
        <attr name="name" type="string" value="dolly enters" />
 
        <attr name="time" type="string" value="3" />
 
        <attr name="page" type="string" value="2-4-31" />
 
        <attr name="cuenum" type="string" value="89" />
 
        <attr name="sub_levels" type="string" value="finale_with_dolly : 1" />
 
        <attr name="desc" type="string" value="SPOT ON" />
 
      </item>
 
      <item type="PyObject" id="142455004" class="Cue">
 
        <attr name="name" type="string" value="curtains close" />
 
        <attr name="time" type="string" value="4" />
 
        <attr name="page" type="string" value="2-4-31+" />
 
        <attr name="cuenum" type="string" value="90" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="desc" type="string" value="SPOT OUT when all the way down" />
 
      </item>
 
      <item type="PyObject" id="142454764" class="Cue">
 
        <attr name="name" type="string" value="temporary worklights" />
 
        <attr name="time" type="string" value="0" />
 
        <attr name="page" type="string" value="2-4-31+" />
 
        <attr name="cuenum" type="string" value="90.33" />
 
        <attr name="sub_levels" type="string" value="worklights : 1, sky : .7" />
 
        <attr name="desc" type="string" value="right after curtain closes" />
 
      </item>
 
      <item type="PyObject" id="142449476" class="Cue">
 
        <attr name="name" type="string" value="worklights out" />
 
        <attr name="time" type="string" value="0" />
 
        <attr name="page" type="string" value="2-4-31+" />
 
        <attr name="cuenum" type="string" value="90.66" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142429916" class="Cue">
 
        <attr name="name" type="string" value="curtains reopen" />
 
        <attr name="time" type="string" value="2" />
 
        <attr name="page" type="string" value="2-4-31+" />
 
        <attr name="cuenum" type="string" value="91" />
 
        <attr name="sub_levels" type="string" value="finale_with_dolly : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142457252" class="Cue">
 
        <attr name="name" type="string" value="curtains close again" />
 
        <attr name="time" type="string" value="5" />
 
        <attr name="page" type="string" value="2-4-31+" />
 
        <attr name="cuenum" type="string" value="92" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142457492" class="Cue">
 
        <attr name="name" type="string" value="house comes up" />
 
        <attr name="time" type="string" value="8" />
 
        <attr name="page" type="string" value="2-4-31+" />
 
        <attr name="cuenum" type="string" value="93" />
 
        <attr name="sub_levels" type="string" value="curtain_warmers: .5, house : 1" />
 
        <attr name="desc" type="string" value="" />
 
      </item>
 
      <item type="PyObject" id="142431788" class="Cue">
 
        <attr name="name" type="string" value="emergency blackout" />
 
        <attr name="time" type="string" value="0" />
 
        <attr name="page" type="string" value="--" />
 
        <attr name="cuenum" type="string" value="###" />
 
        <attr name="sub_levels" type="string" value="" />
 
        <attr name="desc" type="string" value="use with caution" />
 
      </item>
 
    </val>
 
  </entry>
 
</attr>
 
</PyObject>
flax/curvecalc
Show inline comments
 
new file 100644
 
#!/usr/bin/python
 

	
 
"""
 
todo: curveview should preserve more objects, for speed maybe
 

	
 
"""
 
from __future__ import division
 
import xmlrpclib,time,socket,sys,textwrap,math,glob
 
from bisect import bisect_left,bisect,bisect_right
 
import Tkinter as tk
 
from dispatch import dispatcher
 
from twisted.internet import reactor,tksupport
 
from twisted.web.xmlrpc import Proxy
 

	
 
sys.path.append("../light8")
 
import dmxclient
 
import Submaster
 
from TLUtility import make_attributes_from_args
 

	
 
from zoomcontrol import Zoomcontrol
 

	
 
class Curve:
 
    """curve does not know its name. see Curveset"""
 
    points = None # x-sorted list of (x,y)
 
    def __init__(self):
 
        self.points = []
 

	
 
    def load(self,filename):
 
        for line in file(filename):
 
            self.points.append(tuple([float(a) for a in line.split()]))
 
        self.points.sort()
 
        dispatcher.send("points changed",sender=self)
 

	
 
    def save(self,filename):
 
        f = file(filename,'w')
 
        for p in self.points:
 
            f.write("%s %s\n" % p)
 
        f.close()
 

	
 
    def eval(self,t):
 
        i = bisect_left(self.points,(t,None))-1
 

	
 
        if self.points[i][0]>t:
 
            return self.points[i][1]
 
        if i>=len(self.points)-1:
 
            return self.points[i][1]
 

	
 
        p1,p2 = self.points[i],self.points[i+1]
 
        frac = (t-p1[0])/(p2[0]-p1[0])
 
        y = p1[1]+(p2[1]-p1[1])*frac
 
        return y
 

	
 
    __call__=eval
 

	
 
class Curveview(tk.Canvas):
 
    def __init__(self,master,curve,**kw):
 
        self.curve=curve
 
        tk.Canvas.__init__(self,master,width=10,height=10,
 
                           relief='sunken',bd=1,
 
                           closeenough=5,**kw)
 
        self.selected_points=[] # idx of points being dragged
 
        self.update()
 
        self.bind("<Enter>",self.focus)
 
        dispatcher.connect(self.input_time,"input time")
 
        dispatcher.connect(self.update,"zoom changed")
 
        dispatcher.connect(self.update,"points changed",sender=self.curve)
 
        self.bind("<Configure>",self.update)
 
    def screen_from_world(self,p):
 
        start,end = self.zoom
 
        ht = self.winfo_height()
 
        return (p[0]-start)/(end-start)*self.winfo_width(), (ht-5)-p[1]*(ht-10)
 
    def world_from_screen(self,x,y):
 
        start,end = self.zoom
 
        ht = self.winfo_height()
 
        return x/self.winfo_width()*(end-start)+start, ((ht-5)-y)/(ht-10)
 
    
 
    def input_time(self,val):
 
        t=val
 
        pts = self.screen_from_world((val,0))+self.screen_from_world((val,1))
 
        self.delete('timecursor')
 
        self.create_line(*pts,**dict(width=2,fill='red',tags=('timecursor',)))
 
    def update(self,*args):
 

	
 
        self.zoom = dispatcher.send("zoom area")[0][1]
 
        cp = self.curve.points
 

	
 
        visible_x = (self.world_from_screen(0,0)[0],
 
                     self.world_from_screen(self.winfo_width(),0)[0])
 

	
 
        visleftidx = max(0,bisect_left(cp,(visible_x[0],None))-1)
 
        visrightidx = min(len(cp)-1,bisect_left(cp,(visible_x[1],None))+1)
 
                             
 
        visible_points = cp[visleftidx:visrightidx+1]
 
        
 
        self.delete('curve')
 
        linepts=[]
 
        for p in visible_points:
 
            linepts.extend(self.screen_from_world(p))
 
        if not linepts:
 
            return
 
        line = self.create_line(*linepts,**{'tags':'curve'})
 

	
 
        # canvas doesnt have keyboard focus, so i can't easily change the
 
        # cursor when ctrl is pressed
 
        #        def curs(ev):
 
        #            print ev.state
 
        #        self.bind("<KeyPress>",curs)
 
        #        self.bind("<KeyRelease-Control_L>",lambda ev: curs(0))
 
        self.tag_bind(line,"<Control-ButtonPress-1>",self.newpoint)
 

	
 
        self.dots = {} # idx : canvas rectangle
 

	
 
        if len(visible_points)<50: 
 
            for i,p in enumerate(visible_points):
 
                rad=3
 
                p = self.screen_from_world(p)
 
                dot = self.create_rectangle(p[0]-rad,p[1]-rad,p[0]+rad,p[1]+rad,
 
                                            outline='black',fill='blue',
 
                                            tags=('curve','point'))
 
                self.tag_bind(dot,"<ButtonPress-1>",
 
                              lambda ev,i=i: self.dotpress(ev,i))
 
                self.bind("<Motion>",
 
                          lambda ev,i=i: self.dotmotion(ev,i))
 
                self.bind("<ButtonRelease-1>",
 
                          lambda ev,i=i: self.dotrelease(ev,i))
 
                self.dots[i]=dot
 

	
 
            self.highlight_selected_dots()
 

	
 
    def newpoint(self,ev):
 
        cp = self.curve.points
 
        
 
        p = self.world_from_screen(ev.x,ev.y)
 
        i = bisect(cp,(p[0],None))
 

	
 
        self.unselect()
 
        cp.insert(i,p)
 
        self.update()
 

	
 
    def highlight_selected_dots(self):
 
        for i,d in self.dots.items():
 
            if i in self.selected_points:
 
                self.itemconfigure(d,fill='red')
 
            else:
 
                self.itemconfigure(d,fill='blue')
 
        
 
    def dotpress(self,ev,dotidx):
 
        self.selected_points=[dotidx]
 
        self.highlight_selected_dots()
 

	
 
    def dotmotion(self,ev,dotidx):
 
        cp = self.curve.points
 

	
 
        moved=0
 
        for idx in self.selected_points:
 
            x,y = self.world_from_screen(ev.x,ev.y)
 
            y = max(0,min(1,y))
 
            if idx>0 and x<=cp[idx-1][0]:
 
                continue
 
            if idx<len(cp)-1 and x>=cp[idx+1][0]:
 
                continue
 
            moved=1
 
            cp[idx] = (x,y)
 
        if moved:
 
            self.update()
 
    def unselect(self):
 
        self.selected_points=[]
 
        self.highlight_selected_dots()
 
        
 
    def dotrelease(self,ev,dotidx):
 
        self.unselect()
 
        
 
class Curveset:
 
    curves = None # curvename : curve
 
    def __init__(self):
 
        self.curves = {}
 
    def load(self,basename):
 
        """find all files that look like basename-curvename and add
 
        curves with their contents"""
 
        for filename in glob.glob("%s-*"%basename):
 
            curvename = filename[filename.rfind('-')+1:]
 
            c=Curve()
 
            c.load(filename)
 
            self.add_curve(curvename,c)            
 
    def save(self,basename):
 
        """writes a file for each curve with a name
 
        like basename-curvename"""
 
        for name,cur in self.curves.items():
 
            cur.save("%s-%s" % (basename,name))
 
    def add_curve(self,name,curve):
 
        self.curves[name] = curve
 
        dispatcher.send("add_curve",sender=self,name=name)
 
    def globalsdict(self):
 
        return self.curves.copy()
 

	
 
class Curvesetview(tk.Frame):
 
    curves = None # curvename : Curveview
 
    def __init__(self,master,curveset,**kw):
 
        self.curves = {}
 
        self.curveset = curveset
 
        tk.Frame.__init__(self,master,**kw)
 
        dispatcher.connect(self.add_curve,"add_curve",sender=self.curveset)
 
    def add_curve(self,name):
 
        f = tk.Frame(self,relief='raised',bd=1)
 
        f.pack(side='top',fill='both',exp=1)
 
        tk.Label(f,text="curve %r"%name).pack(side='left')
 
        cv = Curveview(f,self.curveset.curves[name])
 
        cv.pack(side='right',fill='both',exp=1)
 
        self.curves[name] = cv
 

	
 
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("http://spot:8040")
 
            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):
 
        self.curveset = curveset
 
        self.lasteval = None
 
        self.expr=""
 
    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
 
        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="no errors")
 
        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(master,textvariable=self.evar)
 
        e.pack(side='left',fill='both',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(master)
 
        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 scaled(self,t):
 
        return self.sub * 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)
 
        tk.Label(self,text="sub %r" % self.subterm.sub.name).pack(side='left')
 
        sev=Subexprview(self,self.subterm.subexpr)
 
        sev.pack(side='left',fill='both',exp=1)
 

	
 
class Output:
 
    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)
 
        dispatcher.send("output levels",val=out.get_levels())
 
        dmxclient.outputlevels(out.get_dmx_list(),twisted=1)
 

	
 
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()]),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):
 
    f = file(filename,'w')
 
    for st in subterms:
 
        f.write("%s %s\n" % (st.sub.name,st.subexpr.expr))
 
    f.close()
 

	
 
def save(song,subterms,curveset):
 
    savesubterms("subterms/"+song,subterms)
 
    curveset.save(basename="curves/"+song)
 

	
 
#######################################################################
 
root=tk.Tk()
 
root.wm_geometry("790x930")
 
#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)
 

	
 
song = "16mix.wav"
 

	
 
curveset.load(basename="curves/"+song)
 

	
 
subterms = []
 
for line in file("subterms/"+song):
 
    subname,expr = line.strip().split(" ",1)
 

	
 
    term = Subterm()
 

	
 
    sexpr = Subexpr(curveset)
 
    sexpr.expr = expr
 
    
 
    term.sub = Submaster.Submaster(subname)
 
    term.subexpr = sexpr
 
    subterms.append(term)
 
    
 
    stv=Subtermview(root,term)
 
    stv.pack(side='top',fill='x')
 

	
 
out = Output(subterms)
 

	
 
#save(song,subterms,curveset)
 

	
 
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()
 

	
 
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()
flax/dmxchanedit.py
Show inline comments
 
new file 100644
 
"""
 

	
 
widget to show all dmx channel levels and allow editing. levels might
 
not actually match what dmxserver is outputting.
 

	
 
"""
 
from __future__ import nested_scopes,division
 
import Tkinter as tk
 
import sys
 
sys.path.append("../light8")
 
import Patch
 
from uihelpers import make_frame, colorlabel, eventtoparent
 
from dispatch import dispatcher
 

	
 
stdfont = ('Arial', 10)
 

	
 
class Onelevel(tk.Frame):
 
    """a name/level pair"""
 
    def __init__(self, parent, channelnum):
 
        """channelnum is 1..68, like the real dmx"""
 
        tk.Frame.__init__(self,parent)
 

	
 
        self.channelnum=channelnum
 
        self.currentlevel=0 # the level we're displaying, 0..1
 
        
 
        # 3 widgets, left-to-right:
 

	
 
        # channel number -- will turn yellow when being altered
 
        self.num_lab = tk.Label(self, text=str(channelnum),
 
                                width=3, bg='grey40', 
 
                                fg='white', font=stdfont,
 
                                padx=0, pady=0, bd=0, height=1)
 
        self.num_lab.pack(side='left')
 

	
 
        # text description of channel
 
        self.desc_lab=tk.Label(self, text=Patch.get_channel_name(channelnum),
 
                               width=14, font=stdfont, anchor='w',
 
                               padx=0, pady=0, bd=0, 
 
                 height=1, bg='black', fg='white')
 
        self.desc_lab.pack(side='left')
 
        
 
        # current level of channel, shows intensity with color
 
        self.level_lab = tk.Label(self, width=3, bg='lightBlue',
 
                                  font=stdfont, anchor='e', 
 
                                  padx=1, pady=0, bd=0, height=1)
 
        self.level_lab.pack(side='left')
 

	
 
        self.setlevel(0)
 
        self.setupmousebindings()
 
        
 
    def setupmousebindings(self):
 
        def b1down(ev):
 
            self.desc_lab.config(bg='cyan')
 
            self._start_y=ev.y
 
            self._start_lev=self.currentlevel
 
#        self.bind("<ButtonPress-1>",b1down)
 
        def b1motion(ev):
 
            delta=self._start_y-ev.y
 
            self.changelevel(self._start_lev+delta*.005)
 
#        self.bind("<B1-Motion>",b1motion)
 
        def b1up(ev):
 
            self.desc_lab.config(bg='black')
 
#        self.bind("<B1-ButtonRelease>",b1up)
 

	
 
        # make the buttons work in the child windows
 
        for w in self.winfo_children():
 
            for e,func in (('<ButtonPress-1>',b1down),
 
                           ('<B1-Motion>',b1motion),
 
                           ('<ButtonRelease-1>',b1up)):
 
                w.bind(e,func)
 
#                w.bind(e,lambda ev,e=e: eventtoparent(ev,e))
 
        
 
    def colorlabel(self):
 
        """color the level label based on its own text (which is 0..100)"""
 
        txt=self.level_lab['text'] or "0"
 
        lev=float(txt)/100
 
        low=(80,80,180)
 
        high=(255,55,050)
 
        out = [int(l+lev*(h-l)) for h,l in zip(high,low)]
 
        col="#%02X%02X%02X" % tuple(out)
 
        self.level_lab.config(bg=col)
 

	
 
    def setlevel(self,newlev):
 
        """the main program is telling us to change our
 
        display. newlev is 0..1"""
 
        self.currentlevel=newlev
 
        newlev="%d"%(newlev*100)
 
        olddisplay=self.level_lab.cget('text')
 
        if newlev!=olddisplay:
 
            self.level_lab.config(text=newlev)
 
            self.colorlabel()
 

	
 
    def getlevel(self):
 
        """returns currently displayed level, 0..1"""
 
        return self.currentlevel
 

	
 
    def changelevel(self,newlev):
 

	
 
        """the user is adjusting the level on this widget.  the main
 
        program needs to hear about it. then the main program will
 
        call setlevel()"""
 

	
 
        dispatcher.send("levelchanged",channel=self.channelnum,newlevel=newlev)
 
    
 
class Levelbox(tk.Frame):
 
    def __init__(self, parent, num_channels=68):
 
        tk.Frame.__init__(self,parent)
 

	
 
        self.levels = [] # Onelevel objects
 

	
 
        frames = (make_frame(self), make_frame(self))
 

	
 
        for channel in range(1, num_channels+1):
 

	
 
            # frame for this channel
 
            f = Onelevel(frames[channel > (num_channels/2)],channel)
 

	
 
            self.levels.append(f)
 
            f.pack(side='top')
 

	
 
        #dispatcher.connect(setalevel,"setlevel")
 

	
 
    def setlevels(self,newlevels):
 
        """sets levels to the new list of dmx levels (0..1). list can
 
        be any length"""
 
        for l,newlev in zip(self.levels,newlevels):
 
            l.setlevel(newlev)
flax/dollycues
Show inline comments
 
new file 100644
 
1-1-1 curtain opens, cast frozen
 
1-1-1 music cue, cast wakes up
 
1-1-1 cast freezes, dolly walks RC to LC
 
1-1-2 unfreeze
 
1-1-2 freeze, scene is RC and C
 
1-1-3 unfreeze
 
1-1-4 freeze, scene RC
 
1-1-4 dolly walks to her R runway corner
 
1-1-5 unfreeze
 
1-1-5 stage clears, band walks runway
 
1-2-6 scene opens outside store L
 
1-2-6 enters store
 
1-2-7 trapdoor area
 
1-2-9 "fade up to end" on final verse of song
 
1-2-9 "restore" after song finishes
 
1-2-12 dolly alone
 
1-2-12 dolly upstairs
 
1-2-12 cross to trapdoor
 
1-2-14 cross to upstairs
 
1-2-15 cross to trapdoor and register area
 
1-2-16 cross to upstairs
 
1-2-16 whole scene
 
1-2-16 set breaks apart as they sing, follow the guys
 
1-2-17 chorus fills DL then whole stage
 
1-2-18 fadeout
 
1-3-19 outside hatshop, DL
 
1-3-20 enter hatshop
 
1-3-21 spot for ribbons song, walks R then LC
 
1-3-22 spot out, return to scene
 
1-3-23 song again
 
1-3-23 return to scene
 
1-3-36 set breaks apart, actors are DL to runway
 
1-3-36 stage fills with dancers
 
1-3-37 dolly alone in L runway corner
 
1-3-38 guys enter from UL
 
1-3-38 spot dolly alone
 
1-3-38 crowd arrives
 
1-3-39 spot out
 
1-3-39 add patio
 
1-3-39 center scene
 
1-3-40 dolly DC alone
 
1-3-40 fadeout
 

	
 
2-1-1 "night" scene
 
2-1-3 lights change scene to later at night
 
2-1-4 mini scene
 
2-1-4 another mini scene
 
2-1-4 fadeout
 
2-2-5 lewis at top of stairs
 
2-2-5 left table
 
2-2-6 all
 
2-2-6 right table
 
2-2-7 all
 
2-2-7 left table
 
2-2-7 all, when curtain closes
 
2-2-7 right table, curtain opens
 
2-2-8 all
 
2-2-8 right table and stairs, for mo+lewis
 
2-2-8 all
 
2-2-8 left table ("champagne")
 
2-2-8 all
 
2-2-8 right table ("let's dance")
 
2-2-9 all
 
2-2-10 waiters talk about dolly at the base of stairs
 
2-2-11 dimmer..
 
2-2-12 dolly arrives and walks down stairs
 
2-2-14 "down center" dolly's scene
 
2-2-18 "dim" for polka dance
 
2-2-18 "light stairs"
 
2-2-19 "bright" during chase
 
2-2-19 fadeout right after freeze
 
2-3-20 "cold" courtroom
 
2-3-22 room warms up
 
2-3-22 spot on cornelius song
 
2-3-23 spot done, room is cold again
 
2-3-25 fadeout
 
2-4-26 store
 
2-4-26 trapdoor up
 
2-4-26 trapdoor down
 
2-4-28 dolly alone at top of R stairs
 
2-4-28 restore
 
2-4-30 fade to front
 
2-4-30 crowd arrives
 
2-4-31 flat splits
 
2-4-31 dolly enters
 

	
 

	
flax/dollysubnames
Show inline comments
 
new file 100644
 
from the photos:
 
1 front white
 
2 all stage
 
3 1-1
 
4 1-2
 
5 trap (add F5)
 
6 upstairs
 
7 1-2 song downstairs
 
8 1-3 outside 
 
9 1-3 inside
 
10 1-3-36 dance
 
11 big parade
 
12 xxxxx
 
13 2-1 outside
 
14 2-2 all
 
15 2-2 r table
 
16 2-2 l table
 
17 runway
 
18 front of stage
 
19 2-3 cold
 
20 2-3 brighter
 
21 finale w/o dolly
 
22 finale w dolly
 
23 ?
 
24 ?
 
\ No newline at end of file
flax/editline
Show inline comments
 
new file 100644
 
#!/usr/bin/python
 

	
 
"""loads and saves timelines. lets you edit the timings and select the
 
functions. you should be able to edit the functions' parameters also.
 
"""
 

	
 
import Tkinter as tk
 

	
flax/editsub
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 

	
 
import sys
 
import Subcomposer
 
Subcomposer.open_sub_editing_window(sys.argv[-1], use_mainloop=1, dmxdummy=0)
flax/fieldsets/demo
Show inline comments
 
new file 100644
 
<?xml version='1.0' encoding='UTF-8'?>
 
<fieldset version='1'>
 
  <field y='64.518605176' x='64.4226431666' name='main 2' falloff='10'/>
 
  <field y='71.6297059084' x='64.1501958858' name='main 3' falloff='9.44161122397'/>
 
  <field y='62.2614165793' x='49.4797562176' name='main 4' falloff='9.02748404992'/>
 
  <field y='64.7105473806' x='39.4725745351' name='main 5' falloff='9.02748404992'/>
 
  <field y='70.0008620159' x='45.644638445' name='main 7' falloff='9.02748404992'/>
 
  <field y='70.7801243766' x='53.963092297' name='main 8' falloff='9.02748404992'/>
 
  <field y='68.1676620898' x='74.8416350959' name='main 9' falloff='10.5270359255'/>
 
  <field y='62.0408841822' x='79.8677043872' name='main 10' falloff='9.02748404992'/>
 
</fieldset>
flax/littletimeline.py
Show inline comments
 
new file 100644
 
#!/usr/bin/python
 

	
 
""" a test that listens to ascoltami player and outputs a light to
 
dmxserver """
 

	
 
from __future__ import division
 
import xmlrpclib,time,socket,sys
 
sys.path.append("../light8")
 
import dmxclient
 

	
 
player=xmlrpclib.Server("http://localhost:8040")
 
print "found player"
 

	
 
t1=time.time()
 
while 1:
 
    try:
 
        playtime=player.gettime()
 
    except socket.error,e:
 
        print "server error %r, waiting"%e
 
        time.sleep(2)
 

	
 
    lev=0
 
    for low,high,func in ((0,20,0),
 
                          (20,30,(playtime-20)/10),
 
                          (30,170,1),
 
                          (170,189,1-(playtime-170)/19),
 
                          ):
 
        if low<=playtime<high:
 
            lev=func
 

	
 
    print "Send",lev
 
    dmxclient.outputlevels([lev])
 
  
 
    time.sleep(.01)
flax/subs/1-1
Show inline comments
 
new file 100644
 
44 : 0.25
 
c-scp : 0.68
 
diag : 0.61
 
downfill-left : 0.49
 
downfill-right : 0.49
 
f1 : 0.615
 
f1.5 : 0.455
 
f2 : 0.99
 
f3 : 1.0
 
f4 : 0.82
 
f5 : 0.67
 
f6 : 0.9
 
f7 : 0.695
 
l-scp : 0.68
 
main-c1 : 0.29
 
main-c2 : 0.33
 
main-c3 : 0.51
 
main-l-blue : 0.41
 
main-l-red : 0.38
 
main-lc-fill : 0.29
 
main-r-blue : 0.41
 
main-r-red : 0.32
 
r-scp : 0.68
 
storefill : 0.25
flax/subs/1-1_sill
Show inline comments
 
new file 100644
 
c-scp : 0.66
 
downfill-left : 0.245
 
downfill-right : 0.245
 
f1 : 0.33
 
f1.5 : 0.25
 
f2 : 0.255
 
f3 : 0.185
 
f4 : 0.14
 
f5 : 0.1
 
f6 : 0.23
 
f7 : 0.14
 
l-scp : 0.805
 
r-scp : 0.8
flax/subs/1-2
Show inline comments
 
new file 100644
 
44 : 1.0
 
c-scp : 0.765
 
dayback : 0.52
 
l-scp : 0.755
 
main-c1 : 0.84
 
main-c2 : 1.0
 
main-c3 : 1.0
 
main-l-blue : 0.09
 
main-l-red : 0.715
 
main-lc-fill : 0.43
 
main-r-blue : 0.555
 
main-r-red : 0.78
 
r-scp : 0.755
 
ramp9-stairs : 0.57
 
storefill : 0.9
 
upstairs above : 0.44
flax/subs/1-2_outside
Show inline comments
 
new file 100644
 
44 : 0.36
 
blue1 : 0.47
 
blue2 : 0.47
 
blue3 : 0.47
 
blue4 : 0.47
 
dayback : 0.36
 
downfill-left : 0.43
 
downfill-right : 0.25
 
f1 : 0.39
 
f1.5 : 0.25
 
main-l-blue : 0.94
 
main-l-red : 0.48
 
main-lc-fill : 0.135
 
storefill : 0.36
flax/subs/1-2_register
Show inline comments
 
new file 100644
 
main-c1 : 0.28
 
main-c2 : 0.525
 
main-r-blue : 0.665
 
main-r-red : 0.285
flax/subs/1-2_song_downstairs
Show inline comments
 
new file 100644
 
44 : 0.35
 
diag-back : 0.85
 
f3 : 0.32
 
f4 : 0.55
 
f5 : 0.48
 
f6 : 0.6
 
main-c1 : 0.44
 
main-c2 : 0.52
 
storefill : 0.35
flax/subs/1-3-36_dance
Show inline comments
 
new file 100644
 
2 : 0.18
 
blue1 : 0.86
 
blue2 : 0.86
 
blue3 : 0.86
 
blue4 : 0.86
 
dayback : 0.1
 
diag : 0.18
 
diag-back : 0.18
 
downfill-left : 0.39
 
downfill-right : 0.4
 
f1.5 : 0.18
 
f2 : 0.18
 
f3 : 0.18
 
f4 : 0.18
 
f5 : 0.18
 
f6 : 0.35
 
f7 : 0.4
 
l-scp : 0.1
 
c-scp : 0.1
 
main-c1 : 0.18
 
main-c2 : 0.18
 
main-l-blue : 0.18
 
main-lc-fill : 0.18
 
main-r-red : 0.18
 
r-scp : 0.16
 
ramp9-stairs : 0.18
flax/subs/1-3_inside
Show inline comments
 
new file 100644
 
44 : 0.16
 
blue1 : 0.47
 
blue2 : 0.47
 
blue3 : 0.47
 
blue4 : 0.47
 
c-scp : 0.54
 
dayback : 0.36
 
downfill-left : 0.23
 
downfill-right : 0.22
 
f1.5 : 0.25
 
f2 : 0.94
 
f3 : 0.96
 
f4 : 0.49
 
f5 : 0.46
 
f6 : 0.46
 
l-scp : 0.54
 
main-c1 : 0.92
 
main-c2 : 1.0
 
main-c3 : 0.78
 
main-lc-fill : 0.76
 
main-r-blue : 0.49
 
main-r-red : 0.39
 
r-scp : 0.48
 
storefill : 0.16
flax/subs/1-3_outside
Show inline comments
 
new file 100644
 
44 : 0.36
 
blue1 : 0.47
 
blue2 : 0.47
 
blue3 : 0.47
 
blue4 : 0.47
 
c-scp : 0.49
 
dayback : 0.36
 
downfill-left : 0.43
 
downfill-right : 0.25
 
f1.5 : 0.25
 
l-scp : 0.485
 
main-l-blue : 0.65
 
r-scp : 0.43
 
storefill : 0.36
flax/subs/2-1_darker
Show inline comments
 
new file 100644
 
18 : 0.18
 
blue1 : 1.0
 
blue2 : 1.0
 
blue3 : 1.0
 
blue4 : 1.0
 
c-scp : 0.18
 
downfill-left : 0.325
 
downfill-right : 0.335
 
f1.5 : 0.46
 
f2 : 0.54
 
f3 : 0.59
 
f4 : 0.66
 
f5 : 0.42
 
l-scp : 0.15
 
main-c1 : 0.27
 
main-c2 : 0.52
 
main-c3 : 0.36
 
main-lc-fill : 0.18
 
main-r-blue : 0.26
 
main-r-red : 0.23
 
oran1 : 0.81
 
r-scp : 0.11
flax/subs/2-1_outside
Show inline comments
 
new file 100644
 
18 : 0.18
 
blue1 : 1.0
 
blue2 : 1.0
 
blue3 : 1.0
 
blue4 : 1.0
 
c-scp : 0.18
 
downfill-left : 0.4
 
downfill-right : 0.405
 
f1.5 : 0.46
 
f2 : 0.54
 
f3 : 0.59
 
f4 : 0.66
 
f5 : 0.42
 
l-scp : 0.15
 
main-c1 : 0.27
 
main-c2 : 0.52
 
main-c3 : 0.36
 
main-lc-fill : 0.18
 
main-r-blue : 0.26
 
main-r-red : 0.23
 
oran1 : 0.81
 
r-scp : 0.11
flax/subs/2-2_all
Show inline comments
 
new file 100644
 
44 : 0.87
 
blue1 : 0.63
 
blue2 : 0.63
 
blue3 : 0.63
 
blue4 : 0.63
 
downfill-left : 0.23
 
downfill-right : 0.24
 
f1 : 0.36
 
f1.5 : 0.3
 
f2 : 0.4
 
f3 : 0.37
 
f4 : 0.25
 
f5 : 0.26
 
f6 : 0.24
 
f7 : 0.26
 
c-scp : 0.49
 
l-scp : 0.49
 
main-c1 : 0.87
 
main-c2 : 0.87
 
main-c3 : 0.69
 
main-l-blue : 0.34
 
main-l-red : 0.63
 
main-lc-fill : 0.59
 
main-r-blue : 0.65
 
main-r-red : 0.6
 
r-scp : 0.34
 
storefill : 0.87
flax/subs/2-2_l_table
Show inline comments
 
new file 100644
 
blue1 : 0.63
 
blue2 : 0.63
 
blue3 : 0.63
 
blue4 : 0.63
 
downfill-left : 0.57
 
l-scp : 0.6
 
main-l-red : 1.0
 
main-lc-fill : 0.84
flax/subs/2-2_r_table
Show inline comments
 
new file 100644
 
43 : 0.8
 
blue1 : 0.63
 
blue2 : 0.63
 
blue3 : 0.63
 
blue4 : 0.63
 
downfill-right : 0.46
 
main-r-blue : 0.71
 
main-r-red : 0.48
 
r-scp : 0.63
 
upstairs above : 0.3
 
upstairs front : 0.8
flax/subs/2-3_brighter
Show inline comments
 
new file 100644
 
44 : 0.49
 
blue1 : 0.47
 
blue2 : 0.47
 
blue3 : 0.47
 
blue4 : 0.47
 
dayback : 0.28
 
diag : 0.68
 
diag-back : 0.5
 
downfill-left : 0.23
 
downfill-right : 0.4
 
f1 : 0.35
 
f1.5 : 0.51
 
f2 : 0.5
 
f3 : 0.45
 
f4 : 0.63
 
f5 : 0.52
 
judge : 1.0
 
main-l-red : 0.5
 
main-lc-fill : 0.35
 
storefill : 0.49
flax/subs/2-3_cold
Show inline comments
 
new file 100644
 
44 : 0.49
 
blue1 : 0.47
 
blue2 : 0.47
 
blue3 : 0.47
 
blue4 : 0.47
 
dayback : 0.28
 
diag : 0.46
 
diag-back : 0.5
 
downfill-left : 0.23
 
downfill-right : 0.4
 
f1 : 0.34
 
f1.5 : 0.4
 
f2 : 0.41
 
f3 : 0.38
 
f4 : 0.44
 
f5 : 0.37
 
judge : 0.74
 
main-l-red : 0.36
 
main-lc-fill : 0.12
 
storefill : 0.49
flax/subs/2-4_money
Show inline comments
 
new file 100644
 
f6 : 0.38
 
f7 : 0.78
 
ramp9-stairs : 1
flax/subs/allstage
Show inline comments
 
new file 100644
 
blue1 : 0.89
 
blue2 : 0.89
 
blue3 : 0.89
 
diag : 1.0
 
diag-back : 1.0
 
downfill-left : 1.0
 
downfill-right : 1.0
 
f1.5 : 1.0
 
f2 : 1.0
 
f3 : 1.0
 
f4 : 1.0
 
f5 : 1.0
 
f6 : 1.0
 
main-c1 : 1.0
 
main-c2 : 1.0
 
main-l-blue : 1.0
 
main-lc-fill : 1.0
 
main-r-red : 1.0
 
ramp9-stairs : 1.0
flax/subs/bottom_of_stairs
Show inline comments
 
new file 100644
 
judge : 0.765
 
main-c2 : 0.885
 
storefill : 0.78
flax/subs/candles
Show inline comments
 
new file 100644
 
42 : 0.3
flax/subs/candles-bright
Show inline comments
 
new file 100644
 
42 : .6
flax/subs/curtain_warmers
Show inline comments
 
new file 100644
 
downfill-left : 0.64
 
downfill-right : 0.565
 
f1 : 0.27
 
f1.5 : 0.865
 
f2 : 0.86
 
f3 : 1
 
f4 : 0.805
 
f5 : 0.725
 
f6 : 0.425
 
f7 : 0.28
 
main-c1 : 0.6
 
main-r-red : 0.35
flax/subs/dolly_runway_left
Show inline comments
 
new file 100644
 
ramp1 : 1
flax/subs/dolly_runway_right
Show inline comments
 
new file 100644
 
ramp8 : 1
flax/subs/dolly_stairs_right
Show inline comments
 
new file 100644
 
f7 : 0.27
 
ramp9-stairs : 1
flax/subs/finale
Show inline comments
 
new file 100644
 
43 : 0.12
 
44 : 0.28
 
blue1 : 0.85
 
blue2 : 0.85
 
blue3 : 0.85
 
blue4 : 0.85
 
diag : 0.61
 
diag-back : 0.68
 
f1 : 0.65
 
f1.5 : 0.74
 
f2 : 0.8
 
f3 : 0.7
 
f4 : 0.77
 
f5 : 0.44
 
f6 : 0.68
 
f7 : 0.73
 
gree1 : 0.85
 
gree2 : 0.85
 
gree3 : 0.85
 
gree4 : 0.85
 
judge : 1.0
 
c-scp : 0.5
 
l-scp : 1.0
 
main-c1 : 0.93
 
main-c2 : 0.89
 
main-l-blue : 0.27
 
main-l-red : 0.83
 
main-lc-fill : 0.92
 
main-r-blue : 0.94
 
main-r-red : 0.79
 
r-scp : 0.6
 
red1 : 0.91
 
red2 : 0.91
 
red3 : 0.91
 
red4 : 0.91
 
storefill : 0.28
 
upstairs above : 0.13
 
upstairs front : 0.12
flax/subs/finale_with_dolly
Show inline comments
 
new file 100644
 
44 : 0.81
 
blue1 : 1.0
 
blue2 : 1.0
 
blue3 : 1.0
 
blue4 : 1.0
 
diag : 1.0
 
diag-back : 1.0
 
downfill-right : 0.27
 
f1 : 1.0
 
f1.5 : 0.93
 
f2 : 1.0
 
f3 : 1.0
 
f4 : 1.0
 
f5 : 0.83
 
f6 : 1.0
 
f7 : 1.0
 
gree1 : 1.0
 
gree2 : 1.0
 
gree3 : 1.0
 
gree4 : 1.0
 
judge : 1.0
 
c-scp : 1.0
 
l-scp : 1.0
 
main-c1 : 1.0
 
main-c2 : 1.0
 
main-l-blue : 0.51
 
main-l-red : 1.0
 
main-lc-fill : 1.0
 
main-r-blue : 1.0
 
main-r-red : 1.0
 
r-scp : 0.81
 
red1 : 1.0
 
red2 : 1.0
 
red3 : 1.0
 
red4 : 1.0
 
storefill : 0.81
flax/subs/front_c
Show inline comments
 
new file 100644
 
diag : 0.325
 
f3 : 1
 
f4 : 1
 
main-c1 : 0.495
 
main-c3 : 0.57
flax/subs/front_lc
Show inline comments
 
new file 100644
 
f1 : 0.235
 
f1.5 : 1
 
f2 : 0.745
 
f3 : 1
flax/subs/front_of_stage
Show inline comments
 
new file 100644
 
f1 : 0.62
 
f2 : 0.86
 
f3 : 0.83
 
f4 : 0.79
 
f5 : 0.79
 
f6 : 0.87
 
f7 : 0.54
flax/subs/front_rc
Show inline comments
 
new file 100644
 
f4 : 0.825
 
f5 : 1
 
f6 : 1
flax/subs/frontwhite
Show inline comments
 
new file 100644
 
2 : 1.0
 
diag : 1.0
 
diag-back : 1.0
 
f1 : 1.0
 
f1.5 : 1.0
 
f2 : 1.0
 
f3 : 1.0
 
f4 : 1.0
 
f5 : 1.0
 
f6 : 1.0
 
f7 : 0.5
flax/subs/frontwhite-dolly
Show inline comments
 
new file 100644
 
diag : 1.0
 
diag-back : 1.0
 
f1.5 : 1.0
 
f2 : 1.0
 
f3 : 1.0
 
f4 : 1.0
 
f5 : 1.0
 
f6 : 1.0
flax/subs/garden_stairs
Show inline comments
 
new file 100644
 
c-scp : 1
 
judge : 1
 
l-scp : 0.725
 
r-scp : 0.645
flax/subs/god
Show inline comments
 
new file 100644
 
god : 1
flax/subs/house
Show inline comments
 
new file 100644
 
house : 1.0
flax/subs/nsi_cue_1
Show inline comments
 
new file 100644
 
2 : 1.0
 
diag : 1.0
 
diag-back : 1.0
 
f1.5 : 1.0
 
f2 : 1.0
 
f3 : 1.0
 
f4 : 1.0
 
f5 : 1.0
 
f6 : 1.0
 
main-c2 : 0.32
flax/subs/nsi_cue_2
Show inline comments
 
new file 100644
 
2 : 1.0
 
blue1 : 0.24
 
blue2 : 0.24
 
blue3 : 0.24
 
blue4 : 0.24
 
diag : 1.0
 
diag-back : 1.0
 
downfill-left : 0.27
 
downfill-right : 0.27
 
f1.5 : 1.0
 
f2 : 1.0
 
f3 : 1.0
 
f4 : 1.0
 
f5 : 1.0
 
f6 : 1.0
 
judge : 1.0
 
c-scp : 0.42
 
l-scp : 0.42
 
main-c1 : 0.27
 
main-c2 : 1.0
 
main-l-blue : 0.27
 
main-lc-fill : 0.27
 
main-r-red : 0.27
 
r-scp : 0.43
 
ramp9-stairs : 0.27
flax/subs/nsi_cue_3
Show inline comments
 
new file 100644
 
2 : 1.0
 
43 : 0.59
 
diag : 1.0
 
diag-back : 1.0
 
f1.5 : 1.0
 
f2 : 1.0
 
f3 : 1.0
 
f4 : 1.0
 
f5 : 1.0
 
f6 : 1.0
 
l-scp : 0.55
 
upstairs above : 0.39
 
upstairs front : 0.59
flax/subs/parade
Show inline comments
 
new file 100644
 
2 : 0.79
 
blue1 : 0.7
 
blue2 : 0.7
 
blue3 : 0.7
 
blue4 : 0.7
 
diag : 0.79
 
diag-back : 0.79
 
downfill-left : 0.79
 
downfill-right : 0.79
 
f1.5 : 0.79
 
f2 : 0.79
 
f3 : 0.79
 
f4 : 0.79
 
f5 : 0.79
 
f6 : 0.79
 
c-scp : 0.7
 
l-scp : 0.7
 
main-c1 : 0.79
 
main-c2 : 0.79
 
main-l-blue : 0.79
 
main-lc-fill : 0.79
 
main-r-red : 0.79
 
r-scp : 0.67
 
ramp0-stairs : 1.0
 
ramp1 : 1.0
 
ramp2 : 1.0
 
ramp3 : 1.0
 
ramp4 : 1.0
 
ramp5 : 1.0
 
ramp6 : 1.0
 
ramp7 : 1.0
 
ramp8 : 1.0
 
ramp9-stairs : 0.79
 
side l : 0.45
 
side r : 0.45
flax/subs/patio
Show inline comments
 
new file 100644
 
side r : 1
flax/subs/runway
Show inline comments
 
new file 100644
 
ramp0-stairs : 1.0
 
ramp1 : 1.0
 
ramp2 : 1.0
 
ramp3 : 1.0
 
ramp4 : 1.0
 
ramp5 : 1.0
 
ramp6 : 1.0
 
ramp7 : 1.0
 
ramp8 : 1.0
flax/subs/runway_stairs
Show inline comments
 
new file 100644
 
main-l-blue : 0.23
 
ramp0-stairs : 1.0
 
ramp1 : 1.0
 
ramp2 : 1.0
 
ramp3 : 1.0
 
ramp4 : 1.0
 
ramp5 : 1.0
 
ramp6 : 1.0
 
ramp7 : 1.0
 
ramp8 : 1.0
 
ramp9-stairs : 1.0
flax/subs/runway_stairs_left
Show inline comments
 
new file 100644
 
ramp0-stairs : 1
flax/subs/sky
Show inline comments
 
new file 100644
 
c-scp : 1
 
l-scp : 1
 
r-scp : 1
flax/subs/sky-outside_store
Show inline comments
 
new file 100644
 
c-scp : 1.0
 
l-scp : 1.0
 
r-scp : 0.78
flax/subs/trap
Show inline comments
 
new file 100644
 
ramp7 : 0.57
 
ramp8 : 0.43
 
ramp9-stairs : 0.84
flax/subs/upstairs
Show inline comments
 
new file 100644
 
43 : 0.64
 
ramp7 : 0.12
 
ramp9-stairs : 0.12
 
upstairs above : 0.72
 
upstairs front : 0.64
flax/subs/worklights
Show inline comments
 
new file 100644
 
blue1 : 0.3
 
blue2 : 0.3
 
blue3 : 0.3
 
blue4 : 0.3
flax/subs/zip_blue
Show inline comments
 
new file 100644
 
blue1 : 1.0
 
blue2 : 1.0
 
blue3 : 1.0
 
blue4 : 1.0
flax/subs/zip_green
Show inline comments
 
new file 100644
 
gree1 : 0.2
 
gree2 : 0.3
 
gree3 : 0.9
 
gree4 : 0.8
flax/subs/zip_orange
Show inline comments
 
new file 100644
 
oran1 : 1.0
 
oran2 : 1.0
 
oran3 : 1.0
 
oran4 : 1.0
flax/subs/zip_red
Show inline comments
 
new file 100644
 
red1 : 1.0
 
red2 : 1.0
 
red3 : 1.0
 
red4 : 1.0
flax/textcues_to_cuelist
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 

	
 
import sys
 
from CueFaders import CueList, Cue
 

	
 
cuelist = CueList('cues/dolly')
 

	
 
for line in sys.stdin.readlines():
 
    line = line.strip()
 
    if not line:
 
        continue
 
    page, name = line.split(' ', 1)
 
    print page, '|', name
 
    newcue = Cue(name)
 
    newcue.page = page
 
    cuelist.add_cue(newcue)
flax/timelines/song02
Show inline comments
 
new file 100644
 
song02:
 
-3 0
 
0 1
 
161.3 1
 
166.3 0
 

	
 
frontwhite:
 
150 0
 
152 1
 
161.3 1
 
166.3 0
flax/timelines/song03
Show inline comments
 
new file 100644
 
song03:
 
-2 0
 
0 1
 
162.9 1
 
167.9 0
 

	
 
frontwhite:
 
153 0
 
155 1
 
162.9 1
 
167.9 0
 

	
flax/timelines/song06
Show inline comments
 
new file 100644
 
blacklight:
 
0 0
 
0.01 1
 
145 1
 
145.01 0
 

	
 
frontwhite:
 
8 0
 
9.5 0.45
 
113 0.45
 
114 0
 
127 0
 
130 0.45
 
140 0.45
 
144 1
 
155 1
 
160 0
flax/timelines/song07
Show inline comments
 
new file 100644
 
frontwhite:
 
0 0
 
2 .38
 
180 .38
 
184 .1
 
331 .1
 
335 0.6
 
361.5 0.6
 
362 0
 
379 0
 
380 0.5
 
461 0.5
 
464 1
 
472 1
 
477 0
 

	
 
upfill:
 
0 0
 
2 .53
 
180 .53
 
184 .2
 
361 .2
 
362 0
 

	
 
blacklight:
 
361.99 0
 
362 1
 
464 1
 
464.01 0
 

	
 
blue: 
 
0 0
 
180 0
 
184 0.9
 
461 0.9
 
464 0
 

	
 
orange: 
 
0 0
 
180 0
 
184 0.45
 
361 0.45
 
362 0
flax/timelines/song08
Show inline comments
 
new file 100644
 
frontwhite:
 
196 0
 
199 1
 
210 1
 
215 0
flax/timelines/song10
Show inline comments
 
new file 100644
 
song10:
 
-1 0
 
0 1
 
183 1
 
188 0
 

	
 
frontwhite:
 
170 0
 
180 1
 
183 1
 
188 0
flax/timelines/song11
Show inline comments
 
new file 100644
 
song11:
 
-1 0
 
2  1
 
157 1
 
162 0
 

	
 
frontwhite:
 
141 0
 
146 1
 
157 1
 
162 0
flax/timelines/song12
Show inline comments
 
new file 100644
 
song12:
 
-3 0
 
0 1
 
132.5 1
 
137.5 0
 

	
 
frontwhite:
 
123 0
 
125 1
 
132.5 1
 
137.5 0
flax/timelines/song13
Show inline comments
 
new file 100644
 
song13:
 
-3 0
 
0 1
 
170.3 1
 
175.3 0
 

	
 
frontwhite:
 
161 0
 
163 1
 
170.3 1
 
175.3 0
flax/timelines/song14
Show inline comments
 
new file 100644
 
song14:
 
0 0 
 
5 1
 
159.8 1
 
164.8 0
 

	
 
frontwhite:
 
150 0
 
152 .57
 
159.8 .57
 
164.8 0
flax/timelines/song15
Show inline comments
 
new file 100644
 
song15:
 
0 0
 
1 1
 
155.5 1
 
160.5 0
 

	
 
frontwhite:
 
144 0
 
146 1
 
155.5 1
 
160.5 0
 

	
 
blacklight: 65 0   65.01 1   95 1   95.01 0
 
song15:     65 1   65.01 0   87 0   95    1
flax/timelines/song16
Show inline comments
 
new file 100644
 
song16:
 
-3 0
 
1 1
 
138.6 1
 
143.6 0
flax/timelines/song17
Show inline comments
 
new file 100644
 
song17:
 
0 0
 
2 1
 
120 1
 
123 0.08
 
144 0.08
 
147 1
 
252 1
 
257 0
 

	
 
frontwhite:
 
239 0
 
243 1
 
252 1
 
257 0
 

	
 
blacklight:
 
119.99 0
 
120 1
 
147 1
 
147.01 0
flax/timelines/song18
Show inline comments
 
new file 100644
flax/tracker
Show inline comments
 
new file 100644
 
#!/usr/bin/python
 
from __future__ import division,nested_scopes
 

	
 
import sys
 
sys.path.append("../../editor/pour")
 
sys.path.append("../light8")
 

	
 
from Submaster import Submaster
 
from skim.zooming import Zooming,Pair
 
from math import sqrt,sin,cos
 
from pygame.rect import Rect
 
from xmlnodebase import xmlnodeclass,collectiveelement,xmldocfile
 
from dispatch import dispatcher
 

	
 
import dmxclient
 

	
 
import Tkinter as tk
 

	
 
defaultfont="arial 8"
 

	
 
def pairdist(pair1,pair2):
 
    return pair1.dist(pair2)
 

	
 
def canvashighlighter(canvas,obj,attribute,normalval,highlightval):
 
    """creates bindings on a canvas obj that make attribute go
 
    from normal to highlight when the mouse is over the obj"""
 
    canvas.tag_bind(obj,"<Enter>",
 
                    lambda ev: canvas.itemconfig(obj,**{attribute:highlightval}))
 
    canvas.tag_bind(obj,"<Leave>",
 
                    lambda ev: canvas.itemconfig(obj,**{attribute:normalval}))
 

	
 
class Field(xmlnodeclass):
 
    
 
    """one light has a field of influence. for any point on the
 
    canvas, you can ask this field how strong it is. """
 

	
 
    def name(self,newval=None):
 
        """light/sub name"""
 
        return self._getorsetattr("name",newval)
 
    def center(self,x=None,y=None):
 
        """x,y float coords for the center of this light in the field. returns
 
        a Pair, although it accepts x,y"""
 
        return Pair(self._getorsettypedattr("x",float,x),
 
                    self._getorsettypedattr("y",float,y))
 

	
 
    def falloff(self,dist=None):
 
        
 
        """linear falloff from 1 at center, to 0 at dist pixels away
 
        from center"""
 
        return self._getorsettypedattr("falloff",float,dist)
 

	
 
    def getdistforintensity(self,intens):
 
        """returns the distance you'd have to be for the given intensity (0..1)"""
 
        return (1-intens)*self.falloff()
 

	
 
    def calc(self,x,y):
 
        """returns field strength at point x,y"""
 
        dist=pairdist(Pair(x,y),self.center())
 
        return max(0,(self.falloff()-dist)/self.falloff())
 

	
 
class Fieldset(collectiveelement):
 
    """group of fields. persistent."""
 
    def childtype(self): return Field
 

	
 
    def version(self):
 
        """read-only version attribute on fieldset tag"""
 
        return self._getorsetattr("version",None)
 

	
 
    def report(self,x,y):
 
        """reports active fields and their intensities"""
 
        active=0
 
        for f in self.getall():
 
            name=f.name()
 
            intens=f.calc(x,y)
 
            if intens>0:
 
                print name,intens,
 
                active+=1
 
        if active>0:
 
            print
 
        self.dmxsend(x,y)
 
    def dmxsend(self,x,y):
 
        """output lights to dmx"""
 
        levels=dict([(f.name(),f.calc(x,y)) for f in self.getall()])
 
        dmxlist=Submaster(None,levels).get_dmx_list()
 
        dmxclient.outputlevels(dmxlist)
 
        
 
        
 
    def getbounds(self):
 
        """returns xmin,xmax,ymin,ymax for the non-zero areas of this field"""
 
        r=None
 
        for f in self.getall():
 
            rad=f.getdistforintensity(0)
 
            fx,fy=f.center()
 
            fieldrect=Rect(fx-rad,fy-rad,rad*2,rad*2)
 
            if r is None:
 
                r=fieldrect
 
            else:
 
                r=r.union(fieldrect)
 
        return r.left,r.right,r.top,r.bottom
 

	
 
class Fieldsetfile(xmldocfile):
 
    def __init__(self,filename):
 
        self._openornew(filename,topleveltype=Fieldset)
 
    def fieldset(self):
 
        return self._gettoplevel()
 

	
 
########################################################################
 
########################################################################
 

	
 
class FieldDisplay:
 
    """the view for a Field."""
 
    def __init__(self,canvas,field):
 
        self.canvas=canvas
 
        self.field=field
 
        self.tags=[str(id(self))] # canvas tag to id our objects
 
        
 
    def setcoords(self):
 
        """adjust canvas obj coords to match the field"""
 
        # this uses the canvas object ids saved by makeobjs
 
        f=self.field
 
        c=self.canvas
 
        w2c=self.canvas.world2canvas
 

	
 
        # rings
 
        for intens,ring in self.rings.items():
 
            rad=f.getdistforintensity(intens)
 
            p1=w2c(*(f.center()-Pair(rad,rad)))
 
            p2=w2c(*(f.center()+Pair(rad,rad)))
 
            c.coords(ring,p1[0],p1[1],p2[0],p2[1])
 

	
 
        # text
 
        p1=w2c(*f.center())
 
        c.coords(self.txt,*p1)
 

	
 
    def makeobjs(self):
 
        """(re)create the canvas objs (null coords) and make their bindings"""
 
        c=self.canvas
 
        f=self.field
 
        c.delete(self.tags)
 

	
 
        w2c=self.canvas.world2canvas
 

	
 
        # make rings
 
        self.rings={} # rad,canvasobj
 
        for intens,color in (#(1,'white'),
 
                             (.8,'gray90'),(.6,'gray80'),(.4,'gray60'),(.2,'gray50'),
 
                             (0,'#000080')):
 
            self.rings[intens]=c.create_oval(0,0,0,0,
 
                                          outline=color,width=2,tags=self.tags,
 
                                          outlinestipple='gray50')
 

	
 
        # make text
 
        self.txt=c.create_text(0,0,text=f.name(),font=defaultfont+" bold",
 
                               fill='white',anchor='c',
 
                               tags=self.tags)
 

	
 
        # highlight text bindings
 
        canvashighlighter(c,self.txt,'fill',normalval='white',highlightval='red')
 

	
 
        # position drag bindings
 
        def press(ev):
 
            self._lastmouse=ev.x,ev.y
 
        def motion(ev):
 
            dcan=Pair(*[a-b for a,b in zip((ev.x,ev.y),self._lastmouse)])
 
            dworld=c.canvas2world_vector(*dcan)
 
            self.field.center(*(self.field.center()+dworld))
 
            self._lastmouse=ev.x,ev.y
 
            self.setcoords() # redraw
 
        def release(ev):
 
            if hasattr(self,'_lastmouse'):
 
                del self._lastmouse
 
            dispatcher.send("field coord changed") # updates bounds
 
            
 
        c.tag_bind(self.txt,"<ButtonPress-1>",press)
 
        c.tag_bind(self.txt,"<B1-Motion>",motion)
 
        c.tag_bind(self.txt,"<B1-ButtonRelease>",release)
 

	
 
        # radius drag bindings
 
        outerring=self.rings[0]
 
        canvashighlighter(c,outerring,
 
                          'outline',normalval='#000080',highlightval='#4040ff')
 
        def motion(ev):
 
            worldmouse=self.canvas.canvas2world(ev.x,ev.y)
 
            currentdist=pairdist(worldmouse,self.field.center())
 
            self.field.falloff(currentdist)
 
            self.setcoords()
 
        c.tag_bind(outerring,"<B1-Motion>",motion)
 
        c.tag_bind(outerring,"<B1-ButtonRelease>",release) # from above
 

	
 
        self.setcoords()
 
    
 
class Tracker(tk.Frame):
 

	
 
    """whole tracker widget, which is mostly a view for a
 
    Fieldset. tracker makes its own fieldset"""
 

	
 
    # world coords of the visible canvas (preserved even in window resizes)
 
    xmin=0
 
    xmax=100
 
    ymin=0
 
    ymax=100
 

	
 
    fieldsetfile=None
 
    displays=None # Field : FieldDisplay. we keep these in sync with the fieldset
 
    
 
    def __init__(self,master):
 
        tk.Frame.__init__(self,master)
 

	
 
        self.displays={}
 
        
 
        c=self.canvas=Zooming(self,bg='black',closeenough=5)
 
        c.pack(fill='both',exp=1)
 

	
 
        # preserve edge coords over window resize
 
        c.bind("<Configure>",self.configcoords)
 
        
 
        c.bind("<Motion>",
 
               lambda ev: self._fieldset().report(*c.canvas2world(ev.x,ev.y)))
 
        def save(ev):
 
            print "saving"
 
            self.fieldsetfile.save()
 
        master.bind("<Key-s>",save)
 
        dispatcher.connect(self.autobounds,"field coord changed")
 

	
 
    def _fieldset(self):
 
        return self.fieldsetfile.fieldset()
 

	
 
    def load(self,filename):
 
        self.fieldsetfile=Fieldsetfile(filename)
 
        self.displays.clear()
 
        for f in self.fieldsetfile.fieldset().getall():
 
            self.displays[f]=FieldDisplay(self.canvas,f)
 
            self.displays[f].makeobjs()
 
        self.autobounds()
 

	
 
    def configcoords(self,*args):
 
        # force our canvas coords to stay at the edges of the window
 
        c=self.canvas
 
        cornerx,cornery=c.canvas2world(0,0)
 
        c.move(cornerx-self.xmin,
 
               cornery-self.ymin)
 
        c.setscale(0,0,
 
                   c.winfo_width()/(self.xmax-self.xmin),
 
                   c.winfo_height()/(self.ymax-self.ymin))
 

	
 
    def autobounds(self):
 
        """figure out our bounds from the fieldset, and adjust the display zooms.
 
        writes the corner coords onto the canvas."""
 
        self.xmin,self.xmax,self.ymin,self.ymax=self._fieldset().getbounds()
 

	
 
        self.configcoords()
 
        
 
        c=self.canvas
 
        c.delete('cornercoords')
 
        for x,anc2 in ((self.xmin,'w'),(self.xmax,'e')):
 
            for y,anc1 in ((self.ymin,'n'),(self.ymax,'s')):
 
                pos=c.world2canvas(x,y)
 
                c.create_text(pos[0],pos[1],text="%s,%s"%(x,y),
 
                              fill='white',anchor=anc1+anc2,
 
                              tags='cornercoords')
 
        [d.setcoords() for d in self.displays.values()]
 

	
 
########################################################################
 
########################################################################
 
                
 
root=tk.Tk()
 
root.wm_geometry('700x350')
 
tra=Tracker(root)
 
tra.pack(fill='both',exp=1)
 

	
 
tra.load("fieldsets/demo")
 

	
 
root.mainloop()
flax/zoomcontrol.py
Show inline comments
 
new file 100644
 
from __future__ import division
 
import Tkinter as tk
 
from dispatch import dispatcher
 

	
 
class Zoomcontrol(object,tk.Canvas):
 

	
 
    def maxtime():
 
        doc = "seconds at the right edge of the bar"
 
        def fget(self): return self._maxtime
 
        def fset(self, value):
 
            self._maxtime = value
 
            self.updatewidget()
 
        return locals()
 
    maxtime = property(**maxtime())
 
    
 
    def start():
 
        def fget(self): return self._start
 
        def fset(self,v): self._start = max(0,v)
 
        return locals()
 
    start = property(**start())
 

	
 
    def end():
 
        def fget(self): return self._end
 
        def fset(self,v): self._end = min(self.maxtime,v)
 
        return locals()
 
    end = property(**end())
 
        
 

	
 
    def __init__(self,master,**kw):
 
        self.maxtime=370
 
        self.start=0
 
        self.end=20
 
        tk.Canvas.__init__(self,master,width=250,height=30,
 
                           relief='raised',bd=1,bg='gray60',**kw)
 
        self.leftbrack = self.create_line(0,0,0,0,0,0,0,0,width=5)
 
        self.rightbrack = self.create_line(0,0,0,0,0,0,0,0,width=5)
 
        self.shade = self.create_rectangle(0,0,0,0,fill='gray70',outline=None)
 
        self.time = self.create_line(0,0,0,0,fill='red',width=2)
 
        self.updatewidget()
 
        self.bind("<Configure>",self.updatewidget)
 

	
 
        self.bind("<ButtonPress-1>",lambda ev: setattr(self,'lastx',ev.x))
 
        self.tag_bind(self.leftbrack,"<B1-Motion>",
 
                      lambda ev: self.adjust('start',ev))
 
        self.tag_bind(self.rightbrack,"<B1-Motion>",
 
                      lambda ev: self.adjust('end',ev))
 
        self.tag_bind(self.shade,"<B1-Motion>",
 
                      lambda ev: self.adjust('offset',ev))
 
        dispatcher.connect(lambda: (self.start,self.end),"zoom area",weak=0)
 
        dispatcher.connect(self.input_time,"input time")
 
        dispatcher.connect(lambda maxtime: (setattr(self,'maxtime',maxtime),
 
                                            self.updatewidget()),"max time",weak=0)
 
        self.created=1
 
    def input_time(self,val):
 
        t=val
 
        x=self.can_for_t(t)
 
        self.coords(self.time,x,0,x,self.winfo_height())
 

	
 
    def adjust(self,attr,ev):
 
        if not hasattr(self,'lastx'):
 
            return
 
        new = self.can_for_t(getattr(self,attr)) + (ev.x - self.lastx)
 
        self.lastx = ev.x
 
        setattr(self,attr,self.t_for_can(new))
 
        self.updatewidget()
 
        dispatcher.send("zoom changed")
 
        
 
    def offset():
 
        doc = "virtual attr that adjusts start and end together"
 
        def fget(self):
 
            return self.start
 
        def fset(self, value):
 
            d = self.end-self.start
 
            self.start = value
 
            self.end = self.start+d
 
        return locals()
 
    offset = property(**offset())
 

	
 
    def can_for_t(self,t):
 
        return t/self.maxtime*(self.winfo_width()-30)+20
 
    def t_for_can(self,x):
 
        return (x-20)/(self.winfo_width()-30)*self.maxtime
 

	
 
    def updatewidget(self,*args):
 
        """redraw pieces based on start/end"""
 
        if not hasattr(self,'created'): return
 
        y1,y2=3,self.winfo_height()-3
 
        lip = 6
 
        scan = self.can_for_t(self.start)
 
        ecan = self.can_for_t(self.end)
 
        self.coords(self.leftbrack,scan+lip,y1,scan,y1,scan,y2,scan+lip,y2)
 
        self.coords(self.rightbrack,ecan-lip,y1,ecan,y1,ecan,y2,ecan-lip,y2)
 
        self.coords(self.shade,scan+3,y1+lip,ecan-3,y2-lip)
 
        self.delete("tics")
 
        lastx=-1000
 
        for t in range(0,int(self.maxtime)):
 
            x = self.can_for_t(t)
 
            if 0<x<self.winfo_width() and x-lastx>30:
 
                txt=str(t)
 
                if lastx==-1000:
 
                    txt=txt+"sec"
 
                self.create_line(x,0,x,15,
 
                                 tags=('tics',))
 
                self.create_text(x,self.winfo_height()-1,anchor='s',
 
                                 text=txt,tags=('tics',),font='6x13')
 
                lastx = x

Changeset was too big and was cut off... Show full diff anyway

0 comments (0 inline, 0 general)