2 new styles: Wood and Hybreed
[calf.git] / src / ctl_vumeter.cpp
blob8ac909bca48fb56ee9301027aff4f783d1d62c45
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 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
45 float r, g, b;
47 int x = widget->allocation.x;
48 int y = widget->allocation.y;
49 int width = widget->allocation.width;
50 int height = widget->allocation.height;
51 int border_x = widget->style->xthickness;
52 int border_y = widget->style->ythickness;
53 int space_x = 1; int space_y = 1; // inner border around led bar
54 int led = 2; // single LED size
55 int led_m = 1; // margin between LED
56 int led_s = led + led_m; // size of LED with margin
57 int led_x = widget->style->xthickness;
58 int led_y = widget->style->ythickness; // position of first LED
59 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)
60 int led_h = height - 2 * led_y; // height of LED bar w/o text calc
61 int text_x = 0; int text_y = 0;
62 int text_w = 0; int text_h = 0;
64 // only valid if vumeter is enabled
65 cairo_text_extents_t extents;
67 if(vu->vumeter_position) {
68 cairo_select_font_face(c, "cairo:sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
69 cairo_set_font_size(c, 8);
71 cairo_text_extents(c, "-88.88", &extents);
72 text_w = extents.width;
73 text_h = extents.height;
74 switch(vu->vumeter_position) {
75 case 1:
76 text_x = width / 2 - text_w / 2;
77 text_y = border_y + led_y - extents.y_bearing;
78 led_y += text_h + led_y;
79 led_h -= text_h + led_y;
80 break;
81 case 2:
82 text_x = width - border_x - led_x - text_w;
83 text_y = height / 2 - text_h / 2 - extents.y_bearing;
84 led_w -= led_x + text_w;
85 break;
86 case 3:
87 text_x = width / 2 - text_w / 2;
88 text_y = height - border_y - led_y - text_h - extents.y_bearing;
89 led_h -= led_y + text_h;
90 break;
91 case 4:
92 text_x = border_x + led_x;
93 text_y = height / 2 - text_h / 2 - extents.y_bearing;
94 led_x += led_x + text_w;
95 led_w -= led_x + text_w;
96 break;
100 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
102 if( vu->cache_surface == NULL ) {
103 // looks like its either first call or the widget has been resized.
104 // create the cache_surface.
105 vu->cache_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height );
106 cairo_t *cache_cr = cairo_create( vu->cache_surface );
108 float radius, bevel;
109 get_bg_color(widget, NULL, &r, &g, &b);
110 gtk_widget_style_get(widget, "border-radius", &radius, "bevel", &bevel, NULL);
111 create_rectangle(cache_cr, 0, 0, width, height, radius);
112 cairo_set_source_rgb(cache_cr, r, g, b);
113 cairo_fill(cache_cr);
114 draw_bevel(cache_cr, 0, 0, width, height, radius, bevel);
116 // border around LED
117 cairo_rectangle(cache_cr, led_x, led_y, led_w, led_h);
118 cairo_set_source_rgb (cache_cr, 0, 0, 0);
119 cairo_fill(cache_cr);
121 led_x += space_x;
122 led_y += space_y;
123 led_w -= 2 * space_x;
124 led_h -= 2 * space_y;
126 // LED bases
127 cairo_set_line_width(cache_cr, 1);
128 for (int x = led_x; x + led <= led_x + led_w; x += led_s)
130 float ts = (x - led_x) * 1.0 / led_w;
131 float r = 0.f, g = 0.f, b = 0.f;
132 switch(vu->mode)
134 case VU_STANDARD:
135 default:
136 if (ts < 0.75)
137 r = ts / 0.75, g = 0.5 + ts * 0.66, b = 1 - ts / 0.75;
138 else
139 r = 1, g = 1 - (ts - 0.75) / 0.25, b = 0;
140 // if (vu->value < ts || vu->value <= 0)
141 // r *= 0.5, g *= 0.5, b *= 0.5;
142 break;
143 case VU_STANDARD_CENTER:
144 if (ts < 0.25)
145 // 0.0 -> 0.25
146 // green: 0.f -> 1.f
147 r = 1, g = (ts) / 0.25, b = 0;
148 else if (ts > 0.75)
149 // 0.75 -> 1.0
150 // green: 1.f -> 0.f
151 r = 1, g = 1 - (ts - 0.75) / 0.25, b = 0;
152 else if (ts > 0.5)
153 // 0.5 -> 0.75
154 // red: 0.f -> 1.f
155 // green: 0.5 -> 1.f
156 // blue: 1.f -> 0.f
157 r = (ts - 0.5) / 0.25, g = 0.5 + (ts - 0.5) * 2.f, b = 1 - (ts - 0.5) / 0.25;
158 else
159 // 0.25 -> 0.5
160 // red: 1.f -> 0.f
161 // green: 1.f -> 0.5
162 // blue: 0.f -> 1.f
163 r = 1 - (ts - 0.25) / 0.25, g = 1.f - (ts * 2.f - .5f), b = (ts - 0.25) / 0.25;
164 // if (vu->value < ts || vu->value <= 0)
165 // r *= 0.5, g *= 0.5, b *= 0.5;
166 break;
167 case VU_MONOCHROME_REVERSE:
168 r = 0, g = 170.0 / 255.0, b = 1;
169 // if (!(vu->value < ts) || vu->value >= 1.0)
170 // r *= 0.5, g *= 0.5, b *= 0.5;
171 break;
172 case VU_MONOCHROME:
173 r = 0, g = 170.0 / 255.0, b = 1;
174 // if (vu->value < ts || vu->value <= 0)
175 // r *= 0.5, g *= 0.5, b *= 0.5;
176 break;
177 case VU_MONOCHROME_CENTER:
178 r = 0, g = 170.0 / 255.0, b = 1;
179 // if (vu->value < ts || vu->value <= 0)
180 // r *= 0.5, g *= 0.5, b *= 0.5;
181 break;
183 GdkColor sc2 = { 0, (guint16)(65535 * r + 0.2), (guint16)(65535 * g), (guint16)(65535 * b) };
184 GdkColor sc3 = { 0, (guint16)(65535 * r * 0.7), (guint16)(65535 * g * 0.7), (guint16)(65535 * b * 0.7) };
185 gdk_cairo_set_source_color(cache_cr, &sc2);
186 cairo_move_to(cache_cr, x + 0.5, led_y);
187 cairo_line_to(cache_cr, x + 0.5, led_y + led_h);
188 cairo_stroke(cache_cr);
189 gdk_cairo_set_source_color(cache_cr, &sc3);
190 cairo_move_to(cache_cr, x + 1.5, led_y + led_h);
191 cairo_line_to(cache_cr, x + 1.5, led_y);
192 cairo_stroke(cache_cr);
194 // create blinder pattern
195 cairo_pattern_t *pat = cairo_pattern_create_linear (led_x, led_y, led_x, led_y + led_h);
196 cairo_pattern_add_color_stop_rgba (pat, 0, 1, 1, 1, 0.25);
197 cairo_pattern_add_color_stop_rgba (pat, 0.5, 0.5, 0.5, 0.5, 0.0);
198 cairo_pattern_add_color_stop_rgba (pat, 1, 0.0, 0.0, 0.0, 0.25);
199 cairo_rectangle(cache_cr, led_x, led_y, led_w, led_h);
200 cairo_set_source(cache_cr, pat);
201 cairo_fill(cache_cr);
203 // create overlay
204 vu->cache_overlay = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
205 cairo_t *over_cr = cairo_create(vu->cache_overlay);
207 // copy surface to overlay
208 cairo_set_source_surface(over_cr, vu->cache_surface, 0, 0);
209 cairo_rectangle(over_cr, 0, 0, width, height);
210 cairo_fill(over_cr);
212 // create blinder pattern
213 pat = cairo_pattern_create_linear (led_x, led_y, led_x, led_y + led_h);
214 cairo_pattern_add_color_stop_rgba (pat, 0, 0.2, 0.2, 0.2, 0.7);
215 cairo_pattern_add_color_stop_rgba (pat, 0.4, 0.05, 0.05, 0.05, 0.7);
216 cairo_pattern_add_color_stop_rgba (pat, 0.401, 0.05, 0.05, 0.05, 0.9);
217 cairo_pattern_add_color_stop_rgba (pat, 1, 0.05, 0.05, 0.05, 0.75);
219 // draw on top of overlay
220 cairo_set_source(over_cr, pat);
221 cairo_rectangle(over_cr, 0, 0, width, height);
222 cairo_paint(over_cr);
224 // clean up
225 cairo_destroy(cache_cr);
226 cairo_destroy(over_cr);
227 } else {
228 led_x += space_x;
229 led_y += space_y;
230 led_w -= 2 * space_x;
231 led_h -= 2 * space_y;
233 led_x += x;
234 led_y += y;
235 text_x += x;
236 text_y += y;
237 // draw LED blinder
238 cairo_set_source_surface( c, vu->cache_surface, x, y );
239 cairo_paint( c );
240 cairo_set_source_surface( c, vu->cache_overlay, x, y );
242 // get microseconds
243 timeval tv;
244 gettimeofday(&tv, 0);
245 long time = tv.tv_sec * 1000 * 1000 + tv.tv_usec;
247 // limit to 1.f
248 float value_orig = std::max(std::min(vu->value, 1.f), 0.f);
249 float value = 0.f;
251 // falloff?
252 if(vu->vumeter_falloff > 0.f and vu->mode != VU_MONOCHROME_REVERSE) {
253 // fall off a bit
254 float s = ((float)(time - vu->last_falltime) / 1000000.0);
255 float m = vu->last_falloff * s * vu->vumeter_falloff;
256 vu->last_falloff -= m;
257 // new max value?
258 if(value_orig > vu->last_falloff) {
259 vu->last_falloff = value_orig;
261 value = vu->last_falloff;
262 vu->last_falltime = time;
263 vu->falling = vu->last_falloff > 0.00000001;
264 } else {
265 // falloff disabled
266 vu->last_falloff = 0.f;
267 vu->last_falltime = 0.f;
268 value = value_orig;
269 vu->falling = false;
272 float draw = 0.f;
273 float draw_last = 0.f;
275 if(vu->vumeter_hold > 0.0) {
276 // peak hold timer
277 if(time - (long)(vu->vumeter_hold * 1000 * 1000) > vu->last_hold) {
278 // time's up, reset
279 vu->last_value = value;
280 vu->last_hold = time;
281 vu->holding = false;
282 vu->disp_value = value_orig;
284 if( vu->mode == VU_MONOCHROME_REVERSE ) {
285 if(value < vu->last_value) {
286 // value is above peak hold
287 vu->last_value = value;
288 vu->last_hold = time;
289 vu->holding = true;
291 draw = log10(1 + value * 9);
292 draw_last = log10(1 + vu->last_value * 9);
294 // blinder left -> hold LED
295 int hold_x = round((draw_last) * (led_w + led_m)); // add last led_m removed earlier
296 hold_x -= hold_x % led_s + led_m;
297 hold_x = std::max(0, hold_x);
298 cairo_rectangle( c, led_x, led_y, hold_x, led_h);
300 // blinder hold LED -> value
301 int val_x = round((1 - draw) * (led_w + led_m)); // add last led_m removed earlier
302 val_x -= val_x % led_s;
303 int blind_x = std::min(hold_x + led_s, led_w);
304 int blind_w = std::min(std::max(led_w - val_x - hold_x - led_s, 0), led_w);
305 cairo_rectangle(c, led_x + blind_x, led_y, blind_w, led_h);
306 } else if( vu->mode == VU_STANDARD_CENTER ) {
307 if(value > vu->last_value) {
308 // value is above peak hold
309 vu->last_value = value;
310 vu->last_hold = time;
311 vu->holding = true;
313 draw = log10(1 + value * 9);
314 int val_x = round((1 - draw) / 2.f * (led_w + led_m)); // add last led_m removed earlier
315 cairo_rectangle(c, led_x, led_y, val_x, led_h);
316 cairo_rectangle(c, led_x + led_w - val_x, led_y, val_x, led_h);
318 } else {
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 draw_last = log10(1 + vu->last_value * 9);
328 int hold_x = round((1 - draw_last) * (led_w + led_m)); // add last led_m removed earlier
329 hold_x -= hold_x % led_s;
330 int val_x = round(draw * (led_w + led_m)); // add last led_m removed earlier
331 val_x -= val_x % led_s;
332 int blind_w = led_w - hold_x - led_s - val_x;
333 blind_w = std::min(std::max(blind_w, 0), led_w);
334 cairo_rectangle(c, led_x + val_x, led_y, blind_w, led_h);
335 cairo_rectangle( c, led_x + led_w - hold_x, led_y, hold_x, led_h);
337 } else {
338 // darken normally
339 float draw = log10(1 + value * 9);
340 if( vu->mode == VU_MONOCHROME_REVERSE )
341 cairo_rectangle( c, led_x, led_y, draw * led_w, led_h);
342 else if( vu->mode == VU_STANDARD_CENTER ) {
343 int val_x = round((1 - draw) / 2.f * (led_w + led_m)); // add last led_m removed earlier
344 cairo_rectangle(c, led_x, led_y, val_x, led_h);
345 cairo_rectangle(c, led_x + led_w - val_x, led_y, val_x, led_h);
346 } else
347 cairo_rectangle( c, led_x + draw * led_w, led_y, led_w * (1 - draw), led_h);
349 cairo_fill( c );
351 if (vu->vumeter_position)
353 char str[32];
354 if((vu->value > vu->disp_value and vu->mode != VU_MONOCHROME_REVERSE)
355 or (vu->value < vu->disp_value and vu->mode == VU_MONOCHROME_REVERSE))
356 vu->disp_value = vu->value;
357 if (vu->disp_value < 1.0 / 32768.0)
358 snprintf(str, sizeof(str), "-inf");
359 else
360 snprintf(str, sizeof(str), "%0.2f", dsp::amp2dB(vu->disp_value));
361 // draw value as number
362 cairo_text_extents(c, str, &extents);
363 cairo_move_to(c, text_x + (text_w - extents.width) / 2.0, text_y);
364 GtkStateType state;
365 if(vu->disp_value > 1.f and vu->mode != VU_MONOCHROME_REVERSE)
366 state = GTK_STATE_ACTIVE;
367 else
368 state = GTK_STATE_NORMAL;
369 get_fg_color(widget, &state, &r, &g, &b);
370 cairo_set_source_rgba (c, r, g, b, 1);
371 cairo_show_text(c, str);
372 cairo_fill(c);
374 cairo_destroy(c);
375 //gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 2, oy - 2, sx + 4, sy + 4);
376 //printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
378 return TRUE;
381 static void
382 calf_vumeter_size_request (GtkWidget *widget,
383 GtkRequisition *requisition)
385 g_assert(CALF_IS_VUMETER(widget));
386 CalfVUMeter *self = CALF_VUMETER(widget);
387 requisition->width = self->vumeter_width;
388 requisition->height = self->vumeter_height;
391 static void
392 calf_vumeter_unrealize (GtkWidget *widget, CalfVUMeter *vu)
394 if( vu->cache_surface )
395 cairo_surface_destroy( vu->cache_surface );
396 vu->cache_surface = NULL;
397 if( vu->cache_overlay )
398 cairo_surface_destroy( vu->cache_overlay );
399 vu->cache_overlay = NULL;
402 static void
403 calf_vumeter_size_allocate (GtkWidget *widget,
404 GtkAllocation *allocation)
406 g_assert(CALF_IS_VUMETER(widget));
407 CalfVUMeter *vu = CALF_VUMETER(widget);
409 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_VUMETER_GET_CLASS( vu ) );
411 parent_class->size_allocate( widget, allocation );
413 calf_vumeter_unrealize(widget, vu);
416 static void
417 calf_vumeter_class_init (CalfVUMeterClass *klass)
419 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
420 widget_class->expose_event = calf_vumeter_expose;
421 widget_class->size_request = calf_vumeter_size_request;
422 widget_class->size_allocate = calf_vumeter_size_allocate;
423 gtk_widget_class_install_style_property(
424 widget_class, g_param_spec_float("border-radius", "Border Radius", "Generate round edges",
425 0, 24, 4, GParamFlags(G_PARAM_READWRITE)));
426 gtk_widget_class_install_style_property(
427 widget_class, g_param_spec_float("bevel", "Bevel", "Bevel the object",
428 -2, 2, 0.2, GParamFlags(G_PARAM_READWRITE)));
431 static void
432 calf_vumeter_init (CalfVUMeter *self)
434 GtkWidget *widget = GTK_WIDGET(self);
435 //GTK_WIDGET_SET_FLAGS (widget, GTK_NO_WINDOW);
436 widget->requisition.width = self->vumeter_width;
437 widget->requisition.height = self->vumeter_height;
438 self->cache_surface = NULL;
439 self->falling = false;
440 self->holding = false;
441 self->meter_width = 0;
442 self->disp_value = 0.f;
443 self->value = 0.f;
444 gtk_widget_set_has_window(widget, FALSE);
445 g_signal_connect(GTK_OBJECT(widget), "unrealize", G_CALLBACK(calf_vumeter_unrealize), (gpointer)self);
448 GtkWidget *
449 calf_vumeter_new()
451 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER, NULL ));
454 GType
455 calf_vumeter_get_type (void)
457 static GType type = 0;
458 if (!type) {
459 static const GTypeInfo type_info = {
460 sizeof(CalfVUMeterClass),
461 NULL, /* base_init */
462 NULL, /* base_finalize */
463 (GClassInitFunc)calf_vumeter_class_init,
464 NULL, /* class_finalize */
465 NULL, /* class_data */
466 sizeof(CalfVUMeter),
467 0, /* n_preallocs */
468 (GInstanceInitFunc)calf_vumeter_init
471 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
473 for (int i = 0; ; i++) {
474 const char *name = "CalfVUMeter";
475 //char *name = g_strdup_printf("CalfVUMeter%u%d", ((unsigned int)(intptr_t)calf_vumeter_class_init) >> 16, i);
476 if (g_type_from_name(name)) {
477 //free(name);
478 continue;
480 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
481 name,
482 type_info_copy,
483 (GTypeFlags)0);
484 //free(name);
485 break;
488 return type;
491 extern void calf_vumeter_set_value(CalfVUMeter *meter, float value)
493 if (value != meter->value or meter->holding or meter->falling)
495 meter->value = value;
496 gtk_widget_queue_draw(GTK_WIDGET(meter));
500 extern float calf_vumeter_get_value(CalfVUMeter *meter)
502 return meter->value;
505 extern void calf_vumeter_set_mode(CalfVUMeter *meter, CalfVUMeterMode mode)
507 if (mode != meter->mode)
509 meter->mode = mode;
510 if(mode == VU_MONOCHROME_REVERSE) {
511 meter->value = 1.f;
512 meter->last_value = 1.f;
513 } else {
514 meter->value = 0.f;
515 meter->last_value = 0.f;
517 meter->vumeter_falloff = 0.f;
518 meter->last_falloff = (long)0;
519 meter->last_hold = (long)0;
520 gtk_widget_queue_draw(GTK_WIDGET(meter));
524 extern CalfVUMeterMode calf_vumeter_get_mode(CalfVUMeter *meter)
526 return meter->mode;
529 extern void calf_vumeter_set_falloff(CalfVUMeter *meter, float value)
531 if (value != meter->vumeter_falloff)
533 meter->vumeter_falloff = value;
534 gtk_widget_queue_draw(GTK_WIDGET(meter));
538 extern float calf_vumeter_get_falloff(CalfVUMeter *meter)
540 return meter->vumeter_falloff;
543 extern void calf_vumeter_set_hold(CalfVUMeter *meter, float value)
545 if (value != meter->vumeter_hold)
547 meter->vumeter_hold = value;
548 gtk_widget_queue_draw(GTK_WIDGET(meter));
552 extern float calf_vumeter_get_hold(CalfVUMeter *meter)
554 return meter->vumeter_hold;
557 extern void calf_vumeter_set_width(CalfVUMeter *meter, int value)
559 if (value != meter->vumeter_width)
561 meter->vumeter_width = value;
562 gtk_widget_queue_draw(GTK_WIDGET(meter));
566 extern int calf_vumeter_get_width(CalfVUMeter *meter)
568 return meter->vumeter_width;
571 extern void calf_vumeter_set_height(CalfVUMeter *meter, int value)
573 if (value != meter->vumeter_height)
575 meter->vumeter_height = value;
576 gtk_widget_queue_draw(GTK_WIDGET(meter));
580 extern int calf_vumeter_get_height(CalfVUMeter *meter)
582 return meter->vumeter_height;
584 extern void calf_vumeter_set_position(CalfVUMeter *meter, int value)
586 if (value != meter->vumeter_height)
588 meter->vumeter_position = value;
589 gtk_widget_queue_draw(GTK_WIDGET(meter));
593 extern int calf_vumeter_get_position(CalfVUMeter *meter)
595 return meter->vumeter_position;