Changeset - 2ed9bfd1dd0e
[Not reviewed]
default
0 2 0
dmcc - 22 years ago 2003-06-13 15:06:14

I totally wrecked Timeline so that it can run the show. (I hope it can
I totally wrecked Timeline so that it can run the show. (I hope it can
at least do that.) Sleepy...
2 files changed with 24 insertions and 227 deletions:
0 comments (0 inline, 0 general)
flax/KeyboardComposer.py
Show inline comments
 
@@ -60,7 +60,7 @@ class KeyboardComposer(Frame):
 

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

	
flax/Timeline.py
Show inline comments
 
@@ -8,79 +8,8 @@ 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.
 

	
 
Timeline idea
 
=============
 
 time    | 0   1   2   3   4   5   6
 
---------+-----------------------------
 
frame    | F       F           F
 
blenders |  \  b /  \- b ----/
 

	
 
Update: this is more or less what happened.  However, there are 
 
TimelineTracks as well.  FunctionFrames go on their own track.
 
LevelFrames must have unique times, FunctionFrames do not.
 

	
 
Level propagation
 
=================
 
Cue1 is a CueNode.  CueNodes consist of a timeline with any number
 
of LevelFrame nodes and LinearBlenders connecting all the frames.
 
At time 0, Cue1's timeline has LevelFrame1.  At time 5, the timeline
 
has NodeFrame1.  NodeFrame1 points to Node1, which has it's own sets
 
of levels.  It could be a Cue, for all Cue1 cares.  No matter what,
 
we always get down to LevelFrames holding the levels at the bottom,
 
then getting combined by Blenders.
 

	
 
             /------\
 
             | Cue1 |                                       
 
             |---------------\
 
             |  Timeline:    |                                     
 
             |  0   ... 5    |                                     
 
     /--------- LF1     NF1 -------\                               
 
     |       |   \      /    |     |                          
 
     |       | LinearBlender |     |                                 
 
     |       \---------------/     |                        
 
     |                          points to                  
 
   /---------------\            a port in Cue1,
 
   | blueleft : 20 |            which connects to a Node
 
   | redwash  : 12 |                                                       
 
   |   .           |                                        
 
   |   :           |    
 
   |               |
 
   \---------------/
 
                                                            
 
PS. blueleft and redwash are other nodes at the bottom of the tree.
 
The include their real channel number for the DimmerNode to process.
 

	
 
When Cue1 requests levels, the timeline looks at the current position.
 
If it is directly over a Frame (or Frames), that frame is handled.
 
If it is LevelFrame, those are the levels that it returns.  If there is
 
a FunctionFrame, that function is activated.  Thus, the order of Frames
 
at a specific time is very significant, since the FunctionFrame could
 
set the time to 5s in the future.  If we are not currently over any 
 
LevelFrames, we call upon a Blender to determine the value between.
 
Say that we are at 2.3s.  We use the LinearBlender with 2.3/5.0s = 0.46%
 
and it determines that the levels are 1 - 0.46% = 0.54% of LF1 and
 
0.46% of NF1.  NF1 asks Node9 for its levels and this process starts
 
all over.
 

	
 
Graph theory issues (node related issues, should be moved elsewhere)
 
====================================================================
 
1.  We need to determine dependencies for updating (topological order).
 
2.  We need to do cyclicity tests.
 

	
 
Guess who wishes they had brought their theory book home?
 
I think we can do both with augmented DFS.  An incremental version of both
 
would be very nice, though hopefully unnecessary.
 

	
 
"""
 

	
 
class InvalidFrameOperation(Exception):
 
    """You get these when you try to perform some operation on a frame
 
    that doesn't make sense.  The interface is advised to tell the user,
 
    and indicate that a Blender or FunctionFramea should be disconnected
 
    or fixed."""
 
    pass
 

	
 
class MissingBlender(Exception):
 
    """Raised when a TimedEvent is missing a blender."""
 
    def __init__(self, timedevent):
 
@@ -93,105 +22,13 @@ class MissingBlender(Exception):
 
FORWARD = 1
 
BACKWARD = -1
 

	
 
class Frame:
 
    """Frame is an event that happens at a specific time.  There are two
 
    types of frames: LevelFrames and FunctionFrames.  LevelFrames provide
 
    levels via their get_levels() function.  FunctionFrames alter the 
 
    timeline (e.g. bouncing, looping, speed changes, etc.).  They call
 
    __call__'ed instead."""
 
    def __init__(self, name):
 
        self.name = name
 
        self.timeline = DummyClass(use_warnings=0, raise_exceptions=0)
 
    def set_timeline(self, timeline):
 
        """Tell the Frame who the controlling Timeline is"""
 
        self.timeline = timeline
 
    def __mul__(self, percent):
 
        """Generate a new Frame by multiplying the 'effect' of this frame by
 
        a percent."""
 
        raise InvalidFrameOperation, "Can't use multiply this Frame"
 
    def __add__(self, otherframe):
 
        """Combines this frame with another frame, generating a new one."""
 
        raise InvalidFrameOperation, "Can't use add on this Frame"
 

	
 
class LevelFrame(Frame):
 
    """LevelFrames provide levels.  They can also be combined with other
 
    LevelFrames."""
 
    def __init__(self, name, levels):
 
        Frame.__init__(self, name)
 
        self.levels = levels
 
    def __mul__(self, percent):
 
        """Returns a new LevelFrame made by multiplying all levels by a 
 
        percentage.  Percent is a float greater than 0.0"""
 
        newlevels = dict_scale(self.get_levels(), percent)
 
        return LevelFrame('(%s * %f)' % (self.name, percent), newlevels)
 
    def __add__(self, otherframe):
 
        """Combines this LevelFrame with another LevelFrame, generating a new 
 
        one.  Values are max() together."""
 
        theselevels, otherlevels = self.get_levels(), otherframe.get_levels()
 
        return LevelFrame('(%s + %s)' % (self.name, otherframe.name),
 
                          dict_max(theselevels, otherlevels))
 
    def get_levels(self):
 
        """This function returns the levels held by this frame."""
 
        return self.levels
 
    def __repr__(self):
 
        return "<%s %r %r>" % (str(self.__class__), self.name, self.levels)
 

	
 
class EmptyFrame(LevelFrame):
 
    """An empty LevelFrame, for the purposes of extending the timeline."""
 
    def __init__(self, name='Empty Frame'):
 
        EmptyFrame.__init__(self, name, {})
 

	
 
class NodeFrame(LevelFrame):
 
    """A LevelFrame that gets its levels from another Node.  This must be
 
    used from a Timeline that is enclosed in TimelineNode.  Node is a string
 
    describing the node requested."""
 
    def __init__(self, name, node):
 
        LevelFrame.__init__(self, name, {})
 
        self.node = node
 
    def get_levels(self):
 
        """Ask the node that we point to for its levels"""
 
        node = self.timeline.get_node(self.node)
 
        self.levels = node.get_levels()
 
        return self.levels
 

	
 
class FunctionFrame(Frame):
 
    def __init__(self, name):
 
        Frame.__init__(self, name)
 
    def __call__(self, timeline, timedevent, node):
 
        """Called when the FunctionFrame is activated.  It is given a pointer
 
        to it's master timeline, the TimedEvent containing it, and Node that
 
        the timeline is contained in, if available."""
 
        pass
 

	
 
# this is kinda broken
 
class BounceFunction(FunctionFrame):
 
    def __call__(self, timeline, timedevent, node):
 
        """Reverses the direction of play."""
 
        timeline.reverse_direction()
 
        print "boing! new dir:", timeline.direction
 

	
 
# this too
 
class LoopFunction(FunctionFrame):
 
    def __call__(self, timeline, timedevent, node):
 
        timeline.set_time(0)
 
        # print 'looped!'
 

	
 
class DoubleTimeFunction(FunctionFrame):
 
    def __call__(self, timeline, timedevent, node):
 
        timeline.set_rate(2 * timeline.rate)
 
        print 'doubled!', timeline.rate
 

	
 
class HalfTimeFunction(FunctionFrame):
 
    def __call__(self, timeline, timedevent, node):
 
        timeline.set_rate(0.5 * timeline.rate)
 
        print 'halved!', timeline.rate
 

	
 
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, blender=None):
 
    def __init__(self, time, frame, 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):
 
