2 * Custom controls (line graph, knob).
3 * Copyright (C) 2007-2010 Krzysztof Foltman, Torben Hohn, Markus Schmidt
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
23 #include <calf/primitives.h>
24 #include <calf/ctl_vumeter.h>
25 #include <gdk/gdkkeysyms.h>
26 #include <cairo/cairo.h>
33 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
36 calf_vumeter_expose (GtkWidget
*widget
, GdkEventExpose
*event
)
38 g_assert(CALF_IS_VUMETER(widget
));
40 CalfVUMeter
*vu
= CALF_VUMETER(widget
);
42 style
= gtk_widget_get_style(widget
);
43 cairo_t
*c
= gdk_cairo_create(GDK_DRAWABLE(widget
->window
));
45 int width
= widget
->allocation
.width
; int height
= widget
->allocation
.height
;
46 int border_x
= 1; int border_y
= 1; // outer border
47 int space_x
= 1; int space_y
= 1; // inner border around led bar
48 int led
= 2; // single LED size
49 int led_m
= 1; // margin between LED
50 int led_s
= led
+ led_m
; // size of LED with margin
51 int led_x
= 5; int led_y
= 4; // position of first LED
52 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)
53 int led_h
= height
- 2 * led_y
; // height of LED bar w/o text calc
54 int text_x
= 0; int text_y
= 0;
55 int text_w
= 0; int text_h
= 0;
56 int text_m
= 3; // text margin
58 // only valid if vumeter is enabled
59 cairo_text_extents_t extents
;
61 if(vu
->vumeter_position
) {
62 cairo_select_font_face(c
, "cairo:sans-serif", CAIRO_FONT_SLANT_NORMAL
, CAIRO_FONT_WEIGHT_NORMAL
);
63 cairo_set_font_size(c
, 8);
65 cairo_text_extents(c
, "-88.88", &extents
);
66 text_w
= extents
.width
;
67 text_h
= extents
.height
;
68 switch(vu
->vumeter_position
) {
70 text_x
= width
/ 2 - text_w
/ 2;
71 text_y
= border_y
+ text_m
- extents
.y_bearing
;
72 led_y
+= text_h
+ text_m
;
73 led_h
-= text_h
+ text_m
;
76 text_x
= width
- border_x
- text_m
* 2 - text_w
;
77 text_y
= height
/ 2 - text_h
/ 2 - extents
.y_bearing
;
78 led_w
-= text_m
* 2 + text_w
;
81 text_x
= width
/ 2 - text_w
/ 2;
82 text_y
= height
- border_y
- text_m
- text_h
- extents
.y_bearing
;
83 led_h
-= text_m
* 2 + text_h
;
86 text_x
= border_x
+ text_m
;
87 text_y
= height
/ 2 - text_h
/ 2 - extents
.y_bearing
;
88 led_x
+= text_m
* 2 + text_w
;
89 led_w
-= text_m
* 2 + text_w
;
94 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
96 if( vu
->cache_surface
== NULL
) {
97 // looks like its either first call or the widget has been resized.
98 // create the cache_surface.
99 cairo_surface_t
*window_surface
= cairo_get_target( c
);
100 vu
->cache_surface
= cairo_surface_create_similar( window_surface
,
102 widget
->allocation
.width
,
103 widget
->allocation
.height
);
105 // And render the meterstuff
107 cairo_t
*cache_cr
= cairo_create( vu
->cache_surface
);
108 gdk_cairo_set_source_color(cache_cr
,&style
->bg
[GTK_STATE_NORMAL
]);
109 cairo_paint(cache_cr
);
112 cairo_rectangle(cache_cr
, 0, 0, width
, height
);
113 cairo_set_source_rgb(cache_cr
, 0, 0, 0);
114 cairo_fill(cache_cr
);
117 cairo_rectangle(cache_cr
,
120 width
- border_x
* 2,
121 height
- border_y
* 2);
122 cairo_pattern_t
*pat2
= cairo_pattern_create_linear (border_x
,
125 height
- border_y
* 2);
126 cairo_pattern_add_color_stop_rgba (pat2
, 0, 0.23, 0.23, 0.23, 1);
127 cairo_pattern_add_color_stop_rgba (pat2
, 0.5, 0, 0, 0, 1);
128 cairo_set_source (cache_cr
, pat2
);
129 cairo_fill(cache_cr
);
130 cairo_pattern_destroy(pat2
);
133 cairo_rectangle(cache_cr
,
137 led_h
+ space_y
* 2);
138 cairo_set_source_rgb (cache_cr
, 0, 0, 0);
139 cairo_fill(cache_cr
);
142 cairo_set_line_width(cache_cr
, 1);
143 for (int x
= led_x
; x
+ led
<= led_x
+ led_w
; x
+= led_s
)
145 float ts
= (x
- led_x
) * 1.0 / led_w
;
146 float r
= 0.f
, g
= 0.f
, b
= 0.f
;
152 r
= ts
/ 0.75, g
= 0.5 + ts
* 0.66, b
= 1 - ts
/ 0.75;
154 r
= 1, g
= 1 - (ts
- 0.75) / 0.25, b
= 0;
155 // if (vu->value < ts || vu->value <= 0)
156 // r *= 0.5, g *= 0.5, b *= 0.5;
158 case VU_STANDARD_CENTER
:
162 r
= 1, g
= (ts
) / 0.25, b
= 0;
166 r
= 1, g
= 1 - (ts
- 0.75) / 0.25, b
= 0;
172 r
= (ts
- 0.5) / 0.25, g
= 0.5 + (ts
- 0.5) * 2.f
, b
= 1 - (ts
- 0.5) / 0.25;
178 r
= 1 - (ts
- 0.25) / 0.25, g
= 1.f
- (ts
* 2.f
- .5f
), b
= (ts
- 0.25) / 0.25;
179 // if (vu->value < ts || vu->value <= 0)
180 // r *= 0.5, g *= 0.5, b *= 0.5;
182 case VU_MONOCHROME_REVERSE
:
183 r
= 0, g
= 170.0 / 255.0, b
= 1;
184 // if (!(vu->value < ts) || vu->value >= 1.0)
185 // r *= 0.5, g *= 0.5, b *= 0.5;
188 r
= 0, g
= 170.0 / 255.0, b
= 1;
189 // if (vu->value < ts || vu->value <= 0)
190 // r *= 0.5, g *= 0.5, b *= 0.5;
192 case VU_MONOCHROME_CENTER
:
193 r
= 0, g
= 170.0 / 255.0, b
= 1;
194 // if (vu->value < ts || vu->value <= 0)
195 // r *= 0.5, g *= 0.5, b *= 0.5;
198 GdkColor sc2
= { 0, (guint16
)(65535 * r
+ 0.2), (guint16
)(65535 * g
), (guint16
)(65535 * b
) };
199 GdkColor sc3
= { 0, (guint16
)(65535 * r
* 0.7), (guint16
)(65535 * g
* 0.7), (guint16
)(65535 * b
* 0.7) };
200 gdk_cairo_set_source_color(cache_cr
, &sc2
);
201 cairo_move_to(cache_cr
, x
+ 0.5, led_y
);
202 cairo_line_to(cache_cr
, x
+ 0.5, led_y
+ led_h
);
203 cairo_stroke(cache_cr
);
204 gdk_cairo_set_source_color(cache_cr
, &sc3
);
205 cairo_move_to(cache_cr
, x
+ 1.5, led_y
+ led_h
);
206 cairo_line_to(cache_cr
, x
+ 1.5, led_y
);
207 cairo_stroke(cache_cr
);
209 // create blinder pattern
210 cairo_pattern_t
*pat
= cairo_pattern_create_linear (led_x
, led_y
, led_x
, led_y
+ led_h
);
211 cairo_pattern_add_color_stop_rgba (pat
, 0, 1, 1, 1, 0.25);
212 cairo_pattern_add_color_stop_rgba (pat
, 0.5, 0.5, 0.5, 0.5, 0.0);
213 cairo_pattern_add_color_stop_rgba (pat
, 1, 0.0, 0.0, 0.0, 0.25);
214 cairo_rectangle(cache_cr
, led_x
, led_y
, led_w
, led_h
);
215 cairo_set_source(cache_cr
, pat
);
216 cairo_fill(cache_cr
);
219 vu
->cache_overlay
= cairo_surface_create_similar(window_surface
,
221 widget
->allocation
.width
,
222 widget
->allocation
.height
);
223 cairo_t
*over_cr
= cairo_create(vu
->cache_overlay
);
225 // copy surface to overlay
226 cairo_set_source_surface(over_cr
, vu
->cache_surface
, 0, 0);
227 cairo_rectangle(over_cr
, 0, 0, width
, height
);
230 // create blinder pattern
231 pat
= cairo_pattern_create_linear (led_x
, led_y
, led_x
, led_y
+ led_h
);
232 cairo_pattern_add_color_stop_rgba (pat
, 0, 0.2, 0.2, 0.2, 0.7);
233 cairo_pattern_add_color_stop_rgba (pat
, 0.4, 0.05, 0.05, 0.05, 0.7);
234 cairo_pattern_add_color_stop_rgba (pat
, 0.401, 0.05, 0.05, 0.05, 0.9);
235 cairo_pattern_add_color_stop_rgba (pat
, 1, 0.05, 0.05, 0.05, 0.75);
237 // draw on top of overlay
238 cairo_set_source(over_cr
, pat
);
239 cairo_rectangle(over_cr
, 0, 0, width
, height
);
240 cairo_paint(over_cr
);
243 cairo_destroy(cache_cr
);
244 cairo_destroy(over_cr
);
248 cairo_set_source_surface( c
, vu
->cache_surface
, 0,0 );
250 cairo_set_source_surface( c
, vu
->cache_overlay
, 0, 0 );
254 gettimeofday(&tv
, 0);
255 long time
= tv
.tv_sec
* 1000 * 1000 + tv
.tv_usec
;
258 float value_orig
= std::max(std::min(vu
->value
, 1.f
), 0.f
);
262 if(vu
->vumeter_falloff
> 0.f
and vu
->mode
!= VU_MONOCHROME_REVERSE
) {
264 float s
= ((float)(time
- vu
->last_falltime
) / 1000000.0);
265 float m
= vu
->last_falloff
* s
* vu
->vumeter_falloff
;
266 vu
->last_falloff
-= m
;
268 if(value_orig
> vu
->last_falloff
) {
269 vu
->last_falloff
= value_orig
;
271 value
= vu
->last_falloff
;
272 vu
->last_falltime
= time
;
273 vu
->falling
= vu
->last_falloff
> 0.00000001;
276 vu
->last_falloff
= 0.f
;
277 vu
->last_falltime
= 0.f
;
283 float draw_last
= 0.f
;
285 if(vu
->vumeter_hold
> 0.0) {
287 if(time
- (long)(vu
->vumeter_hold
* 1000 * 1000) > vu
->last_hold
) {
289 vu
->last_value
= value
;
290 vu
->last_hold
= time
;
292 vu
->disp_value
= value_orig
;
294 if( vu
->mode
== VU_MONOCHROME_REVERSE
) {
295 if(value
< vu
->last_value
) {
296 // value is above peak hold
297 vu
->last_value
= value
;
298 vu
->last_hold
= time
;
301 draw
= log10(1 + value
* 9);
302 draw_last
= log10(1 + vu
->last_value
* 9);
304 // blinder left -> hold LED
305 int hold_x
= round((draw_last
) * (led_w
+ led_m
)); // add last led_m removed earlier
306 hold_x
-= hold_x
% led_s
+ led_m
;
307 hold_x
= std::max(0, hold_x
);
308 cairo_rectangle( c
, led_x
, led_y
, hold_x
, led_h
);
310 // blinder hold LED -> value
311 int val_x
= round((1 - draw
) * (led_w
+ led_m
)); // add last led_m removed earlier
312 val_x
-= val_x
% led_s
;
313 int blind_x
= std::min(hold_x
+ led_s
, led_w
);
314 int blind_w
= std::min(std::max(led_w
- val_x
- hold_x
- led_s
, 0), led_w
);
315 cairo_rectangle(c
, led_x
+ blind_x
, led_y
, blind_w
, led_h
);
316 } else if( vu
->mode
== VU_STANDARD_CENTER
) {
317 if(value
> vu
->last_value
) {
318 // value is above peak hold
319 vu
->last_value
= value
;
320 vu
->last_hold
= time
;
323 draw
= log10(1 + value
* 9);
324 int val_x
= round((1 - draw
) / 2.f
* (led_w
+ led_m
)); // add last led_m removed earlier
325 cairo_rectangle(c
, led_x
, led_y
, val_x
, led_h
);
326 cairo_rectangle(c
, led_x
+ led_w
- val_x
, led_y
, val_x
, led_h
);
329 if(value
> vu
->last_value
) {
330 // value is above peak hold
331 vu
->last_value
= value
;
332 vu
->last_hold
= time
;
335 draw
= log10(1 + value
* 9);
336 draw_last
= log10(1 + vu
->last_value
* 9);
338 int hold_x
= round((1 - draw_last
) * (led_w
+ led_m
)); // add last led_m removed earlier
339 hold_x
-= hold_x
% led_s
;
340 int val_x
= round(draw
* (led_w
+ led_m
)); // add last led_m removed earlier
341 val_x
-= val_x
% led_s
;
342 int blind_w
= led_w
- hold_x
- led_s
- val_x
;
343 blind_w
= std::min(std::max(blind_w
, 0), led_w
);
344 cairo_rectangle(c
, led_x
+ val_x
, led_y
, blind_w
, led_h
);
345 cairo_rectangle( c
, led_x
+ led_w
- hold_x
, led_y
, hold_x
, led_h
);
349 float draw
= log10(1 + value
* 9);
350 if( vu
->mode
== VU_MONOCHROME_REVERSE
)
351 cairo_rectangle( c
, led_x
, led_y
, draw
* led_w
, led_h
);
352 else if( vu
->mode
== VU_STANDARD_CENTER
) {
353 int val_x
= round((1 - draw
) / 2.f
* (led_w
+ led_m
)); // add last led_m removed earlier
354 cairo_rectangle(c
, led_x
, led_y
, val_x
, led_h
);
355 cairo_rectangle(c
, led_x
+ led_w
- val_x
, led_y
, val_x
, led_h
);
357 cairo_rectangle( c
, led_x
+ draw
* led_w
, led_y
, led_w
* (1 - draw
), led_h
);
361 if (vu
->vumeter_position
)
364 if((vu
->value
> vu
->disp_value
and vu
->mode
!= VU_MONOCHROME_REVERSE
)
365 or (vu
->value
< vu
->disp_value
and vu
->mode
== VU_MONOCHROME_REVERSE
))
366 vu
->disp_value
= vu
->value
;
367 if (vu
->disp_value
< 1.0 / 32768.0)
368 snprintf(str
, sizeof(str
), "-inf");
370 snprintf(str
, sizeof(str
), "%0.2f", dsp::amp2dB(vu
->disp_value
));
371 // draw value as number
372 cairo_text_extents(c
, str
, &extents
);
373 cairo_move_to(c
, text_x
+ (text_w
- extents
.width
) / 2.0, text_y
);
374 if(vu
->disp_value
> 1.f
and vu
->mode
!= VU_MONOCHROME_REVERSE
)
375 cairo_set_source_rgba (c
, 1, 0, 0, 0.8);
377 cairo_set_source_rgba (c
, 0, 0.9, 1, 0.8);
378 cairo_show_text(c
, str
);
382 //gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 2, oy - 2, sx + 4, sy + 4);
383 //printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
389 calf_vumeter_size_request (GtkWidget
*widget
,
390 GtkRequisition
*requisition
)
392 g_assert(CALF_IS_VUMETER(widget
));
393 CalfVUMeter
*self
= CALF_VUMETER(widget
);
394 requisition
->width
= self
->vumeter_width
;
395 requisition
->height
= self
->vumeter_height
;
399 calf_vumeter_unrealize (GtkWidget
*widget
, CalfVUMeter
*vu
)
401 if( vu
->cache_surface
)
402 cairo_surface_destroy( vu
->cache_surface
);
403 vu
->cache_surface
= NULL
;
404 if( vu
->cache_overlay
)
405 cairo_surface_destroy( vu
->cache_overlay
);
406 vu
->cache_overlay
= NULL
;
410 calf_vumeter_size_allocate (GtkWidget
*widget
,
411 GtkAllocation
*allocation
)
413 g_assert(CALF_IS_VUMETER(widget
));
414 CalfVUMeter
*vu
= CALF_VUMETER(widget
);
416 GtkWidgetClass
*parent_class
= (GtkWidgetClass
*) g_type_class_peek_parent( CALF_VUMETER_GET_CLASS( vu
) );
418 parent_class
->size_allocate( widget
, allocation
);
420 calf_vumeter_unrealize(widget
, vu
);
424 calf_vumeter_class_init (CalfVUMeterClass
*klass
)
426 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS(klass
);
427 widget_class
->expose_event
= calf_vumeter_expose
;
428 widget_class
->size_request
= calf_vumeter_size_request
;
429 widget_class
->size_allocate
= calf_vumeter_size_allocate
;
433 calf_vumeter_init (CalfVUMeter
*self
)
435 GtkWidget
*widget
= GTK_WIDGET(self
);
436 //GTK_WIDGET_SET_FLAGS (widget, GTK_NO_WINDOW);
437 widget
->requisition
.width
= self
->vumeter_width
;
438 widget
->requisition
.height
= self
->vumeter_height
;
439 self
->cache_surface
= NULL
;
440 self
->falling
= false;
441 self
->holding
= false;
442 self
->meter_width
= 0;
443 self
->disp_value
= 0.f
;
445 g_signal_connect(GTK_OBJECT(widget
), "unrealize", G_CALLBACK(calf_vumeter_unrealize
), (gpointer
)self
);
451 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER
, NULL
));
455 calf_vumeter_get_type (void)
457 static GType type
= 0;
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 */
468 (GInstanceInitFunc
)calf_vumeter_init
471 GTypeInfo
*type_info_copy
= new GTypeInfo(type_info
);
473 for (int i
= 0; ; i
++) {
474 char *name
= g_strdup_printf("CalfVUMeter%u%d", ((unsigned int)(intptr_t)calf_vumeter_class_init
) >> 16, i
);
475 if (g_type_from_name(name
)) {
479 type
= g_type_register_static( GTK_TYPE_DRAWING_AREA
,
490 extern void calf_vumeter_set_value(CalfVUMeter
*meter
, float value
)
492 if (value
!= meter
->value
or meter
->holding
or meter
->falling
)
494 meter
->value
= value
;
495 gtk_widget_queue_draw(GTK_WIDGET(meter
));
499 extern float calf_vumeter_get_value(CalfVUMeter
*meter
)
504 extern void calf_vumeter_set_mode(CalfVUMeter
*meter
, CalfVUMeterMode mode
)
506 if (mode
!= meter
->mode
)
509 if(mode
== VU_MONOCHROME_REVERSE
) {
511 meter
->last_value
= 1.f
;
514 meter
->last_value
= 0.f
;
516 meter
->vumeter_falloff
= 0.f
;
517 meter
->last_falloff
= (long)0;
518 meter
->last_hold
= (long)0;
519 gtk_widget_queue_draw(GTK_WIDGET(meter
));
523 extern CalfVUMeterMode
calf_vumeter_get_mode(CalfVUMeter
*meter
)
528 extern void calf_vumeter_set_falloff(CalfVUMeter
*meter
, float value
)
530 if (value
!= meter
->vumeter_falloff
)
532 meter
->vumeter_falloff
= value
;
533 gtk_widget_queue_draw(GTK_WIDGET(meter
));
537 extern float calf_vumeter_get_falloff(CalfVUMeter
*meter
)
539 return meter
->vumeter_falloff
;
542 extern void calf_vumeter_set_hold(CalfVUMeter
*meter
, float value
)
544 if (value
!= meter
->vumeter_hold
)
546 meter
->vumeter_hold
= value
;
547 gtk_widget_queue_draw(GTK_WIDGET(meter
));
551 extern float calf_vumeter_get_hold(CalfVUMeter
*meter
)
553 return meter
->vumeter_hold
;
556 extern void calf_vumeter_set_width(CalfVUMeter
*meter
, int value
)
558 if (value
!= meter
->vumeter_width
)
560 meter
->vumeter_width
= value
;
561 gtk_widget_queue_draw(GTK_WIDGET(meter
));
565 extern int calf_vumeter_get_width(CalfVUMeter
*meter
)
567 return meter
->vumeter_width
;
570 extern void calf_vumeter_set_height(CalfVUMeter
*meter
, int value
)
572 if (value
!= meter
->vumeter_height
)
574 meter
->vumeter_height
= value
;
575 gtk_widget_queue_draw(GTK_WIDGET(meter
));
579 extern int calf_vumeter_get_height(CalfVUMeter
*meter
)
581 return meter
->vumeter_height
;
583 extern void calf_vumeter_set_position(CalfVUMeter
*meter
, int value
)
585 if (value
!= meter
->vumeter_height
)
587 meter
->vumeter_position
= value
;
588 gtk_widget_queue_draw(GTK_WIDGET(meter
));
592 extern int calf_vumeter_get_position(CalfVUMeter
*meter
)
594 return meter
->vumeter_position
;