| 1 | #!/usr/bin/env python |
|---|
| 2 | # |
|---|
| 3 | # Copyright (C) 2008,2009 Nedko Arnaudov <nedko@arnaudov.name> |
|---|
| 4 | # Copyright (C) 2006 Leonard Ritter <contact@leonard-ritter.com> |
|---|
| 5 | # Filter response code by Fons Adriaensen |
|---|
| 6 | # |
|---|
| 7 | # This program is free software; you can redistribute it and/or modify |
|---|
| 8 | # it under the terms of the GNU General Public License as published by |
|---|
| 9 | # the Free Software Foundation; version 2 of the License |
|---|
| 10 | # |
|---|
| 11 | # This program is distributed in the hope that it will be useful, |
|---|
| 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 14 | # GNU General Public License for more details. |
|---|
| 15 | # |
|---|
| 16 | # You should have received a copy of the GNU General Public License |
|---|
| 17 | # along with this program; if not, write to the Free Software |
|---|
| 18 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|---|
| 19 | |
|---|
| 20 | import sys |
|---|
| 21 | import os |
|---|
| 22 | import fcntl |
|---|
| 23 | import gtk |
|---|
| 24 | import gobject |
|---|
| 25 | import cairo |
|---|
| 26 | from math import pi, sin, cos, atan2, log, sqrt, hypot, log10 |
|---|
| 27 | from colorsys import hls_to_rgb, rgb_to_hls |
|---|
| 28 | |
|---|
| 29 | def map_coords_linear(x,y): |
|---|
| 30 | return x,1.0-y |
|---|
| 31 | |
|---|
| 32 | def map_coords_spheric(x,y): |
|---|
| 33 | nx = cos(x * 2 * pi) * y |
|---|
| 34 | ny = -sin(x * 2 * pi) * y |
|---|
| 35 | return nx, ny |
|---|
| 36 | |
|---|
| 37 | def get_peaks(f, tolerance=0.01, maxd=0.01, mapfunc=map_coords_linear): |
|---|
| 38 | corners = 360 |
|---|
| 39 | yc = 1.0/corners |
|---|
| 40 | peaks = [] |
|---|
| 41 | x0,y0 = 0.0,0.0 |
|---|
| 42 | t0 = -9999.0 |
|---|
| 43 | i0 = 0 |
|---|
| 44 | for i in xrange(int(corners)): |
|---|
| 45 | p = i*yc |
|---|
| 46 | a = f(p) |
|---|
| 47 | x,y = mapfunc(p, a) |
|---|
| 48 | if i == 0: |
|---|
| 49 | x0,y0 = x,y |
|---|
| 50 | t = atan2((y0 - y), (x0 - x)) / (2*pi) |
|---|
| 51 | td = t - t0 |
|---|
| 52 | if (abs(td) >= tolerance): |
|---|
| 53 | t0 = t |
|---|
| 54 | peaks.append((x,y)) |
|---|
| 55 | x0,y0 = x,y |
|---|
| 56 | return peaks |
|---|
| 57 | |
|---|
| 58 | def make_knobshape(gaps, gapdepth): |
|---|
| 59 | def knobshape_func(x): |
|---|
| 60 | x = (x*gaps)%1.0 |
|---|
| 61 | w = 0.5 |
|---|
| 62 | g1 = 0.5 - w*0.5 |
|---|
| 63 | g2 = 0.5 + w*0.5 |
|---|
| 64 | if (x >= g1) and (x < 0.5): |
|---|
| 65 | x = (x-g1)/(w*0.5) |
|---|
| 66 | return 0.5 - gapdepth * x * 0.9 |
|---|
| 67 | elif (x >= 0.5) and (x < g2): |
|---|
| 68 | x = (x-0.5)/(w*0.5) |
|---|
| 69 | return 0.5 - gapdepth * (1-x) * 0.9 |
|---|
| 70 | else: |
|---|
| 71 | return 0.5 |
|---|
| 72 | return get_peaks(knobshape_func, 0.03, 0.05, map_coords_spheric) |
|---|
| 73 | |
|---|
| 74 | def hls_to_color(h,l,s): |
|---|
| 75 | r,g,b = hls_to_rgb(h,l,s) |
|---|
| 76 | return gtk.gdk.color_parse('#%04X%04X%04X' % (int(r*65535),int(g*65535),int(b*65535))) |
|---|
| 77 | |
|---|
| 78 | def color_to_hls(color): |
|---|
| 79 | string = color.to_string() |
|---|
| 80 | r = int(string[1:5], 16) / 65535.0 |
|---|
| 81 | g = int(string[5:9], 16) / 65535.0 |
|---|
| 82 | b = int(string[9:13], 16) / 65535.0 |
|---|
| 83 | return rgb_to_hls(r, g, b) |
|---|
| 84 | |
|---|
| 85 | MARKER_NONE = '' |
|---|
| 86 | MARKER_LINE = 'line' |
|---|
| 87 | MARKER_ARROW = 'arrow' |
|---|
| 88 | MARKER_DOT = 'dot' |
|---|
| 89 | |
|---|
| 90 | LEGEND_NONE = '' |
|---|
| 91 | LEGEND_DOTS = 'dots' # painted dots |
|---|
| 92 | LEGEND_LINES = 'lines' # painted ray-like lines |
|---|
| 93 | LEGEND_RULER = 'ruler' # painted ray-like lines + a circular one |
|---|
| 94 | LEGEND_RULER_INWARDS = 'ruler-inwards' # same as ruler, but the circle is on the outside |
|---|
| 95 | LEGEND_LED_SCALE = 'led-scale' # an LCD scale |
|---|
| 96 | LEGEND_LED_DOTS = 'led-dots' # leds around the knob |
|---|
| 97 | |
|---|
| 98 | class KnobTooltip: |
|---|
| 99 | def __init__(self): |
|---|
| 100 | self.tooltip_window = gtk.Window(gtk.WINDOW_POPUP) |
|---|
| 101 | self.tooltip = gtk.Label() |
|---|
| 102 | #self.tooltip.modify_fg(gtk.STATE_NORMAL, hls_to_color(0.0, 1.0, 0.0)) |
|---|
| 103 | self.tooltip_timeout = None |
|---|
| 104 | vbox = gtk.VBox() |
|---|
| 105 | vbox2 = gtk.VBox() |
|---|
| 106 | vbox2.add(self.tooltip) |
|---|
| 107 | vbox2.set_border_width(2) |
|---|
| 108 | vbox.add(vbox2) |
|---|
| 109 | self.tooltip_window.add(vbox) |
|---|
| 110 | vbox.connect('expose-event', self.on_tooltip_expose) |
|---|
| 111 | |
|---|
| 112 | def show_tooltip(self, knob): |
|---|
| 113 | text = knob.format_value() |
|---|
| 114 | rc = knob.get_allocation() |
|---|
| 115 | x,y = knob.window.get_origin() |
|---|
| 116 | self.tooltip_window.show_all() |
|---|
| 117 | w,h = self.tooltip_window.get_size() |
|---|
| 118 | wx,wy = x+rc.x-w, y+rc.y+rc.height/2-h/2 |
|---|
| 119 | self.tooltip_window.move(wx,wy) |
|---|
| 120 | rc = self.tooltip_window.get_allocation() |
|---|
| 121 | self.tooltip_window.window.invalidate_rect((0,0,rc.width,rc.height), False) |
|---|
| 122 | self.tooltip.set_text(text) |
|---|
| 123 | if self.tooltip_timeout: |
|---|
| 124 | gobject.source_remove(self.tooltip_timeout) |
|---|
| 125 | self.tooltip_timeout = gobject.timeout_add(500, self.hide_tooltip) |
|---|
| 126 | |
|---|
| 127 | def hide_tooltip(self): |
|---|
| 128 | self.tooltip_window.hide_all() |
|---|
| 129 | |
|---|
| 130 | def on_tooltip_expose(self, widget, event): |
|---|
| 131 | ctx = widget.window.cairo_create() |
|---|
| 132 | rc = widget.get_allocation() |
|---|
| 133 | #ctx.set_source_rgb(*hls_to_rgb(0.0, 0.0, 0.5)) |
|---|
| 134 | #ctx.paint() |
|---|
| 135 | ctx.set_source_rgb(*hls_to_rgb(0.0, 0.0, 0.5)) |
|---|
| 136 | ctx.translate(0.5, 0.5) |
|---|
| 137 | ctx.set_line_width(1) |
|---|
| 138 | ctx.rectangle(rc.x, rc.y,rc.width-1,rc.height-1) |
|---|
| 139 | ctx.stroke() |
|---|
| 140 | return False |
|---|
| 141 | |
|---|
| 142 | |
|---|
| 143 | |
|---|
| 144 | knob_tooltip = None |
|---|
| 145 | def get_knob_tooltip(): |
|---|
| 146 | global knob_tooltip |
|---|
| 147 | if not knob_tooltip: |
|---|
| 148 | knob_tooltip = KnobTooltip() |
|---|
| 149 | return knob_tooltip |
|---|
| 150 | |
|---|
| 151 | class SmartAdjustment(gtk.Adjustment): |
|---|
| 152 | def __init__(self, log=False, value=0, lower=0, upper=0, step_incr=0, page_incr=0, page_size=0): |
|---|
| 153 | self.log = log |
|---|
| 154 | gtk.Adjustment.__init__(self, value, lower, upper, step_incr, page_incr, page_size) |
|---|
| 155 | self.normalized_value = self.real2norm(self.value) |
|---|
| 156 | |
|---|
| 157 | def real2norm(self, value): |
|---|
| 158 | if self.log: |
|---|
| 159 | return log(value / self.lower, self.upper / self.lower) |
|---|
| 160 | else: |
|---|
| 161 | return (value - self.lower) / (self.upper - self.lower) |
|---|
| 162 | |
|---|
| 163 | def norm2real(self, value): |
|---|
| 164 | if self.log: |
|---|
| 165 | return self.lower * pow(self.upper / self.lower, value) |
|---|
| 166 | else: |
|---|
| 167 | return value * (self.upper - self.lower) + self.lower |
|---|
| 168 | |
|---|
| 169 | def set_value(self, value): |
|---|
| 170 | self.normalized_value = self.real2norm(value) |
|---|
| 171 | gtk.Adjustment.set_value(self, value) |
|---|
| 172 | |
|---|
| 173 | def get_normalized_value(self): |
|---|
| 174 | return self.normalized_value |
|---|
| 175 | |
|---|
| 176 | def set_normalized_value(self, value): |
|---|
| 177 | self.normalized_value = value |
|---|
| 178 | |
|---|
| 179 | if self.normalized_value < 0.0: |
|---|
| 180 | self.normalized_value = 0.0 |
|---|
| 181 | elif self.normalized_value > 1.0: |
|---|
| 182 | self.normalized_value = 1.0 |
|---|
| 183 | |
|---|
| 184 | self.set_value(self.norm2real(self.normalized_value)) |
|---|
| 185 | |
|---|
| 186 | class Knob(gtk.VBox): |
|---|
| 187 | def __init__(self): |
|---|
| 188 | gtk.VBox.__init__(self) |
|---|
| 189 | self.gapdepth = 4 |
|---|
| 190 | self.gaps = 10 |
|---|
| 191 | self.value = 0.0 |
|---|
| 192 | self.min_value = 0.0 |
|---|
| 193 | self.max_value = 127.0 |
|---|
| 194 | self.fg_hls = 0.0, 0.7, 0.0 |
|---|
| 195 | self.legend_hls = None |
|---|
| 196 | self.dragging = False |
|---|
| 197 | self.start = 0.0 |
|---|
| 198 | self.digits = 2 |
|---|
| 199 | self.segments = 13 |
|---|
| 200 | self.label = '' |
|---|
| 201 | self.marker = MARKER_LINE |
|---|
| 202 | self.angle = (3.0/4.0) * 2 * pi |
|---|
| 203 | self.knobshape = None |
|---|
| 204 | self.legend = LEGEND_DOTS |
|---|
| 205 | self.lsize = 2 |
|---|
| 206 | self.lscale = False |
|---|
| 207 | self.set_double_buffered(True) |
|---|
| 208 | self.connect('realize', self.on_realize) |
|---|
| 209 | self.connect("size_allocate", self.on_size_allocate) |
|---|
| 210 | self.connect('expose-event', self.on_expose) |
|---|
| 211 | self.set_border_width(6) |
|---|
| 212 | self.set_size_request(50, 50) |
|---|
| 213 | self.tooltip_enabled = False |
|---|
| 214 | self.adj = None |
|---|
| 215 | |
|---|
| 216 | def set_adjustment(self, adj): |
|---|
| 217 | self.min_value = 0.0 |
|---|
| 218 | self.max_value = 1.0 |
|---|
| 219 | self.value = adj.get_normalized_value() |
|---|
| 220 | if self.adj: |
|---|
| 221 | self.adj.disconnect(self.adj_id) |
|---|
| 222 | self.adj = adj |
|---|
| 223 | self.adj_id = adj.connect("value-changed", self.on_adj_value_changed) |
|---|
| 224 | |
|---|
| 225 | def is_sensitive(self): |
|---|
| 226 | return self.get_property("sensitive") |
|---|
| 227 | |
|---|
| 228 | def format_value(self): |
|---|
| 229 | if self.adj: |
|---|
| 230 | value = self.adj.value |
|---|
| 231 | else: |
|---|
| 232 | value = self.value |
|---|
| 233 | return ("%%.%if" % self.digits) % value |
|---|
| 234 | |
|---|
| 235 | def show_tooltip(self): |
|---|
| 236 | if self.tooltip_enabled: |
|---|
| 237 | get_knob_tooltip().show_tooltip(self) |
|---|
| 238 | |
|---|
| 239 | def on_realize(self, widget): |
|---|
| 240 | self.root = self.get_toplevel() |
|---|
| 241 | self.root.add_events(gtk.gdk.ALL_EVENTS_MASK) |
|---|
| 242 | self.root.connect('scroll-event', self.on_mousewheel) |
|---|
| 243 | self.root.connect('button-press-event', self.on_left_down) |
|---|
| 244 | self.root.connect('button-release-event', self.on_left_up) |
|---|
| 245 | self.root.connect('motion-notify-event', self.on_motion) |
|---|
| 246 | self.update_knobshape() |
|---|
| 247 | |
|---|
| 248 | def update_knobshape(self): |
|---|
| 249 | rc = self.get_allocation() |
|---|
| 250 | b = self.get_border_width() |
|---|
| 251 | size = min(rc.width, rc.height) - 2*b |
|---|
| 252 | gd = float(self.gapdepth*0.5) / size |
|---|
| 253 | self.gd = gd |
|---|
| 254 | self.knobshape = make_knobshape(self.gaps, gd) |
|---|
| 255 | |
|---|
| 256 | def set_legend_scale(self, scale): |
|---|
| 257 | self.lscale = scale |
|---|
| 258 | self.refresh() |
|---|
| 259 | |
|---|
| 260 | def set_legend_line_width(self, width): |
|---|
| 261 | self.lsize = width |
|---|
| 262 | self.refresh() |
|---|
| 263 | |
|---|
| 264 | def set_segments(self, segments): |
|---|
| 265 | self.segments = segments |
|---|
| 266 | self.refresh() |
|---|
| 267 | |
|---|
| 268 | def set_marker(self, marker): |
|---|
| 269 | self.marker = marker |
|---|
| 270 | self.refresh() |
|---|
| 271 | |
|---|
| 272 | def set_range(self, minvalue, maxvalue): |
|---|
| 273 | self.min_value = minvalue |
|---|
| 274 | self.max_value = maxvalue |
|---|
| 275 | self.set_value(self.value) |
|---|
| 276 | |
|---|
| 277 | def quantize_value(self, value): |
|---|
| 278 | scaler = 10**self.digits |
|---|
| 279 | value = int((value*scaler)+0.5) / float(scaler) |
|---|
| 280 | return value |
|---|
| 281 | |
|---|
| 282 | def on_adj_value_changed(self, adj): |
|---|
| 283 | new_value = adj.get_normalized_value() |
|---|
| 284 | if self.value != new_value: |
|---|
| 285 | self.value = new_value |
|---|
| 286 | self.refresh() |
|---|
| 287 | |
|---|
| 288 | def set_value(self, value): |
|---|
| 289 | oldval = self.value |
|---|
| 290 | self.value = min(max(self.quantize_value(value), self.min_value), self.max_value) |
|---|
| 291 | if self.value != oldval: |
|---|
| 292 | if self.adj: |
|---|
| 293 | self.adj.set_normalized_value(value) |
|---|
| 294 | self.refresh() |
|---|
| 295 | |
|---|
| 296 | def get_value(self): |
|---|
| 297 | return self.value |
|---|
| 298 | |
|---|
| 299 | def set_top_color(self, h, l, s): |
|---|
| 300 | self.fg_hls = h,l,s |
|---|
| 301 | self.refresh() |
|---|
| 302 | |
|---|
| 303 | def set_legend_color(self, h, l, s): |
|---|
| 304 | self.legend_hls = h,l,s |
|---|
| 305 | self.refresh() |
|---|
| 306 | |
|---|
| 307 | def get_top_color(self): |
|---|
| 308 | return self.fg_hls |
|---|
| 309 | |
|---|
| 310 | def set_gaps(self, gaps): |
|---|
| 311 | self.gaps = gaps |
|---|
| 312 | self.knobshape = None |
|---|
| 313 | self.refresh() |
|---|
| 314 | |
|---|
| 315 | def get_gaps(self): |
|---|
| 316 | return self.gaps |
|---|
| 317 | |
|---|
| 318 | def set_gap_depth(self, gapdepth): |
|---|
| 319 | self.gapdepth = gapdepth |
|---|
| 320 | self.knobshape = None |
|---|
| 321 | self.refresh() |
|---|
| 322 | |
|---|
| 323 | def get_gap_depth(self): |
|---|
| 324 | return self.gapdepth |
|---|
| 325 | |
|---|
| 326 | def set_angle(self, angle): |
|---|
| 327 | self.angle = angle |
|---|
| 328 | self.refresh() |
|---|
| 329 | |
|---|
| 330 | def get_angle(self): |
|---|
| 331 | return self.angle |
|---|
| 332 | |
|---|
| 333 | def set_legend(self, legend): |
|---|
| 334 | self.legend = legend |
|---|
| 335 | self.refresh() |
|---|
| 336 | |
|---|
| 337 | def get_legend(self): |
|---|
| 338 | return self.legend |
|---|
| 339 | |
|---|
| 340 | def on_left_down(self, widget, event): |
|---|
| 341 | #print "on_left_down" |
|---|
| 342 | |
|---|
| 343 | # dont drag insensitive widgets |
|---|
| 344 | if not self.is_sensitive(): |
|---|
| 345 | return False |
|---|
| 346 | |
|---|
| 347 | if not sum(self.get_allocation().intersect((int(event.x), int(event.y), 1, 1))): |
|---|
| 348 | return False |
|---|
| 349 | if event.button == 1: |
|---|
| 350 | #print "start draggin" |
|---|
| 351 | self.startvalue = self.value |
|---|
| 352 | self.start = event.y |
|---|
| 353 | self.dragging = True |
|---|
| 354 | self.show_tooltip() |
|---|
| 355 | self.grab_add() |
|---|
| 356 | return True |
|---|
| 357 | return False |
|---|
| 358 | |
|---|
| 359 | def on_left_up(self, widget, event): |
|---|
| 360 | #print "on_left_up" |
|---|
| 361 | if not self.dragging: |
|---|
| 362 | return False |
|---|
| 363 | if event.button == 1: |
|---|
| 364 | #print "stop draggin" |
|---|
| 365 | self.dragging = False |
|---|
| 366 | self.grab_remove() |
|---|
| 367 | return True |
|---|
| 368 | return False |
|---|
| 369 | |
|---|
| 370 | def on_motion(self, widget, event): |
|---|
| 371 | #print "on_motion" |
|---|
| 372 | |
|---|
| 373 | # dont drag insensitive widgets |
|---|
| 374 | if not self.is_sensitive(): |
|---|
| 375 | return False |
|---|
| 376 | |
|---|
| 377 | if self.dragging: |
|---|
| 378 | x,y,state = self.window.get_pointer() |
|---|
| 379 | rc = self.get_allocation() |
|---|
| 380 | range = self.max_value - self.min_value |
|---|
| 381 | scale = rc.height |
|---|
| 382 | if event.state & gtk.gdk.SHIFT_MASK: |
|---|
| 383 | scale = rc.height*8 |
|---|
| 384 | value = self.startvalue - ((y - self.start)*range)/scale |
|---|
| 385 | oldval = self.value |
|---|
| 386 | self.set_value(value) |
|---|
| 387 | self.show_tooltip() |
|---|
| 388 | if oldval != self.value: |
|---|
| 389 | self.start = y |
|---|
| 390 | self.startvalue = self.value |
|---|
| 391 | return True |
|---|
| 392 | return False |
|---|
| 393 | |
|---|
| 394 | def on_mousewheel(self, widget, event): |
|---|
| 395 | |
|---|
| 396 | # dont move insensitive widgets |
|---|
| 397 | if not self.is_sensitive(): |
|---|
| 398 | return False |
|---|
| 399 | |
|---|
| 400 | if not sum(self.get_allocation().intersect((int(event.x), int(event.y), 1, 1))): |
|---|
| 401 | return |
|---|
| 402 | range = self.max_value - self.min_value |
|---|
| 403 | minstep = 1.0 / (10**self.digits) |
|---|
| 404 | if event.state & (gtk.gdk.SHIFT_MASK | gtk.gdk.BUTTON1_MASK): |
|---|
| 405 | step = minstep |
|---|
| 406 | else: |
|---|
| 407 | step = max(self.quantize_value(range/25.0), minstep) |
|---|
| 408 | value = self.value |
|---|
| 409 | if event.direction == gtk.gdk.SCROLL_UP: |
|---|
| 410 | value += step |
|---|
| 411 | elif event.direction == gtk.gdk.SCROLL_DOWN: |
|---|
| 412 | value -= step |
|---|
| 413 | self.set_value(value) |
|---|
| 414 | self.show_tooltip() |
|---|
| 415 | |
|---|
| 416 | def on_size_allocate(self, widget, allocation): |
|---|
| 417 | #print allocation.x, allocation.y, allocation.width, allocation.height |
|---|
| 418 | self.update_knobshape() |
|---|
| 419 | |
|---|
| 420 | def draw_points(self, ctx, peaks): |
|---|
| 421 | ctx.move_to(*peaks[0]) |
|---|
| 422 | for peak in peaks[1:]: |
|---|
| 423 | ctx.line_to(*peak) |
|---|
| 424 | |
|---|
| 425 | def draw(self, ctx): |
|---|
| 426 | if not self.legend_hls: |
|---|
| 427 | self.legend_hls = color_to_hls(self.style.fg[gtk.STATE_NORMAL]) |
|---|
| 428 | |
|---|
| 429 | if not self.knobshape: |
|---|
| 430 | self.update_knobshape() |
|---|
| 431 | startangle = pi*1.5 - self.angle*0.5 |
|---|
| 432 | angle = ((self.value - self.min_value) / (self.max_value - self.min_value)) * self.angle + startangle |
|---|
| 433 | rc = self.get_allocation() |
|---|
| 434 | size = min(rc.width, rc.height) |
|---|
| 435 | |
|---|
| 436 | kh = self.get_border_width() # knob height |
|---|
| 437 | |
|---|
| 438 | ps = 1.0/size # pixel size |
|---|
| 439 | ps2 = 1.0 / (size-(2*kh)-1) # pixel size inside knob |
|---|
| 440 | ss = ps * kh # shadow size |
|---|
| 441 | lsize = ps2 * self.lsize # legend line width |
|---|
| 442 | # draw spherical |
|---|
| 443 | ctx.translate(rc.x, rc.y) |
|---|
| 444 | ctx.translate(0.5,0.5) |
|---|
| 445 | ctx.translate(size*0.5, size*0.5) |
|---|
| 446 | ctx.scale(size-(2*kh)-1, size-(2*kh)-1) |
|---|
| 447 | if self.legend == LEGEND_DOTS: |
|---|
| 448 | ctx.save() |
|---|
| 449 | ctx.set_source_rgb(*hls_to_rgb(*self.legend_hls)) |
|---|
| 450 | dots = self.segments |
|---|
| 451 | for i in xrange(dots): |
|---|
| 452 | s = float(i)/(dots-1) |
|---|
| 453 | a = startangle + self.angle*s |
|---|
| 454 | ctx.save() |
|---|
| 455 | ctx.rotate(a) |
|---|
| 456 | r = lsize*0.5 |
|---|
| 457 | if self.lscale: |
|---|
| 458 | r = max(r*s,ps2) |
|---|
| 459 | ctx.arc(0.5+lsize, 0.0, r, 0.0, 2*pi) |
|---|
| 460 | ctx.fill() |
|---|
| 461 | ctx.restore() |
|---|
| 462 | ctx.restore() |
|---|
| 463 | elif self.legend in (LEGEND_LINES, LEGEND_RULER, LEGEND_RULER_INWARDS): |
|---|
| 464 | ctx.save() |
|---|
| 465 | ctx.set_source_rgb(*hls_to_rgb(*self.legend_hls)) |
|---|
| 466 | dots = self.segments |
|---|
| 467 | n = ps2*(kh-1) |
|---|
| 468 | for i in xrange(dots): |
|---|
| 469 | s = float(i)/(dots-1) |
|---|
| 470 | a = startangle + self.angle*s |
|---|
| 471 | ctx.save() |
|---|
| 472 | ctx.rotate(a) |
|---|
| 473 | r = n*0.9 |
|---|
| 474 | if self.lscale: |
|---|
| 475 | r = max(r*s,ps2) |
|---|
| 476 | ctx.move_to(0.5+ps2+n*0.1, 0.0) |
|---|
| 477 | ctx.line_to(0.5+ps2+n*0.1+r, 0.0) |
|---|
| 478 | ctx.set_line_width(lsize) |
|---|
| 479 | ctx.stroke() |
|---|
| 480 | ctx.restore() |
|---|
| 481 | ctx.restore() |
|---|
| 482 | if self.legend == LEGEND_RULER: |
|---|
| 483 | ctx.save() |
|---|
| 484 | ctx.set_source_rgb(*hls_to_rgb(*self.legend_hls)) |
|---|
| 485 | ctx.set_line_width(lsize) |
|---|
| 486 | ctx.arc(0.0, 0.0, 0.5+ps2+n*0.1, startangle, startangle+self.angle) |
|---|
| 487 | ctx.stroke() |
|---|
| 488 | ctx.restore() |
|---|
| 489 | elif self.legend == LEGEND_RULER_INWARDS: |
|---|
| 490 | ctx.save() |
|---|
| 491 | ctx.set_source_rgb(*hls_to_rgb(*self.legend_hls)) |
|---|
| 492 | ctx.set_line_width(lsize) |
|---|
| 493 | ctx.arc(0.0, 0.0, 0.5+ps2+n, startangle, startangle+self.angle) |
|---|
| 494 | ctx.stroke() |
|---|
| 495 | |
|---|
| 496 | # draw shadow only for sensitive widgets that have height |
|---|
| 497 | if self.is_sensitive() and kh: |
|---|
| 498 | ctx.save() |
|---|
| 499 | ctx.translate(ss, ss) |
|---|
| 500 | ctx.rotate(angle) |
|---|
| 501 | self.draw_points(ctx, self.knobshape) |
|---|
| 502 | ctx.close_path() |
|---|
| 503 | ctx.restore() |
|---|
| 504 | ctx.set_source_rgba(0,0,0,0.3) |
|---|
| 505 | ctx.fill() |
|---|
| 506 | |
|---|
| 507 | if self.legend in (LEGEND_LED_SCALE, LEGEND_LED_DOTS): |
|---|
| 508 | ch,cl,cs = self.legend_hls |
|---|
| 509 | n = ps2*(kh-1) |
|---|
| 510 | ctx.save() |
|---|
| 511 | ctx.set_line_cap(cairo.LINE_CAP_ROUND) |
|---|
| 512 | ctx.set_source_rgb(*hls_to_rgb(ch,cl*0.2,cs)) |
|---|
| 513 | ctx.set_line_width(lsize) |
|---|
| 514 | ctx.arc(0.0, 0.0, 0.5+ps2+n*0.5, startangle, startangle+self.angle) |
|---|
| 515 | ctx.stroke() |
|---|
| 516 | ctx.set_source_rgb(*hls_to_rgb(ch,cl,cs)) |
|---|
| 517 | if self.legend == LEGEND_LED_SCALE: |
|---|
| 518 | ctx.set_line_width(lsize-ps2*2) |
|---|
| 519 | ctx.arc(0.0, 0.0, 0.5+ps2+n*0.5, startangle, angle) |
|---|
| 520 | ctx.stroke() |
|---|
| 521 | elif self.legend == LEGEND_LED_DOTS: |
|---|
| 522 | dots = self.segments |
|---|
| 523 | dsize = lsize-ps2*2 |
|---|
| 524 | seg = self.angle/dots |
|---|
| 525 | endangle = startangle + self.angle |
|---|
| 526 | for i in xrange(dots): |
|---|
| 527 | s = float(i)/(dots-1) |
|---|
| 528 | a = startangle + self.angle*s |
|---|
| 529 | if ((a-seg*0.5) > angle) or (angle == startangle): |
|---|
| 530 | break |
|---|
| 531 | ctx.save() |
|---|
| 532 | ctx.rotate(a) |
|---|
| 533 | r = dsize*0.5 |
|---|
| 534 | if self.lscale: |
|---|
| 535 | r = max(r*s,ps2) |
|---|
| 536 | ctx.arc(0.5+ps2+n*0.5, 0.0, r, 0.0, 2*pi) |
|---|
| 537 | ctx.fill() |
|---|
| 538 | ctx.restore() |
|---|
| 539 | ctx.restore() |
|---|
| 540 | pat = cairo.LinearGradient(-0.5, -0.5, 0.5, 0.5) |
|---|
| 541 | pat.add_color_stop_rgb(1.0, 0.2,0.2,0.2) |
|---|
| 542 | pat.add_color_stop_rgb(0.0, 0.3,0.3,0.3) |
|---|
| 543 | ctx.set_source(pat) |
|---|
| 544 | ctx.rotate(angle) |
|---|
| 545 | self.draw_points(ctx, self.knobshape) |
|---|
| 546 | ctx.close_path() |
|---|
| 547 | ctx.fill_preserve() |
|---|
| 548 | ctx.set_source_rgba(0.1,0.1,0.1,1) |
|---|
| 549 | ctx.save() |
|---|
| 550 | ctx.identity_matrix() |
|---|
| 551 | ctx.set_line_width(1.0) |
|---|
| 552 | ctx.stroke() |
|---|
| 553 | ctx.restore() |
|---|
| 554 | |
|---|
| 555 | ctx.arc(0.0, 0.0, 0.5-self.gd, 0.0, pi*2.0) |
|---|
| 556 | ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], max(self.fg_hls[1]*0.4,0.0), self.fg_hls[2])) |
|---|
| 557 | ctx.fill() |
|---|
| 558 | ctx.arc(0.0, 0.0, 0.5-self.gd-ps, 0.0, pi*2.0) |
|---|
| 559 | ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], min(self.fg_hls[1]*1.2,1.0), self.fg_hls[2])) |
|---|
| 560 | ctx.fill() |
|---|
| 561 | ctx.arc(0.0, 0.0, 0.5-self.gd-(2*ps), 0.0, pi*2.0) |
|---|
| 562 | ctx.set_source_rgb(*hls_to_rgb(*self.fg_hls)) |
|---|
| 563 | ctx.fill() |
|---|
| 564 | |
|---|
| 565 | # dont draw cap for insensitive widgets |
|---|
| 566 | if not self.is_sensitive(): |
|---|
| 567 | return |
|---|
| 568 | |
|---|
| 569 | #~ ctx.set_line_cap(cairo.LINE_CAP_ROUND) |
|---|
| 570 | #~ ctx.move_to(0.5-0.3-self.gd-ps, 0.0) |
|---|
| 571 | #~ ctx.line_to(0.5-self.gd-ps*5, 0.0) |
|---|
| 572 | |
|---|
| 573 | if self.marker == MARKER_LINE: |
|---|
| 574 | ctx.set_line_cap(cairo.LINE_CAP_BUTT) |
|---|
| 575 | ctx.move_to(0.5-0.3-self.gd-ps, 0.0) |
|---|
| 576 | ctx.line_to(0.5-self.gd-ps, 0.0) |
|---|
| 577 | ctx.save() |
|---|
| 578 | ctx.identity_matrix() |
|---|
| 579 | ctx.translate(0.5,0.5) |
|---|
| 580 | ctx.set_line_width(5) |
|---|
| 581 | ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], min(self.fg_hls[1]*1.2,1.0), self.fg_hls[2])) |
|---|
| 582 | ctx.stroke_preserve() |
|---|
| 583 | ctx.set_line_width(3) |
|---|
| 584 | ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], max(self.fg_hls[1]*0.4,0.0), self.fg_hls[2])) |
|---|
| 585 | ctx.stroke() |
|---|
| 586 | ctx.restore() |
|---|
| 587 | elif self.marker == MARKER_DOT: |
|---|
| 588 | ctx.arc(0.5-0.05-self.gd-ps*5, 0.0, 0.05, 0.0, 2*pi) |
|---|
| 589 | ctx.save() |
|---|
| 590 | ctx.identity_matrix() |
|---|
| 591 | ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], min(self.fg_hls[1]*1.2,1.0), self.fg_hls[2])) |
|---|
| 592 | ctx.stroke_preserve() |
|---|
| 593 | ctx.set_line_width(1) |
|---|
| 594 | ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], max(self.fg_hls[1]*0.4,0.0), self.fg_hls[2])) |
|---|
| 595 | ctx.fill() |
|---|
| 596 | ctx.restore() |
|---|
| 597 | elif self.marker == MARKER_ARROW: |
|---|
| 598 | ctx.set_line_cap(cairo.LINE_CAP_BUTT) |
|---|
| 599 | ctx.move_to(0.5-0.3-self.gd-ps, 0.1) |
|---|
| 600 | ctx.line_to(0.5-0.1-self.gd-ps, 0.0) |
|---|
| 601 | ctx.line_to(0.5-0.3-self.gd-ps, -0.1) |
|---|
| 602 | ctx.close_path() |
|---|
| 603 | ctx.save() |
|---|
| 604 | ctx.identity_matrix() |
|---|
| 605 | #~ ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], min(self.fg_hls[1]*1.2,1.0), self.fg_hls[2])) |
|---|
| 606 | #~ ctx.stroke_preserve() |
|---|
| 607 | ctx.set_line_width(1) |
|---|
| 608 | ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], max(self.fg_hls[1]*0.4,0.0), self.fg_hls[2])) |
|---|
| 609 | ctx.fill() |
|---|
| 610 | ctx.restore() |
|---|
| 611 | |
|---|
| 612 | def refresh(self): |
|---|
| 613 | rect = self.get_allocation() |
|---|
| 614 | if self.window: |
|---|
| 615 | self.window.invalidate_rect(rect, False) |
|---|
| 616 | return True |
|---|
| 617 | |
|---|
| 618 | def on_expose(self, widget, event): |
|---|
| 619 | self.context = self.window.cairo_create() |
|---|
| 620 | self.draw(self.context) |
|---|
| 621 | return False |
|---|
| 622 | |
|---|
| 623 | class filter_band: |
|---|
| 624 | def __init__(self): |
|---|
| 625 | self.fsamp = 48e3 |
|---|
| 626 | |
|---|
| 627 | def set_params(self, freq, bandw, gain): |
|---|
| 628 | freq_ratio = freq / self.fsamp |
|---|
| 629 | gain2 = pow(10.0, 0.05 * gain) |
|---|
| 630 | b = 7 * bandw * freq_ratio / sqrt(gain2) |
|---|
| 631 | self.gn = 0.5 * (gain2 - 1) |
|---|
| 632 | self.v1 = -cos(2 * pi * freq_ratio) |
|---|
| 633 | self.v2 = (1 - b) / (1 + b) |
|---|
| 634 | self.v1 *= (1 + self.v2) |
|---|
| 635 | self.gn *= (1 - self.v2) |
|---|
| 636 | |
|---|
| 637 | def get_response(self, freq): |
|---|
| 638 | w = 2 * pi * (freq / self.fsamp) |
|---|
| 639 | c1 = cos(w) |
|---|
| 640 | s1 = sin(w) |
|---|
| 641 | c2 = cos(2 * w) |
|---|
| 642 | s2 = sin(2 * w) |
|---|
| 643 | |
|---|
| 644 | x = c2 + self.v1 * c1 + self.v2 |
|---|
| 645 | y = s2 + self.v1 * s1 |
|---|
| 646 | t1 = hypot(x, y) |
|---|
| 647 | x += self.gn * (c2 - 1) |
|---|
| 648 | y += self.gn * s2 |
|---|
| 649 | t2 = hypot(x, y) |
|---|
| 650 | |
|---|
| 651 | #return t2 / t1 |
|---|
| 652 | return 20 * log10(t2 / t1) |
|---|
| 653 | |
|---|
| 654 | class frequency_response(gtk.DrawingArea): |
|---|
| 655 | def __init__(self): |
|---|
| 656 | gtk.DrawingArea.__init__(self) |
|---|
| 657 | |
|---|
| 658 | self.connect("expose-event", self.on_expose) |
|---|
| 659 | self.connect("size-request", self.on_size_request) |
|---|
| 660 | self.connect("size_allocate", self.on_size_allocate) |
|---|
| 661 | |
|---|
| 662 | self.color_bg = gtk.gdk.Color(0,0,0) |
|---|
| 663 | self.color_value = gtk.gdk.Color(int(65535 * 0.8), int(65535 * 0.7), 0) |
|---|
| 664 | self.color_mark = gtk.gdk.Color(int(65535 * 0.3), int(65535 * 0.3), int(65535 * 0.3)) |
|---|
| 665 | self.color_sum = gtk.gdk.Color(int(65535 * 1.0), int(65535 * 1.0), int(65535 * 1.0)) |
|---|
| 666 | self.width = 0 |
|---|
| 667 | self.height = 0 |
|---|
| 668 | self.margin = 10 |
|---|
| 669 | self.db_range = 30 |
|---|
| 670 | self.master_gain = 0.0 |
|---|
| 671 | self.master_enabled = False |
|---|
| 672 | |
|---|
| 673 | self.filters = {} |
|---|
| 674 | |
|---|
| 675 | def on_expose(self, widget, event): |
|---|
| 676 | cairo_ctx = widget.window.cairo_create() |
|---|
| 677 | |
|---|
| 678 | # set a clip region for the expose event |
|---|
| 679 | cairo_ctx.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) |
|---|
| 680 | cairo_ctx.clip() |
|---|
| 681 | |
|---|
| 682 | self.draw(cairo_ctx) |
|---|
| 683 | |
|---|
| 684 | return False |
|---|
| 685 | |
|---|
| 686 | def on_size_allocate(self, widget, allocation): |
|---|
| 687 | #print allocation.x, allocation.y, allocation.width, allocation.height |
|---|
| 688 | self.width = float(allocation.width) |
|---|
| 689 | self.height = float(allocation.height) |
|---|
| 690 | self.font_size = 10 |
|---|
| 691 | |
|---|
| 692 | def on_size_request(self, widget, requisition): |
|---|
| 693 | #print "size-request, %u x %u" % (requisition.width, requisition.height) |
|---|
| 694 | requisition.width = 150 |
|---|
| 695 | requisition.height = 150 |
|---|
| 696 | return |
|---|
| 697 | |
|---|
| 698 | def invalidate_all(self): |
|---|
| 699 | self.queue_draw_area(0, 0, int(self.width), int(self.height)) |
|---|
| 700 | |
|---|
| 701 | def get_x(self, hz): |
|---|
| 702 | width = self.width - 3.5 * self.margin |
|---|
| 703 | #x = self.margin + width * (hz - 20) / (20000 - 20) |
|---|
| 704 | x = 2.5 * self.margin + width * log(hz / 20.0, 1000.0) |
|---|
| 705 | #print x |
|---|
| 706 | return x |
|---|
| 707 | |
|---|
| 708 | def get_freq(self, x): |
|---|
| 709 | width = self.width - 3.5 * self.margin |
|---|
| 710 | return 20 * pow(1000, (x - 2.5 * self.margin) / width) |
|---|
| 711 | |
|---|
| 712 | def get_y(self, db): |
|---|
| 713 | height = self.height - 2.5 * self.margin |
|---|
| 714 | y = self.margin + height * (self.db_range - db) / (self.db_range * 2) |
|---|
| 715 | #print y |
|---|
| 716 | return y |
|---|
| 717 | |
|---|
| 718 | def draw_db_grid(self, cairo_ctx, db): |
|---|
| 719 | x = self.get_x(20) |
|---|
| 720 | y = self.get_y(db) |
|---|
| 721 | cairo_ctx.move_to(x, y) |
|---|
| 722 | cairo_ctx.line_to(self.get_x(20000), y) |
|---|
| 723 | |
|---|
| 724 | if db % 10 == 0: |
|---|
| 725 | x -= 20 |
|---|
| 726 | y += 3 |
|---|
| 727 | cairo_ctx.move_to(x, y) |
|---|
| 728 | label = "%+d" % db |
|---|
| 729 | if db == 0: |
|---|
| 730 | label = " " + label |
|---|
| 731 | cairo_ctx.show_text(label) |
|---|
| 732 | |
|---|
| 733 | cairo_ctx.stroke() |
|---|
| 734 | |
|---|
| 735 | def invalidate_all(self): |
|---|
| 736 | self.queue_draw_area(0, 0, int(self.width), int(self.height)) |
|---|
| 737 | |
|---|
| 738 | def draw(self, cairo_ctx): |
|---|
| 739 | cairo_ctx.select_font_face("Fixed") |
|---|
| 740 | |
|---|
| 741 | cairo_ctx.set_source_color(self.color_bg) |
|---|
| 742 | cairo_ctx.rectangle(0, 0, self.width, self.height) |
|---|
| 743 | cairo_ctx.fill() |
|---|
| 744 | |
|---|
| 745 | cairo_ctx.set_source_color(self.color_mark) |
|---|
| 746 | cairo_ctx.set_line_width(1); |
|---|
| 747 | |
|---|
| 748 | for hz in range(20, 101, 10) + range(100, 1001, 100) + range(1000, 10001, 1000) + range(10000, 20001, 10000): |
|---|
| 749 | if hz >= 10000: |
|---|
| 750 | label = "%dk" % int(hz / 1000) |
|---|
| 751 | elif hz >= 1000: |
|---|
| 752 | label = "%dk" % int(hz / 1000) |
|---|
| 753 | else: |
|---|
| 754 | label = "%d" % int(hz) |
|---|
| 755 | first_digit = int(label[0]) |
|---|
| 756 | if first_digit > 5 or (first_digit > 3 and (len(label) == 3)): |
|---|
| 757 | label = None |
|---|
| 758 | |
|---|
| 759 | x = self.get_x(hz) |
|---|
| 760 | cairo_ctx.move_to(x, self.get_y(self.db_range)) |
|---|
| 761 | y = self.get_y(-self.db_range) |
|---|
| 762 | cairo_ctx.line_to(x, y) |
|---|
| 763 | if label: |
|---|
| 764 | y += 10 |
|---|
| 765 | if hz == 20000: |
|---|
| 766 | x -= 15 |
|---|
| 767 | elif hz != 20: |
|---|
| 768 | x -= 3 |
|---|
| 769 | cairo_ctx.move_to(x, y) |
|---|
| 770 | cairo_ctx.show_text(label) |
|---|
| 771 | cairo_ctx.stroke() |
|---|
| 772 | |
|---|
| 773 | for db in range(0, self.db_range + 1, 5): |
|---|
| 774 | self.draw_db_grid(cairo_ctx, db) |
|---|
| 775 | |
|---|
| 776 | if db != 0: |
|---|
| 777 | self.draw_db_grid(cairo_ctx, -db) |
|---|
| 778 | |
|---|
| 779 | curves = [[x, {}, self.master_gain, self.get_freq(x)] for x in range(int(self.get_x(20)), int(self.get_x(20e3)))] |
|---|
| 780 | #print repr(curves) |
|---|
| 781 | |
|---|
| 782 | # calculate filter responses |
|---|
| 783 | for label, filter in self.filters.items(): |
|---|
| 784 | if not filter.enabled: |
|---|
| 785 | continue |
|---|
| 786 | |
|---|
| 787 | for point in curves: |
|---|
| 788 | db = filter.get_response(point[3]) |
|---|
| 789 | point[1][label] = [self.get_y(db), db] |
|---|
| 790 | |
|---|
| 791 | # calculate sum curve |
|---|
| 792 | for point in curves: |
|---|
| 793 | for label, filter_point in point[1].items(): |
|---|
| 794 | point[2] += filter_point[1] |
|---|
| 795 | #print point |
|---|
| 796 | |
|---|
| 797 | # draw filter curves |
|---|
| 798 | for label, filter in self.filters.items(): |
|---|
| 799 | if not filter.enabled: |
|---|
| 800 | continue |
|---|
| 801 | |
|---|
| 802 | cairo_ctx.set_source_color(filter.color) |
|---|
| 803 | cairo_ctx.move_to(curves[0][0], curves[0][1][label][0]) |
|---|
| 804 | for point in curves: |
|---|
| 805 | cairo_ctx.line_to(point[0], point[1][label][0]) |
|---|
| 806 | cairo_ctx.stroke() |
|---|
| 807 | |
|---|
| 808 | if self.master_enabled: |
|---|
| 809 | # draw sum curve |
|---|
| 810 | cairo_ctx.set_source_color(self.color_sum) |
|---|
| 811 | cairo_ctx.set_line_width(2); |
|---|
| 812 | cairo_ctx.move_to(curves[0][0], self.get_y(curves[0][2])) |
|---|
| 813 | for point in curves: |
|---|
| 814 | cairo_ctx.line_to(point[0], self.get_y(point[2])) |
|---|
| 815 | cairo_ctx.stroke() |
|---|
| 816 | |
|---|
| 817 | # draw base point markers |
|---|
| 818 | for label, filter in self.filters.items(): |
|---|
| 819 | if not filter.enabled: |
|---|
| 820 | continue |
|---|
| 821 | |
|---|
| 822 | cairo_ctx.set_source_color(self.color_value) |
|---|
| 823 | x = self.get_x(filter.adj_hz.value) |
|---|
| 824 | y = self.get_y(filter.adj_db.value) |
|---|
| 825 | |
|---|
| 826 | cairo_ctx.move_to(x, y) |
|---|
| 827 | cairo_ctx.show_text(label) |
|---|
| 828 | cairo_ctx.stroke() |
|---|
| 829 | |
|---|
| 830 | def add_filter(self, label, adj_hz, adj_db, adj_bw, color): |
|---|
| 831 | #print "filter %s added (%.2f Hz, %.2f dB, %.2f bw)" % (label, adj_hz.value, adj_db.value, adj_bw.value) |
|---|
| 832 | filter = filter_band() |
|---|
| 833 | filter.enabled = False |
|---|
| 834 | filter.label = label |
|---|
| 835 | filter.color = color |
|---|
| 836 | filter.set_params(adj_hz.value, adj_bw.value, adj_db.value) |
|---|
| 837 | adj_hz.filter = filter |
|---|
| 838 | adj_db.filter = filter |
|---|
| 839 | adj_bw.filter = filter |
|---|
| 840 | filter.adj_hz = adj_hz |
|---|
| 841 | filter.adj_db = adj_db |
|---|
| 842 | filter.adj_bw = adj_bw |
|---|
| 843 | adj_hz.connect("value-changed", self.on_value_change_request) |
|---|
| 844 | adj_db.connect("value-changed", self.on_value_change_request) |
|---|
| 845 | adj_bw.connect("value-changed", self.on_value_change_request) |
|---|
| 846 | self.filters[label] = filter |
|---|
| 847 | |
|---|
| 848 | def enable_filter(self, label): |
|---|
| 849 | filter = self.filters[label] |
|---|
| 850 | #print "filter %s enabled (%.2f Hz, %.2f dB, %.2f bw)" % (label, filter.adj_hz.value, filter.adj_db.value, filter.adj_bw.value) |
|---|
| 851 | filter.enabled = True |
|---|
| 852 | self.invalidate_all() |
|---|
| 853 | |
|---|
| 854 | def disable_filter(self, label): |
|---|
| 855 | filter = self.filters[label] |
|---|
| 856 | #print "filter %s disabled (%.2f Hz, %.2f dB, %.2f bw)" % (label, filter.adj_hz.value, filter.adj_db.value, filter.adj_bw.value) |
|---|
| 857 | filter.enabled = False |
|---|
| 858 | self.invalidate_all() |
|---|
| 859 | |
|---|
| 860 | def on_value_change_request(self, adj): |
|---|
| 861 | #print "adj changed" |
|---|
| 862 | adj.filter.set_params(adj.filter.adj_hz.value, adj.filter.adj_bw.value, adj.filter.adj_db.value) |
|---|
| 863 | self.invalidate_all() |
|---|
| 864 | |
|---|
| 865 | def master_enable(self): |
|---|
| 866 | self.master_enabled = True; |
|---|
| 867 | self.invalidate_all() |
|---|
| 868 | |
|---|
| 869 | def master_disable(self): |
|---|
| 870 | self.master_enabled = False; |
|---|
| 871 | self.invalidate_all() |
|---|
| 872 | |
|---|
| 873 | def set_master_gain(self, gain): |
|---|
| 874 | self.master_gain = gain; |
|---|
| 875 | self.invalidate_all() |
|---|
| 876 | |
|---|
| 877 | class filter_ui: |
|---|
| 878 | def __init__(self, argv): |
|---|
| 879 | self.fake = len(argv) == 1 |
|---|
| 880 | |
|---|
| 881 | if self.fake: |
|---|
| 882 | self.plugin_uri = self.human_id = "fake" |
|---|
| 883 | self.bundle_path = "." |
|---|
| 884 | self.shown = False |
|---|
| 885 | else: |
|---|
| 886 | #print repr(argv) |
|---|
| 887 | self.plugin_uri = argv[1] |
|---|
| 888 | self.bundle_path = argv[2] |
|---|
| 889 | self.human_id = argv[3] |
|---|
| 890 | |
|---|
| 891 | self.recv_pipe_fd = int(argv[4]) |
|---|
| 892 | self.send_pipe_fd = int(argv[5]) |
|---|
| 893 | |
|---|
| 894 | oldflags = fcntl.fcntl(self.recv_pipe_fd, fcntl.F_GETFL) |
|---|
| 895 | fcntl.fcntl(self.recv_pipe_fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK) |
|---|
| 896 | |
|---|
| 897 | self.recv_pipe = os.fdopen(self.recv_pipe_fd, 'r') |
|---|
| 898 | self.send_pipe = os.fdopen(self.send_pipe_fd, 'w') |
|---|
| 899 | |
|---|
| 900 | if self.plugin_uri == "http://nedko.aranaudov.org/soft/filter/2/mono": |
|---|
| 901 | self.port_base = 2 |
|---|
| 902 | elif self.plugin_uri == "http://nedko.aranaudov.org/soft/filter/2/stereo": |
|---|
| 903 | self.port_base = 4 |
|---|
| 904 | elif self.plugin_uri == "fake": |
|---|
| 905 | self.port_base = 0 |
|---|
| 906 | else: |
|---|
| 907 | return |
|---|
| 908 | |
|---|
| 909 | self.lv2logo = gtk.gdk.pixbuf_new_from_file(self.bundle_path + "/lv2logo.png") |
|---|
| 910 | |
|---|
| 911 | self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) |
|---|
| 912 | #self.window.set_size_request(600, 400) |
|---|
| 913 | self.window.set_title("%s (4-band parametric filter)" % self.human_id) |
|---|
| 914 | self.window.set_role("plugin_ui") |
|---|
| 915 | |
|---|
| 916 | self.top_vbox = gtk.VBox() |
|---|
| 917 | self.top_vbox.set_spacing(10) |
|---|
| 918 | |
|---|
| 919 | align = gtk.Alignment(0.5, 0.5, 1.0, 1.0) |
|---|
| 920 | align.set_padding(10, 10, 10, 10) |
|---|
| 921 | align.add(self.top_vbox) |
|---|
| 922 | |
|---|
| 923 | self.window.add(align) |
|---|
| 924 | |
|---|
| 925 | self.fr = frequency_response() |
|---|
| 926 | self.fr.set_size_request(400, 200) |
|---|
| 927 | |
|---|
| 928 | frame = gtk.Frame() |
|---|
| 929 | frame.set_shadow_type(gtk.SHADOW_ETCHED_OUT) |
|---|
| 930 | frame.add(self.fr) |
|---|
| 931 | |
|---|
| 932 | self.top_vbox.pack_start(frame, True, True) |
|---|
| 933 | |
|---|
| 934 | self.param_hbox = gtk.HBox() |
|---|
| 935 | self.top_vbox.pack_start(self.param_hbox) |
|---|
| 936 | |
|---|
| 937 | self.param_hbox.set_spacing(10) |
|---|
| 938 | |
|---|
| 939 | self.initator = False |
|---|
| 940 | |
|---|
| 941 | self.ports = [] |
|---|
| 942 | |
|---|
| 943 | misc_box = gtk.VBox() |
|---|
| 944 | misc_box.set_spacing(5) |
|---|
| 945 | |
|---|
| 946 | master_frame = gtk.Frame("Master") |
|---|
| 947 | master_frame.set_label_align(0.5, 0.5) |
|---|
| 948 | |
|---|
| 949 | master_box = gtk.VBox() |
|---|
| 950 | master_box.set_spacing(5) |
|---|
| 951 | |
|---|
| 952 | port = {'index': 0, 'name': 'Active', 'type': 'toggle'} |
|---|
| 953 | self.ports.append(port) |
|---|
| 954 | self.add_param_box(master_box, self.create_toggle_box(port)) |
|---|
| 955 | |
|---|
| 956 | port = {'index': 1, 'name': 'Gain', 'type': 'knob', 'min': -20.0, 'max': 20.0, 'unit': 'dB', 'log': False} |
|---|
| 957 | self.ports.append(port) |
|---|
| 958 | self.add_param_box(master_box, self.create_knob_box(port)) |
|---|
| 959 | |
|---|
| 960 | master_frame.add(master_box) |
|---|
| 961 | misc_box.pack_start(master_frame, False, False) |
|---|
| 962 | |
|---|
| 963 | #logo = gtk.Image() |
|---|
| 964 | #logo.set_from_pixbuf(self.lv2logo) |
|---|
| 965 | #misc_box.pack_start(logo, True, True) |
|---|
| 966 | |
|---|
| 967 | button_box = gtk.VBox() |
|---|
| 968 | |
|---|
| 969 | button = gtk.Button(stock=gtk.STOCK_ABOUT) |
|---|
| 970 | button.connect("clicked", self.on_about) |
|---|
| 971 | button_box.pack_start(button) |
|---|
| 972 | |
|---|
| 973 | button = gtk.Button(stock=gtk.STOCK_CLOSE) |
|---|
| 974 | button.connect("clicked", self.on_window_closed) |
|---|
| 975 | button_box.pack_start(button) |
|---|
| 976 | |
|---|
| 977 | align = gtk.Alignment(0.5, 1.0, 1.0, 0.0) |
|---|
| 978 | align.add(button_box) |
|---|
| 979 | misc_box.pack_start(align, True, True) |
|---|
| 980 | |
|---|
| 981 | band_parameters = [ |
|---|
| 982 | {'name': 'Active', 'type': 'toggle'}, |
|---|
| 983 | {'name': 'Frequency', 'type': 'knob', 'unit': 'Hz', 'log': True}, |
|---|
| 984 | {'name': 'Bandwidth', 'type': 'knob', 'min': 0.125, 'max': 8.0, 'unit': '', 'log': True}, |
|---|
| 985 | {'name': 'Gain', 'type': 'knob', 'min': -20.0, 'max': 20.0, 'unit': 'dB', 'log': False}] |
|---|
| 986 | |
|---|
| 987 | freq_min = [ 20.0, 40.0, 100.0, 200.0] |
|---|
| 988 | freq_max = [2000.0, 4000.0, 10000.0, 20000.0] |
|---|
| 989 | |
|---|
| 990 | port_index = 2 |
|---|
| 991 | |
|---|
| 992 | filter_colors = [gtk.gdk.Color(int(65535 * 1.0), int(65535 * 0.6), int(65535 * 0.0)), |
|---|
| 993 | gtk.gdk.Color(int(65535 * 0.6), int(65535 * 1.0), int(65535 * 0.6)), |
|---|
| 994 | gtk.gdk.Color(int(65535 * 0.0), int(65535 * 0.6), int(65535 * 1.0)), |
|---|
| 995 | gtk.gdk.Color(int(65535 * 0.9), int(65535 * 0.0), int(65535 * 0.5))] |
|---|
| 996 | |
|---|
| 997 | for i in [0, 1, 2, 3]: |
|---|
| 998 | band_frame = gtk.Frame("Band %d" % (i + 1)) |
|---|
| 999 | band_frame.set_label_align(0.5, 0.5) |
|---|
| 1000 | |
|---|
| 1001 | band_box = gtk.VBox() |
|---|
| 1002 | band_box.set_spacing(5) |
|---|
| 1003 | |
|---|
| 1004 | for parameter in band_parameters: |
|---|
| 1005 | |
|---|
| 1006 | port = parameter.copy() |
|---|
| 1007 | port['index'] = port_index |
|---|
| 1008 | port_index += 1 |
|---|
| 1009 | |
|---|
| 1010 | if port['name'] == 'Frequency': |
|---|
| 1011 | port['min'] = freq_min[i] |
|---|
| 1012 | port['max'] = freq_max[i] |
|---|
| 1013 | |
|---|
| 1014 | self.ports.append(port) |
|---|
| 1015 | |
|---|
| 1016 | #param_box.set_spacing(5) |
|---|
| 1017 | if port['type'] == 'knob': |
|---|
| 1018 | self.add_param_box(band_box, self.create_knob_box(port)) |
|---|
| 1019 | elif port['type'] == 'toggle': |
|---|
| 1020 | self.add_param_box(band_box, self.create_toggle_box(port)) |
|---|
| 1021 | |
|---|
| 1022 | self.fr.add_filter( |
|---|
| 1023 | str(i + 1), |
|---|
| 1024 | self.ports[port_index - 3]['adj'], # frequency |
|---|
| 1025 | self.ports[port_index - 1]['adj'], # gain |
|---|
| 1026 | self.ports[port_index - 2]['adj'], # bandwidth |
|---|
| 1027 | filter_colors[i]) |
|---|
| 1028 | |
|---|
| 1029 | band_frame.add(band_box) |
|---|
| 1030 | |
|---|
| 1031 | self.param_hbox.pack_start(band_frame, True, True) |
|---|
| 1032 | |
|---|
| 1033 | self.param_hbox.pack_start(misc_box, True, True) |
|---|
| 1034 | |
|---|
| 1035 | self.initator = True |
|---|
| 1036 | |
|---|
| 1037 | def on_about(self, widget): |
|---|
| 1038 | about = gtk.AboutDialog() |
|---|
| 1039 | about.set_transient_for(self.window) |
|---|
| 1040 | about.set_name("4-band parametric filter") |
|---|
| 1041 | #about.set_website(program_data['website']) |
|---|
| 1042 | about.set_authors(["Nedko Arnaudov - LV2 plugin and GUI", 'Fons Adriaensen - DSP code']) |
|---|
| 1043 | about.set_artists(["LV2 logo has been designed by Thorsten Wilms, based on a concept from Peter Shorthose."]) |
|---|
| 1044 | about.set_logo(self.lv2logo) |
|---|
| 1045 | about.show() |
|---|
| 1046 | about.run() |
|---|
| 1047 | about.hide() |
|---|
| 1048 | |
|---|
| 1049 | def create_knob_box(self, port): |
|---|
| 1050 | param_box = gtk.VBox() |
|---|
| 1051 | step = (port['max'] - port['min']) / 100 |
|---|
| 1052 | adj = SmartAdjustment(port['log'], port['min'], port['min'], port['max'], step, step * 20) |
|---|
| 1053 | adj.port = port |
|---|
| 1054 | port['adj'] = adj |
|---|
| 1055 | |
|---|
| 1056 | adj.connect("value-changed", self.on_adj_value_changed) |
|---|
| 1057 | |
|---|
| 1058 | knob = Knob() |
|---|
| 1059 | knob.set_adjustment(adj) |
|---|
| 1060 | align = gtk.Alignment(0.5, 0.5, 0, 0) |
|---|
| 1061 | align.set_padding(0, 0, 20, 20) |
|---|
| 1062 | align.add(knob) |
|---|
| 1063 | param_box.pack_start(align, False) |
|---|
| 1064 | |
|---|
| 1065 | adj.label = gtk.Label(self.get_adj_value_text(adj)[0]) |
|---|
| 1066 | param_box.pack_start(adj.label, False) |
|---|
| 1067 | #spin = gtk.SpinButton(adj, 0.0, 2) |
|---|
| 1068 | #param_box.pack_start(spin, False) |
|---|
| 1069 | |
|---|
| 1070 | label = gtk.Label(port['name']) |
|---|
| 1071 | param_box.pack_start(label, False) |
|---|
| 1072 | return param_box |
|---|
| 1073 | |
|---|
| 1074 | def create_toggle_box(self, port): |
|---|
| 1075 | param_box = gtk.VBox() |
|---|
| 1076 | button = gtk.CheckButton(port['name']) |
|---|
| 1077 | button.port = port |
|---|
| 1078 | port['widget'] = button |
|---|
| 1079 | |
|---|
| 1080 | button.connect("toggled", self.on_button_toggled) |
|---|
| 1081 | |
|---|
| 1082 | align = gtk.Alignment(0.5, 0.5, 0, 0) |
|---|
| 1083 | align.add(button) |
|---|
| 1084 | param_box.pack_start(align, False) |
|---|
| 1085 | return param_box |
|---|
| 1086 | |
|---|
| 1087 | def add_param_box(self, container, param_box): |
|---|
| 1088 | align = gtk.Alignment(0.5, 0.5, 1.0, 1.0) |
|---|
| 1089 | align.set_padding(10, 10, 10, 10) |
|---|
| 1090 | align.add(param_box) |
|---|
| 1091 | |
|---|
| 1092 | container.pack_start(align, True) |
|---|
| 1093 | |
|---|
| 1094 | def get_adj_value_text(self, adj): |
|---|
| 1095 | value = adj.get_value() |
|---|
| 1096 | if value >= 10000: |
|---|
| 1097 | format = "%.0f" |
|---|
| 1098 | elif value >= 1000: |
|---|
| 1099 | format = "%.1f" |
|---|
| 1100 | else: |
|---|
| 1101 | format = "%.2f" |
|---|
| 1102 | text = format % value |
|---|
| 1103 | unit = adj.port['unit'] |
|---|
| 1104 | if unit: |
|---|
| 1105 | text += " " + unit |
|---|
| 1106 | |
|---|
| 1107 | return value, text |
|---|
| 1108 | |
|---|
| 1109 | def on_adj_value_changed(self, adj): |
|---|
| 1110 | value, text = self.get_adj_value_text(adj) |
|---|
| 1111 | adj.label.set_text(text) |
|---|
| 1112 | |
|---|
| 1113 | if adj.port['index'] == 1: |
|---|
| 1114 | #print "Master gain = %.2f dB" % adj.get_value() |
|---|
| 1115 | self.fr.set_master_gain(adj.get_value()) |
|---|
| 1116 | |
|---|
| 1117 | if self.initator: |
|---|
| 1118 | #print adj.port, adj.get_value() |
|---|
| 1119 | self.send_port_value(adj.port['index'] + self.port_base, value) |
|---|
| 1120 | |
|---|
| 1121 | def on_button_toggled(self, widget): |
|---|
| 1122 | port_index = widget.port['index'] |
|---|
| 1123 | band_no = (port_index - 2) / 4 + 1 |
|---|
| 1124 | if widget.get_active(): |
|---|
| 1125 | value = 1.0 |
|---|
| 1126 | if band_no > 0: |
|---|
| 1127 | self.fr.enable_filter(str(band_no)) |
|---|
| 1128 | else: |
|---|
| 1129 | self.fr.master_enable() |
|---|
| 1130 | else: |
|---|
| 1131 | value = 0.0 |
|---|
| 1132 | if band_no > 0: |
|---|
| 1133 | self.fr.disable_filter(str(band_no)) |
|---|
| 1134 | else: |
|---|
| 1135 | self.fr.master_disable() |
|---|
| 1136 | |
|---|
| 1137 | if self.initator: |
|---|
| 1138 | self.send_port_value(port_index + self.port_base, value) |
|---|
| 1139 | |
|---|
| 1140 | def send(self, lines): |
|---|
| 1141 | if self.fake: |
|---|
| 1142 | return |
|---|
| 1143 | |
|---|
| 1144 | for line in lines: |
|---|
| 1145 | #print 'send: "' + line + '"' |
|---|
| 1146 | self.send_pipe.write(line + "\n") |
|---|
| 1147 | self.send_pipe.flush() |
|---|
| 1148 | |
|---|
| 1149 | def send_exiting(self): |
|---|
| 1150 | self.send(["exiting"]) |
|---|
| 1151 | |
|---|
| 1152 | def send_port_value(self, port_index, value): |
|---|
| 1153 | self.send(["port_value", str(port_index), "%.10f" % value]) |
|---|
| 1154 | |
|---|
| 1155 | def send_hi(self): |
|---|
| 1156 | self.send([""]) # send empty line (just newline char) |
|---|
| 1157 | |
|---|
| 1158 | def recv_line(self): |
|---|
| 1159 | return self.recv_pipe.readline().strip() |
|---|
| 1160 | |
|---|
| 1161 | def recv_command(self): |
|---|
| 1162 | try: |
|---|
| 1163 | msg = self.recv_line() |
|---|
| 1164 | |
|---|
| 1165 | if msg == "port_value": |
|---|
| 1166 | port_index = int(self.recv_line()) |
|---|
| 1167 | port_value = float(self.recv_line()) |
|---|
| 1168 | #print "port_value_change recevied: %d %f" % (port_index, port_value) |
|---|
| 1169 | self.on_port_value_changed(port_index, port_value) |
|---|
| 1170 | elif msg == "show": |
|---|
| 1171 | self.on_show() |
|---|
| 1172 | elif msg == "hide": |
|---|
| 1173 | self.on_hide() |
|---|
| 1174 | elif msg == "quit": |
|---|
| 1175 | self.on_quit() |
|---|
| 1176 | else: |
|---|
| 1177 | print 'unknown message: "' + msg + '"' |
|---|
| 1178 | |
|---|
| 1179 | return True |
|---|
| 1180 | except IOError: |
|---|
| 1181 | return False |
|---|
| 1182 | |
|---|
| 1183 | def on_recv(self, fd, cond): |
|---|
| 1184 | #print "on_recv" |
|---|
| 1185 | if cond == gobject.IO_HUP: |
|---|
| 1186 | gtk.main_quit() |
|---|
| 1187 | return False |
|---|
| 1188 | |
|---|
| 1189 | while True: |
|---|
| 1190 | if not self.recv_command(): |
|---|
| 1191 | break |
|---|
| 1192 | |
|---|
| 1193 | return True |
|---|
| 1194 | |
|---|
| 1195 | def run(self): |
|---|
| 1196 | self.window.connect("destroy", self.on_window_closed) |
|---|
| 1197 | |
|---|
| 1198 | if self.fake: |
|---|
| 1199 | if not self.shown: |
|---|
| 1200 | self.shown = True |
|---|
| 1201 | self.on_show() |
|---|
| 1202 | else: |
|---|
| 1203 | self.send_hi() |
|---|
| 1204 | gobject.io_add_watch(self.recv_pipe_fd, gobject.IO_IN | gobject.IO_HUP, self.on_recv) |
|---|
| 1205 | |
|---|
| 1206 | gtk.main() |
|---|
| 1207 | |
|---|
| 1208 | def on_port_value_changed(self, port_index, port_value): |
|---|
| 1209 | #print "port %d set to %f" % (port_index, port_value) |
|---|
| 1210 | port_index -= self.port_base |
|---|
| 1211 | port = self.ports[port_index] |
|---|
| 1212 | #print repr(port) |
|---|
| 1213 | port_type = port['type'] |
|---|
| 1214 | if port_type == 'knob': |
|---|
| 1215 | self.initator = False |
|---|
| 1216 | port['adj'].set_value(port_value) |
|---|
| 1217 | self.initator = True |
|---|
| 1218 | elif port_type == 'toggle': |
|---|
| 1219 | if port_value > 0.0: |
|---|
| 1220 | toggled = True |
|---|
| 1221 | else: |
|---|
| 1222 | toggled = False |
|---|
| 1223 | |
|---|
| 1224 | self.initator = False |
|---|
| 1225 | port['widget'].set_active(toggled) |
|---|
| 1226 | self.initator = True |
|---|
| 1227 | |
|---|
| 1228 | def on_show(self): |
|---|
| 1229 | self.window.show_all() |
|---|
| 1230 | |
|---|
| 1231 | def on_hide(self): |
|---|
| 1232 | self.window.hide_all() |
|---|
| 1233 | |
|---|
| 1234 | def on_quit(self): |
|---|
| 1235 | gtk.main_quit() |
|---|
| 1236 | |
|---|
| 1237 | def on_window_closed(self, arg): |
|---|
| 1238 | self.send_exiting() |
|---|
| 1239 | gtk.main_quit() |
|---|
| 1240 | |
|---|
| 1241 | def main(): |
|---|
| 1242 | filter_ui(sys.argv).run() |
|---|
| 1243 | #print "main done" |
|---|
| 1244 | |
|---|
| 1245 | if __name__ == '__main__': |
|---|
| 1246 | main() |
|---|