diff --git a/flax/Submaster.py b/flax/Submaster.py --- a/flax/Submaster.py +++ b/flax/Submaster.py @@ -8,17 +8,14 @@ import Patch class Submaster: "Contain a dictionary of levels, but you didn't need to know that" - def __init__(self, name, leveldict=None, temporary=0): + def __init__(self, name, leveldict=None): self.name = name - self.temporary = temporary if leveldict: self.levels = leveldict else: self.levels = {} self.reload() def reload(self): - if self.temporary: - return try: self.levels.clear() subfile = file("subs/%s" % self.name) @@ -36,9 +33,6 @@ class Submaster: except IOError: print "Can't read file for sub: %s" % self.name def save(self): - if self.temporary: - return - subfile = file("subs/%s" % self.name, 'w') names = self.levels.keys() names.sort() @@ -58,62 +52,24 @@ class Submaster: return self.levels def __mul__(self, scalar): return Submaster("%s*%s" % (self.name, scalar), - dict_scale(self.levels, scalar), temporary=1) + dict_scale(self.levels, scalar)) __rmul__ = __mul__ def max(self, *othersubs): return sub_maxes(self, *othersubs) def __repr__(self): - levels = ' '.join(["%s:%.2f" % item for item in self.levels.items()]) - return "<'%s': [%s]>" % (self.name, levels) + return "<%s: %r>" % (self.name, self.levels) def get_dmx_list(self): leveldict = self.get_levels() # gets levels of sub contents levels = [0] * 68 for k, v in leveldict.items(): - dmxchan = Patch.get_dmx_channel(k) - 1 - levels[dmxchan] = max(v, levels[dmxchan]) + levels[Patch.get_dmx_channel(k) - 1] = v return levels - def normalize_patch_names(self): - """Use only the primary patch names.""" - # possibly busted -- don't use unless you know what you're doing - self.set_all_levels(self.levels.copy()) - def get_normalized_copy(self): - """Get a copy of this sumbaster that only uses the primary patch - names. The levels will be the same.""" - newsub = Submaster("%s (normalized)" % self.name, temporary=1) - newsub.set_all_levels(self.levels) - return newsub - def crossfade(self, othersub, amount): - """Returns a new sub that is a crossfade between this sub and - another submaster. - - NOTE: You should only crossfade between normalized submasters.""" - otherlevels = othersub.get_levels() - keys_set = {} - for k in self.levels.keys() + otherlevels.keys(): - keys_set[k] = 1 - all_keys = keys_set.keys() - - xfaded_sub = Submaster("xfade", temporary=1) - for k in all_keys: - xfaded_sub.set_level(k, - linear_fade(self.levels.get(k, 0), - otherlevels.get(k, 0), - amount)) - - return xfaded_sub - -def linear_fade(start, end, amount): - """Fades between two floats by an amount. amount is a float between - 0 and 1. If amount is 0, it will return the start value. If it is 1, - the end value will be returned.""" - level = start + (amount * (end - start)) - return level def sub_maxes(*subs): return Submaster("max(%r)" % (subs,), - dict_max(*[sub.levels for sub in subs]), temporary=1) + dict_max(*[sub.levels for sub in subs])) class Submasters: "Collection o' Submaster objects" @@ -124,25 +80,12 @@ class Submasters: files = os.listdir('subs') for filename in files: - # we don't want these files - if filename.startswith('.') or filename.endswith('~') or \ - filename.startswith('CVS'): + if filename.startswith('.') or filename.endswith('~'): continue self.submasters[filename] = Submaster(filename) def get_all_subs(self): "All Submaster objects" - l = self.submasters.items() - l.sort() - l = [x[1] for x in l] - songs = [] - notsongs = [] - for s in l: - if s.name.startswith('song'): - songs.append(s) - else: - notsongs.append(s) - combined = notsongs + songs - return combined + return self.submasters.values() def get_sub_by_name(self, name): "Makes a new sub if there isn't one." return self.submasters.get(name, Submaster(name)) @@ -151,10 +94,6 @@ class Submasters: if __name__ == "__main__": Patch.reload_data() s = Submasters() + newsub = s['newsub'] + newsub.set_all_levels({'5' : 1, '7': 0.2}) print s.get_all_subs() - if 0: # turn this on to normalize all subs - for sub in s.get_all_subs(): - print "before", sub - sub.normalize_patch_names() - sub.save() - print "after", sub diff --git a/flax/TheShow.py b/flax/TheShow.py --- a/flax/TheShow.py +++ b/flax/TheShow.py @@ -1,3 +1,5 @@ + + from Timeline import * from Submaster import Submasters, sub_maxes @@ -34,9 +36,6 @@ class Show: "set time of current timeline" self.current_time = time self.current_timeline.set_time(time) - def get_timelines(self): - "Get names of all timelines" - return self.timelines.keys() # this is the default blender linear = LinearBlender() diff --git a/flax/Timeline.py b/flax/Timeline.py --- a/flax/Timeline.py +++ b/flax/Timeline.py @@ -22,16 +22,20 @@ class MissingBlender(Exception): FORWARD = 1 BACKWARD = -1 +MISSING = 'missing' + 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, level=1.0): + def __init__(self, time, frame=MISSING, 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): + if other is None: + raise "I can't compare with a None. I am '%s'" % str(self) if type(other) in (float, int): return cmp(self.time, other) else: @@ -48,11 +52,12 @@ class Blender: """Blenders are functions that merge the effects of two LevelFrames.""" def __init__(self): pass - def __call__(self, startframe, endframe, blendtime): + def __call__(self, startframe, endframe, blendtime, time_since_startframe): """Return a LevelFrame combining two LevelFrames (startframe and endframe). blendtime is how much of the blend should be performed and will be expressed as a percentage divided by 100, i.e. a float - between 0.0 and 1.0. + between 0.0 and 1.0. time_since_startframe is the time since the + startframe was on screen in seconds (float). Very important note: Blenders will *not* be asked for values at end points (i.e. blendtime=0.0 and blendtime=1.0). @@ -74,24 +79,20 @@ 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.""" - # print "linear_blend", startframe, endframe, blendtime if startframe.frame == endframe.frame: - # print "same frames" - startlevel = startframe.level * (1.0 - blendtime) - endlevel = endframe.level * blendtime - levels = {startframe.frame : max(startlevel, endlevel)} + level = startframe.level + (blendtime * \ + (endframe.level - startframe.level)) + levels = {startframe.frame : level} else: - # print "diff frames" levels = {startframe.frame : (1.0 - blendtime) * startframe.level, endframe.frame : blendtime * endframe.level} - # print "return", levels return levels class InstantEnd(Blender): """Instant change from startframe to endframe at the end. In other words, the value returned will be the startframe all the way until the very end of the blend.""" - def __call__(self, startframe, endframe, blendtime): + def __call__(self, startframe, endframe, blendtime, time_since_startframe): # "What!?" you say, "Why don't you care about blendtime?" # This is because Blenders never be asked for blenders at the endpoints # (after all, they wouldn't be blenders if they were). Please see @@ -102,7 +103,7 @@ class InstantStart(Blender): """Instant change from startframe to endframe at the beginning. In other words, the value returned will be the startframe at the very beginning and then be endframe at all times afterwards.""" - def __call__(self, startframe, endframe, blendtime): + def __call__(self, startframe, endframe, blendtime, time_since_startframe): # "What!?" you say, "Why don't you care about blendtime?" # This is because Blenders never be asked for blenders at the endpoints # (after all, they wouldn't be blenders if they were). Please see @@ -111,7 +112,7 @@ class InstantStart(Blender): class LinearBlender(Blender): """Linear fade from one frame to another""" - def __call__(self, startframe, endframe, blendtime): + def __call__(self, startframe, endframe, blendtime, time_since_startframe): return self.linear_blend(startframe, endframe, blendtime) class ExponentialBlender(Blender): @@ -120,7 +121,7 @@ class ExponentialBlender(Blender): as LinearBlender.""" def __init__(self, exponent): self.exponent = exponent - def __call__(self, startframe, endframe, blendtime): + def __call__(self, startframe, endframe, blendtime, time_since_startframe): blendtime = blendtime ** self.exponent return self.linear_blend(startframe, endframe, blendtime) @@ -129,19 +130,42 @@ class ExponentialBlender(Blender): class SmoothBlender(Blender): """Drew's "Smoove" Blender function. Hopefully he'll document and parametrize it.""" - def __call__(self, startframe, endframe, blendtime): + def __call__(self, startframe, endframe, blendtime, time_since_startframe): blendtime = (-1 * blendtime) * blendtime * (blendtime - 1.5) * 2 return self.linear_blend(startframe, endframe, blendtime) +class Strobe(Blender): + "Strobes the frame on the right side between offlevel and onlevel." + def __init__(self, ontime, offtime, onlevel=1, offlevel=0): + "times are in seconds" + make_attributes_from_args('ontime', 'offtime', 'onlevel', 'offlevel') + self.cycletime = ontime + offtime + def __call__(self, startframe, endframe, blendtime, time_since_startframe): + # time into the current period + period_time = time_since_startframe % self.cycletime + if period_time <= self.ontime: + return {endframe.frame : self.onlevel} + else: + return {endframe.frame : self.offlevel} + 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 (which doesn't exist :-/).""" - def __init__(self, name, *timedevents): + def __init__(self, name, *timedevents, **kw): + if kw.get('default_frame'): + self.default_frame = kw['default_frame'] + else: + self.default_frame = None self.name = name self.events = list(timedevents) self.events.sort() + self.fill_in_missing_subs() + def fill_in_missing_subs(self): + for event in self.events: + if event.frame == MISSING: + event.frame = self.default_frame def __str__(self): return "" % self.events def has_events(self): @@ -206,10 +230,10 @@ class TimelineTrack: percent = elapsed / diff if not before.next_blender: raise MissingBlender, before - return before.next_blender(before, after, percent) + return before.next_blender(before, after, percent, elapsed) class Timeline: - def __init__(self, tracks, rate=1, direction=FORWARD): + def __init__(self, name, tracks, rate=1, direction=FORWARD): """ Most/all of this is old: @@ -223,7 +247,7 @@ class Timeline: is bounded by their last frame. You can put an EmptyFrame at some time if you want to extend a Timeline.""" - make_attributes_from_args('tracks', 'rate', 'direction') + make_attributes_from_args('name', 'tracks', 'rate', 'direction') self.current_time = 0 self.last_clock_time = None self.stopped = 1 @@ -323,7 +347,7 @@ if __name__ == '__main__': T(18, 'blue', level=1.0), T(20, 'blue', level=0.0)) - tl = Timeline([track1, track2, track3]) + tl = Timeline('test', [track1, track2, track3]) tl.play() diff --git a/flax/TimelineDMX.py b/flax/TimelineDMX.py --- a/flax/TimelineDMX.py +++ b/flax/TimelineDMX.py @@ -1,34 +1,27 @@ import sys, time, socket sys.path.append("../light8") -import Tix as tk import Patch, Timeline, dmxclient, xmlrpclib import TheShow Patch.reload_data() -class ShowRunner(tk.Frame): - def __init__(self, master, show): - tk.Frame.__init__(self, master) - self.master = master - +class ShowRunner: + def __init__(self, show): self.show = show self.find_player() - self.build_timeline_list() - def build_timeline_list(self): - self.tl_list = tk.Frame(self) - for tl in self.show.get_timelines(): - b=tk.Button(self.tl_list,text=tl, - anchor='w',pady=1) - b.config(command=lambda tl=tl: self.set_timeline(tl)) - b.pack(side='top',fill='x') - self.tl_list.pack() - def set_timeline(self, tlname): - print "TimelineDMX: set timeline to", tlname - self.show.set_timeline(tlname) def find_player(self): self.player = xmlrpclib.Server("http://localhost:8040") def send_levels(self): + """ + sub = self.show.get_levels() # gets levels of subs + leveldict = sub.get_levels() # gets levels of sub contents + print 'resolved levels', leveldict + + levels = [0] * 68 + for k, v in leveldict.items(): + levels[Patch.get_dmx_channel(k)] = v + """ levels = self.show.calc_active_submaster().get_dmx_list() dmxclient.outputlevels(levels) @@ -45,13 +38,10 @@ class ShowRunner(tk.Frame): self.sync_times() self.send_levels() time.sleep(0.01) - self.master.update() except KeyboardInterrupt: sys.exit(0) if __name__ == "__main__": - root = tk.Tk() - s = ShowRunner(root, TheShow.show) + s = ShowRunner(TheShow.show) s.show.set_timeline('strobe test') - s.pack() s.mainloop()