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()