Mercurial > code > home > repos > light9
comparison flax/KeyboardRecorder.py @ 0:45b12307c695
Initial revision
author | drewp |
---|---|
date | Wed, 03 Jul 2002 09:37:57 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:45b12307c695 |
---|---|
1 from __future__ import nested_scopes | |
2 import sys, time | |
3 sys.path.append('..') | |
4 from Widgets.Fadable import Fadable | |
5 | |
6 from Tix import * | |
7 import math, atexit, pickle | |
8 from sets import Set | |
9 from Submaster import Submasters, sub_maxes | |
10 import dmxclient | |
11 from uihelpers import toplevelat | |
12 | |
13 # idea is that one system handles all the level logging persistence | |
14 # (it also needs to know which song we're currently working on) | |
15 # SubLevelLogger is not yet written | |
16 from SubLevelLogger import SubLevelLogger | |
17 | |
18 nudge_keys = { | |
19 'up' : list('qwertyuiop'), | |
20 'down' : list('asdfghjkl') | |
21 } | |
22 nudge_keys['down'].append('semicolon') | |
23 | |
24 class SubScale(Scale, Fadable): | |
25 def __init__(self, master, *args, **kw): | |
26 self.scale_var = kw.get('variable') or DoubleVar() | |
27 kw.update({'variable' : self.scale_var, | |
28 'from' : 1, 'to' : 0, 'showvalue' : 0, | |
29 'sliderlength' : 15, 'res' : 0.01, | |
30 'width' : 40, 'troughcolor' : 'black', 'bg' : 'grey40', | |
31 'highlightthickness' : 1, 'bd' : 1, | |
32 'highlightcolor' : 'red', 'highlightbackground' : 'black', | |
33 'activebackground' : 'red'}) | |
34 Scale.__init__(self, master, *args, **kw) | |
35 Fadable.__init__(self, var=self.scale_var, wheel_step=0.05) | |
36 self.draw_indicator_colors() | |
37 def draw_indicator_colors(self): | |
38 if self.scale_var.get() == 0: | |
39 self['troughcolor'] = 'black' | |
40 else: | |
41 self['troughcolor'] = 'blue' | |
42 | |
43 class SubmasterTk(Frame): | |
44 def __init__(self, master, name, current_level, kbrecorder=None): | |
45 """kbrecorder is a KeyboardRecorder instance -- take that, Java""" | |
46 Frame.__init__(self, master, bd=1, relief='raised', bg='black') | |
47 self.slider_var = DoubleVar() | |
48 self.slider_var.set(current_level) | |
49 self.scale = SubScale(self, variable=self.slider_var, width=20) | |
50 self.namelabel = Label(self, text=name, font="Arial 8", bg='black', | |
51 fg='white') | |
52 self.namelabel.pack(side=TOP) | |
53 self.levellabel = Label(self, | |
54 font="Arial 8", bg='black', fg='white') | |
55 self.levellabel.pack(side=TOP) | |
56 self.scale.pack(side=BOTTOM, expand=1, fill=BOTH) | |
57 | |
58 # recording bits | |
59 self.kbrecorder = kbrecorder | |
60 self.recording = 0 | |
61 self.name = name | |
62 self._last_recorded_value = None | |
63 for obj in (self, self.levellabel, self.namelabel, self.scale): | |
64 obj.bind('<3>', self.toggle_record) | |
65 | |
66 self.slider_var.trace('w', self.draw_level) | |
67 self.draw_level() | |
68 def draw_level(self, *args): | |
69 self.levellabel['text'] = "%d" % (self.slider_var.get() * 100) | |
70 def toggle_record(self, evt): | |
71 self.kbrecorder.toggle_sub_recording(self.name) | |
72 self.recording = not self.recording | |
73 if self.recording: | |
74 conf = {'bg' : 'red'} | |
75 else: | |
76 conf = {'bg' : 'black'} | |
77 | |
78 for obj in (self, self.levellabel, self.namelabel): | |
79 obj.config(**conf) | |
80 def record(self, timestamp): | |
81 """This is called whenever we get a timestamp and we're recording.""" | |
82 new_value = self.scale.scale_var.get() | |
83 if new_value != self._last_recorded_value: | |
84 s = SubLevelLogger() | |
85 s.save_level(name, timestamp, new_value) | |
86 | |
87 self._last_recorded_value = new_value | |
88 def get_recorded_level(self, timestamp): | |
89 """This is called whenever we get a timestamp and we're not | |
90 recording. | |
91 | |
92 TODO: independent subs don't playback from recorded levels""" | |
93 s = SubLevelLogger() | |
94 new_value = s.load_level(name, timestamp) | |
95 self.scale.scale_var.set(new_value) | |
96 | |
97 class KeyboardRecorder(Frame): | |
98 def __init__(self, root, submasters, current_sub_levels=None, dmxdummy=0): | |
99 Frame.__init__(self, root, bg='black') | |
100 self.submasters = submasters | |
101 self.dmxdummy = dmxdummy | |
102 | |
103 self.current_sub_levels = {} | |
104 if current_sub_levels: | |
105 self.current_sub_levels = current_sub_levels | |
106 else: | |
107 try: | |
108 self.current_sub_levels = \ | |
109 pickle.load(file('.keyboardcomposer.savedlevels')) | |
110 except IOError: | |
111 pass | |
112 | |
113 self.subs_being_recorded = Set() # yay, this is the first time I've | |
114 # used Set! | |
115 | |
116 self.draw_ui() | |
117 self.send_levels_loop() | |
118 def draw_ui(self): | |
119 self.rows = [] # this holds Tk Frames for each row | |
120 self.slider_vars = {} # this holds subname:sub Tk vars | |
121 self.slider_table = {} # this holds coords:sub Tk vars | |
122 self.name_to_subtk = {} # subname : SubmasterTk instance | |
123 self.current_row = 0 | |
124 | |
125 self.make_key_hints() | |
126 self.draw_sliders() | |
127 self.highlight_row(self.current_row) | |
128 self.rows[self.current_row].focus() | |
129 | |
130 self.buttonframe = Frame(self, bg='black') | |
131 self.buttonframe.pack(side=BOTTOM) | |
132 self.refreshbutton = Button(self.buttonframe, text="Refresh", | |
133 command=self.refresh, bg='black', fg='white') | |
134 self.refreshbutton.pack(side=LEFT) | |
135 self.save_stage_button = Button(self.buttonframe, text="Save", | |
136 command=lambda: self.save_current_stage(self.sub_name.get()), | |
137 bg='black', fg='white') | |
138 self.save_stage_button.pack(side=LEFT) | |
139 self.sub_name = Entry(self.buttonframe, bg='black', fg='white') | |
140 self.sub_name.pack(side=LEFT) | |
141 self.stop_frequent_update_time = 0 | |
142 def make_key_hints(self): | |
143 keyhintrow = Frame(self) | |
144 | |
145 col = 0 | |
146 for upkey, downkey in zip(nudge_keys['up'], | |
147 nudge_keys['down']): | |
148 # what a hack! | |
149 downkey = downkey.replace('semicolon', ';') | |
150 upkey, downkey = (upkey.upper(), downkey.upper()) | |
151 | |
152 # another what a hack! | |
153 keylabel = Label(keyhintrow, text='%s\n%s' % (upkey, downkey), | |
154 width=1, font=('Arial', 10), bg='red', fg='white', anchor='c') | |
155 keylabel.pack(side=LEFT, expand=1, fill=X) | |
156 col += 1 | |
157 | |
158 keyhintrow.pack(fill=X, expand=0) | |
159 self.keyhints = keyhintrow | |
160 def setup_key_nudgers(self, tkobject): | |
161 for d, keys in nudge_keys.items(): | |
162 for key in keys: | |
163 # lowercase makes full=0 | |
164 keysym = "<KeyPress-%s>" % key | |
165 tkobject.bind(keysym, \ | |
166 lambda evt, num=keys.index(key), d=d: \ | |
167 self.got_nudger(num, d)) | |
168 | |
169 # uppercase makes full=1 | |
170 keysym = "<KeyPress-%s>" % key.upper() | |
171 keysym = keysym.replace('SEMICOLON', 'colon') | |
172 tkobject.bind(keysym, \ | |
173 lambda evt, num=keys.index(key), d=d: \ | |
174 self.got_nudger(num, d, full=1)) | |
175 | |
176 # Row changing: | |
177 # Page dn, C-n, and ] do down | |
178 # Page up, C-p, and ' do up | |
179 for key in '<Prior> <Next> <Control-n> <Control-p> ' \ | |
180 '<Key-bracketright> <Key-apostrophe>'.split(): | |
181 tkobject.bind(key, self.change_row) | |
182 | |
183 def change_row(self, event): | |
184 diff = 1 | |
185 if event.keysym in ('Prior', 'p', 'bracketright'): | |
186 diff = -1 | |
187 old_row = self.current_row | |
188 self.current_row += diff | |
189 self.current_row = max(0, self.current_row) | |
190 self.current_row = min(len(self.rows) - 1, self.current_row) | |
191 self.unhighlight_row(old_row) | |
192 self.highlight_row(self.current_row) | |
193 row = self.rows[self.current_row] | |
194 self.keyhints.pack_configure(before=row) | |
195 def got_nudger(self, number, direction, full=0): | |
196 subtk = self.slider_table[(self.current_row, number)] | |
197 if direction == 'up': | |
198 if full: | |
199 subtk.scale.fade(1) | |
200 else: | |
201 subtk.scale.increase() | |
202 else: | |
203 if full: | |
204 subtk.scale.fade(0) | |
205 else: | |
206 subtk.scale.decrease() | |
207 def draw_sliders(self): | |
208 self.tk_focusFollowsMouse() | |
209 | |
210 rowcount = -1 | |
211 col = 0 | |
212 for sub in self.submasters.get_all_subs(): | |
213 if col == 0: # make new row | |
214 row = self.make_row() | |
215 rowcount += 1 | |
216 current_level = self.current_sub_levels.get(sub.name, 0) | |
217 subtk = self.draw_sub_slider(row, col, sub.name, current_level) | |
218 self.slider_table[(rowcount, col)] = subtk | |
219 self.name_to_subtk[sub.name] = subtk | |
220 col += 1 | |
221 col %= 10 | |
222 | |
223 def slider_changed(x, y, z, subtk=subtk): | |
224 subtk.scale.draw_indicator_colors() | |
225 self.send_levels() | |
226 | |
227 subtk.slider_var.trace('w', slider_changed) | |
228 def make_row(self): | |
229 row = Frame(self, bd=2, bg='black') | |
230 row.pack(expand=1, fill=BOTH) | |
231 self.setup_key_nudgers(row) | |
232 self.rows.append(row) | |
233 return row | |
234 def draw_sub_slider(self, row, col, name, current_level): | |
235 subtk = SubmasterTk(row, name, current_level, self) | |
236 subtk.place(relx=col * 0.1, rely=0, relwidth=0.1, relheight=1) | |
237 self.setup_key_nudgers(subtk.scale) | |
238 | |
239 self.slider_vars[name] = subtk.slider_var | |
240 return subtk | |
241 def highlight_row(self, row): | |
242 row = self.rows[row] | |
243 row['bg'] = 'red' | |
244 def unhighlight_row(self, row): | |
245 row = self.rows[row] | |
246 row['bg'] = 'black' | |
247 def get_levels(self): | |
248 return dict([(name, slidervar.get()) | |
249 for name, slidervar in self.slider_vars.items()]) | |
250 def get_levels_as_sub(self): | |
251 scaledsubs = [self.submasters.get_sub_by_name(sub) * level \ | |
252 for sub, level in self.get_levels().items()] | |
253 | |
254 maxes = sub_maxes(*scaledsubs) | |
255 return maxes | |
256 def save_current_stage(self, subname): | |
257 print "saving current levels as", subname | |
258 sub = self.get_levels_as_sub() | |
259 sub.name = subname | |
260 sub.save() | |
261 | |
262 def save(self): | |
263 pickle.dump(self.get_levels(), | |
264 file('.keyboardcomposer.savedlevels', 'w')) | |
265 def send_frequent_updates(self): | |
266 """called when we get a fade -- send events as quickly as possible""" | |
267 if time.time() <= self.stop_frequent_update_time: | |
268 self.send_levels() | |
269 self.after(10, self.send_frequent_updates) | |
270 | |
271 def get_dmx_list(self): | |
272 maxes = self.get_levels_as_sub() | |
273 return maxes.get_dmx_list() | |
274 def send_levels(self): | |
275 if not self.dmxdummy: | |
276 levels = self.get_dmx_list() | |
277 dmxclient.outputlevels(levels) | |
278 # print "sending levels", levels | |
279 def send_levels_loop(self): | |
280 self.send_levels() | |
281 self.after(1000, self.send_levels_loop) | |
282 def refresh(self): | |
283 self.save() | |
284 self.submasters = Submasters() | |
285 self.current_sub_levels = \ | |
286 pickle.load(file('.keyboardcomposer.savedlevels')) | |
287 for r in self.rows: | |
288 r.destroy() | |
289 self.keyhints.destroy() | |
290 self.buttonframe.destroy() | |
291 self.draw_ui() | |
292 def toggle_sub_recording(self, subname): | |
293 # xor the set with the subname | |
294 self.subs_being_recorded = self.subs_being_recorded ^ Set([subname]) | |
295 def got_timestamp(self, timestamp): | |
296 """Music player should ultimately call this (over XML-RPC). | |
297 | |
298 For subs not being recorded, we bring up their values (unless | |
299 they're independent maybe? -- independence not implemented yet). | |
300 | |
301 For subs being recorded, we record their values to disk. | |
302 | |
303 Each SubmasterTk talks to the SubLevelLogger to record and playback | |
304 levels.""" | |
305 for sub in self.submasters.get_all_subs(): | |
306 name = sub.name | |
307 subtk = self.name_to_subtk[name] | |
308 if name in self.subs_being_recorded: | |
309 subtk.record(timestamp) | |
310 else: | |
311 subtk.get_recorded_level(timestamp) | |
312 | |
313 if __name__ == "__main__": | |
314 s = Submasters() | |
315 | |
316 root = Tk() | |
317 tl = toplevelat("Keyboard Recorder XP MX 2004 Gold", existingtoplevel=root) | |
318 kc = KeyboardRecorder(tl, s, dmxdummy=0) | |
319 kc.pack(fill=BOTH, expand=1) | |
320 atexit.register(kc.save) | |
321 try: | |
322 mainloop() | |
323 except KeyboardInterrupt: | |
324 tl.destroy() | |
325 sys.exit() |