More ticks, less white bevel, new button graphics
[calf.git] / src / ctl_vumeter.cpp
blobd76ffcd96b655e43a49873d2cd9cd51debea3964
1 /* Calf DSP Library
2 * Custom controls (line graph, knob).
3 * Copyright (C) 2007-2010 Krzysztof Foltman, Torben Hohn, Markus Schmidt
4 * and others
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
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 GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General
17 * Public License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301 USA
22 #include "config.h"
23 #include <calf/primitives.h>
24 #include <calf/ctl_vumeter.h>
25 #include <gdk/gdkkeysyms.h>
26 #include <cairo/cairo.h>
27 #include <math.h>
28 #include <gdk/gdk.h>
29 #include <sys/time.h>
30 #include <string>
31 #include <calf/drawingutils.h>
34 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
36 static gboolean
37 calf_vumeter_expose (GtkWidget *widget, GdkEventExpose *event)
39 g_assert(CALF_IS_VUMETER(widget));
41 CalfVUMeter *vu = CALF_VUMETER(widget);
42 GtkStyle *style;
43 style = gtk_widget_get_style(widget);
44 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
46 float r, g, b;
48 int width = widget->allocation.width; int height = widget->allocation.height;
49 int border_x = 0; int border_y = 0; // outer border
50 int space_x = 1; int space_y = 1; // inner border around led bar
51 int led = 2; // single LED size
52 int led_m = 1; // margin between LED
53 int led_s = led + led_m; // size of LED with margin
54 int led_x = widget->style->xthickness;
55 int led_y = widget->style->ythickness; // position of first LED
56 int led_w = width - 2 * led_x + led_m; // width of LED bar w/o text calc (additional led margin, is removed later; used for filling the led bar completely w/o margin gap)
57 int led_h = height - 2 * led_y; // height of LED bar w/o text calc
58 int text_x = 0; int text_y = 0;
59 int text_w = 0; int text_h = 0;
60 int text_m = 3; // text margin
62 // only valid if vumeter is enabled
63 cairo_text_extents_t extents;
65 if(vu->vumeter_position) {
66 cairo_select_font_face(c, "cairo:sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
67 cairo_set_font_size(c, 8);
69 cairo_text_extents(c, "-88.88", &extents);
70 text_w = extents.width;
71 text_h = extents.height;
72 switch(vu->vumeter_position) {
73 case 1:
74 text_x = width / 2 - text_w / 2;
75 text_y = border_y + text_m - extents.y_bearing;
76 led_y += text_h + text_m;
77 led_h -= text_h + text_m;
78 break;
79 case 2:
80 text_x = width - border_x - text_m * 2 - text_w;
81 text_y = height / 2 - text_h / 2 - extents.y_bearing;
82 led_w -= text_m * 2 + text_w;
83 break;
84 case 3:
85 text_x = width / 2 - text_w / 2;
86 text_y = height - border_y - text_m - text_h - extents.y_bearing;
87 led_h -= text_m * 2 + text_h;
88 break;
89 case 4:
90 text_x = border_x + text_m;
91 text_y = height / 2 - text_h / 2 - extents.y_bearing;
92 led_x += text_m * 2 + text_w;
93 led_w -= text_m * 2 + text_w;
94 break;
98 led_w -= led_w % led_s + led_m; //round LED width to LED size and remove margin gap, width is filled with LED without margin gap now
100 if( vu->cache_surface == NULL ) {
101 // looks like its either first call or the widget has been resized.
102 // create the cache_surface.
103 cairo_surface_t *window_surface = cairo_get_target( c );
104 vu->cache_surface = cairo_surface_create_similar( window_surface,
105 CAIRO_CONTENT_COLOR,
106 widget->allocation.width,
107 widget->allocation.height );
109 // And render the meterstuff
111 cairo_t *cache_cr = cairo_create( vu->cache_surface );
112 gdk_cairo_set_source_color(cache_cr,&style->bg[GTK_STATE_NORMAL]);
113 cairo_paint(cache_cr);
115 // outer (black)
116 cairo_rectangle(cache_cr, 0, 0, width, height);
117 cairo_set_source_rgb(cache_cr, 0.3, 0.3, 0.3);
118 cairo_set_operator(cache_cr,CAIRO_OPERATOR_CLEAR);
119 cairo_fill(cache_cr);
120 cairo_set_operator(cache_cr,CAIRO_OPERATOR_OVER);
121 // inner (bevel)
122 create_rectangle(cache_cr,
123 border_x,
124 border_y,
125 width - border_x * 2,
126 height - border_y * 2, 0);
127 cairo_pattern_t *pat2 = cairo_pattern_create_linear (border_x,
128 border_y,
129 border_x,
130 height - border_y * 2);
131 get_bg_color(widget, NULL, &r, &g, &b);
132 cairo_pattern_add_color_stop_rgba (pat2, 0, r*1.11, g*1.11, b*1.11, 1);
133 cairo_pattern_add_color_stop_rgba (pat2, 1, r*0.92, g*0.92, b*0.92, 1);
134 cairo_set_source (cache_cr, pat2);
135 cairo_fill(cache_cr);
136 cairo_pattern_destroy(pat2);
138 // border around LED
139 cairo_rectangle(cache_cr,
140 led_x - space_x,
141 led_y - space_y,
142 led_w + space_x * 2,
143 led_h + space_y * 2);
144 cairo_set_source_rgb (cache_cr, 0, 0, 0);
145 cairo_fill(cache_cr);
147 // LED bases
148 cairo_set_line_width(cache_cr, 1);
149 for (int x = led_x; x + led <= led_x + led_w; x += led_s)
151 float ts = (x - led_x) * 1.0 / led_w;
152 float r = 0.f, g = 0.f, b = 0.f;
153 switch(vu->mode)
155 case VU_STANDARD:
156 default:
157 if (ts < 0.75)
158 r = ts / 0.75, g = 0.5 + ts * 0.66, b = 1 - ts / 0.75;
159 else
160 r = 1, g = 1 - (ts - 0.75) / 0.25, b = 0;
161 // if (vu->value < ts || vu->value <= 0)
162 // r *= 0.5, g *= 0.5, b *= 0.5;
163 break;
164 case VU_STANDARD_CENTER:
165 if (ts < 0.25)
166 // 0.0 -> 0.25
167 // green: 0.f -> 1.f
168 r = 1, g = (ts) / 0.25, b = 0;
169 else if (ts > 0.75)
170 // 0.75 -> 1.0
171 // green: 1.f -> 0.f
172 r = 1, g = 1 - (ts - 0.75) / 0.25, b = 0;
173 else if (ts > 0.5)
174 // 0.5 -> 0.75
175 // red: 0.f -> 1.f
176 // green: 0.5 -> 1.f
177 // blue: 1.f -> 0.f
178 r = (ts - 0.5) / 0.25, g = 0.5 + (ts - 0.5) * 2.f, b = 1 - (ts - 0.5) / 0.25;
179 else
180 // 0.25 -> 0.5
181 // red: 1.f -> 0.f
182 // green: 1.f -> 0.5
183 // blue: 0.f -> 1.f
184 r = 1 - (ts - 0.25) / 0.25, g = 1.f - (ts * 2.f - .5f), b = (ts - 0.25) / 0.25;
185 // if (vu->value < ts || vu->value <= 0)
186 // r *= 0.5, g *= 0.5, b *= 0.5;
187 break;
188 case VU_MONOCHROME_REVERSE:
189 r = 0, g = 170.0 / 255.0, b = 1;
190 // if (!(vu->value < ts) || vu->value >= 1.0)
191 // r *= 0.5, g *= 0.5, b *= 0.5;
192 break;
193 case VU_MONOCHROME:
194 r = 0, g = 170.0 / 255.0, b = 1;
195 // if (vu->value < ts || vu->value <= 0)
196 // r *= 0.5, g *= 0.5, b *= 0.5;
197 break;
198 case VU_MONOCHROME_CENTER:
199 r = 0, g = 170.0 / 255.0, b = 1;
200 // if (vu->value < ts || vu->value <= 0)
201 // r *= 0.5, g *= 0.5, b *= 0.5;
202 break;
204 GdkColor sc2 = { 0, (guint16)(65535 * r + 0.2), (guint16)(65535 * g), (guint16)(65535 * b) };
205 GdkColor sc3 = { 0, (guint16)(65535 * r * 0.7), (guint16)(65535 * g * 0.7), (guint16)(65535 * b * 0.7) };
206 gdk_cairo_set_source_color(cache_cr, &sc2);
207 cairo_move_to(cache_cr, x + 0.5, led_y);
208 cairo_line_to(cache_cr, x + 0.5, led_y + led_h);
209 cairo_stroke(cache_cr);
210 gdk_cairo_set_source_color(cache_cr, &sc3);
211 cairo_move_to(cache_cr, x + 1.5, led_y + led_h);
212 cairo_line_to(cache_cr, x + 1.5, led_y);
213 cairo_stroke(cache_cr);
215 // create blinder pattern
216 cairo_pattern_t *pat = cairo_pattern_create_linear (led_x, led_y, led_x, led_y + led_h);
217 cairo_pattern_add_color_stop_rgba (pat, 0, 1, 1, 1, 0.25);
218 cairo_pattern_add_color_stop_rgba (pat, 0.5, 0.5, 0.5, 0.5, 0.0);
219 cairo_pattern_add_color_stop_rgba (pat, 1, 0.0, 0.0, 0.0, 0.25);
220 cairo_rectangle(cache_cr, led_x, led_y, led_w, led_h);
221 cairo_set_source(cache_cr, pat);
222 cairo_fill(cache_cr);
224 // create overlay
225 vu->cache_overlay = cairo_surface_create_similar(window_surface,
226 CAIRO_CONTENT_COLOR,
227 widget->allocation.width,
228 widget->allocation.height);
229 cairo_t *over_cr = cairo_create(vu->cache_overlay);
231 // copy surface to overlay
232 cairo_set_source_surface(over_cr, vu->cache_surface, 0, 0);
233 cairo_rectangle(over_cr, 0, 0, width, height);
234 cairo_fill(over_cr);
236 // create blinder pattern
237 pat = cairo_pattern_create_linear (led_x, led_y, led_x, led_y + led_h);
238 cairo_pattern_add_color_stop_rgba (pat, 0, 0.2, 0.2, 0.2, 0.7);
239 cairo_pattern_add_color_stop_rgba (pat, 0.4, 0.05, 0.05, 0.05, 0.7);
240 cairo_pattern_add_color_stop_rgba (pat, 0.401, 0.05, 0.05, 0.05, 0.9);
241 cairo_pattern_add_color_stop_rgba (pat, 1, 0.05, 0.05, 0.05, 0.75);
243 // draw on top of overlay
244 cairo_set_source(over_cr, pat);
245 cairo_rectangle(over_cr, 0, 0, width, height);
246 cairo_paint(over_cr);
248 // clean up
249 cairo_destroy(cache_cr);
250 cairo_destroy(over_cr);
253 // draw LED blinder
254 cairo_set_source_surface( c, vu->cache_surface, 0,0 );
255 cairo_paint( c );
256 cairo_set_source_surface( c, vu->cache_overlay, 0, 0 );
258 // get microseconds
259 timeval tv;
260 gettimeofday(&tv, 0);
261 long time = tv.tv_sec * 1000 * 1000 + tv.tv_usec;
263 // limit to 1.f
264 float value_orig = std::max(std::min(vu->value, 1.f), 0.f);
265 float value = 0.f;
267 // falloff?
268 if(vu->vumeter_falloff > 0.f and vu->mode != VU_MONOCHROME_REVERSE) {
269 // fall off a bit
270 float s = ((float)(time - vu->last_falltime) / 1000000.0);
271 float m = vu->last_falloff * s * vu->vumeter_falloff;
272 vu->last_falloff -= m;
273 // new max value?
274 if(value_orig > vu->last_falloff) {
275 vu->last_falloff = value_orig;
277 value = vu->last_falloff;
278 vu->last_falltime = time;
279 vu->falling = vu->last_falloff > 0.00000001;
280 } else {
281 // falloff disabled
282 vu->last_falloff = 0.f;
283 vu->last_falltime = 0.f;
284 value = value_orig;
285 vu->falling = false;
288 float draw = 0.f;
289 float draw_last = 0.f;
291 if(vu->vumeter_hold > 0.0) {
292 // peak hold timer
293 if(time - (long)(vu->vumeter_hold * 1000 * 1000) > vu->last_hold) {
294 // time's up, reset
295 vu->last_value = value;
296 vu->last_hold = time;
297 vu->holding = false;
298 vu->disp_value = value_orig;
300 if( vu->mode == VU_MONOCHROME_REVERSE ) {
301 if(value < vu->last_value) {
302 // value is above peak hold
303 vu->last_value = value;
304 vu->last_hold = time;
305 vu->holding = true;
307 draw = log10(1 + value * 9);
308 draw_last = log10(1 + vu->last_value * 9);
310 // blinder left -> hold LED
311 int hold_x = round((draw_last) * (led_w + led_m)); // add last led_m removed earlier
312 hold_x -= hold_x % led_s + led_m;
313 hold_x = std::max(0, hold_x);
314 cairo_rectangle( c, led_x, led_y, hold_x, led_h);
316 // blinder hold LED -> value
317 int val_x = round((1 - draw) * (led_w + led_m)); // add last led_m removed earlier
318 val_x -= val_x % led_s;
319 int blind_x = std::min(hold_x + led_s, led_w);
320 int blind_w = std::min(std::max(led_w - val_x - hold_x - led_s, 0), led_w);
321 cairo_rectangle(c, led_x + blind_x, led_y, blind_w, led_h);
322 } else if( vu->mode == VU_STANDARD_CENTER ) {
323 if(value > vu->last_value) {
324 // value is above peak hold
325 vu->last_value = value;
326 vu->last_hold = time;
327 vu->holding = true;
329 draw = log10(1 + value * 9);
330 int val_x = round((1 - draw) / 2.f * (led_w + led_m)); // add last led_m removed earlier
331 cairo_rectangle(c, led_x, led_y, val_x, led_h);
332 cairo_rectangle(c, led_x + led_w - val_x, led_y, val_x, led_h);
334 } else {
335 if(value > vu->last_value) {
336 // value is above peak hold
337 vu->last_value = value;
338 vu->last_hold = time;
339 vu->holding = true;
341 draw = log10(1 + value * 9);
342 draw_last = log10(1 + vu->last_value * 9);
344 int hold_x = round((1 - draw_last) * (led_w + led_m)); // add last led_m removed earlier
345 hold_x -= hold_x % led_s;
346 int val_x = round(draw * (led_w + led_m)); // add last led_m removed earlier
347 val_x -= val_x % led_s;
348 int blind_w = led_w - hold_x - led_s - val_x;
349 blind_w = std::min(std::max(blind_w, 0), led_w);
350 cairo_rectangle(c, led_x + val_x, led_y, blind_w, led_h);
351 cairo_rectangle( c, led_x + led_w - hold_x, led_y, hold_x, led_h);
353 } else {
354 // darken normally
355 float draw = log10(1 + value * 9);
356 if( vu->mode == VU_MONOCHROME_REVERSE )
357 cairo_rectangle( c, led_x, led_y, draw * led_w, led_h);
358 else if( vu->mode == VU_STANDARD_CENTER ) {
359 int val_x = round((1 - draw) / 2.f * (led_w + led_m)); // add last led_m removed earlier
360 cairo_rectangle(c, led_x, led_y, val_x, led_h);
361 cairo_rectangle(c, led_x + led_w - val_x, led_y, val_x, led_h);
362 } else
363 cairo_rectangle( c, led_x + draw * led_w, led_y, led_w * (1 - draw), led_h);
365 cairo_fill( c );
367 if (vu->vumeter_position)
369 char str[32];
370 if((vu->value > vu->disp_value and vu->mode != VU_MONOCHROME_REVERSE)
371 or (vu->value < vu->disp_value and vu->mode == VU_MONOCHROME_REVERSE))
372 vu->disp_value = vu->value;
373 if (vu->disp_value < 1.0 / 32768.0)
374 snprintf(str, sizeof(str), "-inf");
375 else
376 snprintf(str, sizeof(str), "%0.2f", dsp::amp2dB(vu->disp_value));
377 // draw value as number
378 cairo_text_extents(c, str, &extents);
379 cairo_move_to(c, text_x + (text_w - extents.width) / 2.0, text_y);
380 GtkStateType state;
381 if(vu->disp_value > 1.f and vu->mode != VU_MONOCHROME_REVERSE)
382 state = GTK_STATE_ACTIVE;
383 else
384 state = GTK_STATE_NORMAL;
385 get_fg_color(widget, &state, &r, &g, &b);
386 cairo_set_source_rgba (c, r, g, b, 1);
387 cairo_show_text(c, str);
388 cairo_fill(c);
390 cairo_destroy(c);
391 //gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 2, oy - 2, sx + 4, sy + 4);
392 //printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
394 return TRUE;
397 static void
398 calf_vumeter_size_request (GtkWidget *widget,
399 GtkRequisition *requisition)
401 g_assert(CALF_IS_VUMETER(widget));
402 CalfVUMeter *self = CALF_VUMETER(widget);
403 requisition->width = self->vumeter_width;
404 requisition->height = self->vumeter_height;
407 static void
408 calf_vumeter_unrealize (GtkWidget *widget, CalfVUMeter *vu)
410 if( vu->cache_surface )
411 cairo_surface_destroy( vu->cache_surface );
412 vu->cache_surface = NULL;
413 if( vu->cache_overlay )
414 cairo_surface_destroy( vu->cache_overlay );
415 vu->cache_overlay = NULL;
418 static void
419 calf_vumeter_size_allocate (GtkWidget *widget,
420 GtkAllocation *allocation)
422 g_assert(CALF_IS_VUMETER(widget));
423 CalfVUMeter *vu = CALF_VUMETER(widget);
425 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_VUMETER_GET_CLASS( vu ) );
427 parent_class->size_allocate( widget, allocation );
429 calf_vumeter_unrealize(widget, vu);
432 static void
433 calf_vumeter_class_init (CalfVUMeterClass *klass)
435 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
436 widget_class->expose_event = calf_vumeter_expose;
437 widget_class->size_request = calf_vumeter_size_request;
438 widget_class->size_allocate = calf_vumeter_size_allocate;
441 static void
442 calf_vumeter_init (CalfVUMeter *self)
444 GtkWidget *widget = GTK_WIDGET(self);
445 //GTK_WIDGET_SET_FLAGS (widget, GTK_NO_WINDOW);
446 widget->requisition.width = self->vumeter_width;
447 widget->requisition.height = self->vumeter_height;
448 self->cache_surface = NULL;
449 self->falling = false;
450 self->holding = false;
451 self->meter_width = 0;
452 self->disp_value = 0.f;
453 self->value = 0.f;
454 g_signal_connect(GTK_OBJECT(widget), "unrealize", G_CALLBACK(calf_vumeter_unrealize), (gpointer)self);
457 GtkWidget *
458 calf_vumeter_new()
460 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER, NULL ));
463 GType
464 calf_vumeter_get_type (void)
466 static GType type = 0;
467 if (!type) {
468 static const GTypeInfo type_info = {
469 sizeof(CalfVUMeterClass),
470 NULL, /* base_init */
471 NULL, /* base_finalize */
472 (GClassInitFunc)calf_vumeter_class_init,
473 NULL, /* class_finalize */
474 NULL, /* class_data */
475 sizeof(CalfVUMeter),
476 0, /* n_preallocs */
477 (GInstanceInitFunc)calf_vumeter_init
480 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
482 for (int i = 0; ; i++) {
483 char *name = g_strdup_printf("CalfVUMeter%u%d", ((unsigned int)(intptr_t)calf_vumeter_class_init) >> 16, i);
484 if (g_type_from_name(name)) {
485 free(name);
486 continue;
488 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
489 name,
490 type_info_copy,
491 (GTypeFlags)0);
492 free(name);
493 break;
496 return type;
499 extern void calf_vumeter_set_value(CalfVUMeter *meter, float value)
501 if (value != meter->value or meter->holding or meter->falling)
503 meter->value = value;
504 gtk_widget_queue_draw(GTK_WIDGET(meter));
508 extern float calf_vumeter_get_value(CalfVUMeter *meter)
510 return meter->value;
513 extern void calf_vumeter_set_mode(CalfVUMeter *meter, CalfVUMeterMode mode)
515 if (mode != meter->mode)
517 meter->mode = mode;
518 if(mode == VU_MONOCHROME_REVERSE) {
519 meter->value = 1.f;
520 meter->last_value = 1.f;
521 } else {
522 meter->value = 0.f;
523 meter->last_value = 0.f;
525 meter->vumeter_falloff = 0.f;
526 meter->last_falloff = (long)0;
527 meter->last_hold = (long)0;
528 gtk_widget_queue_draw(GTK_WIDGET(meter));
532 extern CalfVUMeterMode calf_vumeter_get_mode(CalfVUMeter *meter)
534 return meter->mode;
537 extern void calf_vumeter_set_falloff(CalfVUMeter *meter, float value)
539 if (value != meter->vumeter_falloff)
541 meter->vumeter_falloff = value;
542 gtk_widget_queue_draw(GTK_WIDGET(meter));
546 extern float calf_vumeter_get_falloff(CalfVUMeter *meter)
548 return meter->vumeter_falloff;
551 extern void calf_vumeter_set_hold(CalfVUMeter *meter, float value)
553 if (value != meter->vumeter_hold)
555 meter->vumeter_hold = value;
556 gtk_widget_queue_draw(GTK_WIDGET(meter));
560 extern float calf_vumeter_get_hold(CalfVUMeter *meter)
562 return meter->vumeter_hold;
565 extern void calf_vumeter_set_width(CalfVUMeter *meter, int value)
567 if (value != meter->vumeter_width)
569 meter->vumeter_width = value;
570 gtk_widget_queue_draw(GTK_WIDGET(meter));
574 extern int calf_vumeter_get_width(CalfVUMeter *meter)
576 return meter->vumeter_width;
579 extern void calf_vumeter_set_height(CalfVUMeter *meter, int value)
581 if (value != meter->vumeter_height)
583 meter->vumeter_height = value;
584 gtk_widget_queue_draw(GTK_WIDGET(meter));
588 extern int calf_vumeter_get_height(CalfVUMeter *meter)
590 return meter->vumeter_height;
592 extern void calf_vumeter_set_position(CalfVUMeter *meter, int value)
594 if (value != meter->vumeter_height)
596 meter->vumeter_position = value;
597 gtk_widget_queue_draw(GTK_WIDGET(meter));
601 extern int calf_vumeter_get_position(CalfVUMeter *meter)
603 return meter->vumeter_position;