Fix bypass crossfade read/write out of bounds
[calf.git] / src / ctl_vumeter.cpp
blobe8245818d327fb28a67b6a99c3b95cebcc246cd5
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>
33 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
35 static gboolean
36 calf_vumeter_expose (GtkWidget *widget, GdkEventExpose *event)
38 g_assert(CALF_IS_VUMETER(widget));
40 CalfVUMeter *vu = CALF_VUMETER(widget);
41 GtkStyle *style;
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) {
69 case 1:
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;
74 break;
75 case 2:
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;
79 break;
80 case 3:
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;
84 break;
85 case 4:
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;
90 break;
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,
101 CAIRO_CONTENT_COLOR,
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);
111 // outer (black)
112 cairo_rectangle(cache_cr, 0, 0, width, height);
113 cairo_set_source_rgb(cache_cr, 0, 0, 0);
114 cairo_fill(cache_cr);
116 // inner (bevel)
117 cairo_rectangle(cache_cr,
118 border_x,
119 border_y,
120 width - border_x * 2,
121 height - border_y * 2);
122 cairo_pattern_t *pat2 = cairo_pattern_create_linear (border_x,
123 border_y,
124 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);
132 // border around LED
133 cairo_rectangle(cache_cr,
134 led_x - space_x,
135 led_y - space_y,
136 led_w + space_x * 2,
137 led_h + space_y * 2);
138 cairo_set_source_rgb (cache_cr, 0, 0, 0);
139 cairo_fill(cache_cr);
141 // LED bases
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;
147 switch(vu->mode)
149 case VU_STANDARD:
150 default:
151 if (ts < 0.75)
152 r = ts / 0.75, g = 0.5 + ts * 0.66, b = 1 - ts / 0.75;
153 else
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;
157 break;
158 case VU_STANDARD_CENTER:
159 if (ts < 0.25)
160 // 0.0 -> 0.25
161 // green: 0.f -> 1.f
162 r = 1, g = (ts) / 0.25, b = 0;
163 else if (ts > 0.75)
164 // 0.75 -> 1.0
165 // green: 1.f -> 0.f
166 r = 1, g = 1 - (ts - 0.75) / 0.25, b = 0;
167 else if (ts > 0.5)
168 // 0.5 -> 0.75
169 // red: 0.f -> 1.f
170 // green: 0.5 -> 1.f
171 // blue: 1.f -> 0.f
172 r = (ts - 0.5) / 0.25, g = 0.5 + (ts - 0.5) * 2.f, b = 1 - (ts - 0.5) / 0.25;
173 else
174 // 0.25 -> 0.5
175 // red: 1.f -> 0.f
176 // green: 1.f -> 0.5
177 // blue: 0.f -> 1.f
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;
181 break;
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;
186 break;
187 case VU_MONOCHROME:
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;
191 break;
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;
196 break;
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);
218 // create overlay
219 vu->cache_overlay = cairo_surface_create_similar(window_surface,
220 CAIRO_CONTENT_COLOR,
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);
228 cairo_fill(over_cr);
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);
242 // clean up
243 cairo_destroy(cache_cr);
244 cairo_destroy(over_cr);
247 // draw LED blinder
248 cairo_set_source_surface( c, vu->cache_surface, 0,0 );
249 cairo_paint( c );
250 cairo_set_source_surface( c, vu->cache_overlay, 0, 0 );
252 // get microseconds
253 timeval tv;
254 gettimeofday(&tv, 0);
255 long time = tv.tv_sec * 1000 * 1000 + tv.tv_usec;
257 // limit to 1.f
258 float value_orig = std::max(std::min(vu->value, 1.f), 0.f);
259 float value = 0.f;
261 // falloff?
262 if(vu->vumeter_falloff > 0.f and vu->mode != VU_MONOCHROME_REVERSE) {
263 // fall off a bit
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;
267 // new max value?
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;
274 } else {
275 // falloff disabled
276 vu->last_falloff = 0.f;
277 vu->last_falltime = 0.f;
278 value = value_orig;
279 vu->falling = false;
282 float draw = 0.f;
283 float draw_last = 0.f;
285 if(vu->vumeter_hold > 0.0) {
286 // peak hold timer
287 if(time - (long)(vu->vumeter_hold * 1000 * 1000) > vu->last_hold) {
288 // time's up, reset
289 vu->last_value = value;
290 vu->last_hold = time;
291 vu->holding = false;
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;
299 vu->holding = true;
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;
321 vu->holding = true;
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);
328 } else {
329 if(value > vu->last_value) {
330 // value is above peak hold
331 vu->last_value = value;
332 vu->last_hold = time;
333 vu->holding = true;
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);
347 } else {
348 // darken normally
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);
356 } else
357 cairo_rectangle( c, led_x + draw * led_w, led_y, led_w * (1 - draw), led_h);
359 cairo_fill( c );
361 if (vu->vumeter_position)
363 char str[32];
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");
369 else
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);
376 else
377 cairo_set_source_rgba (c, 0, 0.9, 1, 0.8);
378 cairo_show_text(c, str);
379 cairo_fill(c);
381 cairo_destroy(c);
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);
385 return TRUE;
388 static void
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;
398 static void
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;
409 static void
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);
423 static void
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;
432 static void
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;
444 self->value = 0.f;
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 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)) {
476 free(name);
477 continue;
479 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
480 name,
481 type_info_copy,
482 (GTypeFlags)0);
483 free(name);
484 break;
487 return type;
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)
501 return meter->value;
504 extern void calf_vumeter_set_mode(CalfVUMeter *meter, CalfVUMeterMode mode)
506 if (mode != meter->mode)
508 meter->mode = mode;
509 if(mode == VU_MONOCHROME_REVERSE) {
510 meter->value = 1.f;
511 meter->last_value = 1.f;
512 } else {
513 meter->value = 0.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)
525 return meter->mode;
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;