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