@@ -200,12 +37,10 @@ class TimedEvent:
 
        else:
 
            return cmp(self.time, other.time)
 
    def __repr__(self):
 
        return "<TimedEvent %s at %.2f, next blender=%s>" % \
 
            (self.frame, self.time, self.next_blender)
 
    def get_levels(self):
 
        """Return the Frame's levels.  Hopefully frame is a LevelFrame or
 
        descendent."""
 
        return self.frame.get_levels()
 
        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
 

	
 
class Blender:
 
    """Blenders are functions that merge the effects of two LevelFrames."""
 
@@ -237,7 +72,7 @@ class Blender:
 
        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."""
 
        return (startframe * (1.0 - blendtime)) + (endframe * blendtime)
 
        return {startframe : (1.0 - blendtime), endframe : blendtime}
 

	
 
class InstantEnd(Blender):
 
    """Instant change from startframe to endframe at the end.  In other words,
 
@@ -248,7 +83,7 @@ class InstantEnd(Blender):
 
        # 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
 
        return {startframe : 1.0}
 

	
 
class InstantStart(Blender):
 
    """Instant change from startframe to endframe at the beginning.  In other
 
@@ -259,13 +94,12 @@ class InstantStart(Blender):
 
        # 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
 
        return {endframe : 1.0}
 

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

	
 
class ExponentialBlender(Blender):
 
    """Exponential fade fron one frame to another.  You get to specify
 
@@ -290,7 +124,7 @@ 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."""
 
    EmptyTimedEvent (which doesn't exist :-/)."""
 
    def __init__(self, name, *timedevents):
 
        self.name = name
 
        self.events = list(timedevents)
 
