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