diff --git a/flax/KeyboardComposer.py b/flax/KeyboardComposer.py --- a/flax/KeyboardComposer.py +++ b/flax/KeyboardComposer.py @@ -60,7 +60,7 @@ class KeyboardComposer(Frame): # another what a hack! keylabel = Label(keyhintrow, text='%s\n%s' % (upkey, downkey), - width=3, font=('Arial Bold', 12), bg='red', fg='white') + width=3, font=('Arial', 12), bg='red', fg='white') keylabel.pack(side=LEFT, expand=1, fill=X) col += 1 diff --git a/flax/Timeline.py b/flax/Timeline.py --- a/flax/Timeline.py +++ b/flax/Timeline.py @@ -8,79 +8,8 @@ Quote of the Build (from Ghostbusters II Dana: Okay, but after dinner, I don't want you putting any of your old cheap moves on me. Peter: Ohhhh no! I've got all NEW cheap moves. - -Timeline idea -============= - time | 0 1 2 3 4 5 6 ----------+----------------------------- -frame | F F F -blenders | \ b / \- b ----/ - -Update: this is more or less what happened. However, there are -TimelineTracks as well. FunctionFrames go on their own track. -LevelFrames must have unique times, FunctionFrames do not. - -Level propagation -================= -Cue1 is a CueNode. CueNodes consist of a timeline with any number -of LevelFrame nodes and LinearBlenders connecting all the frames. -At time 0, Cue1's timeline has LevelFrame1. At time 5, the timeline -has NodeFrame1. NodeFrame1 points to Node1, which has it's own sets -of levels. It could be a Cue, for all Cue1 cares. No matter what, -we always get down to LevelFrames holding the levels at the bottom, -then getting combined by Blenders. - - /------\ - | Cue1 | - |---------------\ - | Timeline: | - | 0 ... 5 | - /--------- LF1 NF1 -------\ - | | \ / | | - | | LinearBlender | | - | \---------------/ | - | points to - /---------------\ a port in Cue1, - | blueleft : 20 | which connects to a Node - | redwash : 12 | - | . | - | : | - | | - \---------------/ - -PS. blueleft and redwash are other nodes at the bottom of the tree. -The include their real channel number for the DimmerNode to process. - -When Cue1 requests levels, the timeline looks at the current position. -If it is directly over a Frame (or Frames), that frame is handled. -If it is LevelFrame, those are the levels that it returns. If there is -a FunctionFrame, that function is activated. Thus, the order of Frames -at a specific time is very significant, since the FunctionFrame could -set the time to 5s in the future. If we are not currently over any -LevelFrames, we call upon a Blender to determine the value between. -Say that we are at 2.3s. We use the LinearBlender with 2.3/5.0s = 0.46% -and it determines that the levels are 1 - 0.46% = 0.54% of LF1 and -0.46% of NF1. NF1 asks Node9 for its levels and this process starts -all over. - -Graph theory issues (node related issues, should be moved elsewhere) -==================================================================== -1. We need to determine dependencies for updating (topological order). -2. We need to do cyclicity tests. - -Guess who wishes they had brought their theory book home? -I think we can do both with augmented DFS. An incremental version of both -would be very nice, though hopefully unnecessary. - """ -class InvalidFrameOperation(Exception): - """You get these when you try to perform some operation on a frame - that doesn't make sense. The interface is advised to tell the user, - and indicate that a Blender or FunctionFramea should be disconnected - or fixed.""" - pass - class MissingBlender(Exception): """Raised when a TimedEvent is missing a blender.""" def __init__(self, timedevent): @@ -93,105 +22,13 @@ class MissingBlender(Exception): FORWARD = 1 BACKWARD = -1 -class Frame: - """Frame is an event that happens at a specific time. There are two - types of frames: LevelFrames and FunctionFrames. LevelFrames provide - levels via their get_levels() function. FunctionFrames alter the - timeline (e.g. bouncing, looping, speed changes, etc.). They call - __call__'ed instead.""" - def __init__(self, name): - self.name = name - self.timeline = DummyClass(use_warnings=0, raise_exceptions=0) - def set_timeline(self, timeline): - """Tell the Frame who the controlling Timeline is""" - self.timeline = timeline - def __mul__(self, percent): - """Generate a new Frame by multiplying the 'effect' of this frame by - a percent.""" - raise InvalidFrameOperation, "Can't use multiply this Frame" - def __add__(self, otherframe): - """Combines this frame with another frame, generating a new one.""" - raise InvalidFrameOperation, "Can't use add on this Frame" - -class LevelFrame(Frame): - """LevelFrames provide levels. They can also be combined with other - LevelFrames.""" - def __init__(self, name, levels): - Frame.__init__(self, name) - self.levels = levels - def __mul__(self, percent): - """Returns a new LevelFrame made by multiplying all levels by a - percentage. Percent is a float greater than 0.0""" - newlevels = dict_scale(self.get_levels(), percent) - return LevelFrame('(%s * %f)' % (self.name, percent), newlevels) - def __add__(self, otherframe): - """Combines this LevelFrame with another LevelFrame, generating a new - one. Values are max() together.""" - theselevels, otherlevels = self.get_levels(), otherframe.get_levels() - return LevelFrame('(%s + %s)' % (self.name, otherframe.name), - dict_max(theselevels, otherlevels)) - def get_levels(self): - """This function returns the levels held by this frame.""" - return self.levels - def __repr__(self): - return "<%s %r %r>" % (str(self.__class__), self.name, self.levels) - -class EmptyFrame(LevelFrame): - """An empty LevelFrame, for the purposes of extending the timeline.""" - def __init__(self, name='Empty Frame'): - EmptyFrame.__init__(self, name, {}) - -class NodeFrame(LevelFrame): - """A LevelFrame that gets its levels from another Node. This must be - used from a Timeline that is enclosed in TimelineNode. Node is a string - describing the node requested.""" - def __init__(self, name, node): - LevelFrame.__init__(self, name, {}) - self.node = node - def get_levels(self): - """Ask the node that we point to for its levels""" - node = self.timeline.get_node(self.node) - self.levels = node.get_levels() - return self.levels - -class FunctionFrame(Frame): - def __init__(self, name): - Frame.__init__(self, name) - def __call__(self, timeline, timedevent, node): - """Called when the FunctionFrame is activated. It is given a pointer - to it's master timeline, the TimedEvent containing it, and Node that - the timeline is contained in, if available.""" - pass - -# this is kinda broken -class BounceFunction(FunctionFrame): - def __call__(self, timeline, timedevent, node): - """Reverses the direction of play.""" - timeline.reverse_direction() - print "boing! new dir:", timeline.direction - -# this too -class LoopFunction(FunctionFrame): - def __call__(self, timeline, timedevent, node): - timeline.set_time(0) - # print 'looped!' - -class DoubleTimeFunction(FunctionFrame): - def __call__(self, timeline, timedevent, node): - timeline.set_rate(2 * timeline.rate) - print 'doubled!', timeline.rate - -class HalfTimeFunction(FunctionFrame): - def __call__(self, timeline, timedevent, node): - timeline.set_rate(0.5 * timeline.rate) - print 'halved!', timeline.rate - class TimedEvent: """Container for a Frame which includes a time that it occurs at, and which blender occurs after it.""" - def __init__(self, time, frame, blender=None): + def __init__(self, time, frame, blender=None, level=1.0): make_attributes_from_args('time', 'frame') self.next_blender = blender + self.level = level def __float__(self): return self.time def __cmp__(self, other): @@ -200,12 +37,10 @@ class TimedEvent: else: return cmp(self.time, other.time) def __repr__(self): - return "" % \ - (self.frame, self.time, self.next_blender) - def get_levels(self): - """Return the Frame's levels. Hopefully frame is a LevelFrame or - descendent.""" - return self.frame.get_levels() + return "" % \ + (self.frame, self.level, self.time, self.next_blender) + def get_level(self): + return self.level class Blender: """Blenders are functions that merge the effects of two LevelFrames.""" @@ -237,7 +72,7 @@ class Blender: 0.25 * endframe. This function is included since many blenders are just functions on the percentage and still combine start and end frames in this way.""" - return (startframe * (1.0 - blendtime)) + (endframe * blendtime) + return {startframe : (1.0 - blendtime), endframe : blendtime} class InstantEnd(Blender): """Instant change from startframe to endframe at the end. In other words, @@ -248,7 +83,7 @@ class InstantEnd(Blender): # This is because Blenders never be asked for blenders at the endpoints # (after all, they wouldn't be blenders if they were). Please see # 'Very important note' in Blender.__doc__ - return startframe + return {startframe : 1.0} class InstantStart(Blender): """Instant change from startframe to endframe at the beginning. In other @@ -259,13 +94,12 @@ class InstantStart(Blender): # This is because Blenders never be asked for blenders at the endpoints # (after all, they wouldn't be blenders if they were). Please see # 'Very important note' in Blender.__doc__ - return endframe + return {endframe : 1.0} class LinearBlender(Blender): """Linear fade from one frame to another""" def __call__(self, startframe, endframe, blendtime): return self.linear_blend(startframe, endframe, blendtime) - # return (startframe * (1.0 - blendtime)) + (endframe * blendtime) class ExponentialBlender(Blender): """Exponential fade fron one frame to another. You get to specify @@ -290,7 +124,7 @@ class TimelineTrack: """TimelineTrack is a single track in a Timeline. It consists of a list of TimedEvents and a name. Length is automatically the location of the last TimedEvent. To extend the Timeline past that, add an - EmptyTimedEvent.""" + EmptyTimedEvent (which doesn't exist :-/).""" def __init__(self, name, *timedevents): self.name = name self.events = list(timedevents) @@ -352,7 +186,7 @@ class TimelineTrack: before, after = self.get_surrounding_frames(time) if before == after: - return before.frame + return {before.frame : 1.0} else: # we have a blended value diff = after.time - before.time elapsed = time - before.time @@ -362,9 +196,9 @@ class TimelineTrack: return before.next_blender(before.frame, after.frame, percent) class Timeline: - def __init__(self, tracks, functions, rate=1, direction=FORWARD): + def __init__(self, tracks, rate=1, direction=FORWARD): """ - Most of this is old: + Most/all of this is old: You can have multiple FunctionFrames at the same time. Their order is important though, since FunctionFrames will be applied @@ -377,9 +211,6 @@ class Timeline: some time if you want to extend a Timeline.""" make_attributes_from_args('tracks', 'rate', 'direction') - # the function track is a special track - self.fn_track = TimelineTrack('functions', *functions) - self.current_time = 0 self.last_clock_time = None self.stopped = 1 @@ -387,7 +218,6 @@ class Timeline: """Length of the timeline in pseudoseconds. This is determined by finding the length of the longest track.""" track_lengths = [track.length() for track in self.tracks] - track_lengths.append(self.fn_track.length()) return max(track_lengths) def play(self): """Activates the timeline. Future calls to tick() will advance the @@ -422,21 +252,6 @@ class Timeline: self.last_clock_time = clock_time self.current_time = new_time - # now, find out if we missed any functions - if self.fn_track.has_events(): - lower_time, higher_time = last_time, new_time - if lower_time == higher_time: print "zarg!" - if lower_time > higher_time: - lower_time, higher_time = higher_time, lower_time - - events_to_process = self.fn_track.get_range(lower_time, - higher_time, self.direction) - - for event in events_to_process: - # they better be FunctionFrames - event.frame(self, event, None) # the None should be a Node, - # but that part is coming later - # now we make sure we're in bounds (we don't do this before, since it # can cause us to skip events that are at boundaries. self.current_time = max(self.current_time, 0) @@ -456,17 +271,11 @@ class Timeline: def get_levels(self): """Return the current levels from this timeline. This is done by adding all the non-functional tracks together.""" - current_level_frame = LevelFrame('timeline sum', {}) - for t in self.tracks: - current_level_frame += t.get_levels_at_time(self.current_time) - - return current_level_frame.get_levels() + levels = [t.get_levels_at_time(self.current_time) + for t in self.tracks] + return dict_max(*levels) if __name__ == '__main__': - scene1 = LevelFrame('scene1', {'red' : 50, 'blue' : 25}) - scene2 = LevelFrame('scene2', {'red' : 10, 'blue' : 5, 'green' : 70}) - scene3 = LevelFrame('scene3', {'yellow' : 10, 'blue' : 80, 'purple' : 70}) - T = TimedEvent linear = LinearBlender() @@ -475,25 +284,13 @@ if __name__ == '__main__': smoove = SmoothBlender() track1 = TimelineTrack('lights', - T(0, scene1, blender=linear), - T(5, scene2, blender=quad), - T(10, scene3, blender=smoove), - T(15, scene2)) # last TimedEvent doesn't need a blender + T(0, 'red', blender=linear), + T(5, 'blue', blender=quad), + T(10, 'red', blender=smoove), + T(15, 'blue')) # last TimedEvent doesn't need a blender - halver = HalfTimeFunction('1/2x') - doubler = DoubleTimeFunction('2x') - if 0: - # bounce is semiworking - bouncer = BounceFunction('boing') - tl = Timeline([track1], [T(0, bouncer), - T(0, halver), - T(15, bouncer), - T(15, doubler)]) - else: - looper = LoopFunction('loop1') - tl = Timeline([track1], [T(0, doubler), - T(5, halver), - T(14, looper)]) + tl = Timeline([track1]) + tl.play() import Tix @@ -508,7 +305,7 @@ if __name__ == '__main__': for color in colors: sv = Tix.DoubleVar() scalevars[color] = sv - scale = Tix.Scale(colorscalesframe, from_=100, to_=0, bg=color, + scale = Tix.Scale(colorscalesframe, from_=1, to_=0, res=0.01, bg=color, variable=sv) scale.pack(side=Tix.LEFT)