@@ -352,7 +186,7 @@ class TimelineTrack:
 
        before, after = self.get_surrounding_frames(time)
 
        
 
        if before == after:
 
            return before.frame
 
            return {before.frame : 1.0}
 
        else: # we have a blended value
 
            diff = after.time - before.time
 
            elapsed = time - before.time
 
@@ -362,9 +196,9 @@ class TimelineTrack:
 
            return before.next_blender(before.frame, after.frame, percent)
 

	
 
class Timeline:
 
    def __init__(self, tracks, functions, rate=1, direction=FORWARD):
 
    def __init__(self, tracks, rate=1, direction=FORWARD):
 
        """
 
        Most of this is old:
 
        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
 
@@ -377,9 +211,6 @@ class Timeline:
 
        some time if you want to extend a Timeline."""
 
        
 
        make_attributes_from_args('tracks', 'rate', 'direction')
 
        # the function track is a special track
 
        self.fn_track = TimelineTrack('functions', *functions)
 
        
 
        self.current_time = 0
 
        self.last_clock_time = None
 
        self.stopped = 1
 
@@ -387,7 +218,6 @@ class Timeline:
 
        """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]
 
        track_lengths.append(self.fn_track.length())
 
        return max(track_lengths)
 
    def play(self):
 
        """Activates the timeline.  Future calls to tick() will advance the 
 
@@ -422,21 +252,6 @@ class Timeline:
 
        self.last_clock_time = clock_time
 
        self.current_time = new_time
 
        
 
        # now, find out if we missed any functions
 
        if self.fn_track.has_events():
 
            lower_time, higher_time = last_time, new_time
 
            if lower_time == higher_time: print "zarg!"
 
            if lower_time > higher_time:
 
                lower_time, higher_time = higher_time, lower_time
 
            
 
            events_to_process = self.fn_track.get_range(lower_time, 
 
                higher_time, self.direction)
 
            
 
            for event in events_to_process:
 
                # they better be FunctionFrames
 
                event.frame(self, event, None) # the None should be a Node, 
 
                                               # but that part is coming later
 
        
 
        # 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)
 
@@ -456,17 +271,11 @@ class Timeline:
 
    def get_levels(self):
 
        """Return the current levels from this timeline.  This is done by
 
        adding all the non-functional tracks together."""
 
        current_level_frame = LevelFrame('timeline sum', {})
 
        for t in self.tracks:
 
            current_level_frame += t.get_levels_at_time(self.current_time)
 

	
 
        return current_level_frame.get_levels()
 
        levels = [t.get_levels_at_time(self.current_time)
 
                    for t in self.tracks]
 
        return dict_max(*levels)
 

	
 
if __name__ == '__main__':
 
    scene1 = LevelFrame('scene1', {'red' : 50, 'blue' : 25})
 
    scene2 = LevelFrame('scene2', {'red' : 10, 'blue' : 5, 'green' : 70})
 
    scene3 = LevelFrame('scene3', {'yellow' : 10, 'blue' : 80, 'purple' : 70})
 

	
 
    T = TimedEvent
 

	
 
    linear = LinearBlender()
 
@@ -475,25 +284,13 @@ if __name__ == '__main__':
 
    smoove = SmoothBlender()
 

	
 
    track1 = TimelineTrack('lights',
 
        T(0, scene1, blender=linear),
 
        T(5, scene2, blender=quad),
 
        T(10, scene3, blender=smoove),
 
        T(15, scene2)) # last TimedEvent doesn't need a blender
 
        T(0, 'red', blender=linear),
 
        T(5, 'blue', blender=quad),
 
        T(10, 'red', blender=smoove),
 
        T(15, 'blue')) # last TimedEvent doesn't need a blender
 

	
 
    halver = HalfTimeFunction('1/2x')
 
    doubler = DoubleTimeFunction('2x')
 
    if 0:
 
        # bounce is semiworking
 
        bouncer = BounceFunction('boing')
 
        tl = Timeline([track1], [T(0, bouncer), 
 
                                 T(0, halver),
 
                                 T(15, bouncer),
 
                                 T(15, doubler)])
 
    else:
 
        looper = LoopFunction('loop1')
 
        tl = Timeline([track1], [T(0, doubler),
 
                                 T(5, halver),
 
                                 T(14, looper)])
 
    tl = Timeline([track1])
 

	
 
    tl.play()
 

	
 
    import Tix
 
@@ -508,7 +305,7 @@ if __name__ == '__main__':
 
    for color in colors:
 
        sv = Tix.DoubleVar()
 
        scalevars[color] = sv
 
        scale = Tix.Scale(colorscalesframe, from_=100, to_=0, bg=color,
 
        scale = Tix.Scale(colorscalesframe, from_=1, to_=0, res=0.01, bg=color,
 
            variable=sv)
 
        scale.pack(side=Tix.LEFT)
 

	
0 comments (0 inline, 0 general)