1 /* spectrum analyzer LV2 GUI
2  *
3  * Copyright 2012-2015 Robin Gareus <robin@gareus.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software Foundation,
17  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19 
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <math.h>
23 #include <string.h>
24 #include <assert.h>
25 
26 #define RTK_URI "http://gareus.org/oss/lv2/meters#"
27 #define RTK_GUI "dpmui"
28 #define MTR_URI RTK_URI
29 
30 #define LVGL_RESIZEABLE
31 
32 #define GM_TOP    (ui->display_freq ?  4.5f : 12.5 + GM_PEAK)
33 #define GM_LEFT   (ui->gm_left)
34 #define GM_GIRTH  (ui->gm_girth)
35 #define GM_WIDTH  (ui->gm_width)
36 
37 #define GM_MINH   (396.0f)
38 
39 //#define GM_HEIGHT (ui->display_freq ? 400.0f:460.0f)
40 #define GM_HEIGHT (ui->height)
41 #define GM_SCALE  (GM_TXT - GM_TOP - (ui->display_freq ? 8.5f : 12.5))
42 #define GM_PEAK   ceil(8 + 9.f * ui->scale)
43 #define FQ_ANN    ceil(51.f * ui->scale)
44 #define FQ_WIDTH  (13 + FQ_ANN)
45 #define AN_HEIGHT ceil(46.f * ui->scale)
46 #define AN_WIDTH  ceil(32.f * ui->scale)
47 #define GM_TXT    (GM_HEIGHT - (ui->display_freq ? FQ_ANN : GM_PEAK))
48 
49 #define MA_WIDTH  ceil(30.0f * ui->scale)
50 
51 #define MAX_CAIRO_PATH 32
52 #define MAX_METERS 31
53 
54 #define	TOF ((GM_TOP           ) / GM_HEIGHT)
55 #define	BOF ((GM_TOP + GM_SCALE) / GM_HEIGHT)
56 #define	YVAL(x) ((GM_TOP + GM_SCALE - (x)) / GM_HEIGHT)
57 #define	YPOS(x) (GM_TOP + GM_SCALE - (x))
58 
59 #define UINT_TO_RGB(u,r,g,b) { (*(r)) = ((u)>>16)&0xff; (*(g)) = ((u)>>8)&0xff; (*(b)) = (u)&0xff; }
60 #define UINT_TO_RGBA(u,r,g,b,a) { UINT_TO_RGB(((u)>>8),r,g,b); (*(a)) = (u)&0xff; }
61 
62 /* val: .05 .. 15 1/s  <> 0..100 */
63 #define RESPSCALE(X) ((X) > 0.05 ? rint(400.0 * (1.3f + log10f(X)) )/ 10.0 : 0)
64 #define INV_RESPSCALE(X) powf(10, (X) * .025f - 1.3f)
65 
66 static const char *freq_table [] = {
67 	  " 25 Hz", "31.5 Hz",  " 40 Hz",
68 	  " 50 Hz",  " 63 Hz",  " 80 Hz",
69 	  "100 Hz",  "125 Hz",  "160 Hz",
70 	  "200 Hz",  "250 Hz",  "315 Hz",
71 	  "400 Hz",  "500 Hz",  "630 Hz",
72 	  "800 Hz",  " 1 kHz", "1250 Hz",
73 	 "1.6 kHz",  " 2 kHz", "2.5 kHz",
74 	 "3150 Hz",  " 4 kHz",  " 5 kHz",
75 	 "6.3 kHz",  " 8 kHz",  "10 kHz",
76 	"12.5 kHz",  "16 kHz",  "20 kHz"
77 };
78 
79 typedef struct {
80 	RobWidget *rw;
81 
82 	LV2UI_Write_Function write;
83 	LV2UI_Controller     controller;
84 
85 	RobWidget* m_box;
86 	RobWidget* c_box;
87 	RobWidget* m0;
88 	RobTkScale* fader;
89 	RobTkLbl* lbl_speed;
90 	RobTkCBtn* btn_peaks;
91 	RobTkDial* spn_speed;
92 	RobTkSep* sep_h0;
93 
94 	cairo_surface_t* sf[MAX_METERS];
95 	cairo_surface_t* an[MAX_METERS];
96 	cairo_surface_t* ma[2];
97 	cairo_surface_t* dial;
98 	cairo_pattern_t* mpat;
99 	PangoFontDescription *font[4];
100 
101 	float val[MAX_METERS];
102 	int   val_def[MAX_METERS];
103 	int   val_vis[MAX_METERS];
104 
105 	float peak_val[MAX_METERS];
106 	int   peak_def[MAX_METERS];
107 	int   peak_vis[MAX_METERS];
108 
109 	bool disable_signals;
110 	float gain;
111 	uint32_t num_meters;
112 	bool display_freq;
113 	bool reset_toggle;
114 	int  initialize;
115 	bool metrics_changed;
116 	bool size_changed;
117 	bool show_peaks;
118 	bool show_peaks_changed;
119 	uint32_t misc_state;
120 
121 	float cache_sf;
122 	float cache_ma;
123 	int highlight;
124 
125 	float gm_width;
126 	float gm_girth;
127 	float gm_left;
128 	int min_width;
129 	int cur_width;
130 
131 	int width;
132 	int height;
133 
134 	float _min_w;
135 	float _min_h;
136 
137 	float c_txt[4];
138 	float c_bgr[4];
139 	float scale;
140 
141 } SAUI;
142 
143 static void invalidate_meter(SAUI* ui, int mtr, float val, float peak);
144 
145 /******************************************************************************
146  * meter deflection
147  */
148 
149 static inline float
log_meter(float db)150 log_meter (float db)
151 {
152          float def = 0.0f; /* Meter deflection %age */
153 
154          if (db < -70.0f) {
155                  def = 0.0f;
156          } else if (db < -60.0f) {
157                  def = (db + 70.0f) * 0.25f;
158          } else if (db < -50.0f) {
159                  def = (db + 60.0f) * 0.5f + 2.5f;
160          } else if (db < -40.0f) {
161                  def = (db + 50.0f) * 0.75f + 7.5f;
162          } else if (db < -30.0f) {
163                  def = (db + 40.0f) * 1.5f + 15.0f;
164          } else if (db < -20.0f) {
165                  def = (db + 30.0f) * 2.0f + 30.0f;
166          } else if (db < 6.0f) {
167                  def = (db + 20.0f) * 2.5f + 50.0f;
168          } else {
169 		 def = 115.0f;
170 	 }
171 
172 	 /* 115 is the deflection %age that would be
173 	    when db=6.0. this is an arbitrary
174 	    endpoint for our scaling.
175 	 */
176 
177   return (def/115.0f);
178 }
179 
deflect(SAUI * ui,float val)180 static int deflect(SAUI* ui, float val) {
181 	int lvl = rint(GM_SCALE * log_meter(val));
182 	if (lvl < 2) lvl = 2;
183 	if (lvl >= GM_SCALE) lvl = GM_SCALE;
184 	return lvl;
185 }
186 
187 /******************************************************************************
188  * Drawing
189  */
190 
191 static void render_meter(SAUI*, int, int, int, int, int);
192 
193 enum {
194 	FONT_S06 = 0,
195 	FONT_S08,
196 	FONT_M07,
197 	FONT_M08
198 };
199 
reinitialize_font_cache(SAUI * ui)200 static void reinitialize_font_cache(SAUI* ui) {
201 	char fontname[24];
202 	for (int i=0; i < 4; ++i) {
203 		if (ui->font[i]) {
204 			pango_font_description_free(ui->font[i]);
205 		}
206 	}
207 
208 	sprintf (fontname, "Sans %dpx", (int)floor(10 * ui->scale));
209 	ui->font[FONT_S08] = pango_font_description_from_string(fontname);
210 	sprintf (fontname, "Sans %dpx", (int)floor(8 * ui->scale));
211 	ui->font[FONT_S06] = pango_font_description_from_string(fontname);
212 	sprintf (fontname, "Mono %dpx", (int)floor(9 * ui->scale));
213 	ui->font[FONT_M07] = pango_font_description_from_string(fontname);
214 	sprintf (fontname, "Mono %dpx", (int)floor(10 * ui->scale));
215 	ui->font[FONT_M08] = pango_font_description_from_string(fontname);
216 }
217 
218 
create_meter_pattern(SAUI * ui)219 static void create_meter_pattern(SAUI* ui) {
220 	const int width = GM_WIDTH;
221 	const int height = GM_HEIGHT;
222 
223 	if (ui->mpat) cairo_pattern_destroy(ui->mpat);
224 	int clr[12];
225 	float stp[5];
226 
227 	stp[4] = deflect(ui,  0);
228 	stp[3] = deflect(ui, -3);
229 	stp[2] = deflect(ui, -9);
230 	stp[1] = deflect(ui,-18);
231 	stp[0] = deflect(ui,-40);
232 
233 	if (ui->display_freq) {
234 		clr[ 0]=0x004488ff; clr[ 1]=0x1188bbff;
235 		clr[ 2]=0x228888ff; clr[ 3]=0x00bb00ff;
236 	} else {
237 		clr[ 0]=0x008844ff; clr[ 1]=0x009922ff;
238 		clr[ 2]=0x00aa00ff; clr[ 3]=0x00bb00ff;
239 	}
240 	clr[ 4]=0x00ff00ff; clr[ 5]=0x00ff00ff;
241 	clr[ 6]=0xfff000ff; clr[ 7]=0xfff000ff;
242 	clr[ 8]=0xff8000ff; clr[ 9]=0xff8000ff;
243 	clr[10]=0xff0000ff; clr[11]=0xff0000ff;
244 
245 	guint8 r,g,b,a;
246 	const double onep  =  1.0 / (double) GM_SCALE;
247 	const double softT =  2.0 / (double) GM_SCALE;
248 	const double softB =  2.0 / (double) GM_SCALE;
249 
250 	cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
251 
252 	cairo_pattern_add_color_stop_rgb (pat,  .0, .0 , .0, .0);
253 	cairo_pattern_add_color_stop_rgb (pat, TOF - onep,      .0 , .0, .0);
254 	cairo_pattern_add_color_stop_rgb (pat, TOF, .5 , .5, .5);
255 
256 	// top/clip
257 	UINT_TO_RGBA (clr[11], &r, &g, &b, &a);
258 	cairo_pattern_add_color_stop_rgb (pat, TOF + onep,
259 	                                  r/255.0, g/255.0, b/255.0);
260 
261 	// -0dB
262 	UINT_TO_RGBA (clr[10], &r, &g, &b, &a);
263 	cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[4]) - softT,
264 	                                  r/255.0, g/255.0, b/255.0);
265 	UINT_TO_RGBA (clr[9], &r, &g, &b, &a);
266 	cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[4]) + softB,
267 	                                  r/255.0, g/255.0, b/255.0);
268 
269 	// -3dB || -2dB
270 	UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
271 	cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[3]) - softT,
272 	                                  r/255.0, g/255.0, b/255.0);
273 	UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
274 	cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[3]) + softB,
275 	                                  r/255.0, g/255.0, b/255.0);
276 
277 	// -9dB
278 	UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
279 	cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[2]) - softT,
280 	                                  r/255.0, g/255.0, b/255.0);
281 	UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
282 	cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[2]) + softB,
283 	                                  r/255.0, g/255.0, b/255.0);
284 
285 	// -18dB
286 	UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
287 	cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[1]) - softT,
288 	                                  r/255.0, g/255.0, b/255.0);
289 	UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
290 	cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[1]) + softB,
291 	                                  r/255.0, g/255.0, b/255.0);
292 
293 	// -40dB
294 	UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
295 	cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[0]) - softT,
296 	                                  r/255.0, g/255.0, b/255.0);
297 	UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
298 	cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[0]) + softB,
299 	                                  r/255.0, g/255.0, b/255.0);
300 
301 	// -inf
302 	UINT_TO_RGBA (clr[0], &r, &g, &b, &a);
303 	cairo_pattern_add_color_stop_rgb (pat, BOF - 4 * onep - softT,
304 	                                  r/255.0, g/255.0, b/255.0);
305 
306 	//Bottom
307 	cairo_pattern_add_color_stop_rgb (pat, BOF, .1 , .1, .1);
308 	cairo_pattern_add_color_stop_rgb (pat, BOF + onep, .0 , .0, .0);
309 	cairo_pattern_add_color_stop_rgb (pat, 1.0, .0 , .0, .0);
310 
311 	if (!getenv("NO_METER_SHADE") || strlen(getenv("NO_METER_SHADE")) == 0) {
312 		cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
313 		cairo_pattern_add_color_stop_rgba (shade_pattern,  .0, 0.0, 0.0, 0.0, 0.90);
314 		cairo_pattern_add_color_stop_rgba (shade_pattern, (GM_LEFT-1.0) / GM_WIDTH,   0.0, 0.0, 0.0, 0.55);
315 		cairo_pattern_add_color_stop_rgba (shade_pattern, (GM_LEFT + GM_GIRTH * .35) / GM_WIDTH, 1.0, 1.0, 1.0, 0.12);
316 		cairo_pattern_add_color_stop_rgba (shade_pattern, (GM_LEFT + GM_GIRTH * .53) / GM_WIDTH, 0.0, 0.0, 0.0, 0.05);
317 		cairo_pattern_add_color_stop_rgba (shade_pattern, (GM_LEFT+1.0+GM_GIRTH) / GM_WIDTH,  0.0, 0.0, 0.0, 0.55);
318 		cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 0.0, 0.0, 0.0, 0.90);
319 
320 		cairo_surface_t* surface;
321 		cairo_t* tc = 0;
322 		surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
323 		tc = cairo_create (surface);
324 		cairo_set_source (tc, pat);
325 		cairo_rectangle (tc, 0, 0, width, height);
326 		cairo_fill (tc);
327 		cairo_pattern_destroy (pat);
328 
329 		cairo_set_source (tc, shade_pattern);
330 		cairo_rectangle (tc, 0, 0, width, height);
331 		cairo_fill (tc);
332 		cairo_pattern_destroy (shade_pattern);
333 
334 		// LED stripes
335 		cairo_save (tc);
336 		cairo_set_line_width(tc, 1.0);
337 		cairo_set_source_rgba(tc, .0, .0, .0, 0.6);
338 		for (float y=0.5; y < height; y+= 2.0) {
339 			cairo_move_to(tc, 0, y);
340 			cairo_line_to(tc, width, y);
341 			cairo_stroke (tc);
342 		}
343 		cairo_restore (tc);
344 
345 		pat = cairo_pattern_create_for_surface (surface);
346 		cairo_destroy (tc);
347 		cairo_surface_destroy (surface);
348 	}
349 
350 	ui->mpat= pat;
351 }
352 
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)353 static void write_text(
354 		cairo_t* cr,
355 		const char *txt,
356 		PangoFontDescription *font,
357 		const float x, const float y,
358 		const float ang, const int align,
359 		const float * const col) {
360 	write_text_full(cr, txt, font, x, y, ang, align, col);
361 }
362 
363 
364 #define FONT_LBL ui->font[FONT_S08]
365 #define FONT_MTR ui->font[FONT_S06]
366 #define FONT_VAL ui->font[FONT_M07]
367 #define FONT_SPK ui->font[FONT_M08]
368 
369 #define INIT_ANN_BG(VAR, WIDTH, HEIGHT) \
370 	if (VAR) cairo_surface_destroy(VAR); \
371 	VAR = cairo_image_surface_create (CAIRO_FORMAT_RGB24, WIDTH, HEIGHT); \
372 	cr = cairo_create (VAR);
373 
374 #define INIT_BLACK_BG(VAR, WIDTH, HEIGHT) \
375 	INIT_ANN_BG(VAR, WIDTH, HEIGHT) \
376 	CairoSetSouerceRGBA(c_blk); \
377 	cairo_rectangle (cr, 0, 0, WIDTH, WIDTH); \
378 	cairo_fill (cr);
379 
alloc_annotations(SAUI * ui)380 static void alloc_annotations(SAUI* ui) {
381 	cairo_t* cr;
382 	if (ui->display_freq) {
383 		/* frequecy table */
384 		for (uint32_t i = 0; i < ui->num_meters; ++i) {
385 			INIT_BLACK_BG(ui->an[i], 24, FQ_WIDTH)
386 			write_text(cr, freq_table[i], FONT_LBL, 0, 0, -M_PI/2, 7, c_g90);
387 			cairo_destroy (cr);
388 		}
389 	}
390 
391 	if (ui->dial) {
392 		return; // XXX TODO *dynamic* widget scaling
393 		cairo_surface_destroy(ui->dial);
394 	}
395 	ui->dial = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 2 * (GED_WIDTH), 2 * (GED_HEIGHT+10));
396 	cr = cairo_create (ui->dial);
397 	cairo_scale (cr, 2.0, 2.0);
398 	CairoSetSouerceRGBA(c_trs);
399 	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
400 	cairo_rectangle (cr, 0, 0, GED_WIDTH, GED_HEIGHT+10);
401 	cairo_fill (cr);
402 	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
403 
404 	float xlp, ylp;
405 #define RESPLABLEL(V) \
406 	{ \
407 		float ang = (-.75 * M_PI) + (1.5 * M_PI) * (RESPSCALE(V) - RESPSCALE(.05)) / (RESPSCALE(8.) - RESPSCALE(.05)); \
408 		xlp = GED_CX + .5 + sinf (ang) * (GED_RADIUS + 3.0); \
409 		ylp = GED_CY + 10.5 - cosf (ang) * (GED_RADIUS + 3.0); \
410 		cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); \
411 		CairoSetSouerceRGBA(ui->c_txt); \
412 		cairo_set_line_width(cr, 1.5); \
413 		cairo_move_to(cr, rint(xlp)-.5, rint(ylp)-.5); \
414 		cairo_close_path(cr); \
415 		cairo_stroke(cr); \
416 		xlp = GED_CX + .5 + sinf (ang) * (GED_RADIUS + 9.5); \
417 		ylp = GED_CY + 10.5 - cosf (ang) * (GED_RADIUS + 9.5); \
418 	}
419 
420 	RESPLABLEL(8.0); write_text(cr, "1/8", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
421 	RESPLABLEL(4.0); write_text(cr, " 1/4", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
422 	RESPLABLEL(2.0); write_text(cr, " 1/2", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
423 	RESPLABLEL(1.0); write_text(cr, "1", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
424 	RESPLABLEL(1/2.f); write_text(cr, "2", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
425 	RESPLABLEL(1/4.f); write_text(cr, "4", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
426 	RESPLABLEL(1/10.f); write_text(cr, "10 ", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
427 	RESPLABLEL(1/20.f); write_text(cr, "20", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
428 	cairo_destroy (cr);
429 }
430 
realloc_metrics(SAUI * ui)431 static void realloc_metrics(SAUI* ui) {
432 	const float dboff = ui->gain;
433 	if (!ui->size_changed && rint(ui->cache_ma * 5) == rint(dboff * 5)) {
434 		return;
435 	}
436 	ui->cache_ma = dboff;
437 	cairo_t* cr;
438 #define DO_THE_METER(DB, TXT) \
439 	if (dboff + DB < 6.0 && dboff + DB >= -60) \
440 	write_text(cr,  TXT , FONT_MTR, MA_WIDTH - 3, YPOS(deflect(ui, dboff + DB)), 0, 1, c_g90);
441 
442 #define DO_THE_METRICS \
443 	DO_THE_METER(  25, "+25dB") \
444 	DO_THE_METER(  20, "+20dB") \
445 	DO_THE_METER(  18, "+18dB") \
446 	DO_THE_METER(  15, "+15dB") \
447 	DO_THE_METER(   9,  "+9dB") \
448 	DO_THE_METER(   6,  "+6dB") \
449 	DO_THE_METER(   3,  "+3dB") \
450 	DO_THE_METER(   0,  " 0dB") \
451 	DO_THE_METER(  -3,  "-3dB") \
452 	DO_THE_METER(  -9,  "-9dB") \
453 	DO_THE_METER( -15, "-15dB") \
454 	DO_THE_METER( -18, "-18dB") \
455 	DO_THE_METER( -20, "-20dB") \
456 	DO_THE_METER( -25, "-25dB") \
457 	DO_THE_METER( -30, "-30dB") \
458 	DO_THE_METER( -40, "-40dB") \
459 	DO_THE_METER( -50, "-50dB") \
460 	DO_THE_METER( -60, "-60dB") \
461 
462 	INIT_ANN_BG(ui->ma[0], MA_WIDTH, GM_HEIGHT);
463 	CairoSetSouerceRGBA(ui->c_bgr);
464 	cairo_rectangle (cr, 0, 0, MA_WIDTH, GM_HEIGHT);
465 	cairo_fill (cr);
466 	DO_THE_METRICS
467 	if (ui->display_freq) {
468 		write_text(cr,  "dBFS", FONT_MTR, MA_WIDTH - 5, GM_TXT - 5, 0, 1, c_g90);
469 	}
470 	cairo_destroy (cr);
471 
472 	INIT_ANN_BG(ui->ma[1], MA_WIDTH, GM_HEIGHT)
473 	CairoSetSouerceRGBA(ui->c_bgr);
474 	cairo_rectangle (cr, 0, 0, MA_WIDTH, GM_HEIGHT);
475 	cairo_fill (cr);
476 	DO_THE_METRICS
477 	if (ui->display_freq) {
478 		write_text(cr,  "dBFS", FONT_MTR, MA_WIDTH - 5, GM_TXT - 5, 0, 1, c_g90);
479 	} else {
480 		write_text(cr,  "dBTP", FONT_MTR, MA_WIDTH - 5, GM_TXT - 5 + ceil(GM_PEAK * .5), 0, 1, c_g90);
481 	}
482 	cairo_destroy (cr);
483 }
484 
prepare_metersurface(SAUI * ui)485 static void prepare_metersurface(SAUI* ui) {
486 	const float dboff = ui->gain;
487 
488 	if (!ui->size_changed && rint(ui->cache_sf * 5) == rint(dboff * 5)) {
489 		return;
490 	}
491 	ui->cache_sf = dboff;
492 
493 	cairo_t* cr;
494 #define ALLOC_SF(VAR) \
495 	if (VAR) cairo_surface_destroy(VAR); \
496 	VAR = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, GM_WIDTH, GM_HEIGHT);\
497 	cr = cairo_create (VAR);\
498 	CairoSetSouerceRGBA(ui->c_bgr); \
499 	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);\
500 	cairo_rectangle (cr, 0, 0, GM_WIDTH, GM_HEIGHT);\
501 	cairo_fill (cr);
502 
503 #define GAINLINE(DB) \
504 	if (dboff + DB < 5.99) { \
505 		const float yoff = GM_TOP + GM_SCALE - deflect(ui, dboff + DB); \
506 		cairo_move_to(cr, 0, yoff); \
507 		cairo_line_to(cr, GM_WIDTH, yoff); \
508 		cairo_stroke(cr); \
509 }
510 
511 	for (uint32_t i = 0; i < ui->num_meters; ++i) {
512 		ALLOC_SF(ui->sf[i])
513 
514 		/* metric background */
515 		cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
516 		cairo_set_line_width(cr, 1.0);
517 		CairoSetSouerceRGBA(c_g80);
518 		GAINLINE(25);
519 		GAINLINE(20);
520 		GAINLINE(18);
521 		GAINLINE(15);
522 		GAINLINE(9);
523 		GAINLINE(6);
524 		GAINLINE(3);
525 		GAINLINE(0);
526 		GAINLINE(-3);
527 		GAINLINE(-9);
528 		GAINLINE(-15);
529 		GAINLINE(-18);
530 		GAINLINE(-20);
531 		GAINLINE(-25);
532 		GAINLINE(-30);
533 		GAINLINE(-40);
534 		GAINLINE(-50);
535 		GAINLINE(-60);
536 		cairo_destroy(cr);
537 
538 		render_meter(ui, i, GM_SCALE, 2, 0, 0);
539 		ui->val_vis[i] = 2;
540 		ui->peak_vis[i] = 0;
541 	}
542 }
543 
render_meter(SAUI * ui,int i,int v_old,int v_new,int m_old,int m_new)544 static void render_meter(SAUI* ui, int i, int v_old, int v_new, int m_old, int m_new) {
545 	cairo_t* cr = cairo_create (ui->sf[i]);
546 	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
547 
548 	CairoSetSouerceRGBA(c_blk);
549 	rounded_rectangle (cr, GM_LEFT-.5, GM_TOP, GM_GIRTH+1, GM_SCALE, 6);
550 	cairo_fill_preserve(cr);
551 	cairo_clip(cr);
552 
553 	cairo_set_source(cr, ui->mpat);
554 	cairo_rectangle (cr, GM_LEFT, GM_TOP + GM_SCALE - v_new - 1, GM_GIRTH, v_new + 1);
555 	cairo_fill(cr);
556 
557 	if (ui->show_peaks) {
558 		/* peak hold */
559 		cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
560 		cairo_rectangle (cr, GM_LEFT, GM_TOP + GM_SCALE - m_new - 0.5, GM_GIRTH, 3);
561 		cairo_fill_preserve (cr);
562 		CairoSetSouerceRGBA(c_hlt);
563 		cairo_fill(cr);
564 	}
565 
566 	/* border */
567 	cairo_reset_clip(cr);
568 	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
569 
570 	cairo_set_line_width(cr, .75);
571 	CairoSetSouerceRGBA(c_g60);
572 	rounded_rectangle (cr, GM_LEFT, GM_TOP, GM_GIRTH, GM_SCALE, 6);
573 	cairo_stroke(cr);
574 
575 	cairo_destroy(cr);
576 }
577 
578 /******************************************************************************
579  * main drawing
580  */
581 
format_db(char * buf,const float val)582 static void format_db(char *buf, const float val) {
583 	if (val > 99) {
584 		sprintf(buf, "++++");
585 	}
586 	else if (val > -10) {
587 		sprintf(buf, "%+.1f", val);
588 	}
589 	else if (val > -99.9) {
590 		sprintf(buf, "%.0f ", val);
591 	}
592 	else {
593 		sprintf(buf, " -\u221E ");
594 	}
595 }
596 
format_val(char * buf,const float val)597 static void format_val(char *buf, const float val) {
598 	if (val > 99) {
599 		sprintf(buf, "+++++");
600 	}
601 	else if (val > -99.9) {
602 		sprintf(buf, "%+5.1f", val);
603 	}
604 	else {
605 		sprintf(buf, " -\u221E  ");
606 	}
607 }
608 
609 
expose_event(RobWidget * handle,cairo_t * cr,cairo_rectangle_t * ev)610 static bool expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t *ev) {
611 	SAUI* ui = (SAUI*)GET_HANDLE(handle);
612 
613 	if (ui->size_changed) {
614 		reinitialize_font_cache(ui);
615 		alloc_annotations(ui);
616 		//robtk_dial_set_surface(ui->spn_speed, ui->dial); // XXX
617 		create_meter_pattern(ui);
618 	}
619 	if (ui->metrics_changed || ui->size_changed) {
620 		realloc_metrics(ui);
621 		prepare_metersurface(ui);
622 		ui->metrics_changed = false;
623 		ui->size_changed = false;
624 	}
625 
626 	cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height);
627 	cairo_clip (cr);
628 
629 	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
630 
631 	/* metric areas */
632 	if (rect_intersect_a(ev, 0, 0, MA_WIDTH, GM_HEIGHT)) {
633 		cairo_set_source_surface(cr, ui->ma[0], 0, 0);
634 		cairo_paint (cr);
635 	}
636 	if (rect_intersect_a(ev, MA_WIDTH + GM_WIDTH * ui->num_meters, 0, MA_WIDTH, GM_HEIGHT)) {
637 		cairo_set_source_surface(cr, ui->ma[1], MA_WIDTH + GM_WIDTH * ui->num_meters, 0);
638 		cairo_paint (cr);
639 	}
640 
641 	for (uint32_t i = 0; i < ui->num_meters ; ++i) {
642 		if (!rect_intersect_a(ev, MA_WIDTH + GM_WIDTH * i, 0, GM_WIDTH, GM_HEIGHT)) continue;
643 
644 		const int v_old = ui->val_vis[i];
645 		const int v_new = ui->val_def[i];
646 		const int m_old = ui->peak_vis[i];
647 		const int m_new = ui->peak_def[i];
648 
649 		if (v_old != v_new || m_old != m_new || ui->show_peaks_changed) {
650 			ui->val_vis[i] = v_new;
651 			ui->peak_vis[i] = m_new;
652 			render_meter(ui, i, v_old, v_new, m_old, m_new);
653 		}
654 		cairo_set_source_surface(cr, ui->sf[i], MA_WIDTH + GM_WIDTH * i, 0);
655 		cairo_paint (cr);
656 	}
657 
658 	if (ev->width >= ui->width && ev->height >= ui->height) {
659 		/* full expose/redraw */
660 		ui->show_peaks_changed = false;
661 	}
662 
663 	/* numerical peak and value */
664 	if (!ui->display_freq) {
665 		cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
666 		for (uint32_t i = 0; i < ui->num_meters ; ++i) {
667 			char buf[8];
668 			if (rect_intersect_a(ev, MA_WIDTH + GM_WIDTH * i + 2, 5, GM_WIDTH-4, GM_PEAK)) {
669 				cairo_save(cr);
670 				rounded_rectangle (cr, MA_WIDTH + GM_WIDTH * i + 2, 5, GM_WIDTH-4, GM_PEAK, 4);
671 				if (ui->peak_val[i] >= -1.0) {
672 					CairoSetSouerceRGBA(c_ptr);
673 				} else {
674 					CairoSetSouerceRGBA(c_blk);
675 				}
676 				cairo_fill(cr);
677 				rounded_rectangle (cr, MA_WIDTH + GM_WIDTH * i + 1.5, 5.5, GM_WIDTH-3, GM_PEAK - 1, 4);
678 				cairo_set_line_width(cr, 1);
679 				CairoSetSouerceRGBA(c_g60);
680 				cairo_stroke_preserve (cr);
681 				cairo_clip (cr);
682 				format_db(buf, ui->peak_val[i]);
683 
684 				write_text(cr, buf, FONT_VAL, MA_WIDTH + GM_WIDTH * (i + 1) - 5, 5 + ceil(GM_PEAK * .5), 0, 1, c_g90);
685 				cairo_restore(cr);
686 			}
687 
688 			if (rect_intersect_a(ev, MA_WIDTH + GM_WIDTH * i + 2, GM_TXT - 5, GM_WIDTH-4, GM_PEAK)) {
689 				cairo_save(cr);
690 				rounded_rectangle (cr, MA_WIDTH + GM_WIDTH * i + 2, GM_TXT - 5, GM_WIDTH-4, GM_PEAK, 4);
691 				CairoSetSouerceRGBA(ui->c_bgr);
692 				cairo_fill(cr);
693 				rounded_rectangle (cr, MA_WIDTH + GM_WIDTH * i + 1.5, GM_TXT - 4.5, GM_WIDTH-3, GM_PEAK - 1, 4);
694 				cairo_set_line_width(cr, 1.0);
695 				CairoSetSouerceRGBA(c_g60);
696 				cairo_stroke_preserve (cr);
697 				cairo_clip (cr);
698 				format_db(buf, ui->val[i]);
699 				write_text(cr, buf, FONT_VAL, MA_WIDTH + GM_WIDTH * (i + 1) - 5, GM_TXT - 5 + ceil(GM_PEAK * .5), 0, 1, c_g90);
700 				cairo_restore(cr);
701 			}
702 		}
703 	}
704 
705 	/* labels */
706 	if (ui->display_freq) {
707 		cairo_set_operator (cr, CAIRO_OPERATOR_SCREEN);
708 		for (uint32_t i = 0; i < ui->num_meters ; ++i) {
709 			if (!rect_intersect_a(ev, MA_WIDTH + GM_WIDTH * i, GM_TXT, 24, 64)) continue;
710 			cairo_set_source_surface(cr, ui->an[i], MA_WIDTH + GM_WIDTH * i + rintf(.5 * (GM_WIDTH - 13)), GM_TXT);
711 			cairo_paint (cr);
712 		}
713 	}
714 
715 	/* highlight */
716 	if (ui->highlight >= 0 && ui->highlight < (int) ui->num_meters &&
717 			rect_intersect_a(ev, MA_WIDTH + GM_WIDTH * ui->highlight + GM_WIDTH/2 - AN_WIDTH, GM_TXT -4.5, 2 * AN_WIDTH, AN_HEIGHT)) {
718 		cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
719 		const int i = ui->highlight;
720 		char buf[32], bufv[8], bufp[8];
721 		format_val(bufv, ui->val[i]);
722 		format_val(bufp, ui->peak_val[i]);
723 		sprintf(buf, "%s\nc:%s\np:%s"
724 				, freq_table[i], bufv, bufp);
725 		cairo_save(cr);
726 		cairo_set_line_width(cr, 0.75);
727 		CairoSetSouerceRGBA(c_g90);
728 		cairo_move_to(cr, MA_WIDTH + GM_WIDTH * i + GM_LEFT + GM_GIRTH/2, GM_TXT - 8.5);
729 		cairo_line_to(cr, MA_WIDTH + GM_WIDTH * i + GM_LEFT + GM_GIRTH/2, GM_TXT - 4.5);
730 		cairo_stroke(cr);
731 		rounded_rectangle (cr, MA_WIDTH + GM_WIDTH * i + GM_WIDTH/2 - AN_WIDTH, GM_TXT -4.5, 2 * AN_WIDTH, AN_HEIGHT, 3);
732 		CairoSetSouerceRGBA(c_xfb);
733 		cairo_fill_preserve (cr);
734 		CairoSetSouerceRGBA(c_g60);
735 		cairo_stroke_preserve (cr);
736 		cairo_clip (cr);
737 		write_text(cr, buf, FONT_SPK, MA_WIDTH + GM_WIDTH * i + GM_WIDTH/2, GM_TXT + 18 * ui->scale, 0, 2, c_g90);
738 		cairo_restore(cr);
739 	}
740 
741 	return TRUE;
742 }
743 
744 /******************************************************************************
745  * UI callbacks
746  */
747 
cb_reset_peak(RobWidget * handle,RobTkBtnEvent * event)748 static RobWidget* cb_reset_peak (RobWidget* handle, RobTkBtnEvent *event) {
749 	SAUI* ui = (SAUI*)GET_HANDLE(handle);
750 	ui->reset_toggle = !ui->reset_toggle;
751 	/* reset peak-hold in backend */
752 	float temp = ui->reset_toggle ? 1.0 : 2.0;
753 	ui->write(ui->controller, ui->display_freq? 61 : 0,
754 			sizeof(float), 0, (const void*) &temp);
755 
756 	for (uint32_t i=0; i < ui->num_meters ; ++i) {
757 		ui->peak_val[i] = -100;
758 		ui->peak_def[i] = deflect(ui, -100);
759 	}
760 	queue_draw(ui->m0);
761 	return NULL;
762 }
763 
set_gain(RobWidget * w,void * handle)764 static bool set_gain(RobWidget* w, void* handle) {
765 	SAUI* ui = (SAUI*)handle;
766 	float oldgain = ui->gain;
767 	ui->gain = robtk_scale_get_value(ui->fader);
768 #if 1
769 	if (ui->gain < -12) ui->gain = -12;
770 	if (ui->gain >= 32.0) ui->gain = 32.0;
771 #endif
772 	if (oldgain == ui->gain) return TRUE;
773 	if (!ui->disable_signals) {
774 		ui->write(ui->controller, 62, sizeof(float), 0, (const void*) &ui->gain);
775 	}
776 	if (ui->display_freq) { // should actually always be true here
777 #if 0
778 		for (uint32_t pidx=0; pidx < ui->num_meters ; ++pidx) {
779 			invalidate_meter(ui, pidx, ui->val[pidx], ui->peak_val[pidx]);
780 		}
781 #else
782 		ui->initialize = 1;
783 		float temp = -3;
784 		ui->write(ui->controller, ui->display_freq? 61 : 0,
785 				sizeof(float), 0, (const void*) &temp);
786 #endif
787 	}
788 	ui->metrics_changed = true;
789 	queue_draw(ui->m0);
790 	return TRUE;
791 }
792 
set_speed(RobWidget * w,void * handle)793 static bool set_speed(RobWidget* w, void* handle) {
794 	SAUI* ui = (SAUI*)handle;
795 	if (!ui->disable_signals) {
796 		float val = INV_RESPSCALE(robtk_dial_get_value(ui->spn_speed));
797 		//printf("set_speed %f -> %f\n", robtk_dial_get_value(ui->spn_speed), val);
798 		ui->write(ui->controller, 60, sizeof(float), 0, (const void*) &val);
799 	}
800 	return TRUE;
801 }
802 
set_peakdisplay(RobWidget * w,void * handle)803 static bool set_peakdisplay(RobWidget* w, void* handle) {
804 	SAUI* ui = (SAUI*)handle;
805 	bool show_peaks = robtk_cbtn_get_active(ui->btn_peaks);
806 	ui->misc_state &=~1;
807 	if (show_peaks) ui->misc_state |=~1;
808 	ui->show_peaks = show_peaks;
809 	ui->show_peaks_changed = true;
810 	if (!ui->disable_signals) {
811 		float misc_state = ui->misc_state;
812 		ui->write(ui->controller, 63, sizeof(float), 0, (const void*) &misc_state);
813 	}
814 	queue_draw(ui->m0);
815 	return TRUE;
816 }
817 
mousemove(RobWidget * handle,RobTkBtnEvent * event)818 static RobWidget* mousemove(RobWidget* handle, RobTkBtnEvent *event) {
819 	SAUI* ui = (SAUI*)GET_HANDLE(handle);
820 	if (event->y < GM_TOP || event->y > (GM_TOP + GM_SCALE)) {
821 		if (ui->highlight != -1) { queue_draw(ui->m0); }
822 		ui->highlight = -1;
823 		return NULL;
824 	}
825 	const int x = event->x - MA_WIDTH;
826 	const int remain =  x % ((int) GM_WIDTH);
827 	if (remain < GM_LEFT || remain > GM_LEFT + GM_GIRTH) {
828 		if (ui->highlight != -1) { queue_draw(ui->m0); }
829 		ui->highlight = -1;
830 		return NULL;
831 	}
832 
833 	const uint32_t mtr = x / ((int) GM_WIDTH);
834 	if (mtr < ui->num_meters) {
835 		if (ui->highlight != (int) mtr) { queue_draw(ui->m0); }
836 		ui->highlight = mtr;
837 	} else {
838 		if (ui->highlight != -1) { queue_draw(ui->m0); }
839 		ui->highlight = -1;
840 	}
841 	return handle;
842 }
843 
844 /******************************************************************************
845  * widget hackery
846  */
847 
848 static void
size_request(RobWidget * handle,int * w,int * h)849 size_request(RobWidget* handle, int *w, int *h) {
850 	SAUI* ui = (SAUI*)GET_HANDLE(handle);
851 	*w = ui->min_width;
852 	*h = GM_MINH;
853 }
854 
855 static void
top_size_allocate(RobWidget * rw,int w,int h)856 top_size_allocate(RobWidget* rw, int w, int h) {
857 	assert (rw->childcount == 3);
858 	SAUI* ui = (SAUI*)GET_HANDLE(rw->children[0]->children[0]);
859 	GLrobtkLV2UI * const self = (GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw);
860 
861 	if (ui->_min_w == 0 && ui->_min_h == 0) {
862 		if (rw->widget_scale == 1.0) {
863 			ui->_min_w = rw->area.width;
864 			ui->_min_h = rw->area.height;
865 		} else {
866 			rhbox_size_allocate (rw, w, h);
867 			return;
868 		}
869 	}
870 	assert (ui->_min_w > 1 && ui->_min_h > 1);
871 
872 	const float scale_x = w / ui->_min_w;
873 	const float scale_y = h / ui->_min_h;
874 	rw->widget_scale =  MAX(1.0, MIN(2.0, floor (MIN(scale_y, scale_x) * 10) / 10));
875 
876 	if (self->queue_widget_scale != rw->widget_scale) {
877 		self->queue_widget_scale = rw->widget_scale;
878 		puglPostResize(self->view);
879 		queue_draw (rw);
880 	}
881 	rhbox_size_allocate (rw, w, h);
882 }
883 
884 static void
size_allocate(RobWidget * handle,int w,int h)885 size_allocate(RobWidget* handle, int w, int h) {
886 	SAUI* ui = (SAUI*)GET_HANDLE(handle);
887 	ui->height = floor(h/2) * 2;
888 	ui->width = w;
889 	ui->size_changed = true;
890 
891 	float scale_y = ui->height / GM_MINH;
892 	float scale_x = (float) ui->width / ui->min_width;
893 	ui->scale = MAX(1.0, MIN(2.5, MIN(scale_y, scale_x)));
894 
895 	if (ui->display_freq) {
896 		ui->gm_width = MIN(40, floor ((w - 2.0 * MA_WIDTH) / ui->num_meters));
897 		ui->gm_girth = rintf (ui->gm_width * .75);
898 		ui->gm_left = .5 + floor(.5 * (ui->gm_width - ui->gm_girth));
899 		ui->cur_width = 2.0 * MA_WIDTH + ui->num_meters * GM_WIDTH;
900 	} else {
901 		ui->gm_width = floor ((w - 2.0 * MA_WIDTH) / ui->num_meters);
902 		if (ui->gm_width > 60) ui->gm_width = 60;
903 		ui->gm_girth = rintf (ui->gm_width * .42);
904 		ui->gm_left = .5 + floor(.5 * (ui->gm_width - ui->gm_girth));
905 		ui->cur_width = 2.0 * MA_WIDTH + ui->num_meters * GM_WIDTH;
906 	}
907 	robwidget_set_size(handle, MIN(ui->width, ui->cur_width), h);
908 	queue_draw(ui->m0);
909 }
910 
toplevel(SAUI * ui,void * const top)911 static RobWidget * toplevel(SAUI* ui, void * const top)
912 {
913 	/* main widget: layout */
914 	ui->rw = rob_hbox_new(FALSE, 2);
915 	robwidget_make_toplevel(ui->rw, top);
916 	if (ui->display_freq) {
917 		robwidget_toplevel_enable_scaling (ui->rw);
918 	}
919 
920 	/* DPM main drawing area */
921 	ui->m0 = robwidget_new(ui);
922 	ROBWIDGET_SETNAME(ui->m0, "dpm (m0)");
923 
924 	robwidget_set_expose_event(ui->m0, expose_event);
925 	robwidget_set_size_request(ui->m0, size_request);
926 	robwidget_set_size_allocate(ui->m0, size_allocate);
927 	robwidget_set_mousedown(ui->m0, cb_reset_peak);
928 	if (ui->display_freq) {
929 		robwidget_set_mousemove(ui->m0, mousemove);
930 	}
931 	ui->sep_h0 = robtk_sep_new(FALSE);
932 
933 	/* vbox on the right */
934 	ui->c_box = rob_vbox_new(FALSE, 2);
935 
936 	ui->fader     = robtk_scale_new(-12, 32.0, .05, FALSE);
937 	ui->lbl_speed = robtk_lbl_new("Response [s]");
938 	ui->spn_speed = robtk_dial_new_with_size(RESPSCALE(.05), RESPSCALE(8), .1, GED_WIDTH, GED_HEIGHT+10, GED_CX, GED_CY+10, GED_RADIUS);
939 	ui->btn_peaks = robtk_cbtn_new("Peak Hold", GBT_LED_LEFT, true);
940 	robtk_cbtn_set_active(ui->btn_peaks, true);
941 	robtk_dial_set_default(ui->spn_speed, RESPSCALE(1.0f));
942 	robtk_dial_set_scaled_surface_scale (ui->spn_speed, ui->dial, 2.0);
943 
944 	robtk_scale_set_default(ui->fader, 0);
945 	robtk_scale_set_value(ui->fader, 0);
946 	robtk_scale_add_mark(ui->fader,  -9,  "-9dB");
947 	robtk_scale_add_mark(ui->fader,  -6,  "-6dB");
948 	robtk_scale_add_mark(ui->fader,  -3,  "-3dB");
949 	robtk_scale_add_mark(ui->fader,   0,   "0dB");
950 	robtk_scale_add_mark(ui->fader,   3,  "+3dB");
951 	robtk_scale_add_mark(ui->fader,   6,  "+6dB");
952 	robtk_scale_add_mark(ui->fader,   9,  "+9dB");
953 	robtk_scale_add_mark(ui->fader,  10,  "");
954 	robtk_scale_add_mark(ui->fader,  12, "+12dB");
955 	robtk_scale_add_mark(ui->fader,  15, "+15dB");
956 	robtk_scale_add_mark(ui->fader,  18, "+18dB");
957 	robtk_scale_add_mark(ui->fader,  20, "+20dB");
958 	robtk_scale_add_mark(ui->fader,  25, "+25dB");
959 	robtk_scale_add_mark(ui->fader,  30, "+30dB");
960 
961 	/* layout */
962 
963 	ui->m_box = rob_vbox_new(FALSE, 0);
964 	rob_vbox_child_pack(ui->m_box, ui->m0, TRUE, TRUE);
965 	rob_hbox_child_pack(ui->rw, ui->m_box, TRUE, TRUE);
966 
967 	if (ui->display_freq) {
968 		rob_hbox_child_pack(ui->rw, robtk_sep_widget(ui->sep_h0), FALSE, TRUE);
969 		rob_hbox_child_pack(ui->rw, ui->c_box, FALSE, TRUE);
970 
971 		rob_vbox_child_pack(ui->c_box, robtk_scale_widget(ui->fader), TRUE, TRUE);
972 #if 1 // display speed, value-LPF config
973 		rob_vbox_child_pack(ui->c_box, robtk_lbl_widget(ui->lbl_speed), FALSE, FALSE);
974 		rob_vbox_child_pack(ui->c_box, robtk_dial_widget(ui->spn_speed), FALSE, FALSE);
975 #endif
976 		rob_vbox_child_pack(ui->c_box, robtk_cbtn_widget(ui->btn_peaks), FALSE, FALSE);
977 	}
978 
979 	/* callbacks */
980 	robtk_scale_set_callback(ui->fader, set_gain, ui);
981 	robtk_dial_set_callback(ui->spn_speed, set_speed, ui);
982 	robtk_cbtn_set_callback(ui->btn_peaks, set_peakdisplay, ui);
983 
984 	/* change _after_ packing, (packing checks allocate fn ptr) */
985 	if (ui->display_freq) {
986 		ui->rw->size_allocate = top_size_allocate;
987 	}
988 
989 	return ui->rw;
990 }
991 
992 /******************************************************************************
993  * LV2 callbacks
994  */
995 
ui_enable(LV2UI_Handle handle)996 static void ui_enable(LV2UI_Handle handle) { }
ui_disable(LV2UI_Handle handle)997 static void ui_disable(LV2UI_Handle handle) { }
998 
999 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)1000 instantiate(
1001 		void* const               ui_toplevel,
1002 		const LV2UI_Descriptor*   descriptor,
1003 		const char*               plugin_uri,
1004 		const char*               bundle_path,
1005 		LV2UI_Write_Function      write_function,
1006 		LV2UI_Controller          controller,
1007 		RobWidget**               widget,
1008 		const LV2_Feature* const* features)
1009 {
1010 	SAUI* ui = (SAUI*) calloc(1,sizeof(SAUI));
1011 	*widget = NULL;
1012 
1013 	if      (!strcmp(plugin_uri, MTR_URI "spectr30mono")) { ui->num_meters = 30; ui->display_freq = true; }
1014 	else if (!strcmp(plugin_uri, MTR_URI "spectr30stereo")) { ui->num_meters = 30; ui->display_freq = true; }
1015 	else if (!strcmp(plugin_uri, MTR_URI "dBTPmono")) { ui->num_meters = 1; ui->display_freq = false; }
1016 	else if (!strcmp(plugin_uri, MTR_URI "dBTPstereo")) { ui->num_meters = 2; ui->display_freq = false; }
1017 	else {
1018 		free(ui);
1019 		return NULL;
1020 	}
1021 	ui->write      = write_function;
1022 	ui->controller = controller;
1023 	ui->scale = 1.0;
1024 
1025 	get_color_from_theme(0, ui->c_txt);
1026 	get_color_from_theme(1, ui->c_bgr);
1027 
1028 	ui->gain = 0;
1029 	ui->cache_sf = -100;
1030 	ui->cache_ma = -100;
1031 	ui->highlight = -1;
1032 	ui->metrics_changed = true;
1033 	ui->size_changed = true;
1034 
1035 	ui->_min_w = 0;
1036 	ui->_min_h = 0;
1037 
1038 	ui->show_peaks_changed = false;
1039 	ui->show_peaks = true;
1040 	ui->misc_state = 1;
1041 
1042 	for (uint32_t i=0; i < ui->num_meters ; ++i) {
1043 		ui->val[i] = -100.0;
1044 		ui->val_def[i] = deflect(ui, -100);
1045 		ui->peak_val[i] = -100.0;
1046 		ui->peak_def[i] = deflect(ui, -100);
1047 	}
1048 	ui->disable_signals = false;
1049 
1050 	if (ui->display_freq) {
1051 		ui->gm_width = 13.f;
1052 		ui->gm_girth = 10.f;
1053 		ui->gm_left  = 1.5f;
1054 	} else {
1055 		ui->gm_width = 28.f;
1056 		ui->gm_girth = 12.f;
1057 		ui->gm_left  = 8.5f;
1058 	}
1059 	ui->min_width = 2.0 * MA_WIDTH + ui->num_meters * GM_WIDTH;
1060 	ui->width = ui->min_width;
1061 	ui->height = GM_MINH;
1062 
1063 	reinitialize_font_cache(ui);
1064 	alloc_annotations(ui);
1065 
1066 	*widget = toplevel(ui, ui_toplevel);
1067 
1068 	ui->initialize = 0;
1069 	ui->reset_toggle = false;
1070 	return ui;
1071 }
1072 
1073 static enum LVGLResize
plugin_scale_mode(LV2UI_Handle handle)1074 plugin_scale_mode(LV2UI_Handle handle)
1075 {
1076 	return LVGL_LAYOUT_TO_FIT;
1077 }
1078 
1079 static void
cleanup(LV2UI_Handle handle)1080 cleanup(LV2UI_Handle handle)
1081 {
1082 	SAUI* ui = (SAUI*)handle;
1083 	for (uint32_t i=0; i < ui->num_meters ; ++i) {
1084 		cairo_surface_destroy(ui->sf[i]);
1085 		cairo_surface_destroy(ui->an[i]);
1086 	}
1087 	for (int i=0; i < 4; ++i) {
1088 		pango_font_description_free(ui->font[i]);
1089 	}
1090 	cairo_pattern_destroy(ui->mpat);
1091 	cairo_surface_destroy(ui->ma[0]);
1092 	cairo_surface_destroy(ui->ma[1]);
1093 	cairo_surface_destroy(ui->dial);
1094 
1095 	robtk_scale_destroy(ui->fader);
1096 	robtk_lbl_destroy(ui->lbl_speed);
1097 	robtk_dial_destroy(ui->spn_speed);
1098 	robtk_cbtn_destroy(ui->btn_peaks);
1099 	robtk_sep_destroy(ui->sep_h0);
1100 	rob_box_destroy(ui->c_box);
1101 
1102 	robwidget_destroy(ui->m0);
1103 	rob_box_destroy(ui->m_box);
1104 	rob_box_destroy(ui->rw);
1105 
1106 	free(ui);
1107 }
1108 
1109 static const void*
extension_data(const char * uri)1110 extension_data(const char* uri)
1111 {
1112 	return NULL;
1113 }
1114 
1115 /******************************************************************************
1116  * backend communication
1117  */
invalidate_meter(SAUI * ui,int mtr,float val,float peak)1118 static void invalidate_meter(SAUI* ui, int mtr, float val, float peak) {
1119 	const int v_old = ui->val_def[mtr];
1120 	const int v_new = deflect(ui, val + ui->gain);
1121 
1122 	const int m_old = ui->peak_def[mtr];
1123 	const int m_new = ceilf(deflect(ui, peak + ui->gain) / 2.0) * 2.0;
1124 	int t, h;
1125 
1126 #define INVALIDATE_RECT(XX,YY,WW,HH) \
1127 		queue_tiny_area(ui->m0, XX, YY, WW, HH);
1128 
1129 	if (rintf(ui->val[mtr] * 10.0f) != rintf(val * 10.0f) && !ui->display_freq) {
1130 		INVALIDATE_RECT(mtr * GM_WIDTH + MA_WIDTH, GM_TXT - 5, GM_WIDTH, GM_PEAK + 1);
1131 	}
1132 
1133 	if (ui->highlight == mtr && ui->display_freq &&
1134 			(rintf(ui->val[mtr] * 10.0f) != rintf(val * 10.0f) || rintf(m_old * 10.0f) != rintf(m_new * 10.0f))) {
1135 		queue_tiny_area(ui->m0, mtr * GM_WIDTH + MA_WIDTH + GM_WIDTH/2 - AN_WIDTH -.5, GM_TXT - 8, 1 + 2 * AN_WIDTH, FQ_ANN);
1136 	}
1137 
1138 	if (rintf(ui->peak_val[mtr] * 10.0f) != rintf(peak * 10.0f) && !ui->display_freq) {
1139 		INVALIDATE_RECT(mtr * GM_WIDTH + MA_WIDTH, 5, GM_WIDTH, GM_PEAK + 1);
1140 	}
1141 
1142 	ui->val[mtr] = val;
1143 	ui->val_def[mtr] = v_new;
1144 	ui->peak_val[mtr] = peak;
1145 	ui->peak_def[mtr] = m_new;
1146 
1147 	if (v_old != v_new) {
1148 		if (v_old > v_new) {
1149 			t = v_old;
1150 			h = v_old - v_new;
1151 		} else {
1152 			t = v_new;
1153 			h = v_new - v_old;
1154 		}
1155 
1156 		INVALIDATE_RECT(
1157 				mtr * GM_WIDTH + MA_WIDTH + GM_LEFT - 1,
1158 				GM_TOP + GM_SCALE - t - 1,
1159 				GM_GIRTH + 2, h+3);
1160 	}
1161 
1162 	if (m_old != m_new && ui->show_peaks) {
1163 		if (m_old > m_new) {
1164 			t = m_old;
1165 			h = m_old - m_new;
1166 		} else {
1167 			t = m_new;
1168 			h = m_new - m_old;
1169 		}
1170 
1171 		INVALIDATE_RECT(
1172 				mtr * GM_WIDTH + MA_WIDTH + GM_LEFT - 1,
1173 				GM_TOP + GM_SCALE - t - 1,
1174 				GM_GIRTH + 2, h+4);
1175 	}
1176 }
1177 
1178 /******************************************************************************
1179  * handle data from backend
1180  */
1181 
handle_spectrum_connections(SAUI * ui,uint32_t port_index,float v)1182 static void handle_spectrum_connections(SAUI* ui, uint32_t port_index, float v) {
1183 
1184 	if (port_index == 62) {
1185 		if (v >= -12 && v <= 32.0) {
1186 			ui->disable_signals = true;
1187 			robtk_scale_set_value(ui->fader, v);
1188 			ui->disable_signals = false;
1189 		}
1190 	} else
1191 	if (port_index == 63) {
1192 		if (v >= 0 && v <= 256.0) {
1193 			ui->disable_signals = true;
1194 			robtk_cbtn_set_active(ui->btn_peaks, (((int)v)&1) == 1);
1195 			ui->disable_signals = false;
1196 		}
1197 	} else
1198 	if (port_index == 60) {
1199 		ui->disable_signals = true;
1200 		if (v > 0 && v < 15) {
1201 			robtk_dial_set_value(ui->spn_speed, RESPSCALE(v));
1202 		}
1203 		ui->disable_signals = false;
1204 	} else
1205 	if (v > -500 && port_index < 30) {
1206 		int pidx = port_index;
1207 		float np = ui->peak_val[pidx];
1208 		invalidate_meter(ui, pidx, v, np);
1209 	}
1210 	if (v > -500 && port_index >= 30  && port_index < 60) {
1211 		int pidx = port_index - 30;
1212 		float nv = ui->val[pidx];
1213 		invalidate_meter(ui, pidx, nv, v);
1214 	}
1215 }
1216 
handle_meter_connections(SAUI * ui,uint32_t port_index,float v)1217 static void handle_meter_connections(SAUI* ui, uint32_t port_index, float v) {
1218 	if (v <= -500) return;
1219 	v = v > .00001f ? 20.0 * log10f(v) : -100.0;
1220 	if (port_index == 3) {
1221 		invalidate_meter(ui, 0, v, ui->peak_val[0]);
1222 	}
1223 	else if (port_index == 6) {
1224 		invalidate_meter(ui, 1, v, ui->peak_val[1]);
1225 	}
1226 
1227 	/* peak data from backend */
1228 	if (ui->num_meters == 1) {
1229 		if (port_index == 4) {
1230 			float np = ui->peak_val[0];
1231 			if (v != np)  { np = v; }
1232 			invalidate_meter(ui, 0, ui->val[0], np);
1233 		}
1234 	} else if (ui->num_meters == 2) {
1235 		if (port_index == 7) {
1236 			float np = ui->peak_val[0];
1237 			if (v != np)  { np = v; }
1238 			invalidate_meter(ui, 0, ui->val[0], np);
1239 		}
1240 		else if (port_index == 8) {
1241 			float np = ui->peak_val[1];
1242 			if (v != np)  { np = v; }
1243 			invalidate_meter(ui, 1, ui->val[1], np);
1244 		}
1245 	}
1246 }
1247 
1248 static void
port_event(LV2UI_Handle handle,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)1249 port_event(LV2UI_Handle handle,
1250            uint32_t     port_index,
1251            uint32_t     buffer_size,
1252            uint32_t     format,
1253            const void*  buffer)
1254 {
1255 	SAUI* ui = (SAUI*)handle;
1256 	if (format != 0) return;
1257 
1258 	if (ui->initialize == 0 && port_index == (ui->display_freq? 61 : 0)) {
1259 		ui->initialize = 1;
1260 		float temp = -3;
1261 		ui->write(ui->controller, ui->display_freq? 61 : 0,
1262 				sizeof(float), 0, (const void*) &temp);
1263 	} else if (ui->initialize == 1 && *(float *)buffer <= -500 && (
1264 			(ui->display_freq && port_index >= 30 && port_index < 60)
1265 			|| (!ui->display_freq && ui->num_meters == 1 && port_index == 4)
1266 			|| (!ui->display_freq && ui->num_meters == 2 && port_index == 7)
1267 			)) {
1268 		ui->initialize = 2;
1269 		float temp = -4;
1270 		ui->write(ui->controller, ui->display_freq? 61 : 0,
1271 				sizeof(float), 0, (const void*) &temp);
1272 	}
1273 
1274 	if (ui->display_freq) {
1275 		handle_spectrum_connections(ui, port_index, *(float *)buffer);
1276 	} else {
1277 		handle_meter_connections(ui, port_index, *(float *)buffer);
1278 	}
1279 
1280 }
1281