1 /* ebu-r128 LV2 GUI
2  *
3  * Copyright 2012-2013 Robin Gareus <robin@gareus.org>
4  * Copyright 2011-2012 David Robillard <d@drobilla.net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
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
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <math.h>
24 
25 #define RTK_URI "http://gareus.org/oss/lv2/meters#"
26 #define RTK_GUI "eburui"
27 
28 #define GBT_W(PTR) robtk_cbtn_widget(PTR)
29 #define GRB_W(PTR) robtk_rbtn_widget(PTR)
30 #define GSP_W(PTR) robtk_spin_widget(PTR)
31 #define GPB_W(PTR) robtk_pbtn_widget(PTR)
32 #define GLB_W(PTR) robtk_lbl_widget(PTR)
33 
34 #include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
35 #include "src/uris.h"
36 
37 #ifndef MAX
38 #define MAX(A,B) ( (A) > (B) ? (A) : (B) )
39 #endif
40 
41 /*************************/
42 #define CX  (178.5f)
43 #define CY  (196.5f)
44 
45 #define COORD_ALL_W 356
46 #define COORD_ALL_H 412
47 
48 #define COORD_MTR_X 23
49 #define COORD_MTR_Y 52
50 
51 #define COORD_MX_X 298  // max display X
52 #define COORD_TP_X 25   // true peak
53 
54 #define COORD_ML_Y 11   // TOP
55 
56 #define COORD_BI_X 8    // integrating X
57 #define COORD_BR_X (COORD_MX_X-65) // bottom right info
58 #define COORD_BI_Y 341  // integrating
59 
60 #define COORD_LEVEL_X 118 //used w/ COORD_ML_Y
61 #define COORD_BINFO_Y 314 // used with COORD_BI_X
62 
63 #define CLPX(X) (X+13)
64 #define CLPY(Y) (Y+6)
65 
66 /* some width and radii, */
67 
68 #define COORD_BINTG_W 340
69 
70 #define COORD_BINFO_W 117
71 #define COORD_BINFO_H 40
72 #define COORD_LEVEL_W 120
73 #define COORD_LEVEL_H 24
74 
75 #define RADIUS   (120.0f)
76 #define RADIUS1  (122.0f)
77 #define RADIUS5  (125.0f)
78 #define RADIUS10 (130.0f)
79 #define RADIUS19 (139.0f)
80 #define RADIUS22 (142.0f)
81 #define RADIUS23 (143.0f)
82 
83 /*************************/
84 
85 enum {
86 	FONT_M14 = 0,
87 	FONT_M12,
88 	FONT_M09,
89 	FONT_M08,
90 	FONT_S09,
91 	FONT_S08
92 };
93 
94 typedef struct {
95 	LV2_Atom_Forge forge;
96 
97 	LV2_URID_Map* map;
98 	EBULV2URIs   uris;
99 
100 	LV2UI_Write_Function write;
101 	LV2UI_Controller     controller;
102 
103 	RobWidget* box;
104 
105 	RobTkCBtn* btn_start;
106 	RobTkPBtn* btn_reset;
107 
108 	RobWidget* cbx_box;
109 	RobTkRBtn* cbx_lufs;
110 	RobTkRBtn* cbx_lu;
111 	RobTkRBtn* cbx_sc9;
112 	RobTkRBtn* cbx_sc18;
113 	RobTkRBtn* cbx_sc24;
114 	RobTkRBtn* cbx_ring_short;
115 	RobTkRBtn* cbx_ring_mom;
116 	RobTkRBtn* cbx_hist_short;
117 	RobTkRBtn* cbx_hist_mom;
118 	RobTkCBtn* cbx_transport;
119 	RobTkCBtn* cbx_autoreset;
120 	RobTkCBtn* cbx_truepeak;
121 
122 	RobTkRBtn* cbx_radar;
123 	RobTkRBtn* cbx_histogram;
124 
125 	RobTkSpin* spn_radartime;
126 	RobTkLbl* lbl_ringinfo;
127 	RobTkLbl* lbl_radarinfo;
128 
129 	RobTkSep* sep_h0;
130 	RobTkSep* sep_h1;
131 	RobTkSep* sep_h2;
132 	RobTkSep* sep_v0;
133 
134 	RobWidget* m0;
135 
136 	cairo_pattern_t * cpattern;
137 	cairo_pattern_t * lpattern9;
138 	cairo_pattern_t * lpattern18;
139 	cairo_pattern_t * hpattern9;
140 	cairo_pattern_t * hpattern18;
141 
142 	cairo_surface_t * level_surf;
143 	cairo_surface_t * radar_surf;
144 	cairo_surface_t * lvl_label;
145 	cairo_surface_t * hist_label;
146 
147 	bool redraw_labels;
148 	bool fontcache;
149 	PangoFontDescription *font[6];
150 
151 	bool disable_signals;
152 
153 	/* current data */
154 	float lm, mm, ls, ms, il, rn, rx, it, tp;
155 
156 	float *radarS;
157 	float *radarM;
158 	int radar_pos_cur;
159 	int radar_pos_max;
160 
161 	int histS[HIST_LEN];
162 	int histM[HIST_LEN];
163 	int histLenS;
164 	int histLenM;
165 
166 	/* displayed data */
167 	int radar_pos_disp;
168 	int circ_max;
169 	int circ_val;
170 	bool fullhist;
171 	int  fastradar;
172 	bool fasthist;
173 
174 	bool fasttracked[5];
175 	float prev_lvl[5]; // ls,lm,mm,ms, tp
176 	const char *nfo;
177 } EBUrUI;
178 
179 
180 /******************************************************************************
181  * fast math helpers
182  */
183 
fast_log2(float val)184 static inline float fast_log2 (float val)
185 {
186 	union {float f; int i;} t;
187 	t.f = val;
188 	int * const    exp_ptr =  &t.i;
189 	int            x = *exp_ptr;
190 	const int      log_2 = ((x >> 23) & 255) - 128;
191 	x &= ~(255 << 23);
192 	x += 127 << 23;
193 	*exp_ptr = x;
194 
195 	val = ((-1.0f/3) * t.f + 2) * t.f - 2.0f/3;
196 
197 	return (val + log_2);
198 }
199 
fast_log10(const float val)200 static inline float fast_log10 (const float val)
201 {
202 	return fast_log2(val) * 0.301029996f;
203 }
204 
205 /******************************************************************************
206  * meter colors
207  */
208 
radar_deflect(const float v,const float r)209 static float radar_deflect(const float v, const float r) {
210 	if (v < -60) return 0;
211 	if (v > 0) return r;
212 	return (v + 60.0) * r / 60.0;
213 }
214 
radar_color(cairo_t * cr,const float v)215 static void radar_color(cairo_t* cr, const float v) {
216 	const float alpha = 1.0;
217 
218 	if (v < -70) {
219 		cairo_set_source_rgba (cr, .3, .3, .3, alpha);
220 	} else if (v < -53) {
221 		cairo_set_source_rgba (cr, .0, .0, .5, alpha);
222 	} else if (v < -47) {
223 		cairo_set_source_rgba (cr, .0, .0, .9, alpha);
224 	} else if (v < -35) {
225 		cairo_set_source_rgba (cr, .0, .6, .0, alpha);
226 	} else if (v < -23) {
227 		cairo_set_source_rgba (cr, .0, .9, .0, alpha);
228 	} else if (v < -11) {
229 		cairo_set_source_rgba (cr, .75, .75, .0, alpha);
230 	} else if (v < -7) {
231 		cairo_set_source_rgba (cr, .8, .4, .0, alpha);
232 	} else if (v < -3.5) {
233 		cairo_set_source_rgba (cr, .75, .0, .0, alpha);
234 	} else {
235 		cairo_set_source_rgba (cr, 1.0, .0, .0, alpha);
236 	}
237 }
238 
radar_pattern(cairo_t * crx,float cx,float cy,float rad)239 static cairo_pattern_t * radar_pattern(cairo_t* crx, float cx, float cy, float rad) {
240 	cairo_pattern_t * pat = cairo_pattern_create_radial(cx, cy, 0, cx, cy, rad);
241 	cairo_pattern_add_color_stop_rgba(pat, 0.0 ,  .05, .05, .05, 1.0); // radar BG
242 	cairo_pattern_add_color_stop_rgba(pat, 0.05,  .0, .0, .0, 1.0); // -57
243 
244 	cairo_pattern_add_color_stop_rgba(pat, radar_deflect(-53.0, 1.0),  .0, .0, .5, 1.0);
245 
246 	cairo_pattern_add_color_stop_rgba(pat, radar_deflect(-48.0, 1.0),  .0, .0, .8, 1.0);
247 	cairo_pattern_add_color_stop_rgba(pat, radar_deflect(-46.5, 1.0),  .0, .5, .4, 1.0);
248 
249 	cairo_pattern_add_color_stop_rgba(pat, radar_deflect(-36.0, 1.0),  .0, .6, .0, 1.0);
250 	cairo_pattern_add_color_stop_rgba(pat, radar_deflect(-34.0, 1.0),  .0, .8, .0, 1.0);
251 
252 	cairo_pattern_add_color_stop_rgba(pat, radar_deflect(-24.0, 1.0),  .1, .9, .1, 1.0);
253 	cairo_pattern_add_color_stop_rgba(pat, radar_deflect(-22.0, 1.0), .75,.75, .0, 1.0);
254 
255 	cairo_pattern_add_color_stop_rgba(pat, radar_deflect(-11.5, 1.0),  .8, .4, .0, 1.0);
256 	cairo_pattern_add_color_stop_rgba(pat, radar_deflect(-10.5, 1.0),  .7, .1, .1, 1.0);
257 
258 	cairo_pattern_add_color_stop_rgba(pat, radar_deflect( -4.5, 1.0),  .9, .0, .0, 1.0);
259 	cairo_pattern_add_color_stop_rgba(pat, radar_deflect( -3.5, 1.0),   1, .1, .0, 1.0);
260 	cairo_pattern_add_color_stop_rgba(pat, 1.0 ,                        1, .1, .0, 1.0);
261 	return pat;
262 }
263 
histogram_pattern(cairo_t * crx,float cx,float cy,float rad,bool plus9)264 static cairo_pattern_t * histogram_pattern(cairo_t* crx, float cx, float cy, float rad, bool plus9) {
265 	cairo_pattern_t * pat = cairo_pattern_create_radial(rad, rad, 0, rad, rad, rad);
266 	cairo_pattern_add_color_stop_rgba(pat, 0.00,  .0, .0, .0, 0.0);
267 	cairo_pattern_add_color_stop_rgba(pat, 0.06,  .0, .0, .0, 0.0);
268 	cairo_pattern_add_color_stop_rgba(pat, 0.09,  .1, .1, .1, 1.0);
269 	cairo_pattern_add_color_stop_rgba(pat, 0.20,  .2, .2, .2, 1.0);
270 	cairo_pattern_add_color_stop_rgba(pat, 0.50,  .4, .4, .4, 1.0);
271 	cairo_pattern_add_color_stop_rgba(pat, 0.91,  .5, .5, .5, 1.0);
272 	cairo_pattern_add_color_stop_rgba(pat, 1.0 , 1.0, .2, .2, 1.0);
273 
274 	cairo_surface_t *sfx = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, rad*2, rad*2);
275 	cairo_t *cr = cairo_create (sfx);
276 	CairoSetSouerceRGBA(c_trs);
277 	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
278 	cairo_rectangle (cr, 0, 0, rad*2, rad*2);
279 	cairo_fill (cr);
280 	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
281 
282 	cairo_move_to(cr, rad, rad);
283 	cairo_set_source (cr, pat);
284 	cairo_move_to(cr, rad, rad);
285 	cairo_arc(cr, rad, rad, rad, 0.5 * M_PI, 2.0 * M_PI);
286 	cairo_fill(cr);
287 	cairo_pattern_destroy (pat);
288 
289 	//cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
290 	cairo_set_operator (cr, CAIRO_OPERATOR_HSL_COLOR);
291 #define PIE(START, END, CR, CG, CB, CA) \
292 	cairo_set_source_rgba(cr, CR, CG, CB, CA); \
293 	cairo_move_to(cr, rad, rad); \
294 	cairo_arc (cr, rad, rad, rad, (.5+(START)) * M_PI, (.5 + (END)) * M_PI); \
295 	cairo_close_path (cr); \
296 	cairo_fill (cr);
297 
298 	if (plus9) {
299 		PIE(  0/6.0,   2/6.0,  .0,  .4, .0, .6);
300 		PIE(  2/6.0,   6/6.0,  .0,  .8, .0, .6);
301 		PIE(  6/6.0,   9/6.0, .75, .75, .0, .6);
302 	} else {
303 		// -36..+18
304 		PIE(  0/6.0,   1/6.0,  .0,  .0, .4, .6);
305 		PIE(  1/6.0,   2/6.0,  .0,  .0, .8, .6);
306 		PIE(  2/6.0,   4/6.0,  .0,  .4, .0, .6);
307 		PIE(  4/6.0,   6/6.0,  .0,  .8, .0, .6);
308 		PIE(  6/6.0,   8/6.0, .75, .75, .0, .6);
309 		PIE(  8/6.0, 8.5/6.0,  .8,  .4, .0, .6);
310 		PIE(8.5/6.0,   9/6.0, 1.0,  .0, .0, .6);
311 	}
312 
313 	//cairo_translate (cr, cx-rad, cy-rad);
314 
315 	cairo_surface_flush(sfx);
316 	cairo_destroy (cr);
317 
318 	pat =  cairo_pattern_create_for_surface (sfx);
319 	cairo_matrix_t m;
320 	cairo_matrix_init_translate (&m, -(cx-rad), -(cy-rad));
321 	cairo_pattern_set_matrix (pat, &m);
322 	cairo_surface_destroy (sfx);
323 
324 	return pat;
325 }
326 
327 /******************************************************************************
328  * custom visuals
329  */
330 
331 #define LUFS(V) ((V) < -100 ? -INFINITY : (lufs ? (V) : (V) + 23.0))
332 #define FONT(A) ui->font[(A)]
333 
format_lufs(char * buf,const float val)334 static char *format_lufs(char *buf, const float val) {
335 	if (val < -100)
336 		sprintf(buf, "   -\u221E");
337 	else
338 		sprintf(buf, "%+5.1f", val);
339 	return buf;
340 }
341 
write_text(cairo_t * cr,const char * txt,PangoFontDescription * font,const float x,const float y,const float ang,const int align,const float * const col)342 static void write_text(
343 		cairo_t* cr,
344 		const char *txt,
345 		PangoFontDescription *font, //const char *font,
346 		const float x, const float y,
347 		const float ang, const int align,
348 		const float * const col) {
349 	write_text_full(cr, txt, font, x, y, ang, align, col);
350 }
351 
hlabel_surface(EBUrUI * ui)352 static cairo_surface_t * hlabel_surface(EBUrUI* ui) {
353 	cairo_surface_t *sf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, RADIUS*2, RADIUS*2);
354 	cairo_t *cr = cairo_create (sf);
355 	cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
356 	cairo_paint (cr);
357 
358 	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
359 	CairoSetSouerceRGBA(c_gry);
360 	cairo_set_line_width(cr, 1.0);
361 
362 #define CIRCLABEL(RDS,LBL) \
363 	{ \
364 	cairo_arc (cr, RADIUS, RADIUS, RADIUS * RDS, 0.5 * M_PI, 2.0 * M_PI); \
365 	cairo_stroke (cr); \
366 	write_text(cr, LBL, FONT(FONT_M08), RADIUS + RADIUS * RDS, RADIUS + 14.5, M_PI * -.5, 2, c_gry);\
367 	}
368 	CIRCLABEL(.301, "20%")
369 	CIRCLABEL(.602, "40%")
370 	CIRCLABEL(.903, "80%")
371 	cairo_destroy (cr);
372 	return sf;
373 }
374 
clabel_surface(EBUrUI * ui,bool plus9,bool plus24,bool lufs)375 static cairo_surface_t * clabel_surface(EBUrUI* ui, bool plus9, bool plus24, bool lufs) {
376 	char buf[128];
377 	cairo_surface_t *sf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, COORD_ALL_W, COORD_ALL_H);
378 	cairo_t *cr = cairo_create (sf);
379 
380 	cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
381 	cairo_paint (cr);
382 
383 	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
384 
385 #define SIN60 0.866025404
386 #define CLABEL(PT, XS, YS, AL) \
387 		sprintf(buf, "%+.0f", LUFS(PT)); \
388 		write_text(cr, buf, FONT(FONT_M08), CX + RADIUS23 * XS, CY + RADIUS23 *YS , 0, AL, c_gry);
389 
390 	if (plus9) {
391 		CLABEL(-41,    0.0,   1.0, 8)
392 		CLABEL(-38,   -0.5, SIN60, 7)
393 		CLABEL(-35, -SIN60,   0.5, 1)
394 		CLABEL(-32,   -1.0,   0.0, 1)
395 		CLABEL(-29, -SIN60,  -0.5, 1)
396 		CLABEL(-26,   -0.5,-SIN60, 4)
397 		CLABEL(-23,    0.0,  -1.0, 5)
398 		CLABEL(-20,    0.5,-SIN60, 6)
399 		CLABEL(-17,  SIN60,  -0.5, 3)
400 		CLABEL(-14,    1.0,   0.0, 3)
401 	} else { // plus18
402 		CLABEL(-59,    0.0,   1.0, 8)
403 		CLABEL(-53,   -0.5, SIN60, 7)
404 		CLABEL(-47, -SIN60,   0.5, 1)
405 		CLABEL(-41,   -1.0,   0.0, 1)
406 		CLABEL(-35, -SIN60,  -0.5, 1)
407 		CLABEL(-29,   -0.5,-SIN60, 4)
408 		CLABEL(-23,    0.0,  -1.0, 5)
409 		CLABEL(-17,    0.5,-SIN60, 6)
410 		CLABEL(-11,  SIN60,  -0.5, 3)
411 		CLABEL( -5,    1.0,   0.0, 3)
412 		if (plus24) { // plus24
413 			CLABEL(1,  SIN60,   0.5, 3)
414 		}
415 	}
416 	cairo_destroy (cr);
417 	return sf;
418 }
419 
ring_leds(EBUrUI * ui,int * l,int * m)420 static void ring_leds(EBUrUI* ui, int *l, int *m) {
421 	const bool rings = robtk_rbtn_get_active(ui->cbx_ring_short);
422 	const bool plus9 = robtk_rbtn_get_active(ui->cbx_sc9);
423 
424 	const float clr = rings ? ui->ls : ui->lm;
425 	const float cmr = rings ? ui->ms : ui->mm;
426 	*l = rint(plus9 ? (clr + 41.0f) * 4 : (clr + 59.0f) * 2.0);
427 	*m = rint(plus9 ? (cmr + 41.0f) * 4 : (cmr + 59.0f) * 2.0);
428 }
429 
initialize_font_cache(EBUrUI * ui)430 static void initialize_font_cache(EBUrUI* ui) {
431 	ui->fontcache = true;
432 	ui->font[FONT_M14] = pango_font_description_from_string("Mono 18px");
433 	ui->font[FONT_M12] = pango_font_description_from_string("Mono 14px");
434 	ui->font[FONT_M09] = pango_font_description_from_string("Mono 12px");
435 	ui->font[FONT_M08] = pango_font_description_from_string("Mono 10px");
436 	ui->font[FONT_S09] = pango_font_description_from_string("Sans 12px");
437 	ui->font[FONT_S08] = pango_font_description_from_string("Sans 10px");
438 	assert(ui->font[FONT_M14]);
439 	assert(ui->font[FONT_M12]);
440 	assert(ui->font[FONT_M09]);
441 	assert(ui->font[FONT_M08]);
442 	assert(ui->font[FONT_S09]);
443 	assert(ui->font[FONT_S08]);
444 }
445 
446 /******************************************************************************
447  * Areas for partial exposure
448  */
449 
leveldisplaypath(cairo_t * cr)450 static void leveldisplaypath(cairo_t *cr) {
451 	cairo_move_to(cr,CLPX( 37), CLPY(190));
452 	cairo_line_to(cr,CLPX( 55), CLPY(124));
453 	cairo_line_to(cr,CLPX( 98), CLPY( 80));
454 	cairo_line_to(cr,CLPX(165), CLPY( 61));
455 	cairo_line_to(cr,CLPX(231), CLPY( 80));
456 	cairo_line_to(cr,CLPX(276), CLPY(126));
457 	cairo_line_to(cr,CLPX(292), CLPY(190));
458 	cairo_line_to(cr,CLPX(292), CLPY(200));
459 	cairo_line_to(cr,CLPX(268), CLPY(268));
460 	cairo_line_to(cr,CLPX(315), CLPY(268));
461 	cairo_line_to(cr,CLPX(330), CLPY(200));
462 	cairo_line_to(cr,CLPX(330), CLPY(185));
463 	cairo_line_to(cr,CLPX(310), CLPY(112));
464 	cairo_line_to(cr,CLPX(242), CLPY( 47));
465 	cairo_line_to(cr,CLPX(165), CLPY( 33));
466 	cairo_line_to(cr,CLPX( 87), CLPY( 47));
467 	cairo_line_to(cr,CLPX( 20), CLPY(113));
468 	cairo_line_to(cr,CLPX(  0), CLPY(190));
469 
470 #if 0 // outside
471 	cairo_line_to(cr,  0, 0);
472 	cairo_line_to(cr,  COORD_ALL_W, 0);
473 	cairo_line_to(cr,  COORD_ALL_W, COORD_ALL_H);
474 	cairo_line_to(cr,  0, COORD_ALL_H);
475 	//cairo_line_to(cr,  0, 0);
476 	cairo_line_to(cr,  CLP(0), CLP(190));
477 #endif
478 
479 	cairo_line_to(cr, CLPX( 25), CLPY(267));
480 	cairo_line_to(cr, CLPX( 80), CLPY(332));
481 	cairo_line_to(cr, CLPX(165), CLPY(345));
482 	cairo_line_to(cr, CLPX(180), CLPY(345));
483 	cairo_line_to(cr, CLPX(180), CLPY(315));
484 	cairo_line_to(cr, CLPX(160), CLPY(316));
485 	cairo_line_to(cr, CLPX(101), CLPY(301));
486 	cairo_line_to(cr, CLPX( 54), CLPY(254));
487 	cairo_close_path(cr);
488 
489 }
490 
491 const static cairo_rectangle_t rect_is_level = {COORD_MTR_X, COORD_MTR_Y, 320, 290}; // match w/COORD_BINFO
492 const static cairo_rectangle_t rect_is_radar = {CX-RADIUS-9, CY-RADIUS-9, 2*RADIUS+12, 2*RADIUS+12};
493 
494 #define RDR_INV_X (CX-RADIUS -2.5) // 43
495 #define RDR_INV_Y (CY-RADIUS -2.5) // 68
496 #define RDR_INV_W (2*RADIUS + 5)
497 #define RDR_INV_H (2*RADIUS + 5)
498 
level_pattern(const float cx,const float cy,const float rad,bool plus9)499 static cairo_pattern_t * level_pattern (const float cx, const float cy, const float rad, bool plus9) {
500 	cairo_surface_t *sfx = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, rad * 2, rad * 2);
501 	cairo_t *cr = cairo_create (sfx);
502 
503 #define COLORPIE(A0, A1, R, G, B) \
504 	cairo_set_source_rgb (cr, R, G, B); \
505 	cairo_move_to(cr, rad, rad); \
506 	cairo_arc(cr, rad, rad, rad, M_PI * (.5 +(A0)) - .0218, M_PI * (.5 + (A1)) - 0.0218); \
507 	cairo_close_path (cr); \
508 	cairo_fill (cr);
509 
510 	if (plus9) {
511 		COLORPIE(  0/6.0,   2/6.0,  .0,  .4, .0);
512 		COLORPIE(  2/6.0,   6/6.0,  .0,  .8, .0);
513 		COLORPIE(  6/6.0,   9/6.0, .75, .75, .0);
514 	} else {
515 		// -36..+18
516 		COLORPIE(  0/6.0,   1/6.0,  .0,  .0, .4);
517 		COLORPIE(  1/6.0,   2/6.0,  .0,  .0, .8);
518 		COLORPIE(  2/6.0,   4/6.0,  .0,  .4, .0);
519 		COLORPIE(  4/6.0,   6/6.0,  .0,  .8, .0);
520 		COLORPIE(  6/6.0,   8/6.0, .75, .75, .0);
521 		COLORPIE(  8/6.0, 8.5/6.0,  .8,  .4, .0);
522 		COLORPIE(8.5/6.0,   9/6.0, 1.0,  .0, .0);
523 		// and + 24
524 		COLORPIE(  9/6.0,  11/6.0, 1.0,  .0, .0);
525 	}
526 
527 	cairo_surface_flush(sfx);
528 	cairo_destroy (cr);
529 
530 	cairo_pattern_t * pat =  cairo_pattern_create_for_surface (sfx);
531 
532 	cairo_matrix_t m;
533 	cairo_matrix_init_translate (&m, -cx + rad, -cy + rad);
534 	cairo_pattern_set_matrix (pat, &m);
535 
536 	cairo_surface_destroy (sfx);
537 	return pat;
538 }
539 
prepare_lvl_surface(EBUrUI * ui)540 static void prepare_lvl_surface (EBUrUI* ui) {
541 	cairo_t *cr;
542 	assert (!ui->level_surf);
543 	ui->level_surf = cairo_image_surface_create(CAIRO_FORMAT_A8, COORD_ALL_W, ceil(CY) + RADIUS22);
544 
545 	cr = cairo_create (ui->level_surf);
546 	cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
547 	cairo_paint(cr);
548 	cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
549 
550 	cairo_set_line_width(cr, 2.5);
551 	cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
552 	CairoSetSouerceRGBA(c_g30);
553 
554 	const int ulp = 120;
555 	for (int rng = 0; rng <= ulp; ++rng) {
556 		const float ang = 0.043633231 * rng + 1.570796327;
557 		float cc = sinf(ang);
558 		float sc = cosf(ang);
559 		cairo_move_to(cr,   CX + RADIUS10 * sc, CY + RADIUS10 * cc);
560 		cairo_line_to(cr, CX + RADIUS19 * sc, CY + RADIUS19 * cc);
561 		cairo_stroke (cr);
562 	}
563 	cairo_destroy (cr);
564 }
565 
render_radar(EBUrUI * ui)566 static void render_radar (EBUrUI* ui) {
567 	cairo_t *cr;
568 	if (!ui->radar_surf) {
569 		ui->radar_surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, RADIUS * 2, RADIUS * 2);
570 		cr = cairo_create (ui->radar_surf);
571 		cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
572 		cairo_paint(cr);
573 	} else {
574 		cr = cairo_create (ui->radar_surf);
575 	}
576 
577 	cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
578 	cairo_translate (cr, RADIUS, RADIUS);
579 
580 	const bool hists = robtk_rbtn_get_active(ui->cbx_hist_short);
581 	const bool plus9 = robtk_rbtn_get_active(ui->cbx_sc9);
582 
583 	if (robtk_rbtn_get_active(ui->cbx_histogram)) {
584 		/* ----- Histogram ----- */
585 		const int *rdr = hists ? ui->histS : ui->histM;
586 		const int  len = hists ? ui->histLenS : ui->histLenM;
587 
588 		cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
589 		cairo_paint(cr);
590 		cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
591 
592 		/* histogram background */
593 		if (len > 0) {
594 			CairoSetSouerceRGBA(c_g05);
595 		} else {
596 			CairoSetSouerceRGBA(c_rd2);
597 		}
598 		cairo_arc (cr, 0, 0, RADIUS, 0, 2.0 * M_PI);
599 		cairo_fill (cr);
600 
601 		if (len > 0) {
602 			int amin, amax;
603 			//  lvlFS = (0.1f * (ang - 700))
604 			//  lvlFS =  .1 * ang - 70
605 			if (plus9) { // -41 .. -14 LUFS
606 				amin = 290; // -41LUFS
607 				amax = 560; // -14LUFS
608 			} else { // -59 .. -5 LUFS
609 				amin = 110; // -59LUFS
610 				amax = 650; // -5LUFS
611 			}
612 			const double astep = 1.5 * M_PI / (double) (amax - amin);
613 			const double aoff = (M_PI / 2.0) - amin * astep;
614 
615 			cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
616 			cairo_set_line_width(cr, 0.75);
617 			if (plus9) {
618 				cairo_set_source (cr, ui->hpattern9);
619 			} else {
620 				cairo_set_source (cr, ui->hpattern18);
621 			}
622 
623 			for (int ang = amin; ang < amax; ++ang) {
624 				if (rdr[ang] <= 0) continue;
625 				const float rad = (float) RADIUS * (1.0 + fast_log10(rdr[ang] / (float) len));
626 				if (rad < 5) continue;
627 
628 				cairo_move_to(cr, 0, 0);
629 				cairo_arc (cr, 0, 0, rad,
630 						(double) (ang-1.0) * astep + aoff, (ang+1.0) * astep + aoff);
631 				cairo_close_path(cr);
632 			}
633 			cairo_fill(cr);
634 
635 			cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
636 
637 			/* outer circle */
638 			cairo_set_line_width(cr, 1.0);
639 			CairoSetSouerceRGBA(c_g20);
640 			cairo_arc (cr, 0, 0, RADIUS, /*0.5 * M_PI */ 0, 2.0 * M_PI);
641 			cairo_stroke (cr);
642 
643 			cairo_save(cr);
644 			cairo_set_source_surface(cr, ui->hist_label, -RADIUS, -RADIUS);
645 			cairo_paint(cr);
646 			cairo_restore(cr);
647 
648 		} else {
649 			write_text(cr, "No integration\ndata available.", FONT(FONT_S08), RADIUS * .5 , 5, 0, 8, c_g80);
650 		}
651 
652 		/* center circle */
653 		const float innercircle = 6;
654 		cairo_set_line_width(cr, 1.0);
655 		CairoSetSouerceRGBA(c_blk);
656 		cairo_arc (cr, 0, 0, innercircle, 0, 2.0 * M_PI);
657 		cairo_fill_preserve (cr);
658 		CairoSetSouerceRGBA(c_gry);
659 		cairo_stroke(cr);
660 
661 		cairo_arc (cr, 0, 0, innercircle + 3, .5 * M_PI, 2.0 * M_PI);
662 		cairo_stroke(cr);
663 
664 		/* gain lines */
665 		const double dashed[] = {3.0, 5.0};
666 		cairo_save(cr);
667 		cairo_set_dash(cr, dashed, 2, 4.0);
668 		cairo_set_line_width(cr, 1.5);
669 		cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
670 		for (int i = 3; i <= 12; ++i) {
671 			const float ang = .5235994f * i;
672 			float cc = sinf(ang);
673 			float sc = cosf(ang);
674 			cairo_move_to(cr, innercircle * sc, innercircle * cc);
675 			cairo_line_to(cr, RADIUS5 * sc, RADIUS5 * cc);
676 			cairo_stroke (cr);
677 		}
678 		cairo_restore(cr);
679 
680 	} else {
681 		/* ----- History ----- */
682 		ui->radar_pos_disp = ui->radar_pos_cur;
683 
684 		/* radar background */
685 		if (ui->fastradar < 0) {
686 			//printf("FAST RDR %d cur: %d / %d\n", ui->fastradar, ui->radar_pos_cur, ui->radar_pos_max);
687 			CairoSetSouerceRGBA(c_g05);
688 			cairo_arc (cr, 0, 0, RADIUS, 0, 2.0 * M_PI);
689 			cairo_fill (cr);
690 		}
691 
692 		cairo_set_line_width(cr, 1.0);
693 
694 		if (ui->radar_pos_max > 0) {
695 			float *rdr = hists ? ui->radarS : ui->radarM;
696 			const double astep = 2.0 * M_PI / (double) ui->radar_pos_max;
697 
698 			int a0 = 0;
699 			int a1 = ui->radar_pos_max;
700 
701 			if (ui->fastradar >= 0) {
702 				a0 = (ui->fastradar - 3 + ui->radar_pos_max) % ui->radar_pos_max;
703 				a1 = a0 + 10;
704 
705 				cairo_move_to(cr, 0, 0);
706 				cairo_arc (cr, 0, 0, RADIUS, (double) (a0 + 1.0) * astep, (a0 + 9.f) * astep);
707 				cairo_close_path(cr);
708 				CairoSetSouerceRGBA(c_g05);
709 				cairo_fill (cr);
710 			}
711 
712 			cairo_set_source (cr, ui->cpattern);
713 
714 			for (int ang = a0; ang < a1; ++ang) {
715 				cairo_move_to(cr, 0, 0);
716 				cairo_arc (cr, 0, 0, radar_deflect(rdr[ang % ui->radar_pos_max], RADIUS),
717 						(double) ang * astep, (ang+1.5) * astep);
718 				cairo_close_path(cr);
719 			}
720 			cairo_fill(cr);
721 
722 			/* fade-out values */
723 			for (int p = 0; p < 7; ++p) {
724 				float pos = ui->radar_pos_cur + 1 + p;
725 				cairo_set_source_rgba (cr, .0, .0, .0, 1.0 - ((p+1.0)/7.0));
726 				cairo_move_to(cr, 0, 0);
727 				cairo_arc (cr, 0, 0, RADIUS, pos * astep, (pos + 1.0) * astep);
728 				cairo_fill(cr);
729 			}
730 
731 			/* current position */
732 			CairoSetSouerceRGBA(c_g7X); // XXX
733 			cairo_move_to(cr, 0, 0);
734 			cairo_arc (cr, 0, 0, RADIUS,
735 						(double) ui->radar_pos_cur * astep, ((double) ui->radar_pos_cur + 1.0) * astep);
736 			cairo_line_to(cr, 0, 0);
737 			cairo_fill (cr);
738 		}
739 	}
740 	cairo_destroy (cr);
741 }
742 
draw_radar_ann(cairo_t * cr)743 static void draw_radar_ann (cairo_t* cr) {
744 	cairo_save (cr);
745 	cairo_translate (cr, CX, CY);
746 	/* radar lines */
747 	cairo_set_line_width(cr, 1.5);
748 	CairoSetSouerceRGBA(c_an0);
749 	cairo_arc (cr, 0, 0, radar_deflect(-23, RADIUS), 0, 2.0 * M_PI);
750 	cairo_stroke (cr);
751 
752 	cairo_set_line_width(cr, 1.0);
753 	CairoSetSouerceRGBA(c_an1);
754 	cairo_arc (cr, 0, 0, radar_deflect(-47, RADIUS), 0, 2.0 * M_PI);
755 	cairo_stroke (cr);
756 	cairo_arc (cr, 0, 0, radar_deflect(-35, RADIUS), 0, 2.0 * M_PI);
757 	cairo_stroke (cr);
758 	cairo_arc (cr, 0, 0, radar_deflect(-11, RADIUS), 0, 2.0 * M_PI);
759 	cairo_stroke (cr);
760 	cairo_arc (cr, 0, 0, radar_deflect( 0, RADIUS), 0, 2.0 * M_PI);
761 	cairo_stroke (cr);
762 
763 	const float innercircle = radar_deflect(-47, RADIUS);
764 	for (int i = 0; i < 12; ++i) {
765 		const float ang = .5235994f * i;
766 		float cc = sinf(ang);
767 		float sc = cosf(ang);
768 		cairo_move_to(cr, innercircle * sc, innercircle * cc);
769 		cairo_line_to(cr, RADIUS * sc, RADIUS * cc);
770 	}
771 	cairo_stroke (cr);
772 	cairo_restore (cr);
773 }
774 
775 //#define DEBUG_DRAW(WHAT) printf("expose: %s\n", WHAT);
776 #define DEBUG_DRAW(WHAT)
777 
778 /******************************************************************************
779  * Main drawing function
780  */
781 
expose_event(RobWidget * handle,cairo_t * cr,cairo_rectangle_t * ev)782 static bool expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t *ev) {
783 	EBUrUI* ui = (EBUrUI*)GET_HANDLE(handle);
784 	const bool lufs =  robtk_rbtn_get_active(ui->cbx_lufs);
785 	const bool rings = robtk_rbtn_get_active(ui->cbx_ring_short);
786 	const bool plus9 = robtk_rbtn_get_active(ui->cbx_sc9);
787 	const bool plus24= robtk_rbtn_get_active(ui->cbx_sc24);
788 	const bool dbtp  = robtk_cbtn_get_active(ui->cbx_truepeak);
789 
790 	char buf[128];
791 	char lufb0[15], lufb1[15];
792 	int redraw_part = 0;
793 	/* initialized */
794 	if (!ui->cpattern) {
795 		ui->cpattern = radar_pattern(cr, 0, 0, RADIUS);
796 	}
797 	if (!ui->hpattern9) {
798 		ui->hpattern9 = histogram_pattern(cr, 0, 0, RADIUS, TRUE);
799 	}
800 	if (!ui->hpattern18) {
801 		ui->hpattern18 = histogram_pattern(cr, 0, 0, RADIUS, FALSE);
802 	}
803 	if (!ui->lpattern9) {
804 		ui->lpattern9 = level_pattern(CX, CY, RADIUS23, TRUE);
805 	}
806 	if (!ui->lpattern18) {
807 		ui->lpattern18 = level_pattern(CX, CY, RADIUS23, FALSE);
808 	}
809 	if (!ui->level_surf) {
810 		prepare_lvl_surface (ui);
811 	}
812 
813 	if (!ui->lvl_label || ui->redraw_labels) {
814 		if (ui->lvl_label) cairo_surface_destroy(ui->lvl_label);
815 		ui->lvl_label = clabel_surface(ui, plus9, plus24, lufs);
816 		ui->redraw_labels = FALSE;
817 	}
818 	if (!ui->hist_label) {
819 		ui->hist_label = hlabel_surface(ui);
820 	}
821 	/* end initialization */
822 
823 	DEBUG_DRAW("->>>START----");
824 #if 0 // DEBUG
825 	printf("IS: %.1f+%.1f  %.1fx%.1f\n", ev->x, ev->y, ev->width, ev->height);
826 #endif
827 	bool clip_set = false;
828 
829 	if (ev->x == 0 && ev->y == 0 && ev->width == COORD_ALL_W && ev->height == COORD_ALL_H) {
830 		redraw_part = 3;
831 	} else if (ev->x == COORD_BR_X && ev->y == COORD_BINFO_Y-1
832 			&& ev->width == COORD_BINFO_W && ev->height == COORD_BINFO_H+1) {
833 		// bottom right info box (when integrating)
834 		redraw_part = 0;
835 	} else if (ev->x == COORD_MTR_X && ev->y == COORD_MTR_Y) {
836 		redraw_part = 1;
837 		if (ui->fullhist) {
838 			ui->fullhist = false;
839 			redraw_part |= 2;
840 		} else {
841 			leveldisplaypath(cr);
842 			cairo_clip (cr);
843 			clip_set = true;
844 		}
845 		//printf("NO RDR BUT LVL\n");
846 	} else if (ev->x >= CX - RADIUS5 && ev->x <= CX+RADIUS5 && ev->y >= CY - RADIUS5 && ev->y <= CY + RADIUS5) {
847 		/* ie. fastradar */
848 		redraw_part = 2;
849 		//printf("RADAR ONLY\n");
850 		cairo_arc (cr, CX, CY, RADIUS1, 0, 2.0 * M_PI);
851 		cairo_clip (cr);
852 		clip_set = true;
853 	} else {
854 		redraw_part = 0;
855 		if (rect_intersect(ev, &rect_is_radar)) {
856 			redraw_part |= 2;
857 		}
858 		if (rect_intersect(ev, &rect_is_level)) {
859 			redraw_part |= 1;
860 		}
861 	}
862 
863 	if (!clip_set) {
864 		cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height);
865 		cairo_clip (cr);
866 	}
867 
868 	ui->fasttracked[0] = ui->fasttracked[1] = ui->fasttracked[2] = ui->fasttracked[3] = ui->fasttracked[4] = false;
869 	ui->fasthist = false;
870 
871 	/* fill background */
872 	rounded_rectangle (cr, 0, 4, COORD_ALL_W, COORD_ALL_H-6, 10);
873 	CairoSetSouerceRGBA(c_blk);
874 	cairo_fill_preserve (cr);
875 	cairo_set_line_width(cr, 0.75);
876 	CairoSetSouerceRGBA(c_g30);
877 	cairo_stroke(cr);
878 
879 	if (rect_intersect_a(ev, 12, COORD_ML_Y, 10, 50)) {
880 		DEBUG_DRAW("version");
881 		write_text(cr,
882 				ui->nfo ? ui->nfo : "x42 EBU R128 LV2",
883 				FONT(FONT_S08), 1, 15, 1.5 * M_PI, 7, c_g30);
884 	}
885 
886 	if (rect_intersect_a(ev, COORD_LEVEL_X, COORD_ML_Y, COORD_LEVEL_W, COORD_LEVEL_H)) {
887 		DEBUG_DRAW("Big Level Num");
888 		/* big level as text */
889 		ui->prev_lvl[0] = rings ? ui->ls : ui->lm;
890 		sprintf(buf, "%s %s", format_lufs(lufb0, LUFS(ui->prev_lvl[0])), lufs ? "LUFS" : "LU");
891 		write_text(cr, buf, FONT(FONT_M14), CX , COORD_ML_Y+4, 0, 8, c_wht);
892 	}
893 
894 	int trw = lufs ? 87 : 75;
895 
896 	if (rect_intersect_a(ev, COORD_MX_X, COORD_ML_Y+25, 40, 30)) {
897 		DEBUG_DRAW("Max Legend");
898 		/* max legend */
899 		CairoSetSouerceRGBA(c_g20);
900 		rounded_rectangle (cr, COORD_MX_X, COORD_ML_Y+25, 40, 30, 10);
901 		cairo_fill (cr);
902 		write_text(cr, !rings ? "Mom":"Short", FONT(FONT_S08), COORD_MX_X+20, COORD_ML_Y+25+15, 0, 8, c_wht);
903 	}
904 
905 	if (rect_intersect_a(ev, COORD_MX_X+50-trw, COORD_ML_Y, trw, 38)) {
906 		DEBUG_DRAW("Max Level");
907 		/* max level background */
908 		CairoSetSouerceRGBA(c_g30);
909 		rounded_rectangle (cr, COORD_MX_X+50-trw, COORD_ML_Y, trw, 38, 10);
910 		cairo_fill (cr);
911 		/* display max level as text */
912 		ui->prev_lvl[1] = rings ? ui->ms: ui->mm;
913 		sprintf(buf, "Max:\n%s %s", format_lufs(lufb0, LUFS(ui->prev_lvl[1])), lufs ? "LUFS" : "LU");
914 		write_text(cr, buf, FONT(FONT_M09), COORD_MX_X+50-10, COORD_ML_Y+5, 0, 7, c_wht);
915 	}
916 
917 	if (dbtp && rect_intersect_a(ev, COORD_TP_X+10, COORD_ML_Y+25, 40, 30)) {
918 		DEBUG_DRAW("dBTP Legend");
919 		/* true-peak legend */
920 		CairoSetSouerceRGBA(c_g20);
921 		rounded_rectangle (cr, COORD_TP_X+10, COORD_ML_Y+25, 40, 30, 10);
922 		cairo_fill (cr);
923 		write_text(cr, "True", FONT(FONT_S08), COORD_TP_X+30, COORD_ML_Y+25+15, 0, 8, c_wht);
924 	}
925 
926 	if (dbtp && rect_intersect_a(ev, COORD_TP_X, COORD_ML_Y, 75, 38)) {
927 		DEBUG_DRAW("dBTP Level");
928 		/* true peak level */
929 		if (ui->tp >= 0.8912f) { // -1dBFS
930 			CairoSetSouerceRGBA(c_prd);
931 		} else {
932 			CairoSetSouerceRGBA(c_g30);
933 		}
934 		rounded_rectangle (cr, COORD_TP_X, COORD_ML_Y, 75, 38, 10);
935 		cairo_fill (cr);
936 
937 		/* true-peak val */
938 		ui->prev_lvl[4] = ui->tp;
939 		sprintf(buf, "%s", format_lufs(lufb0, ui->tp));
940 		write_text(cr, buf, FONT(FONT_M09), COORD_TP_X+65, COORD_ML_Y+5, 0, 7, c_wht);
941 		write_text(cr, "dBTP", FONT(FONT_M09), COORD_TP_X+65, COORD_ML_Y+19, 0, 7, c_wht);
942 	}
943 
944 #if 1
945 	if (!ui->radar_surf || (redraw_part & 2) == 2) {
946 		DEBUG_DRAW("RADAR");
947 		render_radar(ui);
948 		ui->fastradar = -1;
949 		cairo_set_source_surface (cr, ui->radar_surf, CX - RADIUS, CY - RADIUS);
950 		cairo_paint(cr);
951 
952 		if (!robtk_rbtn_get_active(ui->cbx_histogram)) {
953 			draw_radar_ann (cr);
954 		}
955 	}
956 #endif
957 
958 #define MPI108 1.599885148 // M_PI / 2 + M_PI / 108.0
959 #define MPI72 0.043633231  // M_PI / 72
960 #define MPI00 1.541707506  // M_PI / 2 - M_PI / 108.0
961 
962 	if (redraw_part & 1) {
963 		DEBUG_DRAW("CIRC Level");
964 
965 		int cl, cm;
966 		ring_leds(ui, &cl, &cm);
967 		ui->circ_max = cm;
968 		ui->circ_val = cl;
969 
970 		const int ulp = plus24 ? 120 : 108;
971 		if (cl > ulp) cl = ulp;
972 		if (cm > ulp) cm = ulp;
973 
974 		const float ang = MPI108 + MPI72 * (float) MAX(-1, cl);
975 
976 		if (cl >= 0) {
977 			cairo_save (cr);
978 			cairo_move_to(cr, CX, CY);
979 			cairo_arc (cr, CX, CY, RADIUS22, MPI00, ang);
980 			cairo_close_path (cr);
981 			cairo_clip (cr);
982 			if (plus9) {
983 				cairo_set_source (cr, ui->lpattern9);
984 			} else {
985 				cairo_set_source (cr, ui->lpattern18);
986 			}
987 			cairo_mask_surface(cr, ui->level_surf, 0, 0);
988 			cairo_fill (cr);
989 			cairo_restore (cr);
990 		}
991 
992 		if (cl < ulp) {
993 			const float tang = MPI108 + MPI72 * (float) ulp; // TODO + 24
994 			cairo_save (cr);
995 			cairo_move_to(cr, CX, CY);
996 			cairo_arc (cr, CX, CY, RADIUS22, ang, tang);
997 			cairo_close_path (cr);
998 			cairo_clip (cr);
999 			CairoSetSouerceRGBA(c_g30);
1000 			cairo_mask_surface(cr, ui->level_surf, 0, 0);
1001 			cairo_fill (cr);
1002 			cairo_restore (cr);
1003 		}
1004 
1005 		if (cm > 0) {
1006 			cairo_set_line_width(cr, 2.5);
1007 			cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
1008 			const float mang = M_PI * .5 + MPI72 * (float) cm;
1009 			float cc = sinf(mang);
1010 			float sc = cosf(mang);
1011 			cairo_move_to(cr,   CX + RADIUS10 * sc, CY + RADIUS10 * cc);
1012 			radar_color(cr, cm);
1013 			cairo_line_to(cr, CX + RADIUS22 * sc, CY + RADIUS22 * cc);
1014 			cairo_stroke (cr);
1015 		}
1016 
1017 		cairo_set_source_surface(cr, ui->lvl_label, 0, 0);
1018 		cairo_paint(cr);
1019 	}
1020 
1021 	int bottom_max_offset = 50;
1022 
1023 	if (ui->il > -60 || robtk_cbtn_get_active(ui->btn_start)) {
1024 		bottom_max_offset = 3;
1025 	}
1026 	if (rect_intersect_a(ev, COORD_BI_X+10, COORD_BI_Y, 40, 30) && bottom_max_offset == 3) {
1027 		DEBUG_DRAW("Integ. 'Long' Legend");
1028 		CairoSetSouerceRGBA(c_g20);
1029 		rounded_rectangle (cr, COORD_BI_X+10, COORD_BI_Y, 40, 30, 10);
1030 		cairo_fill (cr);
1031 		write_text(cr, "Long", FONT(FONT_S08), COORD_BI_X+30 , COORD_BI_Y+18, 0,  5, c_wht);
1032 	}
1033 	/* integrated level text display */
1034 	if (rect_intersect_a(ev, COORD_BI_X, COORD_BI_Y+20, COORD_BINTG_W, 40) && bottom_max_offset == 3) {
1035 		DEBUG_DRAW("Integrated Level");
1036 		CairoSetSouerceRGBA(c_g30);
1037 		rounded_rectangle (cr, COORD_BI_X, COORD_BI_Y+20, COORD_BINTG_W, 40, 10);
1038 		cairo_fill (cr);
1039 
1040 		if (ui->il > -60) {
1041 			sprintf(buf, "Int:   %s %s", format_lufs(lufb0, LUFS(ui->il)), lufs ? "LUFS" : "LU");
1042 			write_text(cr, buf, FONT(FONT_M09), COORD_BI_X+10 , COORD_BI_Y+40, 0,  6, c_wht);
1043 		} else {
1044 			sprintf(buf, "[Integrating over 5 sec]");
1045 			write_text(cr, buf, FONT(FONT_S09), COORD_BI_X+10 , COORD_BI_Y+40, 0,  6, c_wht);
1046 		}
1047 
1048 		if (ui->rx > -60.0 && ui->rn > -60.0) {
1049 			sprintf(buf, "Range: %s..%s %s (%4.1f)",
1050 					format_lufs(lufb0, LUFS(ui->rn)), format_lufs(lufb1, LUFS(ui->rx)),
1051 					lufs ? "LUFS" : "LU", (ui->rx - ui->rn));
1052 			write_text(cr, buf, FONT(FONT_M09), COORD_BI_X+10 , COORD_BI_Y+55, 0,  6, c_wht);
1053 		} else {
1054 			sprintf(buf, "[Collecting 10 sec range.]");
1055 			write_text(cr, buf, FONT(FONT_S09), COORD_BI_X+10 , COORD_BI_Y+55, 0,  6, c_wht);
1056 		}
1057 
1058 		/* clock */
1059 		if (ui->it < 60) {
1060 			sprintf(buf, "%.1f\"", ui->it);
1061 		} else if (ui->it < 600) {
1062 			int minutes = ui->it / 60;
1063 			int seconds = ((int)floorf(ui->it)) % 60;
1064 			int ds = 10*(ui->it - seconds - 60*minutes);
1065 			sprintf(buf, "%d'%02d\"%d", minutes, seconds, ds);
1066 		} else if (ui->it < 3600) {
1067 			int minutes = ui->it / 60;
1068 			int seconds = ((int)floorf(ui->it)) % 60;
1069 			sprintf(buf, "%d'%02d\"", minutes, seconds);
1070 		} else {
1071 			int hours = ui->it / 3600;
1072 			int minutes = ((int)floorf(ui->it / 60)) % 60;
1073 			sprintf(buf, "%dh%02d'", hours, minutes);
1074 		}
1075 		write_text(cr, buf, FONT(FONT_M12), COORD_BINTG_W-7, COORD_BI_Y+50, 0,  4, c_wht);
1076 	}
1077 
1078 	if (rect_intersect_a(ev, COORD_BR_X+115-trw, COORD_BI_Y-50+bottom_max_offset, trw, 40) && redraw_part != 1) {
1079 		DEBUG_DRAW("Bottom Level");
1080 		/* bottom level text display */
1081 		trw = lufs ? 117 : 105;
1082 		//printf("BOTTOM LVL @ %d+%d %dx%d\n", COORD_BR_X+115-trw, 305+bottom_max_offset, trw, 40);
1083 		CairoSetSouerceRGBA(c_g20);
1084 		rounded_rectangle (cr, COORD_BR_X+64, COORD_BI_Y-50+bottom_max_offset, 40, 30, 10);
1085 		cairo_fill (cr);
1086 		CairoSetSouerceRGBA(c_g30);
1087 		rounded_rectangle (cr, COORD_BR_X+115-trw, COORD_BI_Y-30+bottom_max_offset, trw, 40, 10);
1088 		cairo_fill (cr);
1089 
1090 		ui->prev_lvl[2] = !rings ? ui->ls : ui->lm;
1091 		ui->prev_lvl[3] = !rings ? ui->ms : ui->mm;
1092 		write_text(cr, rings ? "Mom":"Short", FONT(FONT_S08), COORD_BR_X+85, COORD_BI_Y-45+bottom_max_offset, 0, 8, c_wht);
1093 		sprintf(buf, "%s %s", format_lufs(lufb0, LUFS(ui->prev_lvl[2])), lufs ? "LUFS" : "LU");
1094 		write_text(cr, buf, FONT(FONT_M09), COORD_BR_X+105, COORD_BI_Y-25+bottom_max_offset, 0, 7, c_wht);
1095 		sprintf(buf, "Max:%s %s", format_lufs(lufb0, LUFS(ui->prev_lvl[3])), lufs ? "LUFS" : "LU");
1096 		write_text(cr, buf, FONT(FONT_M09), COORD_BR_X+105, COORD_BI_Y-10+bottom_max_offset, 0, 7, c_wht);
1097 	}
1098 
1099 #if 0
1100 	{
1101 		cairo_rectangle (cr, 0, 0, COORD_ALL_W, COORD_ALL_H);
1102 		float c[3];
1103 		c[0] = rand() / (float)RAND_MAX;
1104 		c[1] = rand() / (float)RAND_MAX;
1105 		c[2] = rand() / (float)RAND_MAX;
1106 		cairo_set_source_rgba (cr, c[0], c[1], c[2], 0.1);
1107 		cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height);
1108 		cairo_fill (cr);
1109 	}
1110 #endif
1111 
1112 	DEBUG_DRAW("----END----");
1113 	return TRUE;
1114 }
1115 
1116 /******************************************************************************
1117  * partial exposure
1118  */
1119 
1120 #define INVALIDATE_RECT(XX,YY,WW,HH) queue_draw_area(ui->m0, XX, YY, WW, HH)
1121 #define MIN2(A,B) ( (A) < (B) ? (A) : (B) )
1122 #define MAX2(A,B) ( (A) > (B) ? (A) : (B) )
1123 #define MIN3(A,B,C) (  (A) < (B)  ? MIN2 (A,C) : MIN2 (B,C) )
1124 #define MAX3(A,B,C) (  (A) > (B)  ? MAX2 (A,C) : MAX2 (B,C) )
1125 
invalidate_changed(EBUrUI * ui,int what)1126 static void invalidate_changed(EBUrUI* ui, int what) {
1127 	cairo_rectangle_t rect;
1128 
1129 	if (what == -1) {
1130 		queue_draw(ui->m0);
1131 		ui->fasttracked[0] = ui->fasttracked[1] = ui->fasttracked[2] = ui->fasttracked[3] = ui->fasttracked[4] = true;
1132 		ui->fasthist = true;
1133 		return;
1134 	}
1135 
1136 	if (what == 0 && !ui->fasttracked[0]) {
1137 		// main level display
1138 		const bool rings = robtk_rbtn_get_active(ui->cbx_ring_short);
1139 		const float pl = rings ? ui->ls : ui->lm;
1140 		if (rintf(pl * 10.0f) != rintf(ui->prev_lvl[0] * 10.0f)) {
1141 			ui->fasttracked[0] = true;
1142 			queue_tiny_area(ui->m0, COORD_LEVEL_X, COORD_ML_Y, COORD_LEVEL_W, COORD_LEVEL_H);
1143 		}
1144 	}
1145 
1146 	if (what == 0 && !ui->fasttracked[4] && robtk_cbtn_get_active(ui->cbx_truepeak)) {
1147 		// true peak display
1148 		if (rintf(ui->tp * 10.0f) != rintf(ui->prev_lvl[4] * 10.0f)) {
1149 			ui->fasttracked[4] = true;
1150 			queue_tiny_area(ui->m0, COORD_TP_X, COORD_ML_Y, 75, 38);    // top left side
1151 			//queue_tiny_area(ui->m0, COORD_TP_X+10, COORD_ML_Y+25, 40, 30); // top left side tab
1152 		}
1153 	}
1154 
1155 	if (what == 0 && !ui->fasttracked[1]) {
1156 		// main max display
1157 		const bool rings = robtk_rbtn_get_active(ui->cbx_ring_short);
1158 		const float pl = rings ? ui->ms : ui->mm;
1159 		if (rintf(pl * 10.0f) != rintf(ui->prev_lvl[1] * 10.0f)) {
1160 			ui->fasttracked[1] = true;
1161 			queue_tiny_area(ui->m0, COORD_MX_X-32, COORD_ML_Y, 87, 38);  // top side w/o LU/FS
1162 			//queue_tiny_area(ui->m0, COORD_MX_X, COORD_ML_Y+25, 30, 30);// top side tab
1163 		}
1164 	}
1165 
1166 	if (what == 0) {
1167 		// TODO only when iteg. (time changed) or integ'ed value changed
1168 		if (ui->il > -60 || robtk_cbtn_get_active(ui->btn_start)) {
1169 		 // BIG -- when integrating
1170 			if (!ui->fasttracked[3]) {
1171 				ui->fasttracked[3] = true;
1172 				queue_tiny_area(ui->m0, COORD_BI_X, COORD_BI_Y+20, COORD_BINTG_W, 45);   // bottom bar
1173 			}
1174 		}
1175 
1176 		// bottom lvl+max
1177 		if (!ui->fasttracked[2]) {
1178 			const bool rings = robtk_rbtn_get_active(ui->cbx_ring_short);
1179 			const float pl = !rings ? ui->ls : ui->lm;
1180 			const float pm = !rings ? ui->ms : ui->mm;
1181 			if (rintf(pl * 10.0f) != rintf(ui->prev_lvl[2] * 10.0f)
1182 					|| rintf(pm * 10.0f) != rintf(ui->prev_lvl[3] * 10.0f)) {
1183 				ui->fasttracked[2] = true;
1184 				if (ui->il > -60 || robtk_cbtn_get_active(ui->btn_start)) {
1185 					queue_tiny_area(ui->m0, COORD_BR_X, COORD_BINFO_Y, COORD_BINFO_W, COORD_BINFO_H); // bottom side
1186 				} else {
1187 					queue_tiny_area(ui->m0, COORD_BR_X, COORD_BI_Y+20, 117, 40); // bottom right space w/o tab
1188 				}
1189 			}
1190 		}
1191 	}
1192 
1193 	if ((what & 1) ||
1194 			(robtk_rbtn_get_active(ui->cbx_radar)
1195 			 && ui->radar_pos_cur != ui->radar_pos_disp
1196 			 && ui->fastradar == -1 /* != ui->radar_pos_cur */
1197 			 )
1198 			) {
1199 
1200 		if ((what & 2) == 0 && ui->radar_pos_max > 0) {
1201 			float ang0 = 2.0 * M_PI * (ui->radar_pos_cur - 1) / (float) ui->radar_pos_max;
1202 			int dx0 = rintf(CX + RADIUS1 * cosf(ang0));
1203 			int dy0 = rintf(CY + RADIUS1 * sinf(ang0));
1204 
1205 			float ang1 = 2.0 * M_PI * (ui->radar_pos_cur + 13) / (float) ui->radar_pos_max;
1206 			int dx1 = rint(CX + RADIUS1 * cosf(ang1));
1207 			int dy1 = rint(CY + RADIUS1 * sinf(ang1));
1208 
1209 			rect.x = MIN3(CX, dx0, dx1) -1;
1210 			rect.y = MIN3(CY, dy0, dy1) -1;
1211 
1212 			rect.width  = 2 + MAX3(CX, dx0, dx1) - rect.x;
1213 			rect.height = 2 + MAX3(CY, dy0, dy1) - rect.y;
1214 
1215 			ui->fastradar = ui->radar_pos_cur;
1216 			queue_tiny_area(ui->m0, floorf(rect.x), floorf(rect.y), ceilf(rect.width), ceilf(rect.height));
1217 		} else {
1218 			/// XXX may be ignored IFF coincides with ring-lvl, hence:
1219 			ui->fullhist = true;
1220 			INVALIDATE_RECT(RDR_INV_X, RDR_INV_Y, RDR_INV_W, RDR_INV_H);
1221 		}
1222 	}
1223 
1224 	if (what == 0) {
1225 		int cl, cm;
1226 		ring_leds(ui, &cl, &cm);
1227 
1228 		if (ui->circ_max != cm || ui->circ_val != cl) {
1229 			// TODO invalidate changed parts only
1230 			// -> also update ring-lvl filter in expose_area
1231 			INVALIDATE_RECT(COORD_MTR_X, COORD_MTR_Y, 320, 290); // XXX
1232 		}
1233 	}
1234 }
1235 
invalidate_histogram_line(EBUrUI * ui,int p)1236 static void invalidate_histogram_line(EBUrUI* ui, int p) {
1237 	const bool plus9 = robtk_rbtn_get_active(ui->cbx_sc9);
1238 	cairo_rectangle_t rect;
1239 
1240 	// dup from expose_event()
1241 	int amin, amax;
1242 	if (plus9) {
1243 		amin = 290;
1244 		amax = 560;
1245 	} else {
1246 		amin = 110;
1247 		amax = 650;
1248 	}
1249 	if (p < amin || p > amax) return;
1250 	const double astep = 1.5 * M_PI / (double) (amax - amin);
1251 	const double aoff = (M_PI / 2.0) - amin * astep;
1252 
1253 	float ang0 = (float) (p-1) * astep + aoff;
1254 	float ang1 = (float) (p+1) * astep + aoff;
1255 
1256 	// see also "invalidate changed part of radar only" above
1257 	int dx0 = rintf(CX + RADIUS1 * cosf(ang0));
1258 	int dy0 = rintf(CY + RADIUS1 * sinf(ang0));
1259 	int dx1 = rint(CX + RADIUS1 * cosf(ang1));
1260 	int dy1 = rint(CY + RADIUS1 * sinf(ang1));
1261 
1262 	rect.x = MIN3(CX, dx0, dx1) -1;
1263 	rect.y = MIN3(CY, dy0, dy1) -1;
1264 
1265 	rect.width  = 2 + MAX3(CX, dx0, dx1) - rect.x;
1266 	rect.height = 2 + MAX3(CY, dy0, dy1) - rect.y;
1267 
1268 	//printf("Q HIST: %.1f+%.1f %.1fx%.1f\n", rect.x, rect.y, rect.width, rect.height);
1269 	if (!ui->fasthist) {
1270 		queue_tiny_area(ui->m0, floorf(rect.x), floorf(rect.y), ceilf(rect.width), ceilf(rect.height));
1271 		ui->fasthist = true;
1272 	} else if (!ui->fullhist) {
1273 		ui->fullhist = true;
1274 		INVALIDATE_RECT(43, 68, 245, 245);
1275 		//queue_draw(ui->m0); // trigger radar to be included..
1276 		//queue_draw_area(ui->m0, rect.x, rect.y, rect.width, rect.height);
1277 	}
1278 }
1279 
1280 
1281 /******************************************************************************
1282  * LV2 UI -> plugin communication
1283  */
1284 
forge_message_kv(EBUrUI * ui,LV2_URID uri,int key,float value)1285 static void forge_message_kv(EBUrUI* ui, LV2_URID uri, int key, float value) {
1286 	uint8_t obj_buf[1024];
1287 	if (ui->disable_signals) return;
1288 
1289 	lv2_atom_forge_set_buffer(&ui->forge, obj_buf, 1024);
1290 	LV2_Atom* msg = forge_kvcontrolmessage(&ui->forge, &ui->uris, uri, key, value);
1291 	ui->write(ui->controller, 0, lv2_atom_total_size(msg), ui->uris.atom_eventTransfer, msg);
1292 }
1293 
1294 /******************************************************************************
1295  * UI callbacks
1296  */
1297 
btn_start(RobWidget * w,void * handle)1298 static bool btn_start(RobWidget *w, void* handle) {
1299 	EBUrUI* ui = (EBUrUI*)handle;
1300 	if (robtk_cbtn_get_active(ui->btn_start)) {
1301 		forge_message_kv(ui, ui->uris.mtr_meters_cfg, CTL_START, 0);
1302 	} else {
1303 		forge_message_kv(ui, ui->uris.mtr_meters_cfg, CTL_PAUSE, 0);
1304 	}
1305 	invalidate_changed(ui, -1);
1306 	return TRUE;
1307 }
1308 
btn_reset(RobWidget * w,void * handle)1309 static bool btn_reset(RobWidget *w, void* handle) {
1310 	EBUrUI* ui = (EBUrUI*)handle;
1311 	forge_message_kv(ui, ui->uris.mtr_meters_cfg, CTL_RESET, 0);
1312 	invalidate_changed(ui, -1);
1313 	return TRUE;
1314 }
1315 
cbx_transport(RobWidget * w,void * handle)1316 static bool cbx_transport(RobWidget *w, void* handle) {
1317 	EBUrUI* ui = (EBUrUI*)handle;
1318 	if (robtk_cbtn_get_active(ui->cbx_transport)) {
1319 		robtk_cbtn_set_sensitive(ui->btn_start, false);
1320 		forge_message_kv(ui, ui->uris.mtr_meters_cfg, CTL_TRANSPORTSYNC, 1);
1321 	} else {
1322 		robtk_cbtn_set_sensitive(ui->btn_start, true);
1323 		forge_message_kv(ui, ui->uris.mtr_meters_cfg, CTL_TRANSPORTSYNC, 0);
1324 	}
1325 	return TRUE;
1326 }
1327 
cbx_autoreset(RobWidget * w,void * handle)1328 static bool cbx_autoreset(RobWidget *w, void* handle) {
1329 	EBUrUI* ui = (EBUrUI*)handle;
1330 	if (robtk_cbtn_get_active(ui->cbx_autoreset)) {
1331 		forge_message_kv(ui, ui->uris.mtr_meters_cfg, CTL_AUTORESET, 1);
1332 	} else {
1333 		forge_message_kv(ui, ui->uris.mtr_meters_cfg, CTL_AUTORESET, 0);
1334 	}
1335 	return TRUE;
1336 }
1337 
cbx_lufs(RobWidget * w,void * handle)1338 static bool cbx_lufs(RobWidget *w, void* handle) {
1339 	EBUrUI* ui = (EBUrUI*)handle;
1340 	uint32_t v = 0;
1341 	v |= robtk_rbtn_get_active(ui->cbx_lufs) ? 1 : 0;
1342 	v |= robtk_rbtn_get_active(ui->cbx_sc9) ? 2 : 0;
1343 	v |= robtk_rbtn_get_active(ui->cbx_sc24) ? 32 : 0;
1344 	v |= robtk_rbtn_get_active(ui->cbx_ring_short) ? 4 : 0;
1345 	v |= robtk_rbtn_get_active(ui->cbx_hist_short) ? 8 : 0;
1346 	v |= robtk_rbtn_get_active(ui->cbx_histogram) ? 16 : 0;
1347 	v |= robtk_cbtn_get_active(ui->cbx_truepeak) ? 64 : 0;
1348 	forge_message_kv(ui, ui->uris.mtr_meters_cfg, CTL_UISETTINGS, (float)v);
1349 	ui->redraw_labels = TRUE;
1350 	invalidate_changed(ui, -1);
1351 	return TRUE;
1352 }
1353 
spn_radartime(RobWidget * w,void * handle)1354 static bool spn_radartime(RobWidget *w, void* handle) {
1355 	EBUrUI* ui = (EBUrUI*)handle;
1356 	 float v = robtk_spin_get_value(ui->spn_radartime);
1357 	forge_message_kv(ui, ui->uris.mtr_meters_cfg, CTL_RADARTIME, v);
1358 	return TRUE;
1359 }
1360 
1361 
1362 /******************************************************************************
1363  * widget hackery
1364  */
1365 
1366 static void
size_request(RobWidget * handle,int * w,int * h)1367 size_request(RobWidget* handle, int *w, int *h) {
1368 	*w = COORD_ALL_W;
1369 	*h = COORD_ALL_H;
1370 }
1371 
1372 /******************************************************************************
1373  * LV2 callbacks
1374  */
1375 
ui_enable(LV2UI_Handle handle)1376 static void ui_enable(LV2UI_Handle handle) {
1377 	EBUrUI* ui = (EBUrUI*)handle;
1378 	forge_message_kv(ui, ui->uris.mtr_meters_on, 0, 0); // may be too early
1379 }
1380 
ui_disable(LV2UI_Handle handle)1381 static void ui_disable(LV2UI_Handle handle) {
1382 	EBUrUI* ui = (EBUrUI*)handle;
1383 	forge_message_kv(ui, ui->uris.mtr_meters_off, 0, 0);
1384 }
1385 
1386 static LV2UI_Handle
instantiate(void * const ui_toplevel,const LV2UI_Descriptor * descriptor,const char * plugin_uri,const char * bundle_path,LV2UI_Write_Function write_function,LV2UI_Controller controller,RobWidget ** widget,const LV2_Feature * const * features)1387 instantiate(
1388 		void* const               ui_toplevel,
1389 		const LV2UI_Descriptor*   descriptor,
1390 		const char*               plugin_uri,
1391 		const char*               bundle_path,
1392 		LV2UI_Write_Function      write_function,
1393 		LV2UI_Controller          controller,
1394 		RobWidget**               widget,
1395 		const LV2_Feature* const* features)
1396 {
1397 	EBUrUI* ui = (EBUrUI*)calloc(1,sizeof(EBUrUI));
1398 	ui->write      = write_function;
1399 	ui->controller = controller;
1400 	ui->fastradar = -1;
1401 
1402 	*widget = NULL;
1403 
1404 	for (int i = 0; features[i]; ++i) {
1405 		if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) {
1406 			ui->map = (LV2_URID_Map*)features[i]->data;
1407 		}
1408 	}
1409 
1410 	if (!ui->map) {
1411 		fprintf(stderr, "UI: Host does not support urid:map\n");
1412 		free(ui);
1413 		return NULL;
1414 	}
1415 
1416 	ui->nfo = robtk_info(ui_toplevel);
1417 	map_eburlv2_uris(ui->map, &ui->uris);
1418 
1419 	lv2_atom_forge_init(&ui->forge, ui->map);
1420 
1421 	ui->box = rob_vbox_new(FALSE, 2);
1422 	robwidget_make_toplevel(ui->box, ui_toplevel);
1423 	ROBWIDGET_SETNAME(ui->box, "ebur128");
1424 
1425 	ui->m0 = robwidget_new(ui);
1426 	robwidget_set_alignment(ui->m0, .5, .5);
1427 	robwidget_set_expose_event(ui->m0, expose_event);
1428 	robwidget_set_size_request(ui->m0, size_request);
1429 
1430 	ui->btn_start = robtk_cbtn_new("Integrate", GBT_LED_OFF, false);
1431 	ui->btn_reset = robtk_pbtn_new("Reset");
1432 
1433 	ui->cbx_box = rob_table_new(/*rows*/6, /*cols*/ 5, FALSE);
1434 	ui->cbx_lu         = robtk_rbtn_new("LU", NULL);
1435 	ui->cbx_lufs       = robtk_rbtn_new("LUFS", robtk_rbtn_group(ui->cbx_lu));
1436 
1437 	ui->cbx_ring_mom   = robtk_rbtn_new("Momentary", NULL);
1438 	ui->cbx_ring_short = robtk_rbtn_new("Short", robtk_rbtn_group(ui->cbx_ring_mom));
1439 
1440 	ui->cbx_hist_short = robtk_rbtn_new("Short", NULL);
1441 	ui->cbx_hist_mom   = robtk_rbtn_new("Momentary", robtk_rbtn_group(ui->cbx_hist_short));
1442 
1443 	ui->cbx_sc18       = robtk_rbtn_new("-36..+18LU", NULL);
1444 	ui->cbx_sc9        = robtk_rbtn_new("-18..+9LU", robtk_rbtn_group(ui->cbx_sc18));
1445 	ui->cbx_sc24       = robtk_rbtn_new("-36..+24LU", robtk_rbtn_group(ui->cbx_sc18));
1446 
1447 	ui->cbx_transport  = robtk_cbtn_new("Host Transport", GBT_LED_LEFT, true);
1448 	ui->cbx_autoreset  = robtk_cbtn_new("Reset on Start", GBT_LED_LEFT, true);
1449 	ui->spn_radartime  = robtk_spin_new(30, 600, 15);
1450 	ui->lbl_radarinfo  = robtk_lbl_new("History Length [s]:");
1451 	ui->lbl_ringinfo   = robtk_lbl_new("Level Display");
1452 #ifdef EASTER_EGG
1453 	ui->cbx_truepeak   = robtk_cbtn_new("True-Peak", GBT_LED_LEFT, true);
1454 #else
1455 	ui->cbx_truepeak   = robtk_cbtn_new("Compute True-Peak", GBT_LED_LEFT, true);
1456 #endif
1457 
1458 	ui->sep_h0         = robtk_sep_new(TRUE);
1459 	ui->sep_h1         = robtk_sep_new(TRUE);
1460 	ui->sep_h2         = robtk_sep_new(TRUE);
1461 	ui->sep_v0         = robtk_sep_new(FALSE);
1462 
1463 	ui->cbx_radar      = robtk_rbtn_new("History", NULL);
1464 	ui->cbx_histogram  = robtk_rbtn_new("Histogram", robtk_rbtn_group(ui->cbx_radar));
1465 
1466 	robtk_sep_set_linewidth(ui->sep_h2, 0);
1467 	robtk_lbl_set_alignment(ui->lbl_radarinfo, 0.0f, 0.5f);
1468 	robtk_spin_set_label_pos(ui->spn_radartime, 1);
1469 	robtk_spin_set_alignment(ui->spn_radartime, 1.0f, 0.5f);
1470 	robtk_pbtn_set_alignment(ui->btn_reset, 0.5, 0.5);
1471 	robtk_cbtn_set_alignment(ui->btn_start, 0.5, 0.5);
1472 	robtk_spin_set_default(ui->spn_radartime, 120);
1473 	robtk_spin_set_value(ui->spn_radartime, 120);
1474 	robtk_spin_label_width(ui->spn_radartime, 32.0, -1);
1475 
1476 	int row = 0; // left side
1477 	rob_table_attach((ui->cbx_box), GLB_W(ui->lbl_ringinfo), 0, 2, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1478 	row++;
1479 	rob_table_attach_defaults(ui->cbx_box, robtk_sep_widget(ui->sep_h0), 0, 2, row, row+1);
1480 	row++;
1481 	rob_table_attach(ui->cbx_box, GRB_W(ui->cbx_lu)   , 0, 1, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1482 	rob_table_attach(ui->cbx_box, GRB_W(ui->cbx_lufs) , 1, 2, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1483 	row++;
1484 	rob_table_attach(ui->cbx_box, GRB_W(ui->cbx_sc18) , 0, 1, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1485 	rob_table_attach(ui->cbx_box, GRB_W(ui->cbx_sc9)  , 1, 2, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1486 	row++;
1487 #ifdef EASTER_EGG
1488 	rob_table_attach(ui->cbx_box, GRB_W(ui->cbx_sc24)      , 1, 2, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1489 	rob_table_attach(ui->cbx_box, GBT_W(ui->cbx_truepeak)  , 0, 1, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1490 #else
1491 	rob_table_attach(ui->cbx_box, GBT_W(ui->cbx_truepeak)  , 0, 2, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1492 #endif
1493 	row++;
1494 	rob_table_attach(ui->cbx_box, GRB_W(ui->cbx_ring_mom)  , 0, 1, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1495 	rob_table_attach(ui->cbx_box, GRB_W(ui->cbx_ring_short), 1, 2, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1496 
1497 	rob_table_attach_defaults(ui->cbx_box, robtk_sep_widget(ui->sep_v0), 2, 3, 0, 6);
1498 
1499 	row = 0; // right side
1500 	rob_table_attach(ui->cbx_box, GRB_W(ui->cbx_histogram) , 3, 4, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1501 	rob_table_attach(ui->cbx_box, GRB_W(ui->cbx_radar)     , 4, 5, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1502 	row++;
1503 	rob_table_attach_defaults(ui->cbx_box, robtk_sep_widget(ui->sep_h1), 3, 5, row, row+1);
1504 	row++;
1505 	rob_table_attach(ui->cbx_box, GLB_W(ui->lbl_radarinfo), 3, 4, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1506 	rob_table_attach(ui->cbx_box, GSP_W(ui->spn_radartime), 4, 5, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1507 	row++;
1508 	rob_table_attach(ui->cbx_box, GBT_W(ui->cbx_autoreset), 3, 4, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1509 	rob_table_attach(ui->cbx_box, GPB_W(ui->btn_reset), 4, 5, row, row+1, 0, 0, RTK_FILL, RTK_SHRINK);
1510 	row++;
1511 	rob_table_attach(ui->cbx_box, GBT_W(ui->cbx_transport), 3, 4, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1512 	rob_table_attach(ui->cbx_box, GBT_W(ui->btn_start), 4, 5, row, row+1, 0, 0, RTK_FILL, RTK_SHRINK);
1513 	row++;
1514 	rob_table_attach(ui->cbx_box, GRB_W(ui->cbx_hist_mom)  , 3, 4, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1515 	rob_table_attach(ui->cbx_box, GRB_W(ui->cbx_hist_short), 4, 5, row, row+1, 0, 0, RTK_EXANDF, RTK_SHRINK);
1516 
1517 
1518 	/* global packing */
1519 	rob_vbox_child_pack(ui->box, ui->m0, FALSE, FALSE);
1520 	rob_vbox_child_pack(ui->box, ui->cbx_box, FALSE, FALSE);
1521 	rob_vbox_child_pack(ui->box, robtk_sep_widget(ui->sep_h2), TRUE, FALSE);
1522 
1523 	/* signals */
1524 	robtk_cbtn_set_callback(ui->btn_start, btn_start, ui);
1525 	robtk_pbtn_set_callback_up(ui->btn_reset, btn_reset, ui);
1526 
1527 	robtk_spin_set_callback(ui->spn_radartime, spn_radartime, ui);
1528 
1529 	robtk_rbtn_set_callback(ui->cbx_lufs, cbx_lufs, ui);
1530 	robtk_rbtn_set_callback(ui->cbx_sc18, cbx_lufs, ui);
1531 
1532 	robtk_rbtn_set_callback(ui->cbx_hist_short, cbx_lufs, ui);
1533 	robtk_rbtn_set_callback(ui->cbx_ring_short, cbx_lufs, ui);
1534 	robtk_rbtn_set_callback(ui->cbx_histogram, cbx_lufs, ui);
1535 	robtk_cbtn_set_callback(ui->cbx_truepeak, cbx_lufs, ui);
1536 
1537 	robtk_cbtn_set_callback(ui->cbx_transport, cbx_transport, ui);
1538 	robtk_cbtn_set_callback(ui->cbx_autoreset, cbx_autoreset, ui);
1539 
1540 	*widget = ui->box;
1541 
1542 	initialize_font_cache(ui);
1543 	ui->redraw_labels = TRUE;
1544 
1545 	ui_enable(ui);
1546 	return ui;
1547 }
1548 
1549 static enum LVGLResize
plugin_scale_mode(LV2UI_Handle handle)1550 plugin_scale_mode(LV2UI_Handle handle)
1551 {
1552 	return LVGL_LAYOUT_TO_FIT;
1553 }
1554 
1555 static void
cleanup(LV2UI_Handle handle)1556 cleanup(LV2UI_Handle handle)
1557 {
1558 	EBUrUI* ui = (EBUrUI*)handle;
1559 	ui_disable(handle);
1560 	if (ui->cpattern) {
1561 		cairo_pattern_destroy (ui->cpattern);
1562 	}
1563 	if (ui->lpattern9) {
1564 		cairo_pattern_destroy (ui->lpattern9);
1565 	}
1566 	if (ui->lpattern18) {
1567 		cairo_pattern_destroy (ui->lpattern18);
1568 	}
1569 	if (ui->hpattern9) {
1570 		cairo_pattern_destroy (ui->hpattern9);
1571 	}
1572 	if (ui->hpattern18) {
1573 		cairo_pattern_destroy (ui->hpattern18);
1574 	}
1575 	if (ui->level_surf) {
1576 		cairo_surface_destroy(ui->level_surf);
1577 	}
1578 	if (ui->radar_surf) {
1579 		cairo_surface_destroy(ui->radar_surf);
1580 	}
1581 	if (ui->lvl_label) {
1582 		cairo_surface_destroy(ui->lvl_label);
1583 	}
1584 	if (ui->hist_label) {
1585 		cairo_surface_destroy(ui->hist_label);
1586 	}
1587 
1588 	if (ui->fontcache) {
1589 		for (int i=0; i < 6; ++i) {
1590 			pango_font_description_free(ui->font[i]);
1591 		}
1592 	}
1593 
1594 	free(ui->radarS);
1595 	free(ui->radarM);
1596 
1597 	robtk_rbtn_destroy(ui->cbx_lufs);
1598 	robtk_rbtn_destroy(ui->cbx_lu);
1599 	robtk_rbtn_destroy(ui->cbx_sc9);
1600 	robtk_rbtn_destroy(ui->cbx_sc18);
1601 	robtk_rbtn_destroy(ui->cbx_sc24);
1602 	robtk_rbtn_destroy(ui->cbx_ring_short);
1603 	robtk_rbtn_destroy(ui->cbx_ring_mom);
1604 	robtk_rbtn_destroy(ui->cbx_hist_short);
1605 	robtk_rbtn_destroy(ui->cbx_hist_mom);
1606 	robtk_cbtn_destroy(ui->cbx_transport);
1607 	robtk_cbtn_destroy(ui->cbx_autoreset);
1608 	robtk_cbtn_destroy(ui->cbx_truepeak);
1609 	robtk_spin_destroy(ui->spn_radartime);
1610 	robtk_cbtn_destroy(ui->btn_start);
1611 	robtk_pbtn_destroy(ui->btn_reset);
1612 	robtk_lbl_destroy(ui->lbl_ringinfo);
1613 	robtk_lbl_destroy(ui->lbl_radarinfo);
1614 	robtk_sep_destroy(ui->sep_h0);
1615 	robtk_sep_destroy(ui->sep_h1);
1616 	robtk_sep_destroy(ui->sep_h2);
1617 	robtk_sep_destroy(ui->sep_v0);
1618 	robtk_rbtn_destroy(ui->cbx_radar);
1619 	robtk_rbtn_destroy(ui->cbx_histogram);
1620 
1621 	robwidget_destroy(ui->m0);
1622 	rob_table_destroy(ui->cbx_box);
1623 	rob_box_destroy(ui->box);
1624 	free(ui);
1625 }
1626 
1627 static const void*
extension_data(const char * uri)1628 extension_data(const char* uri)
1629 {
1630 	return NULL;
1631 }
1632 /******************************************************************************
1633  * handle data from backend
1634  */
1635 
1636 #define PARSE_CHANGED_FLOAT(var, dest) \
1637 	if (var && var->type == uris->atom_Float) { \
1638 		float val = ((LV2_Atom_Float*)var)->body; \
1639 		if (val != dest) { \
1640 			dest = val; \
1641 			changed = true; \
1642 		} \
1643 	}
1644 
1645 #define PARSE_A_FLOAT(var, dest) \
1646 	if (var && var->type == uris->atom_Float) { \
1647 		dest = ((LV2_Atom_Float*)var)->body; \
1648 	}
1649 
1650 #define PARSE_A_INT(var, dest) \
1651 	if (var && var->type == uris->atom_Int) { \
1652 		dest = ((LV2_Atom_Int*)var)->body; \
1653 	}
1654 
parse_ebulevels(EBUrUI * ui,const LV2_Atom_Object * obj)1655 static bool parse_ebulevels(EBUrUI* ui, const LV2_Atom_Object* obj) {
1656 	const EBULV2URIs* uris = &ui->uris;
1657 	bool changed = false;
1658 	LV2_Atom *lm = NULL;
1659 	LV2_Atom *mm = NULL;
1660 	LV2_Atom *ls = NULL;
1661 	LV2_Atom *ms = NULL;
1662 	LV2_Atom *il = NULL;
1663 	LV2_Atom *rn = NULL;
1664 	LV2_Atom *rx = NULL;
1665 	LV2_Atom *ii = NULL;
1666 	LV2_Atom *it = NULL;
1667 	LV2_Atom *tp = NULL;
1668 
1669 	lv2_atom_object_get(obj,
1670 			uris->ebu_loudnessM, &lm,
1671 			uris->ebu_maxloudnM, &mm,
1672 			uris->ebu_loudnessS, &ls,
1673 			uris->ebu_maxloudnS, &ms,
1674 			uris->ebu_integrated, &il,
1675 			uris->ebu_range_min, &rn,
1676 			uris->ebu_range_max, &rx,
1677 			uris->mtr_truepeak, &tp,
1678 			uris->ebu_integrating, &ii,
1679 			uris->ebu_integr_time, &it,
1680 			NULL
1681 			);
1682 
1683 
1684 	const float old_time = ui->it;
1685 	PARSE_CHANGED_FLOAT(it, ui->it)
1686 
1687 	if (old_time < ui->it && ui->it - old_time < .2) {
1688 		ui->it = old_time;
1689 		changed = false;
1690 	}
1691 
1692 	PARSE_CHANGED_FLOAT(lm, ui->lm)
1693 	PARSE_CHANGED_FLOAT(mm, ui->mm)
1694 	PARSE_CHANGED_FLOAT(ls, ui->ls)
1695 	PARSE_CHANGED_FLOAT(ms, ui->ms)
1696 	PARSE_CHANGED_FLOAT(il, ui->il)
1697 	PARSE_CHANGED_FLOAT(rn, ui->rn)
1698 	PARSE_CHANGED_FLOAT(rx, ui->rx)
1699 	PARSE_CHANGED_FLOAT(tp, ui->tp)
1700 
1701 	if (ii && ii->type == uris->atom_Bool) {
1702 		bool ix = ((LV2_Atom_Bool*)ii)->body;
1703 	  bool bx = robtk_cbtn_get_active(ui->btn_start);
1704 		if (ix != bx) {
1705 			changed = true;
1706 			ui->disable_signals = true;
1707 			robtk_cbtn_set_active(ui->btn_start, ix);
1708 			ui->disable_signals = false;
1709 		}
1710 	}
1711 	return changed;
1712 }
1713 
parse_radarinfo(EBUrUI * ui,const LV2_Atom_Object * obj)1714 static void parse_radarinfo(EBUrUI* ui, const LV2_Atom_Object* obj) {
1715 	const EBULV2URIs* uris = &ui->uris;
1716 
1717 	LV2_Atom *lm = NULL;
1718 	LV2_Atom *ls = NULL;
1719 	LV2_Atom *pp = NULL;
1720 	LV2_Atom *pc = NULL;
1721 	LV2_Atom *pm = NULL;
1722 
1723 	float xlm, xls;
1724 	int p,c,m;
1725 
1726 	xlm = xls = -INFINITY;
1727 	p=c=m=-1;
1728 
1729 	lv2_atom_object_get(obj,
1730 			uris->ebu_loudnessM, &lm,
1731 			uris->ebu_loudnessS, &ls,
1732 			uris->rdr_pointpos, &pp,
1733 			uris->rdr_pos_cur, &pc,
1734 			uris->rdr_pos_max, &pm,
1735 			NULL
1736 			);
1737 
1738 	PARSE_A_FLOAT(lm, xlm)
1739 	PARSE_A_FLOAT(ls, xls)
1740 	PARSE_A_INT(pp, p);
1741 	PARSE_A_INT(pc, c);
1742 	PARSE_A_INT(pm, m);
1743 
1744 	if (m < 1 || c < 0 || p < 0) return;
1745 
1746 	if (m != ui->radar_pos_max) {
1747 		ui->radarS = (float*) realloc((void*) ui->radarS, sizeof(float) * m);
1748 		ui->radarM = (float*) realloc((void*) ui->radarM, sizeof(float) * m);
1749 		ui->radar_pos_max = m;
1750 		for (int i=0; i < ui->radar_pos_max; ++i) {
1751 			ui->radarS[i] = -INFINITY;
1752 			ui->radarM[i] = -INFINITY;
1753 		}
1754 	}
1755 	ui->radarM[p] = xlm;
1756 	ui->radarS[p] = xls;
1757 	ui->radar_pos_cur = c;
1758 }
1759 
parse_histogram(EBUrUI * ui,const LV2_Atom_Object * obj)1760 static void parse_histogram(EBUrUI* ui, const LV2_Atom_Object* obj) {
1761 	const EBULV2URIs* uris = &ui->uris;
1762 	LV2_Atom *lm = NULL;
1763 	LV2_Atom *ls = NULL;
1764 	LV2_Atom *pp = NULL;
1765 
1766 	int p = -1;
1767 
1768 	lv2_atom_object_get(obj,
1769 			uris->ebu_loudnessM, &lm,
1770 			uris->ebu_loudnessS, &ls,
1771 			uris->rdr_pointpos, &pp,
1772 			NULL
1773 			);
1774 
1775 	PARSE_A_INT(pp, p);
1776 	if (p < 0 || p > HIST_LEN) return;
1777 	const int oldM = ui->histM[p];
1778 	const int oldS = ui->histS[p];
1779 	PARSE_A_INT(lm, ui->histM[p]);
1780 	PARSE_A_INT(ls, ui->histS[p]);
1781 
1782 	if (robtk_rbtn_get_active(ui->cbx_histogram)) {
1783 		const bool hists = robtk_rbtn_get_active(ui->cbx_hist_short);
1784 		if ((hists && oldS != ui->histS[p]) || (!hists && oldM != ui->histM[p])) {
1785 			invalidate_histogram_line(ui, p);
1786 		}
1787 	}
1788 }
1789 
1790 
1791 static void
port_event(LV2UI_Handle handle,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)1792 port_event(LV2UI_Handle handle,
1793            uint32_t     port_index,
1794            uint32_t     buffer_size,
1795            uint32_t     format,
1796            const void*  buffer)
1797 {
1798 	EBUrUI* ui = (EBUrUI*)handle;
1799 	const EBULV2URIs* uris = &ui->uris;
1800 
1801 	if (format == uris->atom_eventTransfer) {
1802 		LV2_Atom* atom = (LV2_Atom*)buffer;
1803 
1804 		if (atom->type == uris->atom_Blank || atom->type == uris->atom_Object) {
1805 			LV2_Atom_Object* obj = (LV2_Atom_Object*)atom;
1806 			if (obj->body.otype == uris->mtr_ebulevels) {
1807 				if (parse_ebulevels(ui, obj)) {
1808 					invalidate_changed(ui, 0);
1809 				}
1810 			} else if (obj->body.otype == uris->mtr_control) {
1811 				int k; float v;
1812 				get_cc_key_value(&ui->uris, obj, &k, &v);
1813 				if (k == CTL_LV2_FTM) {
1814 					int vv = v;
1815 					ui->disable_signals = true;
1816 					robtk_cbtn_set_active(ui->cbx_autoreset, (vv&2)==2);
1817 					robtk_cbtn_set_active(ui->cbx_transport, (vv&1)==1);
1818 					ui->disable_signals = false;
1819 				} else if (k == CTL_LV2_RADARTIME) {
1820 					ui->disable_signals = true;
1821 					robtk_spin_set_value(ui->spn_radartime, v);
1822 					ui->disable_signals = false;
1823 				} else if (k == CTL_LV2_RESETRADAR) {
1824 					ui->radar_pos_cur = 0;
1825 					ui->fastradar = -1;
1826 					ui->tp = 0;
1827 					for (int i=0; i < ui->radar_pos_max; ++i) {
1828 						ui->radarS[i] = -INFINITY;
1829 						ui->radarM[i] = -INFINITY;
1830 					}
1831 					ui->histLenM = 0;
1832 					ui->histLenS = 0;
1833 					for (int i=0; i < HIST_LEN; ++i) {
1834 						ui->histM[i] = 0;
1835 						ui->histS[i] = 0;
1836 					}
1837 					invalidate_changed(ui, -1);
1838 				} else if (k == CTL_LV2_RESYNCDONE) {
1839 					invalidate_changed(ui, -1);
1840 				} else if (k == CTL_UISETTINGS) {
1841 					uint32_t vv = v;
1842 					ui->disable_signals = true;
1843 					if ((vv & 1)) {
1844 						robtk_rbtn_set_active(ui->cbx_lufs, true);
1845 					} else {
1846 						robtk_rbtn_set_active(ui->cbx_lu, true);
1847 					}
1848 					if ((vv & 2)) {
1849 						robtk_rbtn_set_active(ui->cbx_sc9, true);
1850 					} else {
1851 #ifdef EASTER_EGG
1852 						if ((vv & 32)) {
1853 							robtk_rbtn_set_active(ui->cbx_sc24, true);
1854 						} else {
1855 							robtk_rbtn_set_active(ui->cbx_sc18, true);
1856 						}
1857 #else
1858 						robtk_rbtn_set_active(ui->cbx_sc18, true);
1859 #endif
1860 					}
1861 					if ((vv & 4)) {
1862 						robtk_rbtn_set_active(ui->cbx_ring_short, true);
1863 					} else {
1864 						robtk_rbtn_set_active(ui->cbx_ring_mom, true);
1865 					}
1866 					if ((vv & 8)) {
1867 						robtk_rbtn_set_active(ui->cbx_hist_short, true);
1868 					} else {
1869 						robtk_rbtn_set_active(ui->cbx_hist_mom, true);
1870 					}
1871 					if ((vv & 16)) {
1872 						robtk_rbtn_set_active(ui->cbx_histogram, true);
1873 					} else {
1874 						robtk_rbtn_set_active(ui->cbx_radar, true);
1875 					}
1876 					robtk_cbtn_set_active(ui->cbx_truepeak, (vv & 64) ? true: false);
1877 					ui->disable_signals = false;
1878 				}
1879 			} else if (obj->body.otype == uris->rdr_radarpoint) {
1880 				parse_radarinfo(ui, obj);
1881 				if (robtk_rbtn_get_active(ui->cbx_radar)) {
1882 					invalidate_changed(ui, 4);
1883 				}
1884 			} else if (obj->body.otype == uris->rdr_histpoint) {
1885 				parse_histogram(ui, obj);
1886 			} else if (obj->body.otype == uris->rdr_histogram) {
1887 				LV2_Atom *lm = NULL;
1888 				LV2_Atom *ls = NULL;
1889 				lv2_atom_object_get(obj,
1890 						uris->ebu_loudnessM, &lm,
1891 						uris->ebu_loudnessS, &ls,
1892 				NULL
1893 				);
1894 				PARSE_A_INT(lm, ui->histLenM);
1895 				PARSE_A_INT(ls, ui->histLenS);
1896 				if (robtk_rbtn_get_active(ui->cbx_histogram)) {
1897 					invalidate_changed(ui, 3);
1898 				}
1899 			} else {
1900 				fprintf(stderr, "UI: Unknown control message.\n");
1901 			}
1902 		} else {
1903 			fprintf(stderr, "UI: Unknown message type.\n");
1904 		}
1905 	}
1906 }
1907