annotate flax/Timeline.py @ 109:ec82e1eea3c8

- initial checkin: run Timeline.py for a good time
author dmcc
date Sun, 25 May 2003 16:25:35 +0000
parents 45b12307c695
children 490843093506
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
1 from TLUtility import make_attributes_from_args, dict_scale, dict_max, \
45b12307c695 Initial revision
drewp
parents:
diff changeset
2 DummyClass, last_less_than, first_greater_than
45b12307c695 Initial revision
drewp
parents:
diff changeset
3 from time import time
45b12307c695 Initial revision
drewp
parents:
diff changeset
4 from __future__ import division # "I'm sending you back to future!"
45b12307c695 Initial revision
drewp
parents:
diff changeset
5
45b12307c695 Initial revision
drewp
parents:
diff changeset
6 """
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
7 Changelog:
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
8 Fri May 16 15:17:34 PDT 2003
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
9 Project started (roughly).
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
10
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
11 Mon May 19 17:56:24 PDT 2003
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
12 Timeline is more or less working. Still bugs with skipping
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
13 FunctionFrames at random times.
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
14
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
15 Timeline idea
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
16 =============
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
17 time | 0 1 2 3 4 5 6
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
18 ---------+-----------------------------
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
19 frame | F F F
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
20 blenders | \ b / \- b ----/
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
21
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
22 Update: this is more or less what happened. However, there are
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
23 TimelineTracks as well. FunctionFrames go on their own track.
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
24 LevelFrames must have unique times, FunctionFrames do not.
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
25
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
26 Level propagation
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
27 =================
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
28 Cue1 is a CueNode. CueNodes consist of a timeline with any number
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
29 of LevelFrame nodes and LinearBlenders connecting all the frames.
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
30 At time 0, Cue1's timeline has LevelFrame1. At time 5, the timeline
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
31 has NodeFrame1. NodeFrame1 points to Node1, which has it's own sets
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
32 of levels. It could be a Cue, for all Cue1 cares. No matter what,
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
33 we always get down to LevelFrames holding the levels at the bottom,
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
34 then getting combined by Blenders.
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
35
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
36 /------\
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
37 | Cue1 |
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
38 |---------------\
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
39 | Timeline: |
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
40 | 0 ... 5 |
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
41 /--------- LF1 NF1 -------\
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
42 | | \ / | |
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
43 | | LinearBlender | |
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
44 | \---------------/ |
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
45 | points to
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
46 /---------------\ a port in Cue1,
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
47 | blueleft : 20 | which connects to a Node
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
48 | redwash : 12 |
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
49 | . |
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
50 | : |
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
51 | |
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
52 \---------------/
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
53
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
54 PS. blueleft and redwash are other nodes at the bottom of the tree.
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
55 The include their real channel number for the DimmerNode to process.
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
56
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
57 When Cue1 requests levels, the timeline looks at the current position.
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
58 If it is directly over a Frame (or Frames), that frame is handled.
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
59 If it is LevelFrame, those are the levels that it returns. If there is
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
60 a FunctionFrame, that function is activated. Thus, the order of Frames
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
61 at a specific time is very significant, since the FunctionFrame could
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
62 set the time to 5s in the future. If we are not currently over any
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
63 LevelFrames, we call upon a Blender to determine the value between.
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
64 Say that we are at 2.3s. We use the LinearBlender with 2.3/5.0s = 0.46%
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
65 and it determines that the levels are 1 - 0.46% = 0.54% of LF1 and
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
66 0.46% of NF1. NF1 asks Node9 for its levels and this process starts
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
67 all over.
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
68
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
69 Graph theory issues (node related issues, should be moved elsewhere)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
70 ====================================================================
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
71 1. We need to determine dependencies for updating (topological order).
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
72 2. We need to do cyclicity tests.
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
73
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
74 Guess who wishes they had brought their theory book home?
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
75 I think we can do both with augmented DFS. An incremental version of both
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
76 would be very nice, though hopefully unnecessary.
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
77
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
78 """
45b12307c695 Initial revision
drewp
parents:
diff changeset
79
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
80 class InvalidFrameOperation(Exception):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
81 """You get these when you try to perform some operation on a frame
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
82 that doesn't make sense. The interface is advised to tell the user,
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
83 and indicate that a Blender or FunctionFramea should be disconnected
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
84 or fixed."""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
85 pass
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
86
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
87 class MissingBlender(Exception):
45b12307c695 Initial revision
drewp
parents:
diff changeset
88 """Raised when a TimedEvent is missing a blender."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
89 def __init__(self, timedevent):
45b12307c695 Initial revision
drewp
parents:
diff changeset
90 make_attributes_from_args('timedevent')
45b12307c695 Initial revision
drewp
parents:
diff changeset
91 Exception.__init__(self, "%r is missing a blender." % \
45b12307c695 Initial revision
drewp
parents:
diff changeset
92 self.timedevent)
45b12307c695 Initial revision
drewp
parents:
diff changeset
93
45b12307c695 Initial revision
drewp
parents:
diff changeset
94 # these are chosen so we can multiply by -1 to reverse the direction,
45b12307c695 Initial revision
drewp
parents:
diff changeset
95 # and multiply direction by the time difference to determine new times.
45b12307c695 Initial revision
drewp
parents:
diff changeset
96 FORWARD = 1
45b12307c695 Initial revision
drewp
parents:
diff changeset
97 BACKWARD = -1
45b12307c695 Initial revision
drewp
parents:
diff changeset
98
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
99 class Frame:
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
100 """Frame is an event that happens at a specific time. There are two
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
101 types of frames: LevelFrames and FunctionFrames. LevelFrames provide
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
102 levels via their get_levels() function. FunctionFrames alter the
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
103 timeline (e.g. bouncing, looping, speed changes, etc.). They call
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
104 __call__'ed instead."""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
105 def __init__(self, name):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
106 self.name = name
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
107 self.timeline = DummyClass(use_warnings=0, raise_exceptions=0)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
108 def set_timeline(self, timeline):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
109 """Tell the Frame who the controlling Timeline is"""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
110 self.timeline = timeline
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
111 def __mul__(self, percent):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
112 """Generate a new Frame by multiplying the 'effect' of this frame by
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
113 a percent."""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
114 raise InvalidFrameOperation, "Can't use multiply this Frame"
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
115 def __add__(self, otherframe):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
116 """Combines this frame with another frame, generating a new one."""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
117 raise InvalidFrameOperation, "Can't use add on this Frame"
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
118
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
119 class LevelFrame(Frame):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
120 """LevelFrames provide levels. They can also be combined with other
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
121 LevelFrames."""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
122 def __init__(self, name, levels):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
123 Frame.__init__(self, name)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
124 self.levels = levels
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
125 def __mul__(self, percent):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
126 """Returns a new LevelFrame made by multiplying all levels by a
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
127 percentage. Percent is a float greater than 0.0"""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
128 newlevels = dict_scale(self.get_levels(), percent)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
129 return LevelFrame('(%s * %f)' % (self.name, percent), newlevels)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
130 def __add__(self, otherframe):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
131 """Combines this LevelFrame with another LevelFrame, generating a new
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
132 one. Values are max() together."""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
133 theselevels, otherlevels = self.get_levels(), otherframe.get_levels()
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
134 return LevelFrame('(%s + %s)' % (self.name, otherframe.name),
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
135 dict_max(theselevels, otherlevels))
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
136 def get_levels(self):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
137 """This function returns the levels held by this frame."""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
138 return self.levels
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
139 def __repr__(self):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
140 return "<%s %r %r>" % (str(self.__class__), self.name, self.levels)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
141
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
142 class EmptyFrame(LevelFrame):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
143 """An empty LevelFrame, for the purposes of extending the timeline."""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
144 def __init__(self, name='Empty Frame'):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
145 EmptyFrame.__init__(self, name, {})
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
146
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
147 class NodeFrame(LevelFrame):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
148 """A LevelFrame that gets its levels from another Node. This must be
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
149 used from a Timeline that is enclosed in TimelineNode. Node is a string
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
150 describing the node requested."""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
151 def __init__(self, name, node):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
152 LevelFrame.__init__(self, name, {})
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
153 self.node = node
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
154 def get_levels(self):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
155 """Ask the node that we point to for its levels"""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
156 node = self.timeline.get_node(self.node)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
157 self.levels = node.get_levels()
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
158 return self.levels
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
159
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
160 class FunctionFrame(Frame):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
161 def __init__(self, name):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
162 Frame.__init__(self, name)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
163 def __call__(self, timeline, timedevent, node):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
164 """Called when the FunctionFrame is activated. It is given a pointer
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
165 to it's master timeline, the TimedEvent containing it, and Node that
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
166 the timeline is contained in, if available."""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
167 pass
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
168
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
169 # this is kinda broken
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
170 class BounceFunction(FunctionFrame):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
171 def __call__(self, timeline, timedevent, node):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
172 """Reverses the direction of play."""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
173 timeline.reverse_direction()
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
174 print "boing! new dir:", timeline.direction
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
175
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
176 # this too
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
177 class LoopFunction(FunctionFrame):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
178 def __call__(self, timeline, timedevent, node):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
179 timeline.set_time(0)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
180 print 'looped!'
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
181
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
182 class DoubleTimeFunction(FunctionFrame):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
183 def __call__(self, timeline, timedevent, node):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
184 timeline.set_rate(2 * timeline.rate)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
185
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
186 class HalfTimeFunction(FunctionFrame):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
187 def __call__(self, timeline, timedevent, node):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
188 timeline.set_rate(0.5 * timeline.rate)
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
189
45b12307c695 Initial revision
drewp
parents:
diff changeset
190 class TimedEvent:
45b12307c695 Initial revision
drewp
parents:
diff changeset
191 """Container for a Frame which includes a time that it occurs at,
45b12307c695 Initial revision
drewp
parents:
diff changeset
192 and which blender occurs after it."""
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
193 def __init__(self, time, frame, blender=None):
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
194 make_attributes_from_args('time', 'frame')
45b12307c695 Initial revision
drewp
parents:
diff changeset
195 self.next_blender = blender
45b12307c695 Initial revision
drewp
parents:
diff changeset
196 def __float__(self):
45b12307c695 Initial revision
drewp
parents:
diff changeset
197 return self.time
45b12307c695 Initial revision
drewp
parents:
diff changeset
198 def __cmp__(self, other):
45b12307c695 Initial revision
drewp
parents:
diff changeset
199 if type(other) in (float, int):
45b12307c695 Initial revision
drewp
parents:
diff changeset
200 return cmp(self.time, other)
45b12307c695 Initial revision
drewp
parents:
diff changeset
201 else:
45b12307c695 Initial revision
drewp
parents:
diff changeset
202 return cmp(self.time, other.time)
45b12307c695 Initial revision
drewp
parents:
diff changeset
203 def __repr__(self):
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
204 return "<TimedEvent %s at %.2f, next blender=%s>" % \
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
205 (self.frame, self.time, self.next_blender)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
206 def get_levels(self):
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
207 """Return the Frame's levels. Hopefully frame is a LevelFrame or
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
208 descendent."""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
209 return self.frame.get_levels()
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
210
45b12307c695 Initial revision
drewp
parents:
diff changeset
211 class Blender:
45b12307c695 Initial revision
drewp
parents:
diff changeset
212 """Blenders are functions that merge the effects of two LevelFrames."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
213 def __init__(self):
45b12307c695 Initial revision
drewp
parents:
diff changeset
214 pass
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
215 def __call__(self, startframe, endframe, blendtime):
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
216 """Return a LevelFrame combining two LevelFrames (startframe and
45b12307c695 Initial revision
drewp
parents:
diff changeset
217 endframe). blendtime is how much of the blend should be performed
45b12307c695 Initial revision
drewp
parents:
diff changeset
218 and will be expressed as a percentage divided by 100, i.e. a float
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
219 between 0.0 and 1.0.
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
220
45b12307c695 Initial revision
drewp
parents:
diff changeset
221 Very important note: Blenders will *not* be asked for values
45b12307c695 Initial revision
drewp
parents:
diff changeset
222 at end points (i.e. blendtime=0.0 and blendtime=1.0).
45b12307c695 Initial revision
drewp
parents:
diff changeset
223 The LevelFrames will be allowed to specify the values at
45b12307c695 Initial revision
drewp
parents:
diff changeset
224 those times. This is unfortunately for implemementation and
45b12307c695 Initial revision
drewp
parents:
diff changeset
225 simplicity purposes. In other words, if we didn't do this,
45b12307c695 Initial revision
drewp
parents:
diff changeset
226 we could have two blenders covering the same point in time and
45b12307c695 Initial revision
drewp
parents:
diff changeset
227 not know which one to ask for the value. Thus, this saves us
45b12307c695 Initial revision
drewp
parents:
diff changeset
228 a lot of messiness with minimal or no sacrifice."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
229 pass
45b12307c695 Initial revision
drewp
parents:
diff changeset
230 def __str__(self):
45b12307c695 Initial revision
drewp
parents:
diff changeset
231 """As a default, we'll just return the name of the class. Subclasses
45b12307c695 Initial revision
drewp
parents:
diff changeset
232 can add parameters on if they want."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
233 return str(self.__class__)
45b12307c695 Initial revision
drewp
parents:
diff changeset
234 def linear_blend(self, startframe, endframe, blendtime):
45b12307c695 Initial revision
drewp
parents:
diff changeset
235 """Utility function to help you produce linear combinations of two
45b12307c695 Initial revision
drewp
parents:
diff changeset
236 blends. blendtime is the percent/100 that the blend should
45b12307c695 Initial revision
drewp
parents:
diff changeset
237 completed. In other words, 0.25 means it should be 0.75 * startframe +
45b12307c695 Initial revision
drewp
parents:
diff changeset
238 0.25 * endframe. This function is included since many blenders are
45b12307c695 Initial revision
drewp
parents:
diff changeset
239 just functions on the percentage and still combine start and end frames
45b12307c695 Initial revision
drewp
parents:
diff changeset
240 in this way."""
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
241 return (startframe * (1.0 - blendtime)) + (endframe * blendtime)
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
242
45b12307c695 Initial revision
drewp
parents:
diff changeset
243 class InstantEnd(Blender):
45b12307c695 Initial revision
drewp
parents:
diff changeset
244 """Instant change from startframe to endframe at the end. In other words,
45b12307c695 Initial revision
drewp
parents:
diff changeset
245 the value returned will be the startframe all the way until the very end
45b12307c695 Initial revision
drewp
parents:
diff changeset
246 of the blend."""
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
247 def __call__(self, startframe, endframe, blendtime):
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
248 # "What!?" you say, "Why don't you care about blendtime?"
45b12307c695 Initial revision
drewp
parents:
diff changeset
249 # This is because Blenders never be asked for blenders at the endpoints
45b12307c695 Initial revision
drewp
parents:
diff changeset
250 # (after all, they wouldn't be blenders if they were). Please see
45b12307c695 Initial revision
drewp
parents:
diff changeset
251 # 'Very important note' in Blender.__doc__
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
252 return startframe
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
253
45b12307c695 Initial revision
drewp
parents:
diff changeset
254 class InstantStart(Blender):
45b12307c695 Initial revision
drewp
parents:
diff changeset
255 """Instant change from startframe to endframe at the beginning. In other
45b12307c695 Initial revision
drewp
parents:
diff changeset
256 words, the value returned will be the startframe at the very beginning
45b12307c695 Initial revision
drewp
parents:
diff changeset
257 and then be endframe at all times afterwards."""
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
258 def __call__(self, startframe, endframe, blendtime):
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
259 # "What!?" you say, "Why don't you care about blendtime?"
45b12307c695 Initial revision
drewp
parents:
diff changeset
260 # This is because Blenders never be asked for blenders at the endpoints
45b12307c695 Initial revision
drewp
parents:
diff changeset
261 # (after all, they wouldn't be blenders if they were). Please see
45b12307c695 Initial revision
drewp
parents:
diff changeset
262 # 'Very important note' in Blender.__doc__
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
263 return endframe
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
264
45b12307c695 Initial revision
drewp
parents:
diff changeset
265 class LinearBlender(Blender):
45b12307c695 Initial revision
drewp
parents:
diff changeset
266 """Linear fade from one frame to another"""
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
267 def __call__(self, startframe, endframe, blendtime):
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
268 return self.linear_blend(startframe, endframe, blendtime)
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
269 # return (startframe * (1.0 - blendtime)) + (endframe * blendtime)
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
270
45b12307c695 Initial revision
drewp
parents:
diff changeset
271 class ExponentialBlender(Blender):
45b12307c695 Initial revision
drewp
parents:
diff changeset
272 """Exponential fade fron one frame to another. You get to specify
45b12307c695 Initial revision
drewp
parents:
diff changeset
273 the exponent. If my math is correct, exponent=1 means the same thing
45b12307c695 Initial revision
drewp
parents:
diff changeset
274 as LinearBlender."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
275 def __init__(self, exponent):
45b12307c695 Initial revision
drewp
parents:
diff changeset
276 self.exponent = exponent
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
277 def __call__(self, startframe, endframe, blendtime):
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
278 blendtime = blendtime ** self.exponent
45b12307c695 Initial revision
drewp
parents:
diff changeset
279 return self.linear_blend(startframe, endframe, blendtime)
45b12307c695 Initial revision
drewp
parents:
diff changeset
280
45b12307c695 Initial revision
drewp
parents:
diff changeset
281 # 17:02:53 drewp: this makes a big difference for the SmoothBlender
45b12307c695 Initial revision
drewp
parents:
diff changeset
282 # (-x*x*(x-1.5)*2) function
45b12307c695 Initial revision
drewp
parents:
diff changeset
283 class SmoothBlender(Blender):
45b12307c695 Initial revision
drewp
parents:
diff changeset
284 """Drew's "Smoove" Blender function. Hopefully he'll document and
45b12307c695 Initial revision
drewp
parents:
diff changeset
285 parametrize it."""
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
286 def __call__(self, startframe, endframe, blendtime):
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
287 blendtime = (-1 * blendtime) * blendtime * (blendtime - 1.5) * 2
45b12307c695 Initial revision
drewp
parents:
diff changeset
288 return self.linear_blend(startframe, endframe, blendtime)
45b12307c695 Initial revision
drewp
parents:
diff changeset
289
45b12307c695 Initial revision
drewp
parents:
diff changeset
290 class TimelineTrack:
45b12307c695 Initial revision
drewp
parents:
diff changeset
291 """TimelineTrack is a single track in a Timeline. It consists of a
45b12307c695 Initial revision
drewp
parents:
diff changeset
292 list of TimedEvents and a name. Length is automatically the location
45b12307c695 Initial revision
drewp
parents:
diff changeset
293 of the last TimedEvent. To extend the Timeline past that, add an
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
294 EmptyTimedEvent."""
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
295 def __init__(self, name, *timedevents):
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
296 self.name = name
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
297 self.events = list(timedevents)
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
298 self.events.sort()
45b12307c695 Initial revision
drewp
parents:
diff changeset
299 def __str__(self):
45b12307c695 Initial revision
drewp
parents:
diff changeset
300 return "<TimelineTrack with events: %r>" % self.events
45b12307c695 Initial revision
drewp
parents:
diff changeset
301 def has_events(self):
45b12307c695 Initial revision
drewp
parents:
diff changeset
302 """Whether the TimelineTrack has anything in it. In general,
45b12307c695 Initial revision
drewp
parents:
diff changeset
303 empty level Tracks should be avoided. However, empty function tracks
45b12307c695 Initial revision
drewp
parents:
diff changeset
304 might be common."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
305 return len(self.events)
45b12307c695 Initial revision
drewp
parents:
diff changeset
306 def length(self):
45b12307c695 Initial revision
drewp
parents:
diff changeset
307 """Returns the length of this track in pseudosecond time units.
45b12307c695 Initial revision
drewp
parents:
diff changeset
308 This is done by finding the position of the last TimedEvent."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
309 return float(self.events[-1])
45b12307c695 Initial revision
drewp
parents:
diff changeset
310 def get(self, key, direction=FORWARD):
45b12307c695 Initial revision
drewp
parents:
diff changeset
311 """Returns the event at a specific time key. If there is no event
45b12307c695 Initial revision
drewp
parents:
diff changeset
312 at that time, a search will be performed in direction. Also note
45b12307c695 Initial revision
drewp
parents:
diff changeset
313 that if there are multiple events at one time, only the first will
45b12307c695 Initial revision
drewp
parents:
diff changeset
314 be returned. (Probably first in order of adding.) This is not
45b12307c695 Initial revision
drewp
parents:
diff changeset
315 a problem at the present since this method is intended for LevelFrames,
45b12307c695 Initial revision
drewp
parents:
diff changeset
316 which must exist at unique times."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
317 if direction == BACKWARD:
45b12307c695 Initial revision
drewp
parents:
diff changeset
318 func = last_less_than
45b12307c695 Initial revision
drewp
parents:
diff changeset
319 else:
45b12307c695 Initial revision
drewp
parents:
diff changeset
320 func = first_greater_than
45b12307c695 Initial revision
drewp
parents:
diff changeset
321
45b12307c695 Initial revision
drewp
parents:
diff changeset
322 return func(self.events, key)
45b12307c695 Initial revision
drewp
parents:
diff changeset
323 def get_range(self, i, j, direction=FORWARD):
45b12307c695 Initial revision
drewp
parents:
diff changeset
324 """Returns all events between i and j, exclusively. If direction
45b12307c695 Initial revision
drewp
parents:
diff changeset
325 is FORWARD, j will be included. If direction is BACKWARD, i will
45b12307c695 Initial revision
drewp
parents:
diff changeset
326 be included. This is because this is used to find FunctionFrames
45b12307c695 Initial revision
drewp
parents:
diff changeset
327 and we assume that any function frames at the start point (which
45b12307c695 Initial revision
drewp
parents:
diff changeset
328 could be i or j) have been processed."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
329 if direction == FORWARD:
45b12307c695 Initial revision
drewp
parents:
diff changeset
330 return [e for e in self.events if e > i and e <= j]
45b12307c695 Initial revision
drewp
parents:
diff changeset
331 else:
45b12307c695 Initial revision
drewp
parents:
diff changeset
332 return [e for e in self.events if e >= i and e < j]
45b12307c695 Initial revision
drewp
parents:
diff changeset
333 def __getitem__(self, key):
45b12307c695 Initial revision
drewp
parents:
diff changeset
334 """Returns the event at or after a specific time key.
45b12307c695 Initial revision
drewp
parents:
diff changeset
335 For example: timeline[3] will get the first event at time 3.
45b12307c695 Initial revision
drewp
parents:
diff changeset
336
45b12307c695 Initial revision
drewp
parents:
diff changeset
337 If you want to get all events at time 3, you are in trouble, but
45b12307c695 Initial revision
drewp
parents:
diff changeset
338 you could achieve it with something like:
45b12307c695 Initial revision
drewp
parents:
diff changeset
339 timeline.get_range(2.99, 3.01, FORWARD)
45b12307c695 Initial revision
drewp
parents:
diff changeset
340 This is hopefully a bogus problem, since you can't have multiple
45b12307c695 Initial revision
drewp
parents:
diff changeset
341 LevelFrames at the same time."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
342 return self.get(key, direction=FORWARD)
45b12307c695 Initial revision
drewp
parents:
diff changeset
343 def get_surrounding_frames(self, time):
45b12307c695 Initial revision
drewp
parents:
diff changeset
344 """Returns frames before and after a specific time. This returns
45b12307c695 Initial revision
drewp
parents:
diff changeset
345 a 2-tuple: (previousframe, nextframe). If you have chosen the exact
45b12307c695 Initial revision
drewp
parents:
diff changeset
346 time of a frame, it will be both previousframe and nextframe."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
347 return self.get(time, direction=BACKWARD), \
45b12307c695 Initial revision
drewp
parents:
diff changeset
348 self.get(time, direction=FORWARD)
45b12307c695 Initial revision
drewp
parents:
diff changeset
349 def get_levels_at_time(self, time):
45b12307c695 Initial revision
drewp
parents:
diff changeset
350 """Returns a LevelFrame with the levels of this track at that time."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
351 before, after = self.get_surrounding_frames(time)
45b12307c695 Initial revision
drewp
parents:
diff changeset
352
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
353 if before == after:
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
354 return before.frame
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
355 else: # we have a blended value
45b12307c695 Initial revision
drewp
parents:
diff changeset
356 diff = after.time - before.time
45b12307c695 Initial revision
drewp
parents:
diff changeset
357 elapsed = time - before.time
45b12307c695 Initial revision
drewp
parents:
diff changeset
358 percent = elapsed / diff
45b12307c695 Initial revision
drewp
parents:
diff changeset
359 if not before.next_blender:
45b12307c695 Initial revision
drewp
parents:
diff changeset
360 raise MissingBlender, before
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
361 return before.next_blender(before.frame, after.frame, percent)
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
362
45b12307c695 Initial revision
drewp
parents:
diff changeset
363 class Timeline:
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
364 def __init__(self, tracks, functions, rate=1, direction=FORWARD):
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
365 """
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
366 Most of this is old:
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
367
45b12307c695 Initial revision
drewp
parents:
diff changeset
368 You can have multiple FunctionFrames at the same time. Their
45b12307c695 Initial revision
drewp
parents:
diff changeset
369 order is important though, since FunctionFrames will be applied
45b12307c695 Initial revision
drewp
parents:
diff changeset
370 in the order seen in this list. blenders is a list of Blenders.
45b12307c695 Initial revision
drewp
parents:
diff changeset
371 rate is the rate of playback. If set to 1, 1 unit inside the
45b12307c695 Initial revision
drewp
parents:
diff changeset
372 Timeline will be 1 second. direction is the initial direction.
45b12307c695 Initial revision
drewp
parents:
diff changeset
373 If you want to do have looping, place a LoopFunction at the end of
45b12307c695 Initial revision
drewp
parents:
diff changeset
374 the Timeline. Timelines don't have a set length. Their length
45b12307c695 Initial revision
drewp
parents:
diff changeset
375 is bounded by their last frame. You can put an EmptyFrame at
45b12307c695 Initial revision
drewp
parents:
diff changeset
376 some time if you want to extend a Timeline."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
377
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
378 make_attributes_from_args('tracks', 'rate', 'direction')
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
379 # the function track is a special track
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
380 self.fn_track = TimelineTrack('functions', *functions)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
381
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
382 self.current_time = 0
45b12307c695 Initial revision
drewp
parents:
diff changeset
383 self.last_clock_time = None
45b12307c695 Initial revision
drewp
parents:
diff changeset
384 self.stopped = 1
45b12307c695 Initial revision
drewp
parents:
diff changeset
385 def length(self):
45b12307c695 Initial revision
drewp
parents:
diff changeset
386 """Length of the timeline in pseudoseconds. This is determined by
45b12307c695 Initial revision
drewp
parents:
diff changeset
387 finding the length of the longest track."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
388 track_lengths = [track.length() for track in self.tracks]
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
389 track_lengths.append(self.fn_track.length())
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
390 return max(track_lengths)
45b12307c695 Initial revision
drewp
parents:
diff changeset
391 def play(self):
45b12307c695 Initial revision
drewp
parents:
diff changeset
392 """Activates the timeline. Future calls to tick() will advance the
45b12307c695 Initial revision
drewp
parents:
diff changeset
393 timeline in the appropriate direction."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
394 self.stopped = 0
45b12307c695 Initial revision
drewp
parents:
diff changeset
395 def stop(self):
45b12307c695 Initial revision
drewp
parents:
diff changeset
396 """The timeline will no longer continue in either direction, no
45b12307c695 Initial revision
drewp
parents:
diff changeset
397 FunctionFrames will be activated."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
398 self.stopped = 1
45b12307c695 Initial revision
drewp
parents:
diff changeset
399 self.last_clock_time = None
45b12307c695 Initial revision
drewp
parents:
diff changeset
400 def reset(self):
45b12307c695 Initial revision
drewp
parents:
diff changeset
401 """Resets the timeline to 0. Does not change the stoppedness of the
45b12307c695 Initial revision
drewp
parents:
diff changeset
402 timeline."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
403 self.current_time = 0
45b12307c695 Initial revision
drewp
parents:
diff changeset
404 def tick(self):
45b12307c695 Initial revision
drewp
parents:
diff changeset
405 """Updates the current_time and runs any FunctionFrames that the cursor
45b12307c695 Initial revision
drewp
parents:
diff changeset
406 passed over. This call is ignored if the timeline is stopped."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
407 if self.stopped:
45b12307c695 Initial revision
drewp
parents:
diff changeset
408 return
45b12307c695 Initial revision
drewp
parents:
diff changeset
409
45b12307c695 Initial revision
drewp
parents:
diff changeset
410 last_time = self.current_time
45b12307c695 Initial revision
drewp
parents:
diff changeset
411 last_clock = self.last_clock_time
45b12307c695 Initial revision
drewp
parents:
diff changeset
412
45b12307c695 Initial revision
drewp
parents:
diff changeset
413 # first, determine new time
45b12307c695 Initial revision
drewp
parents:
diff changeset
414 clock_time = time()
45b12307c695 Initial revision
drewp
parents:
diff changeset
415 if last_clock is None:
45b12307c695 Initial revision
drewp
parents:
diff changeset
416 last_clock = clock_time
45b12307c695 Initial revision
drewp
parents:
diff changeset
417 diff = clock_time - last_clock
45b12307c695 Initial revision
drewp
parents:
diff changeset
418 new_time = (self.direction * self.rate * diff) + last_time
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
419 new_time = max(new_time, 0)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
420 new_time = min(new_time, self.length())
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
421
45b12307c695 Initial revision
drewp
parents:
diff changeset
422 # update the time
45b12307c695 Initial revision
drewp
parents:
diff changeset
423 self.last_clock_time = clock_time
45b12307c695 Initial revision
drewp
parents:
diff changeset
424 self.current_time = new_time
45b12307c695 Initial revision
drewp
parents:
diff changeset
425
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
426 # now, find out if we missed any functions
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
427 if self.fn_track.has_events():
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
428 lower_time, higher_time = last_time, new_time
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
429 if lower_time > higher_time:
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
430 lower_time, higher_time = higher_time, lower_time
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
431
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
432 events_to_process = self.fn_track.get_range(lower_time,
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
433 higher_time, self.direction)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
434
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
435 for event in events_to_process:
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
436 # they better be FunctionFrames
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
437 event.frame(self, event, None) # the None should be a Node,
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
438 # but that part is coming later
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
439 def reverse_direction(self):
45b12307c695 Initial revision
drewp
parents:
diff changeset
440 """Reverses the direction of play for this node"""
45b12307c695 Initial revision
drewp
parents:
diff changeset
441 self.direction = self.direction * -1
45b12307c695 Initial revision
drewp
parents:
diff changeset
442 def set_direction(self, direction):
45b12307c695 Initial revision
drewp
parents:
diff changeset
443 """Sets the direction of playback."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
444 self.direction = direction
45b12307c695 Initial revision
drewp
parents:
diff changeset
445 def set_rate(self, new_rate):
45b12307c695 Initial revision
drewp
parents:
diff changeset
446 """Sets the rate of playback"""
45b12307c695 Initial revision
drewp
parents:
diff changeset
447 self.rate = new_rate
45b12307c695 Initial revision
drewp
parents:
diff changeset
448 def set_time(self, new_time):
45b12307c695 Initial revision
drewp
parents:
diff changeset
449 """Set the time to a new time."""
45b12307c695 Initial revision
drewp
parents:
diff changeset
450 self.current_time = new_time
45b12307c695 Initial revision
drewp
parents:
diff changeset
451 def get_levels(self):
45b12307c695 Initial revision
drewp
parents:
diff changeset
452 """Return the current levels from this timeline. This is done by
45b12307c695 Initial revision
drewp
parents:
diff changeset
453 adding all the non-functional tracks together."""
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
454 current_level_frame = LevelFrame('timeline sum', {})
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
455 for t in self.tracks:
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
456 current_level_frame += t.get_levels_at_time(self.current_time)
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
457
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
458 return current_level_frame.get_levels()
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
459
45b12307c695 Initial revision
drewp
parents:
diff changeset
460 if __name__ == '__main__':
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
461 scene1 = LevelFrame('scene1', {'red' : 50, 'blue' : 25})
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
462 scene2 = LevelFrame('scene2', {'red' : 10, 'blue' : 5, 'green' : 70})
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
463 scene3 = LevelFrame('scene3', {'yellow' : 10, 'blue' : 80, 'purple' : 70})
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
464
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
465 T = TimedEvent
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
466
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
467 linear = LinearBlender()
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
468 quad = ExponentialBlender(2)
45b12307c695 Initial revision
drewp
parents:
diff changeset
469 invquad = ExponentialBlender(0.5)
45b12307c695 Initial revision
drewp
parents:
diff changeset
470 smoove = SmoothBlender()
45b12307c695 Initial revision
drewp
parents:
diff changeset
471
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
472 track1 = TimelineTrack('lights',
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
473 T(0, scene1, blender=linear),
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
474 T(5, scene2, blender=quad),
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
475 T(10, scene3, blender=smoove),
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
476 T(15, scene2)) # last TimedEvent doesn't need a blender
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
477
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
478 if 1:
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
479 # bounce is semiworking
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
480 bouncer = BounceFunction('boing')
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
481 halver = HalfTimeFunction('1/2x')
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
482 doubler = DoubleTimeFunction('2x')
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
483 tl = Timeline([track1], [T(0, bouncer),
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
484 T(0, halver),
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
485 T(15, bouncer),
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
486 T(15, doubler)])
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
487 else:
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
488 looper = LoopFunction('loop1')
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
489 tl = Timeline([track1], [T(14, looper)])
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
490 tl.play()
45b12307c695 Initial revision
drewp
parents:
diff changeset
491
45b12307c695 Initial revision
drewp
parents:
diff changeset
492 import Tix
45b12307c695 Initial revision
drewp
parents:
diff changeset
493 root = Tix.Tk()
45b12307c695 Initial revision
drewp
parents:
diff changeset
494 colorscalesframe = Tix.Frame(root)
45b12307c695 Initial revision
drewp
parents:
diff changeset
495 scalevars = {}
45b12307c695 Initial revision
drewp
parents:
diff changeset
496 # wow, this works out so well, it's almost like I planned it!
45b12307c695 Initial revision
drewp
parents:
diff changeset
497 # (actually, it's probably just Tk being as cool as it usually is)
45b12307c695 Initial revision
drewp
parents:
diff changeset
498 # ps. if this code ever turns into mainstream code for flax, I'll be
45b12307c695 Initial revision
drewp
parents:
diff changeset
499 # pissed (reason: we need to use classes, not this hacked crap!)
45b12307c695 Initial revision
drewp
parents:
diff changeset
500 colors = 'red', 'blue', 'green', 'yellow', 'purple'
45b12307c695 Initial revision
drewp
parents:
diff changeset
501 for color in colors:
45b12307c695 Initial revision
drewp
parents:
diff changeset
502 sv = Tix.DoubleVar()
45b12307c695 Initial revision
drewp
parents:
diff changeset
503 scalevars[color] = sv
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
504 scale = Tix.Scale(colorscalesframe, from_=100, to_=0, bg=color,
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
505 variable=sv)
45b12307c695 Initial revision
drewp
parents:
diff changeset
506 scale.pack(side=Tix.LEFT)
45b12307c695 Initial revision
drewp
parents:
diff changeset
507
45b12307c695 Initial revision
drewp
parents:
diff changeset
508 def set_timeline_time(time):
45b12307c695 Initial revision
drewp
parents:
diff changeset
509 tl.set_time(float(time))
45b12307c695 Initial revision
drewp
parents:
diff changeset
510 # print 'set_timeline_time', time
45b12307c695 Initial revision
drewp
parents:
diff changeset
511
45b12307c695 Initial revision
drewp
parents:
diff changeset
512 def update_scales():
45b12307c695 Initial revision
drewp
parents:
diff changeset
513 levels = tl.get_levels()
45b12307c695 Initial revision
drewp
parents:
diff changeset
514 for color in colors:
45b12307c695 Initial revision
drewp
parents:
diff changeset
515 scalevars[color].set(levels.get(color, 0))
45b12307c695 Initial revision
drewp
parents:
diff changeset
516
45b12307c695 Initial revision
drewp
parents:
diff changeset
517 colorscalesframe.pack()
109
ec82e1eea3c8 - initial checkin: run Timeline.py for a good time
dmcc
parents: 0
diff changeset
518 time_scale = Tix.Scale(root, from_=0, to_=track1.length(),
0
45b12307c695 Initial revision
drewp
parents:
diff changeset
519 orient=Tix.HORIZONTAL, res=0.01, command=set_timeline_time)
45b12307c695 Initial revision
drewp
parents:
diff changeset
520 time_scale.pack(side=Tix.BOTTOM, fill=Tix.X, expand=1)
45b12307c695 Initial revision
drewp
parents:
diff changeset
521
45b12307c695 Initial revision
drewp
parents:
diff changeset
522 def play_tl():
45b12307c695 Initial revision
drewp
parents:
diff changeset
523 tl.tick()
45b12307c695 Initial revision
drewp
parents:
diff changeset
524 update_scales()
45b12307c695 Initial revision
drewp
parents:
diff changeset
525 time_scale.set(tl.current_time)
45b12307c695 Initial revision
drewp
parents:
diff changeset
526 # print 'time_scale.set', tl.current_time
45b12307c695 Initial revision
drewp
parents:
diff changeset
527 root.after(10, play_tl)
45b12307c695 Initial revision
drewp
parents:
diff changeset
528
45b12307c695 Initial revision
drewp
parents:
diff changeset
529 controlwindow = Tix.Toplevel()
45b12307c695 Initial revision
drewp
parents:
diff changeset
530 Tix.Button(controlwindow, text='Stop',
45b12307c695 Initial revision
drewp
parents:
diff changeset
531 command=lambda: tl.stop()).pack(side=Tix.LEFT)
45b12307c695 Initial revision
drewp
parents:
diff changeset
532 Tix.Button(controlwindow, text='Play',
45b12307c695 Initial revision
drewp
parents:
diff changeset
533 command=lambda: tl.play()).pack(side=Tix.LEFT)
45b12307c695 Initial revision
drewp
parents:
diff changeset
534 Tix.Button(controlwindow, text='Reset',
45b12307c695 Initial revision
drewp
parents:
diff changeset
535 command=lambda: time_scale.set(0)).pack(side=Tix.LEFT)
45b12307c695 Initial revision
drewp
parents:
diff changeset
536 Tix.Button(controlwindow, text='Flip directions',
45b12307c695 Initial revision
drewp
parents:
diff changeset
537 command=lambda: tl.reverse_direction()).pack(side=Tix.LEFT)
45b12307c695 Initial revision
drewp
parents:
diff changeset
538 Tix.Button(controlwindow, text='1/2x',
45b12307c695 Initial revision
drewp
parents:
diff changeset
539 command=lambda: tl.set_rate(0.5 * tl.rate)).pack(side=Tix.LEFT)
45b12307c695 Initial revision
drewp
parents:
diff changeset
540 Tix.Button(controlwindow, text='2x',
45b12307c695 Initial revision
drewp
parents:
diff changeset
541 command=lambda: tl.set_rate(2 * tl.rate)).pack(side=Tix.LEFT)
45b12307c695 Initial revision
drewp
parents:
diff changeset
542
45b12307c695 Initial revision
drewp
parents:
diff changeset
543 root.after(100, play_tl)
45b12307c695 Initial revision
drewp
parents:
diff changeset
544
45b12307c695 Initial revision
drewp
parents:
diff changeset
545 # Timeline.set_time = trace(Timeline.set_time)
45b12307c695 Initial revision
drewp
parents:
diff changeset
546
45b12307c695 Initial revision
drewp
parents:
diff changeset
547 Tix.mainloop()