Mercurial > code > home > repos > light9
annotate flax/Timeline.py @ 121:2f48cb9219ed
now does a little show, with two fades
author | drewp |
---|---|
date | Fri, 13 Jun 2003 14:01:07 +0000 |
parents | 490843093506 |
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() |