Stereo plugin
[lv2fil.git] / ui
blob912ab28ce3e627cec9ba9f8a20ccf7d40372bc52
1 #!/usr/bin/env python
3 # Copyright (C) 2008,2009 Nedko Arnaudov <nedko@arnaudov.name>
4 # Copyright (C) 2006 Leonard Ritter <contact@leonard-ritter.com>
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; version 2 of the License
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19 import os
20 import sys
21 import gtk
22 import gtk.glade
23 import gobject
24 import re
25 import time
26 import xml.dom.minidom
27 import cairo
28 from math import pi, sin, cos, atan2, log
29 from colorsys import hls_to_rgb, rgb_to_hls
30 import liblo
31 import copy
33 def map_coords_linear(x,y):
34 return x,1.0-y
36 def map_coords_spheric(x,y):
37 nx = cos(x * 2 * pi) * y
38 ny = -sin(x * 2 * pi) * y
39 return nx, ny
41 def get_peaks(f, tolerance=0.01, maxd=0.01, mapfunc=map_coords_linear):
42 corners = 360
43 yc = 1.0/corners
44 peaks = []
45 x0,y0 = 0.0,0.0
46 t0 = -9999.0
47 i0 = 0
48 for i in xrange(int(corners)):
49 p = i*yc
50 a = f(p)
51 x,y = mapfunc(p, a)
52 if i == 0:
53 x0,y0 = x,y
54 t = atan2((y0 - y), (x0 - x)) / (2*pi)
55 td = t - t0
56 if (abs(td) >= tolerance):
57 t0 = t
58 peaks.append((x,y))
59 x0,y0 = x,y
60 return peaks
62 def make_knobshape(gaps, gapdepth):
63 def knobshape_func(x):
64 x = (x*gaps)%1.0
65 w = 0.5
66 g1 = 0.5 - w*0.5
67 g2 = 0.5 + w*0.5
68 if (x >= g1) and (x < 0.5):
69 x = (x-g1)/(w*0.5)
70 return 0.5 - gapdepth * x * 0.9
71 elif (x >= 0.5) and (x < g2):
72 x = (x-0.5)/(w*0.5)
73 return 0.5 - gapdepth * (1-x) * 0.9
74 else:
75 return 0.5
76 return get_peaks(knobshape_func, 0.03, 0.05, map_coords_spheric)
78 def hls_to_color(h,l,s):
79 r,g,b = hls_to_rgb(h,l,s)
80 return gtk.gdk.color_parse('#%04X%04X%04X' % (int(r*65535),int(g*65535),int(b*65535)))
82 def color_to_hls(color):
83 string = color.to_string()
84 r = int(string[1:5], 16) / 65535.0
85 g = int(string[5:9], 16) / 65535.0
86 b = int(string[9:13], 16) / 65535.0
87 return rgb_to_hls(r, g, b)
89 MARKER_NONE = ''
90 MARKER_LINE = 'line'
91 MARKER_ARROW = 'arrow'
92 MARKER_DOT = 'dot'
94 LEGEND_NONE = ''
95 LEGEND_DOTS = 'dots' # painted dots
96 LEGEND_LINES = 'lines' # painted ray-like lines
97 LEGEND_RULER = 'ruler' # painted ray-like lines + a circular one
98 LEGEND_RULER_INWARDS = 'ruler-inwards' # same as ruler, but the circle is on the outside
99 LEGEND_LED_SCALE = 'led-scale' # an LCD scale
100 LEGEND_LED_DOTS = 'led-dots' # leds around the knob
102 class KnobTooltip:
103 def __init__(self):
104 self.tooltip_window = gtk.Window(gtk.WINDOW_POPUP)
105 self.tooltip = gtk.Label()
106 #self.tooltip.modify_fg(gtk.STATE_NORMAL, hls_to_color(0.0, 1.0, 0.0))
107 self.tooltip_timeout = None
108 vbox = gtk.VBox()
109 vbox2 = gtk.VBox()
110 vbox2.add(self.tooltip)
111 vbox2.set_border_width(2)
112 vbox.add(vbox2)
113 self.tooltip_window.add(vbox)
114 vbox.connect('expose-event', self.on_tooltip_expose)
116 def show_tooltip(self, knob):
117 text = knob.format_value()
118 rc = knob.get_allocation()
119 x,y = knob.window.get_origin()
120 self.tooltip_window.show_all()
121 w,h = self.tooltip_window.get_size()
122 wx,wy = x+rc.x-w, y+rc.y+rc.height/2-h/2
123 self.tooltip_window.move(wx,wy)
124 rc = self.tooltip_window.get_allocation()
125 self.tooltip_window.window.invalidate_rect((0,0,rc.width,rc.height), False)
126 self.tooltip.set_text(text)
127 if self.tooltip_timeout:
128 gobject.source_remove(self.tooltip_timeout)
129 self.tooltip_timeout = gobject.timeout_add(500, self.hide_tooltip)
131 def hide_tooltip(self):
132 self.tooltip_window.hide_all()
134 def on_tooltip_expose(self, widget, event):
135 ctx = widget.window.cairo_create()
136 rc = widget.get_allocation()
137 #ctx.set_source_rgb(*hls_to_rgb(0.0, 0.0, 0.5))
138 #ctx.paint()
139 ctx.set_source_rgb(*hls_to_rgb(0.0, 0.0, 0.5))
140 ctx.translate(0.5, 0.5)
141 ctx.set_line_width(1)
142 ctx.rectangle(rc.x, rc.y,rc.width-1,rc.height-1)
143 ctx.stroke()
144 return False
148 knob_tooltip = None
149 def get_knob_tooltip():
150 global knob_tooltip
151 if not knob_tooltip:
152 knob_tooltip = KnobTooltip()
153 return knob_tooltip
155 class SmartAdjustment(gtk.Adjustment):
156 def __init__(self, log=False, value=0, lower=0, upper=0, step_incr=0, page_incr=0, page_size=0):
157 self.log = log
158 gtk.Adjustment.__init__(self, value, lower, upper, step_incr, page_incr, page_size)
159 self.normalized_value = self.real2norm(self.value)
161 def real2norm(self, value):
162 if self.log:
163 return log(value / self.lower, self.upper / self.lower)
164 else:
165 return (value - self.lower) / (self.upper - self.lower)
167 def norm2real(self, value):
168 if self.log:
169 return self.lower * pow(self.upper / self.lower, value)
170 else:
171 return value * (self.upper - self.lower) + self.lower
173 def set_value(self, value):
174 self.normalized_value = self.real2norm(value)
175 gtk.Adjustment.set_value(self, value)
177 def get_normalized_value(self):
178 return self.normalized_value
180 def set_normalized_value(self, value):
181 self.normalized_value = value
183 if self.normalized_value < 0.0:
184 self.normalized_value = 0.0
185 elif self.normalized_value > 1.0:
186 self.normalized_value = 1.0
188 self.set_value(self.norm2real(self.normalized_value))
190 class Knob(gtk.VBox):
191 def __init__(self):
192 gtk.VBox.__init__(self)
193 self.gapdepth = 4
194 self.gaps = 10
195 self.value = 0.0
196 self.min_value = 0.0
197 self.max_value = 127.0
198 self.fg_hls = 0.0, 0.7, 0.0
199 self.legend_hls = None
200 self.dragging = False
201 self.start = 0.0
202 self.digits = 2
203 self.segments = 13
204 self.label = ''
205 self.marker = MARKER_LINE
206 self.angle = (3.0/4.0) * 2 * pi
207 self.knobshape = None
208 self.legend = LEGEND_DOTS
209 self.lsize = 2
210 self.lscale = False
211 self.set_double_buffered(True)
212 self.connect('realize', self.on_realize)
213 self.connect("size_allocate", self.on_size_allocate)
214 self.connect('expose-event', self.on_expose)
215 self.set_border_width(6)
216 self.set_size_request(50, 50)
217 self.tooltip_enabled = False
218 self.adj = None
220 def set_adjustment(self, adj):
221 self.min_value = 0.0
222 self.max_value = 1.0
223 self.value = adj.get_normalized_value()
224 if self.adj:
225 self.adj.disconnect(self.adj_id)
226 self.adj = adj
227 self.adj_id = adj.connect("value-changed", self.on_adj_value_changed)
229 def is_sensitive(self):
230 return self.get_property("sensitive")
232 def format_value(self):
233 if self.adj:
234 value = self.adj.value
235 else:
236 value = self.value
237 return ("%%.%if" % self.digits) % value
239 def show_tooltip(self):
240 if self.tooltip_enabled:
241 get_knob_tooltip().show_tooltip(self)
243 def on_realize(self, widget):
244 self.root = self.get_toplevel()
245 self.root.add_events(gtk.gdk.ALL_EVENTS_MASK)
246 self.root.connect('scroll-event', self.on_mousewheel)
247 self.root.connect('button-press-event', self.on_left_down)
248 self.root.connect('button-release-event', self.on_left_up)
249 self.root.connect('motion-notify-event', self.on_motion)
250 self.update_knobshape()
252 def update_knobshape(self):
253 rc = self.get_allocation()
254 b = self.get_border_width()
255 size = min(rc.width, rc.height) - 2*b
256 gd = float(self.gapdepth*0.5) / size
257 self.gd = gd
258 self.knobshape = make_knobshape(self.gaps, gd)
260 def set_legend_scale(self, scale):
261 self.lscale = scale
262 self.refresh()
264 def set_legend_line_width(self, width):
265 self.lsize = width
266 self.refresh()
268 def set_segments(self, segments):
269 self.segments = segments
270 self.refresh()
272 def set_marker(self, marker):
273 self.marker = marker
274 self.refresh()
276 def set_range(self, minvalue, maxvalue):
277 self.min_value = minvalue
278 self.max_value = maxvalue
279 self.set_value(self.value)
281 def quantize_value(self, value):
282 scaler = 10**self.digits
283 value = int((value*scaler)+0.5) / float(scaler)
284 return value
286 def on_adj_value_changed(self, adj):
287 new_value = adj.get_normalized_value()
288 if self.value != new_value:
289 self.value = new_value
290 self.refresh()
292 def set_value(self, value):
293 oldval = self.value
294 self.value = min(max(self.quantize_value(value), self.min_value), self.max_value)
295 if self.value != oldval:
296 if self.adj:
297 self.adj.set_normalized_value(value)
298 self.refresh()
300 def get_value(self):
301 return self.value
303 def set_top_color(self, h, l, s):
304 self.fg_hls = h,l,s
305 self.refresh()
307 def set_legend_color(self, h, l, s):
308 self.legend_hls = h,l,s
309 self.refresh()
311 def get_top_color(self):
312 return self.fg_hls
314 def set_gaps(self, gaps):
315 self.gaps = gaps
316 self.knobshape = None
317 self.refresh()
319 def get_gaps(self):
320 return self.gaps
322 def set_gap_depth(self, gapdepth):
323 self.gapdepth = gapdepth
324 self.knobshape = None
325 self.refresh()
327 def get_gap_depth(self):
328 return self.gapdepth
330 def set_angle(self, angle):
331 self.angle = angle
332 self.refresh()
334 def get_angle(self):
335 return self.angle
337 def set_legend(self, legend):
338 self.legend = legend
339 self.refresh()
341 def get_legend(self):
342 return self.legend
344 def on_left_down(self, widget, event):
345 #print "on_left_down"
347 # dont drag insensitive widgets
348 if not self.is_sensitive():
349 return False
351 if not sum(self.get_allocation().intersect((int(event.x), int(event.y), 1, 1))):
352 return False
353 if event.button == 1:
354 #print "start draggin"
355 self.startvalue = self.value
356 self.start = event.y
357 self.dragging = True
358 self.show_tooltip()
359 self.grab_add()
360 return True
361 return False
363 def on_left_up(self, widget, event):
364 #print "on_left_up"
365 if not self.dragging:
366 return False
367 if event.button == 1:
368 #print "stop draggin"
369 self.dragging = False
370 self.grab_remove()
371 return True
372 return False
374 def on_motion(self, widget, event):
375 #print "on_motion"
377 # dont drag insensitive widgets
378 if not self.is_sensitive():
379 return False
381 if self.dragging:
382 x,y,state = self.window.get_pointer()
383 rc = self.get_allocation()
384 range = self.max_value - self.min_value
385 scale = rc.height
386 if event.state & gtk.gdk.SHIFT_MASK:
387 scale = rc.height*8
388 value = self.startvalue - ((y - self.start)*range)/scale
389 oldval = self.value
390 self.set_value(value)
391 self.show_tooltip()
392 if oldval != self.value:
393 self.start = y
394 self.startvalue = self.value
395 return True
396 return False
398 def on_mousewheel(self, widget, event):
400 # dont move insensitive widgets
401 if not self.is_sensitive():
402 return False
404 if not sum(self.get_allocation().intersect((int(event.x), int(event.y), 1, 1))):
405 return
406 range = self.max_value - self.min_value
407 minstep = 1.0 / (10**self.digits)
408 if event.state & (gtk.gdk.SHIFT_MASK | gtk.gdk.BUTTON1_MASK):
409 step = minstep
410 else:
411 step = max(self.quantize_value(range/25.0), minstep)
412 value = self.value
413 if event.direction == gtk.gdk.SCROLL_UP:
414 value += step
415 elif event.direction == gtk.gdk.SCROLL_DOWN:
416 value -= step
417 self.set_value(value)
418 self.show_tooltip()
420 def on_size_allocate(self, widget, allocation):
421 #print allocation.x, allocation.y, allocation.width, allocation.height
422 self.update_knobshape()
424 def draw_points(self, ctx, peaks):
425 ctx.move_to(*peaks[0])
426 for peak in peaks[1:]:
427 ctx.line_to(*peak)
429 def draw(self, ctx):
430 if not self.legend_hls:
431 self.legend_hls = color_to_hls(self.style.fg[gtk.STATE_NORMAL])
433 if not self.knobshape:
434 self.update_knobshape()
435 startangle = pi*1.5 - self.angle*0.5
436 angle = ((self.value - self.min_value) / (self.max_value - self.min_value)) * self.angle + startangle
437 rc = self.get_allocation()
438 size = min(rc.width, rc.height)
440 kh = self.get_border_width() # knob height
442 ps = 1.0/size # pixel size
443 ps2 = 1.0 / (size-(2*kh)-1) # pixel size inside knob
444 ss = ps * kh # shadow size
445 lsize = ps2 * self.lsize # legend line width
446 # draw spherical
447 ctx.translate(rc.x, rc.y)
448 ctx.translate(0.5,0.5)
449 ctx.translate(size*0.5, size*0.5)
450 ctx.scale(size-(2*kh)-1, size-(2*kh)-1)
451 if self.legend == LEGEND_DOTS:
452 ctx.save()
453 ctx.set_source_rgb(*hls_to_rgb(*self.legend_hls))
454 dots = self.segments
455 for i in xrange(dots):
456 s = float(i)/(dots-1)
457 a = startangle + self.angle*s
458 ctx.save()
459 ctx.rotate(a)
460 r = lsize*0.5
461 if self.lscale:
462 r = max(r*s,ps2)
463 ctx.arc(0.5+lsize, 0.0, r, 0.0, 2*pi)
464 ctx.fill()
465 ctx.restore()
466 ctx.restore()
467 elif self.legend in (LEGEND_LINES, LEGEND_RULER, LEGEND_RULER_INWARDS):
468 ctx.save()
469 ctx.set_source_rgb(*hls_to_rgb(*self.legend_hls))
470 dots = self.segments
471 n = ps2*(kh-1)
472 for i in xrange(dots):
473 s = float(i)/(dots-1)
474 a = startangle + self.angle*s
475 ctx.save()
476 ctx.rotate(a)
477 r = n*0.9
478 if self.lscale:
479 r = max(r*s,ps2)
480 ctx.move_to(0.5+ps2+n*0.1, 0.0)
481 ctx.line_to(0.5+ps2+n*0.1+r, 0.0)
482 ctx.set_line_width(lsize)
483 ctx.stroke()
484 ctx.restore()
485 ctx.restore()
486 if self.legend == LEGEND_RULER:
487 ctx.save()
488 ctx.set_source_rgb(*hls_to_rgb(*self.legend_hls))
489 ctx.set_line_width(lsize)
490 ctx.arc(0.0, 0.0, 0.5+ps2+n*0.1, startangle, startangle+self.angle)
491 ctx.stroke()
492 ctx.restore()
493 elif self.legend == LEGEND_RULER_INWARDS:
494 ctx.save()
495 ctx.set_source_rgb(*hls_to_rgb(*self.legend_hls))
496 ctx.set_line_width(lsize)
497 ctx.arc(0.0, 0.0, 0.5+ps2+n, startangle, startangle+self.angle)
498 ctx.stroke()
500 # draw shadow only for sensitive widgets that have height
501 if self.is_sensitive() and kh:
502 ctx.save()
503 ctx.translate(ss, ss)
504 ctx.rotate(angle)
505 self.draw_points(ctx, self.knobshape)
506 ctx.close_path()
507 ctx.restore()
508 ctx.set_source_rgba(0,0,0,0.3)
509 ctx.fill()
511 if self.legend in (LEGEND_LED_SCALE, LEGEND_LED_DOTS):
512 ch,cl,cs = self.legend_hls
513 n = ps2*(kh-1)
514 ctx.save()
515 ctx.set_line_cap(cairo.LINE_CAP_ROUND)
516 ctx.set_source_rgb(*hls_to_rgb(ch,cl*0.2,cs))
517 ctx.set_line_width(lsize)
518 ctx.arc(0.0, 0.0, 0.5+ps2+n*0.5, startangle, startangle+self.angle)
519 ctx.stroke()
520 ctx.set_source_rgb(*hls_to_rgb(ch,cl,cs))
521 if self.legend == LEGEND_LED_SCALE:
522 ctx.set_line_width(lsize-ps2*2)
523 ctx.arc(0.0, 0.0, 0.5+ps2+n*0.5, startangle, angle)
524 ctx.stroke()
525 elif self.legend == LEGEND_LED_DOTS:
526 dots = self.segments
527 dsize = lsize-ps2*2
528 seg = self.angle/dots
529 endangle = startangle + self.angle
530 for i in xrange(dots):
531 s = float(i)/(dots-1)
532 a = startangle + self.angle*s
533 if ((a-seg*0.5) > angle) or (angle == startangle):
534 break
535 ctx.save()
536 ctx.rotate(a)
537 r = dsize*0.5
538 if self.lscale:
539 r = max(r*s,ps2)
540 ctx.arc(0.5+ps2+n*0.5, 0.0, r, 0.0, 2*pi)
541 ctx.fill()
542 ctx.restore()
543 ctx.restore()
544 pat = cairo.LinearGradient(-0.5, -0.5, 0.5, 0.5)
545 pat.add_color_stop_rgb(1.0, 0.2,0.2,0.2)
546 pat.add_color_stop_rgb(0.0, 0.3,0.3,0.3)
547 ctx.set_source(pat)
548 ctx.rotate(angle)
549 self.draw_points(ctx, self.knobshape)
550 ctx.close_path()
551 ctx.fill_preserve()
552 ctx.set_source_rgba(0.1,0.1,0.1,1)
553 ctx.save()
554 ctx.identity_matrix()
555 ctx.set_line_width(1.0)
556 ctx.stroke()
557 ctx.restore()
559 ctx.arc(0.0, 0.0, 0.5-self.gd, 0.0, pi*2.0)
560 ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], max(self.fg_hls[1]*0.4,0.0), self.fg_hls[2]))
561 ctx.fill()
562 ctx.arc(0.0, 0.0, 0.5-self.gd-ps, 0.0, pi*2.0)
563 ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], min(self.fg_hls[1]*1.2,1.0), self.fg_hls[2]))
564 ctx.fill()
565 ctx.arc(0.0, 0.0, 0.5-self.gd-(2*ps), 0.0, pi*2.0)
566 ctx.set_source_rgb(*hls_to_rgb(*self.fg_hls))
567 ctx.fill()
569 # dont draw cap for insensitive widgets
570 if not self.is_sensitive():
571 return
573 #~ ctx.set_line_cap(cairo.LINE_CAP_ROUND)
574 #~ ctx.move_to(0.5-0.3-self.gd-ps, 0.0)
575 #~ ctx.line_to(0.5-self.gd-ps*5, 0.0)
577 if self.marker == MARKER_LINE:
578 ctx.set_line_cap(cairo.LINE_CAP_BUTT)
579 ctx.move_to(0.5-0.3-self.gd-ps, 0.0)
580 ctx.line_to(0.5-self.gd-ps, 0.0)
581 ctx.save()
582 ctx.identity_matrix()
583 ctx.translate(0.5,0.5)
584 ctx.set_line_width(5)
585 ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], min(self.fg_hls[1]*1.2,1.0), self.fg_hls[2]))
586 ctx.stroke_preserve()
587 ctx.set_line_width(3)
588 ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], max(self.fg_hls[1]*0.4,0.0), self.fg_hls[2]))
589 ctx.stroke()
590 ctx.restore()
591 elif self.marker == MARKER_DOT:
592 ctx.arc(0.5-0.05-self.gd-ps*5, 0.0, 0.05, 0.0, 2*pi)
593 ctx.save()
594 ctx.identity_matrix()
595 ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], min(self.fg_hls[1]*1.2,1.0), self.fg_hls[2]))
596 ctx.stroke_preserve()
597 ctx.set_line_width(1)
598 ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], max(self.fg_hls[1]*0.4,0.0), self.fg_hls[2]))
599 ctx.fill()
600 ctx.restore()
601 elif self.marker == MARKER_ARROW:
602 ctx.set_line_cap(cairo.LINE_CAP_BUTT)
603 ctx.move_to(0.5-0.3-self.gd-ps, 0.1)
604 ctx.line_to(0.5-0.1-self.gd-ps, 0.0)
605 ctx.line_to(0.5-0.3-self.gd-ps, -0.1)
606 ctx.close_path()
607 ctx.save()
608 ctx.identity_matrix()
609 #~ ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], min(self.fg_hls[1]*1.2,1.0), self.fg_hls[2]))
610 #~ ctx.stroke_preserve()
611 ctx.set_line_width(1)
612 ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], max(self.fg_hls[1]*0.4,0.0), self.fg_hls[2]))
613 ctx.fill()
614 ctx.restore()
616 def refresh(self):
617 rect = self.get_allocation()
618 if self.window:
619 self.window.invalidate_rect(rect, False)
620 return True
622 def on_expose(self, widget, event):
623 self.context = self.window.cairo_create()
624 self.draw(self.context)
625 return False
627 class dssi_ui:
628 def __init__(self, argv):
629 self.fake = len(argv) == 1
631 if self.fake:
632 self.label = self.human_id = "fake"
633 self.shown = False
634 return
636 print repr(argv)
637 self.host_osc_url = argv[1]
638 self.plugin_uri = argv[2]
639 self.label = argv[3]
640 self.human_id = argv[4]
641 if self.human_id == "":
642 self.human_id = self.label
644 self.server = liblo.Server()
645 self.server.add_method(None, None, self.on_osc)
646 self.send_update()
648 def send_update(self):
649 if self.fake:
650 return
651 #print "UI -> update -> host"
652 liblo.send(self.host_osc_url, "//update", self.server.get_url())
654 def send_port_value(self, port_index, value):
655 if self.fake:
656 return
657 liblo.send(self.host_osc_url, "//control", int(port_index), float(value))
659 def send_exiting(self):
660 if self.fake:
661 return
662 liblo.send(self.host_osc_url, "//exiting")
664 def run(self):
665 if self.fake:
666 if not self.shown:
667 self.shown = True
668 self.on_show()
669 return
671 while self.server.recv(0):
672 pass
674 def on_osc(self, path, args, types):
675 if path == "/control" and types == "if":
676 self.on_port_value_changed(args[0], args[1])
677 elif path == "/show" and types == "":
678 self.on_show()
679 elif path == "/hide" and types == "":
680 self.on_hide()
681 elif path == "/quit" and types == "":
682 self.on_quit()
683 else:
684 print "Unhandled OSC message. path='%s' types='%s', args=%s" % (path, types, repr(args))
686 def on_port_value_changed(self, port_index, port_value):
687 return
689 def on_show(self):
690 return
692 def on_hide(self):
693 return
695 def on_quit(self):
696 return
698 class filter_ui(dssi_ui):
699 def __init__(self, argv):
700 dssi_ui.__init__(self, argv)
702 if self.plugin_uri == "http://nedko.aranaudov.org/soft/filter/1/mono":
703 self.port_base = 2
704 elif self.plugin_uri == "http://nedko.aranaudov.org/soft/filter/1/stereo":
705 self.port_base = 4
706 else:
707 return
709 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
710 #self.window.set_size_request(600, 400)
711 self.window.set_title("%s (4-band parametric filter)" % self.human_id)
712 self.window.set_role("plugin_ui")
714 self.top_vbox = gtk.VBox()
716 align = gtk.Alignment(0.5, 0.5, 1.0, 1.0)
717 align.set_padding(4, 10, 10, 10)
718 align.add(self.top_vbox)
720 self.window.add(align)
722 self.param_hbox = gtk.HBox()
723 self.top_vbox.pack_start(self.param_hbox)
725 self.param_hbox.set_spacing(10)
727 self.initator = False
729 self.ports = []
731 master_box = gtk.VBox()
732 master_box.set_spacing(5)
734 port = {'index': 0, 'name': 'Active', 'type': 'toggle'}
735 self.ports.append(port)
736 self.add_param_box(master_box, self.create_toggle_box(port))
738 port = {'index': 1, 'name': 'Gain', 'type': 'knob', 'min': -20.0, 'max': 20.0, 'unit': 'dB', 'log': False}
739 self.ports.append(port)
740 self.add_param_box(master_box, self.create_knob_box(port))
742 self.param_hbox.pack_start(master_box, True, True)
744 band_parameters = [
745 {'name': 'Active', 'type': 'toggle'},
746 {'name': 'Frequency', 'type': 'knob', 'unit': 'Hz', 'log': True},
747 {'name': 'Bandwidth', 'type': 'knob', 'min': 0.125, 'max': 8.0, 'unit': '', 'log': True},
748 {'name': 'Gain', 'type': 'knob', 'min': -20.0, 'max': 20.0, 'unit': 'dB', 'log': False}]
750 freq_min = [ 20.0, 40.0, 100.0, 200.0]
751 freq_max = [2000.0, 4000.0, 10000.0, 20000.0]
753 port_index = 2
755 for i in [0, 1, 2, 3]:
756 band_frame = gtk.Frame("Band %d" % (i + 1))
757 band_frame.set_label_align(0.5, 0.5)
759 band_box = gtk.VBox()
760 band_box.set_spacing(5)
762 for parameter in band_parameters:
764 port = parameter.copy()
765 port['index'] = port_index
766 port_index += 1
768 if port['name'] == 'Frequency':
769 port['min'] = freq_min[i]
770 port['max'] = freq_max[i]
772 self.ports.append(port)
774 #param_box.set_spacing(5)
775 if port['type'] == 'knob':
776 self.add_param_box(band_box, self.create_knob_box(port))
777 elif port['type'] == 'toggle':
778 self.add_param_box(band_box, self.create_toggle_box(port))
780 band_frame.add(band_box)
782 self.param_hbox.pack_start(band_frame, True, True)
784 self.initator = True
786 def create_knob_box(self, port):
787 param_box = gtk.VBox()
788 step = (port['max'] - port['min']) / 100
789 adj = SmartAdjustment(port['log'], port['min'], port['min'], port['max'], step, step * 20)
790 adj.port = port
791 port['adj'] = adj
793 adj.connect("value-changed", self.on_adj_value_changed)
795 knob = Knob()
796 knob.set_adjustment(adj)
797 align = gtk.Alignment(0.5, 0.5, 0, 0)
798 align.set_padding(0, 0, 20, 20)
799 align.add(knob)
800 param_box.pack_start(align, False)
802 adj.label = gtk.Label("xxx")
803 param_box.pack_start(adj.label, False)
804 #spin = gtk.SpinButton(adj, 0.0, 2)
805 #param_box.pack_start(spin, False)
807 label = gtk.Label(port['name'])
808 param_box.pack_start(label, False)
809 return param_box
811 def create_toggle_box(self, port):
812 param_box = gtk.VBox()
813 button = gtk.CheckButton(port['name'])
814 button.port = port
816 button.connect("toggled", self.on_button_toggled)
818 align = gtk.Alignment(0.5, 0.5, 0, 0)
819 align.add(button)
820 param_box.pack_start(align, False)
821 return param_box
823 def add_param_box(self, container, param_box):
824 align = gtk.Alignment(0.5, 0.5, 1.0, 1.0)
825 align.set_padding(10, 10, 10, 10)
826 align.add(param_box)
828 container.pack_start(align, True)
830 def on_adj_value_changed(self, adj):
831 value = adj.get_value()
832 if value >= 10000:
833 format = "%.0f"
834 elif value >= 1000:
835 format = "%.1f"
836 else:
837 format = "%.2f"
838 text = format % value
839 unit = adj.port['unit']
840 if unit:
841 text += " " + unit
843 adj.label.set_text(text)
845 if self.initator:
846 #print adj.port, adj.get_value()
847 self.send_port_value(adj.port['index'] + self.port_base, value)
849 def on_button_toggled(self, widget):
850 if self.initator:
851 if widget.get_active():
852 value = 1.0
853 else:
854 value = 0.0
855 self.send_port_value(widget.port['index'] + self.port_base, value)
857 def run(self):
858 self.window.connect("destroy", self.on_window_closed)
859 gobject.timeout_add(50, self.on_idle)
860 gtk.main()
862 def on_idle(self):
863 dssi_ui.run(self)
864 return True
866 def on_port_value_changed(self, port_index, port_value):
867 #print "port %d set to %f" % (port_index, port_value)
868 port_index -= self.port_base
869 port = self.ports[port_index]
870 port_type = port['type']
871 if port_type == 'knob':
872 if port.has_key('adj'):
873 self.initator = False
874 port['adj'].set_value(port_value)
875 self.initator = True
876 elif port_type == 'toggle':
877 if port.has_key('widget'):
878 if port_value < 0.0:
879 toggled = False
880 else:
881 toggled = True
883 self.initator = False
884 port['widget'].set_active(toggled)
885 self.initator = True
887 def on_show(self):
888 self.window.show_all()
890 def on_hide(self):
891 self.window.hide_all()
893 def on_quit(self):
894 gtk.main_quit()
896 def on_window_closed(self, arg):
897 self.send_exiting()
898 gtk.main_quit()
900 def main():
901 filter_ui(sys.argv).run()
902 print "main done"
904 if __name__ == '__main__':
905 main()