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>
31 #include <calf/drawingutils.h>
34 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
37 calf_vumeter_expose (GtkWidget
*widget
, GdkEventExpose
*event
)
39 g_assert(CALF_IS_VUMETER(widget
));
41 CalfVUMeter
*vu
= CALF_VUMETER(widget
);
43 style
= gtk_widget_get_style(widget
);
44 cairo_t
*c
= gdk_cairo_create(GDK_DRAWABLE(widget
->window
));
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
) {
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
;
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
;
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
;
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
;
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
,
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
);
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
);
122 create_rectangle(cache_cr
,
125 width
- border_x
* 2,
126 height
- border_y
* 2, 0);
127 cairo_pattern_t
*pat2
= cairo_pattern_create_linear (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
);
139 cairo_rectangle(cache_cr
,
143 led_h
+ space_y
* 2);
144 cairo_set_source_rgb (cache_cr
, 0, 0, 0);
145 cairo_fill(cache_cr
);
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
;
158 r
= ts
/ 0.75, g
= 0.5 + ts
* 0.66, b
= 1 - ts
/ 0.75;
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;
164 case VU_STANDARD_CENTER
:
168 r
= 1, g
= (ts
) / 0.25, b
= 0;
172 r
= 1, g
= 1 - (ts
- 0.75) / 0.25, b
= 0;
178 r
= (ts
- 0.5) / 0.25, g
= 0.5 + (ts
- 0.5) * 2.f
, b
= 1 - (ts
- 0.5) / 0.25;
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;
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;
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;
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;
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
);
225 vu
->cache_overlay
= cairo_surface_create_similar(window_surface
,
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
);
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
);
249 cairo_destroy(cache_cr
);
250 cairo_destroy(over_cr
);
254 cairo_set_source_surface( c
, vu
->cache_surface
, 0,0 );
256 cairo_set_source_surface( c
, vu
->cache_overlay
, 0, 0 );
260 gettimeofday(&tv
, 0);
261 long time
= tv
.tv_sec
* 1000 * 1000 + tv
.tv_usec
;
264 float value_orig
= std::max(std::min(vu
->value
, 1.f
), 0.f
);
268 if(vu
->vumeter_falloff
> 0.f
and vu
->mode
!= VU_MONOCHROME_REVERSE
) {
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
;
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;
282 vu
->last_falloff
= 0.f
;
283 vu
->last_falltime
= 0.f
;
289 float draw_last
= 0.f
;
291 if(vu
->vumeter_hold
> 0.0) {
293 if(time
- (long)(vu
->vumeter_hold
* 1000 * 1000) > vu
->last_hold
) {
295 vu
->last_value
= value
;
296 vu
->last_hold
= time
;
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
;
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
;
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
);
335 if(value
> vu
->last_value
) {
336 // value is above peak hold
337 vu
->last_value
= value
;
338 vu
->last_hold
= time
;
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
);
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
);
363 cairo_rectangle( c
, led_x
+ draw
* led_w
, led_y
, led_w
* (1 - draw
), led_h
);
367 if (vu
->vumeter_position
)
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");
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
);
381 if(vu
->disp_value
> 1.f
and vu
->mode
!= VU_MONOCHROME_REVERSE
)
382 state
= GTK_STATE_ACTIVE
;
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
);
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);
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
;
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
;
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
);
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
;
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
;
454 g_signal_connect(GTK_OBJECT(widget
), "unrealize", G_CALLBACK(calf_vumeter_unrealize
), (gpointer
)self
);
460 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER
, NULL
));
464 calf_vumeter_get_type (void)
466 static GType type
= 0;
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 */
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
)) {
488 type
= g_type_register_static( GTK_TYPE_DRAWING_AREA
,
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
)
513 extern void calf_vumeter_set_mode(CalfVUMeter
*meter
, CalfVUMeterMode mode
)
515 if (mode
!= meter
->mode
)
518 if(mode
== VU_MONOCHROME_REVERSE
) {
520 meter
->last_value
= 1.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
)
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
;