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