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