New design
[calf.git] / src / ctl_vumeter.cpp
blob2220eff7e88c4e4c3943c5d464b79269ef3314f1
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/custom_ctl.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 int width = widget->allocation.width; int height = widget->allocation.height;
47 int border_x = 0; int border_y = 0; // outer border
48 int space_x = 1; int space_y = 1; // inner border around led bar
49 int led = 2; // single LED size
50 int led_m = 1; // margin between LED
51 int led_s = led + led_m; // size of LED with margin
52 int led_x = 5; int led_y = 4; // position of first LED
53 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)
54 int led_h = height - 2 * led_y; // height of LED bar w/o text calc
55 int text_x = 0; int text_y = 0;
56 int text_w = 0; int text_h = 0;
57 int text_m = 3; // text margin
59 // only valid if vumeter is enabled
60 cairo_text_extents_t extents;
62 if(vu->vumeter_position) {
63 cairo_select_font_face(c, "cairo:sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
64 cairo_set_font_size(c, 8);
66 cairo_text_extents(c, "-88.88", &extents);
67 text_w = extents.width;
68 text_h = extents.height;
69 switch(vu->vumeter_position) {
70 case 1:
71 text_x = width / 2 - text_w / 2;
72 text_y = border_y + text_m - extents.y_bearing;
73 led_y += text_h + text_m;
74 led_h -= text_h + text_m;
75 break;
76 case 2:
77 text_x = width - border_x - text_m * 2 - text_w;
78 text_y = height / 2 - text_h / 2 - extents.y_bearing;
79 led_w -= text_m * 2 + text_w;
80 break;
81 case 3:
82 text_x = width / 2 - text_w / 2;
83 text_y = height - border_y - text_m - text_h - extents.y_bearing;
84 led_h -= text_m * 2 + text_h;
85 break;
86 case 4:
87 text_x = border_x + text_m;
88 text_y = height / 2 - text_h / 2 - extents.y_bearing;
89 led_x += text_m * 2 + text_w;
90 led_w -= text_m * 2 + text_w;
91 break;
95 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
97 if( vu->cache_surface == NULL ) {
98 // looks like its either first call or the widget has been resized.
99 // create the cache_surface.
100 cairo_surface_t *window_surface = cairo_get_target( c );
101 vu->cache_surface = cairo_surface_create_similar( window_surface,
102 CAIRO_CONTENT_COLOR,
103 widget->allocation.width,
104 widget->allocation.height );
106 // And render the meterstuff
108 cairo_t *cache_cr = cairo_create( vu->cache_surface );
109 gdk_cairo_set_source_color(cache_cr,&style->bg[GTK_STATE_NORMAL]);
110 cairo_paint(cache_cr);
112 // outer (black)
113 cairo_rectangle(cache_cr, 0, 0, width, height);
114 cairo_set_source_rgb(cache_cr, 0.3, 0.3, 0.3);
115 cairo_set_operator(cache_cr,CAIRO_OPERATOR_CLEAR);
116 cairo_fill(cache_cr);
117 cairo_set_operator(cache_cr,CAIRO_OPERATOR_OVER);
118 // inner (bevel)
119 create_rectangle(cache_cr,
120 border_x,
121 border_y,
122 width - border_x * 2,
123 height - border_y * 2, 0);
124 cairo_pattern_t *pat2 = cairo_pattern_create_linear (border_x,
125 border_y,
126 border_x,
127 height - border_y * 2);
128 cairo_pattern_add_color_stop_rgba (pat2, 0, 0.96, 0.96, 0.96, 1);
129 cairo_pattern_add_color_stop_rgba (pat2, 1, 0.7, 0.7, 0.7, 1);
130 cairo_set_source (cache_cr, pat2);
131 cairo_fill(cache_cr);
132 cairo_pattern_destroy(pat2);
134 // border around LED
135 cairo_rectangle(cache_cr,
136 led_x - space_x,
137 led_y - space_y,
138 led_w + space_x * 2,
139 led_h + space_y * 2);
140 cairo_set_source_rgb (cache_cr, 0, 0, 0);
141 cairo_fill(cache_cr);
143 // LED bases
144 cairo_set_line_width(cache_cr, 1);
145 for (int x = led_x; x + led <= led_x + led_w; x += led_s)
147 float ts = (x - led_x) * 1.0 / led_w;
148 float r = 0.f, g = 0.f, b = 0.f;
149 switch(vu->mode)
151 case VU_STANDARD:
152 default:
153 if (ts < 0.75)
154 r = ts / 0.75, g = 0.5 + ts * 0.66, b = 1 - ts / 0.75;
155 else
156 r = 1, g = 1 - (ts - 0.75) / 0.25, b = 0;
157 // if (vu->value < ts || vu->value <= 0)
158 // r *= 0.5, g *= 0.5, b *= 0.5;
159 break;
160 case VU_STANDARD_CENTER:
161 if (ts < 0.25)
162 // 0.0 -> 0.25
163 // green: 0.f -> 1.f
164 r = 1, g = (ts) / 0.25, b = 0;
165 else if (ts > 0.75)
166 // 0.75 -> 1.0
167 // green: 1.f -> 0.f
168 r = 1, g = 1 - (ts - 0.75) / 0.25, b = 0;
169 else if (ts > 0.5)
170 // 0.5 -> 0.75
171 // red: 0.f -> 1.f
172 // green: 0.5 -> 1.f
173 // blue: 1.f -> 0.f
174 r = (ts - 0.5) / 0.25, g = 0.5 + (ts - 0.5) * 2.f, b = 1 - (ts - 0.5) / 0.25;
175 else
176 // 0.25 -> 0.5
177 // red: 1.f -> 0.f
178 // green: 1.f -> 0.5
179 // blue: 0.f -> 1.f
180 r = 1 - (ts - 0.25) / 0.25, g = 1.f - (ts * 2.f - .5f), b = (ts - 0.25) / 0.25;
181 // if (vu->value < ts || vu->value <= 0)
182 // r *= 0.5, g *= 0.5, b *= 0.5;
183 break;
184 case VU_MONOCHROME_REVERSE:
185 r = 0, g = 170.0 / 255.0, b = 1;
186 // if (!(vu->value < ts) || vu->value >= 1.0)
187 // r *= 0.5, g *= 0.5, b *= 0.5;
188 break;
189 case VU_MONOCHROME:
190 r = 0, g = 170.0 / 255.0, b = 1;
191 // if (vu->value < ts || vu->value <= 0)
192 // r *= 0.5, g *= 0.5, b *= 0.5;
193 break;
194 case VU_MONOCHROME_CENTER:
195 r = 0, g = 170.0 / 255.0, b = 1;
196 // if (vu->value < ts || vu->value <= 0)
197 // r *= 0.5, g *= 0.5, b *= 0.5;
198 break;
200 GdkColor sc2 = { 0, (guint16)(65535 * r + 0.2), (guint16)(65535 * g), (guint16)(65535 * b) };
201 GdkColor sc3 = { 0, (guint16)(65535 * r * 0.7), (guint16)(65535 * g * 0.7), (guint16)(65535 * b * 0.7) };
202 gdk_cairo_set_source_color(cache_cr, &sc2);
203 cairo_move_to(cache_cr, x + 0.5, led_y);
204 cairo_line_to(cache_cr, x + 0.5, led_y + led_h);
205 cairo_stroke(cache_cr);
206 gdk_cairo_set_source_color(cache_cr, &sc3);
207 cairo_move_to(cache_cr, x + 1.5, led_y + led_h);
208 cairo_line_to(cache_cr, x + 1.5, led_y);
209 cairo_stroke(cache_cr);
211 // create blinder pattern
212 cairo_pattern_t *pat = cairo_pattern_create_linear (led_x, led_y, led_x, led_y + led_h);
213 cairo_pattern_add_color_stop_rgba (pat, 0, 1, 1, 1, 0.25);
214 cairo_pattern_add_color_stop_rgba (pat, 0.5, 0.5, 0.5, 0.5, 0.0);
215 cairo_pattern_add_color_stop_rgba (pat, 1, 0.0, 0.0, 0.0, 0.25);
216 cairo_rectangle(cache_cr, led_x, led_y, led_w, led_h);
217 cairo_set_source(cache_cr, pat);
218 cairo_fill(cache_cr);
220 // create overlay
221 vu->cache_overlay = cairo_surface_create_similar(window_surface,
222 CAIRO_CONTENT_COLOR,
223 widget->allocation.width,
224 widget->allocation.height);
225 cairo_t *over_cr = cairo_create(vu->cache_overlay);
227 // copy surface to overlay
228 cairo_set_source_surface(over_cr, vu->cache_surface, 0, 0);
229 cairo_rectangle(over_cr, 0, 0, width, height);
230 cairo_fill(over_cr);
232 // create blinder pattern
233 pat = cairo_pattern_create_linear (led_x, led_y, led_x, led_y + led_h);
234 cairo_pattern_add_color_stop_rgba (pat, 0, 0.2, 0.2, 0.2, 0.7);
235 cairo_pattern_add_color_stop_rgba (pat, 0.4, 0.05, 0.05, 0.05, 0.7);
236 cairo_pattern_add_color_stop_rgba (pat, 0.401, 0.05, 0.05, 0.05, 0.9);
237 cairo_pattern_add_color_stop_rgba (pat, 1, 0.05, 0.05, 0.05, 0.75);
239 // draw on top of overlay
240 cairo_set_source(over_cr, pat);
241 cairo_rectangle(over_cr, 0, 0, width, height);
242 cairo_paint(over_cr);
244 // clean up
245 cairo_destroy(cache_cr);
246 cairo_destroy(over_cr);
249 // draw LED blinder
250 cairo_set_source_surface( c, vu->cache_surface, 0,0 );
251 cairo_paint( c );
252 cairo_set_source_surface( c, vu->cache_overlay, 0, 0 );
254 // get microseconds
255 timeval tv;
256 gettimeofday(&tv, 0);
257 long time = tv.tv_sec * 1000 * 1000 + tv.tv_usec;
259 // limit to 1.f
260 float value_orig = std::max(std::min(vu->value, 1.f), 0.f);
261 float value = 0.f;
263 // falloff?
264 if(vu->vumeter_falloff > 0.f and vu->mode != VU_MONOCHROME_REVERSE) {
265 // fall off a bit
266 float s = ((float)(time - vu->last_falltime) / 1000000.0);
267 float m = vu->last_falloff * s * vu->vumeter_falloff;
268 vu->last_falloff -= m;
269 // new max value?
270 if(value_orig > vu->last_falloff) {
271 vu->last_falloff = value_orig;
273 value = vu->last_falloff;
274 vu->last_falltime = time;
275 vu->falling = vu->last_falloff > 0.00000001;
276 } else {
277 // falloff disabled
278 vu->last_falloff = 0.f;
279 vu->last_falltime = 0.f;
280 value = value_orig;
281 vu->falling = false;
284 float draw = 0.f;
285 float draw_last = 0.f;
287 if(vu->vumeter_hold > 0.0) {
288 // peak hold timer
289 if(time - (long)(vu->vumeter_hold * 1000 * 1000) > vu->last_hold) {
290 // time's up, reset
291 vu->last_value = value;
292 vu->last_hold = time;
293 vu->holding = false;
294 vu->disp_value = value_orig;
296 if( vu->mode == VU_MONOCHROME_REVERSE ) {
297 if(value < vu->last_value) {
298 // value is above peak hold
299 vu->last_value = value;
300 vu->last_hold = time;
301 vu->holding = true;
303 draw = log10(1 + value * 9);
304 draw_last = log10(1 + vu->last_value * 9);
306 // blinder left -> hold LED
307 int hold_x = round((draw_last) * (led_w + led_m)); // add last led_m removed earlier
308 hold_x -= hold_x % led_s + led_m;
309 hold_x = std::max(0, hold_x);
310 cairo_rectangle( c, led_x, led_y, hold_x, led_h);
312 // blinder hold LED -> value
313 int val_x = round((1 - draw) * (led_w + led_m)); // add last led_m removed earlier
314 val_x -= val_x % led_s;
315 int blind_x = std::min(hold_x + led_s, led_w);
316 int blind_w = std::min(std::max(led_w - val_x - hold_x - led_s, 0), led_w);
317 cairo_rectangle(c, led_x + blind_x, led_y, blind_w, led_h);
318 } else if( vu->mode == VU_STANDARD_CENTER ) {
319 if(value > vu->last_value) {
320 // value is above peak hold
321 vu->last_value = value;
322 vu->last_hold = time;
323 vu->holding = true;
325 draw = log10(1 + value * 9);
326 int val_x = round((1 - draw) / 2.f * (led_w + led_m)); // add last led_m removed earlier
327 cairo_rectangle(c, led_x, led_y, val_x, led_h);
328 cairo_rectangle(c, led_x + led_w - val_x, led_y, val_x, led_h);
330 } else {
331 if(value > vu->last_value) {
332 // value is above peak hold
333 vu->last_value = value;
334 vu->last_hold = time;
335 vu->holding = true;
337 draw = log10(1 + value * 9);
338 draw_last = log10(1 + vu->last_value * 9);
340 int hold_x = round((1 - draw_last) * (led_w + led_m)); // add last led_m removed earlier
341 hold_x -= hold_x % led_s;
342 int val_x = round(draw * (led_w + led_m)); // add last led_m removed earlier
343 val_x -= val_x % led_s;
344 int blind_w = led_w - hold_x - led_s - val_x;
345 blind_w = std::min(std::max(blind_w, 0), led_w);
346 cairo_rectangle(c, led_x + val_x, led_y, blind_w, led_h);
347 cairo_rectangle( c, led_x + led_w - hold_x, led_y, hold_x, led_h);
349 } else {
350 // darken normally
351 float draw = log10(1 + value * 9);
352 if( vu->mode == VU_MONOCHROME_REVERSE )
353 cairo_rectangle( c, led_x, led_y, draw * led_w, led_h);
354 else if( vu->mode == VU_STANDARD_CENTER ) {
355 int val_x = round((1 - draw) / 2.f * (led_w + led_m)); // add last led_m removed earlier
356 cairo_rectangle(c, led_x, led_y, val_x, led_h);
357 cairo_rectangle(c, led_x + led_w - val_x, led_y, val_x, led_h);
358 } else
359 cairo_rectangle( c, led_x + draw * led_w, led_y, led_w * (1 - draw), led_h);
361 cairo_fill( c );
363 if (vu->vumeter_position)
365 char str[32];
366 if((vu->value > vu->disp_value and vu->mode != VU_MONOCHROME_REVERSE)
367 or (vu->value < vu->disp_value and vu->mode == VU_MONOCHROME_REVERSE))
368 vu->disp_value = vu->value;
369 if (vu->disp_value < 1.0 / 32768.0)
370 snprintf(str, sizeof(str), "-inf");
371 else
372 snprintf(str, sizeof(str), "%0.2f", dsp::amp2dB(vu->disp_value));
373 // draw value as number
374 cairo_text_extents(c, str, &extents);
375 cairo_move_to(c, text_x + (text_w - extents.width) / 2.0, text_y);
376 if(vu->disp_value > 1.f and vu->mode != VU_MONOCHROME_REVERSE)
377 cairo_set_source_rgba (c, 0.7, 0, 0, 1);
378 else
379 cairo_set_source_rgba (c, 0, 0.11, 0.11, 1);
380 cairo_show_text(c, str);
381 cairo_fill(c);
383 cairo_destroy(c);
384 //gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 2, oy - 2, sx + 4, sy + 4);
385 //printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
387 return TRUE;
390 static void
391 calf_vumeter_size_request (GtkWidget *widget,
392 GtkRequisition *requisition)
394 g_assert(CALF_IS_VUMETER(widget));
395 CalfVUMeter *self = CALF_VUMETER(widget);
396 requisition->width = self->vumeter_width;
397 requisition->height = self->vumeter_height;
400 static void
401 calf_vumeter_unrealize (GtkWidget *widget, CalfVUMeter *vu)
403 if( vu->cache_surface )
404 cairo_surface_destroy( vu->cache_surface );
405 vu->cache_surface = NULL;
406 if( vu->cache_overlay )
407 cairo_surface_destroy( vu->cache_overlay );
408 vu->cache_overlay = NULL;
411 static void
412 calf_vumeter_size_allocate (GtkWidget *widget,
413 GtkAllocation *allocation)
415 g_assert(CALF_IS_VUMETER(widget));
416 CalfVUMeter *vu = CALF_VUMETER(widget);
418 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_VUMETER_GET_CLASS( vu ) );
420 parent_class->size_allocate( widget, allocation );
422 calf_vumeter_unrealize(widget, vu);
425 static void
426 calf_vumeter_class_init (CalfVUMeterClass *klass)
428 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
429 widget_class->expose_event = calf_vumeter_expose;
430 widget_class->size_request = calf_vumeter_size_request;
431 widget_class->size_allocate = calf_vumeter_size_allocate;
434 static void
435 calf_vumeter_init (CalfVUMeter *self)
437 GtkWidget *widget = GTK_WIDGET(self);
438 //GTK_WIDGET_SET_FLAGS (widget, GTK_NO_WINDOW);
439 widget->requisition.width = self->vumeter_width;
440 widget->requisition.height = self->vumeter_height;
441 self->cache_surface = NULL;
442 self->falling = false;
443 self->holding = false;
444 self->meter_width = 0;
445 self->disp_value = 0.f;
446 self->value = 0.f;
447 g_signal_connect(GTK_OBJECT(widget), "unrealize", G_CALLBACK(calf_vumeter_unrealize), (gpointer)self);
450 GtkWidget *
451 calf_vumeter_new()
453 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER, NULL ));
456 GType
457 calf_vumeter_get_type (void)
459 static GType type = 0;
460 if (!type) {
461 static const GTypeInfo type_info = {
462 sizeof(CalfVUMeterClass),
463 NULL, /* base_init */
464 NULL, /* base_finalize */
465 (GClassInitFunc)calf_vumeter_class_init,
466 NULL, /* class_finalize */
467 NULL, /* class_data */
468 sizeof(CalfVUMeter),
469 0, /* n_preallocs */
470 (GInstanceInitFunc)calf_vumeter_init
473 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
475 for (int i = 0; ; i++) {
476 char *name = g_strdup_printf("CalfVUMeter%u%d", ((unsigned int)(intptr_t)calf_vumeter_class_init) >> 16, i);
477 if (g_type_from_name(name)) {
478 free(name);
479 continue;
481 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
482 name,
483 type_info_copy,
484 (GTypeFlags)0);
485 free(name);
486 break;
489 return type;
492 extern void calf_vumeter_set_value(CalfVUMeter *meter, float value)
494 if (value != meter->value or meter->holding or meter->falling)
496 meter->value = value;
497 gtk_widget_queue_draw(GTK_WIDGET(meter));
501 extern float calf_vumeter_get_value(CalfVUMeter *meter)
503 return meter->value;
506 extern void calf_vumeter_set_mode(CalfVUMeter *meter, CalfVUMeterMode mode)
508 if (mode != meter->mode)
510 meter->mode = mode;
511 if(mode == VU_MONOCHROME_REVERSE) {
512 meter->value = 1.f;
513 meter->last_value = 1.f;
514 } else {
515 meter->value = 0.f;
516 meter->last_value = 0.f;
518 meter->vumeter_falloff = 0.f;
519 meter->last_falloff = (long)0;
520 meter->last_hold = (long)0;
521 gtk_widget_queue_draw(GTK_WIDGET(meter));
525 extern CalfVUMeterMode calf_vumeter_get_mode(CalfVUMeter *meter)
527 return meter->mode;
530 extern void calf_vumeter_set_falloff(CalfVUMeter *meter, float value)
532 if (value != meter->vumeter_falloff)
534 meter->vumeter_falloff = value;
535 gtk_widget_queue_draw(GTK_WIDGET(meter));
539 extern float calf_vumeter_get_falloff(CalfVUMeter *meter)
541 return meter->vumeter_falloff;
544 extern void calf_vumeter_set_hold(CalfVUMeter *meter, float value)
546 if (value != meter->vumeter_hold)
548 meter->vumeter_hold = value;
549 gtk_widget_queue_draw(GTK_WIDGET(meter));
553 extern float calf_vumeter_get_hold(CalfVUMeter *meter)
555 return meter->vumeter_hold;
558 extern void calf_vumeter_set_width(CalfVUMeter *meter, int value)
560 if (value != meter->vumeter_width)
562 meter->vumeter_width = value;
563 gtk_widget_queue_draw(GTK_WIDGET(meter));
567 extern int calf_vumeter_get_width(CalfVUMeter *meter)
569 return meter->vumeter_width;
572 extern void calf_vumeter_set_height(CalfVUMeter *meter, int value)
574 if (value != meter->vumeter_height)
576 meter->vumeter_height = value;
577 gtk_widget_queue_draw(GTK_WIDGET(meter));
581 extern int calf_vumeter_get_height(CalfVUMeter *meter)
583 return meter->vumeter_height;
585 extern void calf_vumeter_set_position(CalfVUMeter *meter, int value)
587 if (value != meter->vumeter_height)
589 meter->vumeter_position = value;
590 gtk_widget_queue_draw(GTK_WIDGET(meter));
594 extern int calf_vumeter_get_position(CalfVUMeter *meter)
596 return meter->vumeter_position;