comparison flax/CueFaders.py @ 168:f8b5cb5fbeed

- CueFader is hopefully done: - CueFader is hopefully done: - The TimedGoButton accepts a wheel to change the times. You can also enter times directly. - TimedGoButton really has a default starting time of 2 now. (there was a variable attached to the wrong widget before) - We send DMX levels with dmxclient now. - Autoload Times is a new option. - We load times from the next cue if Autoload Times is true. - Time predictions in the LabelledScale are slightly better. You still can change the time of an active fade. - Cue cache and DMX level computing now have their own functions, which get called at (hopefully) All The Right Times. - There are even some docs now! - Cues: sub_level parsing is better, will only throw out one line if it encounters problems (instead of the rest of the cue) - CueList: lots of 0 vs. None bugs fixed. - TkCueList: stores a reference to the controlling fader so it can alert it about changed cues. - CueEditron: You can edit sub_levels now. - cuelist1 was edited, checking it in for consistency's sake
author dmcc
date Wed, 09 Jul 2003 03:59:40 +0000
parents 79bc84310e80
children ba83a312d8b1
comparison
equal deleted inserted replaced
167:79bc84310e80 168:f8b5cb5fbeed
1 from __future__ import division, nested_scopes 1 from __future__ import division, nested_scopes
2 import Tix as Tk 2 import Tix as Tk
3 import time 3 import time
4 from TreeDict import TreeDict, allow_class_to_be_pickled 4 from TreeDict import TreeDict, allow_class_to_be_pickled
5 from TLUtility import enumerate 5 from TLUtility import enumerate
6 import Submaster 6 import Submaster, dmxclient
7 7
8 cue_state_indicator_colors = { 8 cue_state_indicator_colors = {
9 # bg fg 9 # bg fg
10 'prev' : ('blue', 'white'), 10 'prev' : ('blue', 'white'),
11 'cur' : ('yellow', 'black'), 11 'cur' : ('yellow', 'black'),
73 self.scale_to_fade = scale_to_fade 73 self.scale_to_fade = scale_to_fade
74 self.button = Tk.Button(self, text=name, command=self.start_fade, **kw) 74 self.button = Tk.Button(self, text=name, command=self.start_fade, **kw)
75 self.button.pack(fill='both', expand=1, side='left') 75 self.button.pack(fill='both', expand=1, side='left')
76 76
77 self.timer_var = Tk.DoubleVar() 77 self.timer_var = Tk.DoubleVar()
78 self.timer_entry = Tk.Control(self, step=0.5, min=0, integer=0) 78 self.timer_entry = Tk.Control(self, step=0.5, min=0, integer=0,
79 self.timer_entry.entry.configure(textvariable=self.timer_var, width=5, 79 variable=self.timer_var, selectmode='immediate')
80 bg='black', fg='white') 80 for widget in (self.timer_entry, self.timer_entry.entry,
81 self.timer_entry.incr, self.timer_entry.decr, self.button, self):
82 widget.bind("<4>", self.wheelscroll)
83 widget.bind("<5>", self.wheelscroll)
84 self.timer_entry.entry.configure(width=5, bg='black', fg='white')
81 self.timer_entry.pack(fill='y', side='left') 85 self.timer_entry.pack(fill='y', side='left')
86 self.timer_var.set(2)
82 self.disabled = (self.button['state'] == 'disabled') 87 self.disabled = (self.button['state'] == 'disabled')
83 self.fading = 0 88 self.fading = 0
89 def wheelscroll(self, event):
90 """Mouse wheel increments or decrements timer."""
91 if event.num == 4: # scroll up
92 self.timer_entry.increment()
93 else: # scroll down
94 self.timer_entry.decrement()
84 def start_fade(self, end_level=1): 95 def start_fade(self, end_level=1):
85 try: 96 try:
86 fade_time = float(self.timer_var.get()) 97 fade_time = float(self.timer_var.get())
87 except ValueError: 98 except ValueError:
88 # since we use a control now, i don't think we need to worry about 99 # since we use a control now, i don't think we need to worry about
89 # validation any more. 100 # validation any more.
90 print "can't fade -- bad time" 101 print ">>> Can't fade -- bad time", self.timer_var.get()
91 return 102 return
92 103
93 self.start_time = time.time() 104 self.start_time = time.time()
94 self.start_level = self.scale_to_fade.scale_var.get() 105 self.start_level = self.scale_to_fade.scale_var.get()
95 self.end_level = end_level 106 self.end_level = end_level
128 139
129 class CueFader(Tk.Frame): 140 class CueFader(Tk.Frame):
130 def __init__(self, master, cuelist): 141 def __init__(self, master, cuelist):
131 Tk.Frame.__init__(self, master, bg='black') 142 Tk.Frame.__init__(self, master, bg='black')
132 self.cuelist = cuelist 143 self.cuelist = cuelist
133 self.auto_shift = Tk.IntVar() 144 self.cuelist.set_fader(self)
134 self.auto_shift.set(1) 145
146 self.last_levels_sent = 0
147 self.current_dmx_levels = [0] * 68
148 self.after(0, self.send_dmx_levels_loop) # start DMX sending loop
135 149
136 # this is a mechanism to stop Tk from autoshifting too much. 150 # this is a mechanism to stop Tk from autoshifting too much.
137 # if this variable is true, the mouse button is down. we don't want 151 # if this variable is true, the mouse button is down. we don't want
138 # to shift until they release it. when it is released, we will 152 # to shift until they release it. when it is released, we will
139 # set it to false and then call autoshift. 153 # set it to false and then call autoshift.
148 self.set_prev_button = Tk.Button(topframe, text='Set Prev', 162 self.set_prev_button = Tk.Button(topframe, text='Set Prev',
149 command=lambda: cuelist.set_selection_as_prev(), 163 command=lambda: cuelist.set_selection_as_prev(),
150 fg='white', bg='blue') 164 fg='white', bg='blue')
151 self.set_prev_button.pack(side='left') 165 self.set_prev_button.pack(side='left')
152 166
167 self.auto_shift = Tk.IntVar()
168 self.auto_shift.set(1)
169
153 self.auto_shift_checkbutton = Tk.Checkbutton(topframe, 170 self.auto_shift_checkbutton = Tk.Checkbutton(topframe,
154 variable=self.auto_shift, text='Autoshift', 171 variable=self.auto_shift, text='Autoshift',
155 command=self.toggle_autoshift, bg='black', fg='white', 172 command=self.toggle_autoshift, bg='black', fg='white',
156 highlightbackground='black') 173 highlightbackground='black')
157 self.auto_shift_checkbutton.pack(fill='both', side='left') 174 self.auto_shift_checkbutton.pack(fill='both', side='left')
175
176 self.auto_load_times = Tk.IntVar()
177 self.auto_load_times.set(1)
178
179 self.auto_load_times_checkbutton = Tk.Checkbutton(topframe,
180 variable=self.auto_load_times, text='Autoload Times',
181 command=self.toggle_autoshift, bg='black', fg='white',
182 highlightbackground='black')
183 self.auto_load_times_checkbutton.pack(fill='both', side='left')
158 184
159 self.set_next_button = Tk.Button(topframe, text='Set Next', 185 self.set_next_button = Tk.Button(topframe, text='Set Next',
160 command=lambda: cuelist.set_selection_as_next(), 186 command=lambda: cuelist.set_selection_as_next(),
161 fg='white', bg='red') 187 fg='white', bg='red')
162 self.set_next_button.pack(side='left') 188 self.set_next_button.pack(side='left')
186 self.shift_buttons[name] = shift 212 self.shift_buttons[name] = shift
187 self.go_buttons[name] = go 213 self.go_buttons[name] = go
188 214
189 scale.scale_var.trace('w', \ 215 scale.scale_var.trace('w', \
190 lambda x, y, z, name=name, scale=scale: self.xfade(name, scale)) 216 lambda x, y, z, name=name, scale=scale: self.xfade(name, scale))
217 go.timer_var.trace('w',
218 lambda x, y, z, scale=scale: scale.update_value_label())
191 219
192 def button_press(event, name=name, scale=scale): 220 def button_press(event, name=name, scale=scale):
193 self.no_shifts_until_release = 1 # prevent shifts until release 221 self.no_shifts_until_release = 1 # prevent shifts until release
194 def button_release(event, name=name, scale=scale): 222 def button_release(event, name=name, scale=scale):
195 self.no_shifts_until_release = 0 223 self.no_shifts_until_release = 0
196 self.autoshift(name, scale) 224 self.autoshift(name, scale)
197 225
198 scale.scale.bind("<ButtonPress>", button_press) 226 scale.scale.bind("<ButtonPress>", button_press)
199 scale.scale.bind("<ButtonRelease>", button_release) 227 scale.scale.bind("<ButtonRelease>", button_release)
200 faderframe.pack(side='bottom', fill='both', expand=1) 228 faderframe.pack(side='bottom', fill='both', expand=1)
229
230 self.current_dir = 'Next'
201 self.cues_as_subs = {} 231 self.cues_as_subs = {}
232 self.update_cue_cache()
233 def reload_cue_times(self):
234 prev, cur, next = self.cuelist.get_current_cues()
235 self.go_buttons['Next'].set_time(next.time)
236 def update_cue_cache(self):
237 """Rebuilds subs from the current cues. As this is expensive, we don't
238 do it unless necessary (i.e. whenever we shift or a cue is edited)"""
239 # load the subs to fade between
240 for cue, name in zip(self.cuelist.get_current_cues(),
241 ('Prev', 'Cur', 'Next')):
242 self.cues_as_subs[name] = cue.get_levels_as_sub()
243 self.compute_dmx_levels()
244 def compute_dmx_levels(self):
245 """Compute the DMX levels to send. This should get called whenever the
246 DMX levels could change: either during a crossfade or when a cue is
247 edited. Since this is called when we know that a change might occur,
248 we will send the new levels too."""
249 cur_sub = self.cues_as_subs.get('Cur')
250 if cur_sub:
251 scale = self.scales[self.current_dir]
252 scale_val = scale.scale_var.get()
253
254 other_sub = self.cues_as_subs[self.current_dir]
255 current_levels_as_sub = cur_sub.crossfade(other_sub, scale_val)
256 self.current_dmx_levels = current_levels_as_sub.get_dmx_list()
257 self.send_dmx_levels()
258 def send_dmx_levels(self):
259 # print "send_dmx_levels", self.current_dmx_levels
260 dmxclient.outputlevels(self.current_dmx_levels)
261 self.last_levels_sent = time.time()
262 def send_dmx_levels_loop(self):
263 diff = time.time() - self.last_levels_sent
264 if diff >= 2: # too long since last send
265 self.send_dmx_levels()
266 self.after(200, self.send_dmx_levels_loop)
267 else:
268 self.after(int((2 - diff) * 100), self.send_dmx_levels_loop)
202 def get_scale_desc(self, val, name): 269 def get_scale_desc(self, val, name):
270 """Returns a description to the TimedGoButton"""
203 go_button = self.go_buttons.get(name) 271 go_button = self.go_buttons.get(name)
204 if go_button: 272 if go_button:
205 time = go_button.get_time() 273 time = go_button.get_time()
206 return "%0.2f%%, %0.1fs left" % (val, time - ((val / 100.0) * time)) 274 return "%0.2f%%, %0.1fs left" % (val, time - ((val / 100.0) * time))
207 else: 275 else:
219 287
220 for scale_name, scale in self.scales.items(): 288 for scale_name, scale in self.scales.items():
221 scale.scale.set(0) 289 scale.scale.set(0)
222 self.cuelist.shift((-1, 1)[name == 'Next']) 290 self.cuelist.shift((-1, 1)[name == 'Next'])
223 291
224 # now load the subs to fade between 292 self.update_cue_cache()
225 for cue, name in zip(self.cuelist.get_current_cues(), 293 if self.auto_load_times.get():
226 ('Prev', 'Cur', 'Next')): 294 self.reload_cue_times()
227 self.cues_as_subs[name] = cue.get_levels_as_sub()
228 print "cues_as_subs", self.cues_as_subs
229 def autoshift(self, name, scale): 295 def autoshift(self, name, scale):
230 scale_val = scale.scale_var.get() 296 scale_val = scale.scale_var.get()
231 297
232 if scale_val == 1: 298 if scale_val == 1:
233 if self.auto_shift.get(): 299 if self.auto_shift.get():
252 else: 318 else:
253 # undo above work 319 # undo above work
254 self.scales[d].enable() 320 self.scales[d].enable()
255 self.go_buttons[d].enable() 321 self.go_buttons[d].enable()
256 322
257 cur_sub = self.cues_as_subs.get('Cur') 323 self.current_dir = name
258 if cur_sub: 324 self.compute_dmx_levels()
259 other_sub = self.cues_as_subs[name]
260 # print 'fade between %s and %s (%.2f)' % (cur_sub, other_sub, scale_val)
261 self.current_levels_as_sub = cur_sub.crossfade(other_sub, scale_val)
262 print "current levels", self.current_levels_as_sub.get_dmx_list()
263 # print
264 def opposite_direction(self, d): 325 def opposite_direction(self, d):
265 if d == 'Next': 326 if d == 'Next':
266 return 'Prev' 327 return 'Prev'
267 else: 328 else:
268 return 'Next' 329 return 'Next'
280 """Get this Cue as a combined Submaster, normalized. This method 341 """Get this Cue as a combined Submaster, normalized. This method
281 should not be called constantly, since it is somewhat expensive. It 342 should not be called constantly, since it is somewhat expensive. It
282 will reload the submasters from disk, combine all subs together, and 343 will reload the submasters from disk, combine all subs together, and
283 then compute the normalized form.""" 344 then compute the normalized form."""
284 subdict = {} 345 subdict = {}
285 try: 346 for line in self.sub_levels.split(','):
286 print self, self.sub_levels 347 try:
287 for line in self.sub_levels.split(','):
288 line = line.strip() 348 line = line.strip()
289 # print 'line', line 349 if not line:
350 continue
290 sub, scale = line.split(':') 351 sub, scale = line.split(':')
291 # print 'sub', sub, 'scale', scale
292 sub = sub.strip() 352 sub = sub.strip()
293 scale = float(scale) 353 scale = float(scale)
294 subdict[sub] = scale 354 subdict[sub] = scale
295 # print 'subdict', subdict 355 except ValueError:
296 except (ValueError, AttributeError): 356 print "Parsing error for '%s' in %s" % (self.sub_levels, self)
297 print "parsing error when computing sub for", self
298 357
299 s = Submaster.Submasters() 358 s = Submaster.Submasters()
300 newsub = Submaster.sub_maxes(*[s[sub] * scale 359 newsub = Submaster.sub_maxes(*[s[sub] * scale
301 for sub, scale in subdict.items()]) 360 for sub, scale in subdict.items()])
302 return newsub.get_normalized_copy() 361 return newsub.get_normalized_copy()
345 def set_next(self, index): 404 def set_next(self, index):
346 self.next_pointer = index 405 self.next_pointer = index
347 def set_prev(self, index): 406 def set_prev(self, index):
348 self.prev_pointer = index 407 self.prev_pointer = index
349 def bound_index(self, index): 408 def bound_index(self, index):
350 if not self.cues: 409 if not self.cues or index < 0:
351 return None 410 return None
352 else: 411 else:
353 return max(0, min(index, len(self.cues) - 1)) 412 return min(index, len(self.cues) - 1)
354 def get_current_cue_indices(self): 413 def get_current_cue_indices(self):
414 """Returns a list of the indices of three cues: the previous cue,
415 the current cue, and the next cue."""
355 cur = self.current_cue_index 416 cur = self.current_cue_index
356 return [self.bound_index(index) for index in 417 return [self.bound_index(index) for index in
357 (self.prev_pointer or cur - 1, 418 (self.prev_pointer or cur - 1,
358 cur, 419 cur,
359 self.next_pointer or cur + 1)] 420 self.next_pointer or cur + 1)]
360 def get_current_cues(self): 421 def get_current_cues(self):
422 """Returns a list of three cues: the previous cue, the current cue,
423 and the next cue."""
361 return [self.get_cue_by_index(index) 424 return [self.get_cue_by_index(index)
362 for index in self.get_current_cue_indices()] 425 for index in self.get_current_cue_indices()]
363 def get_cue_by_index(self, index): 426 def get_cue_by_index(self, index):
364 if index: 427 try:
365 return self.cues[self.bound_index(index)] 428 return self.cues[self.bound_index(index)]
366 else: 429 except TypeError:
367 return empty_cue 430 return empty_cue
368 def __del__(self): 431 def __del__(self):
369 self.save() 432 self.save()
370 def save(self): 433 def save(self):
371 print "Saving cues to", self.filename 434 print "Saving cues to", self.filename
377 440
378 class TkCueList(CueList, Tk.Frame): 441 class TkCueList(CueList, Tk.Frame):
379 def __init__(self, master, filename): 442 def __init__(self, master, filename):
380 CueList.__init__(self, filename) 443 CueList.__init__(self, filename)
381 Tk.Frame.__init__(self, master, bg='black') 444 Tk.Frame.__init__(self, master, bg='black')
445 self.fader = None
382 446
383 self.edit_tl = Tk.Toplevel() 447 self.edit_tl = Tk.Toplevel()
384 self.editor = CueEditron(self.edit_tl, changed_callback=self.redraw_cue) 448 self.editor = CueEditron(self.edit_tl,
449 changed_callback=self.cue_changed)
385 self.editor.pack(fill='both', expand=1) 450 self.editor.pack(fill='both', expand=1)
386 451
387 def edit_cue(index): 452 def edit_cue(index):
388 index = int(index) 453 index = int(index)
389 self.editor.set_cue_to_edit(self.cues[index]) 454 self.editor.set_cue_to_edit(self.cues[index])
409 474
410 self.cue_label_windows = {} 475 self.cue_label_windows = {}
411 for count, cue in enumerate(self.cues): 476 for count, cue in enumerate(self.cues):
412 self.display_cue(count, cue) 477 self.display_cue(count, cue)
413 self.update_cue_indicators() 478 self.update_cue_indicators()
479 def set_fader(self, fader):
480 self.fader = fader
414 def wheelscroll(self, evt): 481 def wheelscroll(self, evt):
415 """Perform mouse wheel scrolling""" 482 """Perform mouse wheel scrolling"""
416 amount = 2 483 if evt.num == 4: # scroll down
417 if evt.num == 4:
418 amount = -2 484 amount = -2
485 else: # scroll up
486 amount = 2
419 self.hlist.yview('scroll', amount, 'units') 487 self.hlist.yview('scroll', amount, 'units')
420 def redraw_cue(self, cue): 488 def cue_changed(self, cue):
421 path = self.cues.index(cue) 489 path = self.cues.index(cue)
422 for col, header in enumerate(self.columns): 490 for col, header in enumerate(self.columns):
423 try: 491 try:
424 text = getattr(cue, header) 492 text = getattr(cue, header)
425 except AttributeError: 493 except AttributeError:
427 495
428 if col == 0: 496 if col == 0:
429 self.cue_label_windows[path]['text'] = text 497 self.cue_label_windows[path]['text'] = text
430 else: 498 else:
431 self.hlist.item_configure(path, col, text=text) 499 self.hlist.item_configure(path, col, text=text)
500
501 if cue in self.get_current_cues() and self.fader:
502 self.fader.update_cue_cache()
503 self.fader.reload_cue_times()
432 def display_cue(self, path, cue): 504 def display_cue(self, path, cue):
433 for col, header in enumerate(self.columns): 505 for col, header in enumerate(self.columns):
434 try: 506 try:
435 text = getattr(cue, header) 507 text = getattr(cue, header)
436 except AttributeError: 508 except AttributeError:
450 self.hlist.item_create(path, col, text=text) 522 self.hlist.item_create(path, col, text=text)
451 def reset_cue_indicators(self, cue_indices=None): 523 def reset_cue_indicators(self, cue_indices=None):
452 """If cue_indices is None, we'll reset all of them.""" 524 """If cue_indices is None, we'll reset all of them."""
453 cue_indices = cue_indices or self.cue_label_windows.keys() 525 cue_indices = cue_indices or self.cue_label_windows.keys()
454 for key in cue_indices: 526 for key in cue_indices:
527 if key is None:
528 continue
455 window = self.cue_label_windows[key] 529 window = self.cue_label_windows[key]
456 window.configure(fg='white', bg='black') 530 window.configure(fg='white', bg='black')
457 def update_cue_indicators(self): 531 def update_cue_indicators(self):
458 states = dict(zip(self.get_current_cue_indices(), 532 states = dict(zip(self.get_current_cue_indices(),
459 ('prev', 'cur', 'next'))) 533 ('prev', 'cur', 'next')))
460 534
461 for count, state in states.items(): 535 for count, state in states.items():
536 if count is None:
537 continue
462 window = self.cue_label_windows[count] 538 window = self.cue_label_windows[count]
463 bg, fg = cue_state_indicator_colors[state] 539 bg, fg = cue_state_indicator_colors[state]
464 window.configure(bg=bg, fg=fg) 540 window.configure(bg=bg, fg=fg)
465 def shift(self, diff): 541 def shift(self, diff):
466 self.reset_cue_indicators(self.get_current_cue_indices()) 542 self.reset_cue_indicators(self.get_current_cue_indices())
467 CueList.shift(self, diff) 543 CueList.shift(self, diff)
468 self.update_cue_indicators() 544 self.update_cue_indicators()
469 # try to see all indices, but next takes priority over all, and cur 545 # try to see all indices, but next takes priority over all, and cur
470 # over prev 546 # over prev
471 for index in self.get_current_cue_indices(): 547 for index in self.get_current_cue_indices():
472 self.hlist.see(index) 548 if index is not None:
549 self.hlist.see(index)
473 def select_callback(self, index): 550 def select_callback(self, index):
474 new_next = int(index) 551 new_next = int(index)
475 self.set_next(new_next) 552 self.set_next(new_next)
476 def set_next(self, index): 553 def set_next(self, index):
477 prev, cur, next = self.get_current_cue_indices() 554 prev, cur, next = self.get_current_cue_indices()
512 self.master.title("Editing '%s'" % self.cue.name) 589 self.master.title("Editing '%s'" % self.cue.name)
513 except AttributeError: 590 except AttributeError:
514 pass 591 pass
515 def setup_editing_forms(self): 592 def setup_editing_forms(self):
516 self.variables = {} 593 self.variables = {}
517 for row, field in enumerate(('name', 'time', 'page', 'desc', 'sub_levels')): 594 for row, field in enumerate(('name', 'time', 'page', 'desc',
595 'sub_levels')):
518 lab = Tk.Label(self, text=field, fg='white', bg='black') 596 lab = Tk.Label(self, text=field, fg='white', bg='black')
519 lab.grid(row=row, column=0, sticky='nsew') 597 lab.grid(row=row, column=0, sticky='nsew')
520 598
521 entryvar = Tk.StringVar() 599 entryvar = Tk.StringVar()
522 entry = Tk.Entry(self, fg='white', bg='black', 600 entry = Tk.Entry(self, fg='white', bg='black',
535 613
536 entryvar.trace('w', field_changed) 614 entryvar.trace('w', field_changed)
537 self.columnconfigure(1, weight=1) 615 self.columnconfigure(1, weight=1)
538 def fill_in_cue_info(self): 616 def fill_in_cue_info(self):
539 self.enable_callbacks = 0 617 self.enable_callbacks = 0
540 for row, field in enumerate(('name', 'time', 'page', 'desc', 'sub_levels')): 618 for row, field in enumerate(('name', 'time', 'page', 'desc',
619 'sub_levels')):
541 text = '' 620 text = ''
542 if self.cue: 621 if self.cue:
543 try: 622 try:
544 text = getattr(self.cue, field) 623 text = getattr(self.cue, field)
545 except AttributeError: 624 except AttributeError: